From c6e9f13ea991ad08e17314bc0d12e0164a908a56 Mon Sep 17 00:00:00 2001 From: anson Date: Fri, 7 Nov 2025 15:51:35 +0000 Subject: [PATCH 1/5] docs: enhance key management documentation with cryptographic schemes and consensus threshold rule --- docs/learning-lit/how-it-works.mdx | 25 +++++++++++++++++++++++- docs/node-ops/staking-and-delegation.mdx | 6 ++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/learning-lit/how-it-works.mdx b/docs/learning-lit/how-it-works.mdx index f8c3c5812..3b024c20e 100644 --- a/docs/learning-lit/how-it-works.mdx +++ b/docs/learning-lit/how-it-works.mdx @@ -15,10 +15,33 @@ Each Lit Protocol node participates in a Distributed Key Generation (DKG) proces The Lit network supports multiple cryptographic curves, signing schemes, and key types. Additional curves and schemes can be added as desired to enable additional interoperability with a wide variety of protocols and standards. +Today this includes: + +| Scheme | Curve | +|--------|-------| +| `Bls12381` | BLS12-381 | +| `Bls12381G1ProofOfPossession` | BLS12-381 | +| `EcdsaK256Sha256` | secp256k1 | +| `EcdsaP256Sha256` | NIST P-256 | +| `EcdsaP384Sha384` | NIST P-384 | +| `SchnorrEd25519Sha512` | ed25519 | +| `SchnorrEd448Shake256` | ed448 | +| `SchnorrK256Taproot` | secp256k1 | +| `SchnorrK256Sha256` | secp256k1 | +| `SchnorrP256Sha256` | NIST P-256 | +| `SchnorrP384Sha384` | NIST P-384 | +| `SchnorrRistretto25519Sha512` | Ristretto25519 | +| `SchnorrRedJubjubBlake2b512` | Jubjub | +| `SchnorrRedDecaf377Blake2b512` | Decaf377 | +| `SchnorrkelSubstrate` | sr25519 | + + +Refer to the [PKP Sign guide](/sdk/auth-context-consumption/pkp-sign#available-signing-schemes) for the full scheme-by-curve matrix that SDK users call directly. + ## Policy Enforcement and Data Orchestration – Lit Actions Each Lit node contains a JavaScript execution environment which allows developers to write arbitrary code that dictates how the secrets and keys managed by the network are used. These programs are called Lit Actions, immutable JS serverless functions that govern signing and encryption / decryption operations. Lit Actions can natively fetch and process data from any on or off-chain source, be used to create complex transaction automations (e.g. dollar-cost-averaging), define rules for usage and access, create spending policies, trigger signature generation, and more. ## Further Reading -For an in-depth overview of how Lit keeps keys and assets secure, please check out the [security](/learning-lit/security) section. \ No newline at end of file +For an in-depth overview of how Lit keeps keys and assets secure, please check out the [security](/learning-lit/security) section. diff --git a/docs/node-ops/staking-and-delegation.mdx b/docs/node-ops/staking-and-delegation.mdx index 2249734c2..286cdf16b 100644 --- a/docs/node-ops/staking-and-delegation.mdx +++ b/docs/node-ops/staking-and-delegation.mdx @@ -41,6 +41,10 @@ After meeting the minimum self-stake requirement, any node operator can increase Each node operator has the ability to set their own commission rate, which determines the percentage of staking rewards that are retained prior to being distributed to delegators. This allows operators to compete on both performance and pricing. Commission rates are managed and displayed on the Lit staking portal. +## Consensus Threshold Rule + +All Lit validator and signing operations are guarded by a threshold rule that requires at least `ROUNDUP(MAX(3, 2N/3))` participating nodes for a committee of size `N`. + ## Slashing Slashing has been implemented to ensure that node operators keep their machines online and responsive at all times, preventing any downtime that could disrupt the network. Unlike some other protocols where slashing may also enforce computational ‘correctness,’ Lit Protocol relies on Trusted Execution Environments (TEEs) and threshold consensus mechanisms to guarantee the accuracy and integrity of operations. As a result, slashing in Lit Protocol is specifically designed to enforce availability and liveness rather than correctness. @@ -62,5 +66,3 @@ Slashed funds are sent to a contract that is initially administered by the Lit P ### Governance Oversight Outside of integrating slashing outcomes with the Lit governance process to ensure consistent enforcement of rules while maintaining flexibility to address edge cases, the Lit Protocol Council will serve an important role in overseeing the parameters associated with the slashing process itself. This includes configuring the slashing penalty itself, as well as managing the kick counter and decay mechanism. - - From edce64792d25f032546a874ee98dc3987c698b2c Mon Sep 17 00:00:00 2001 From: anson Date: Fri, 7 Nov 2025 16:01:20 +0000 Subject: [PATCH 2/5] docs: remove bls --- docs/learning-lit/how-it-works.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/learning-lit/how-it-works.mdx b/docs/learning-lit/how-it-works.mdx index 3b024c20e..6ad5f92cd 100644 --- a/docs/learning-lit/how-it-works.mdx +++ b/docs/learning-lit/how-it-works.mdx @@ -19,8 +19,6 @@ Today this includes: | Scheme | Curve | |--------|-------| -| `Bls12381` | BLS12-381 | -| `Bls12381G1ProofOfPossession` | BLS12-381 | | `EcdsaK256Sha256` | secp256k1 | | `EcdsaP256Sha256` | NIST P-256 | | `EcdsaP384Sha384` | NIST P-384 | From 217d7f6adb45982e88272bd7346fed01647e84f9 Mon Sep 17 00:00:00 2001 From: anson Date: Fri, 7 Nov 2025 16:30:33 +0000 Subject: [PATCH 3/5] tests(signing): cover hashing + all pkp schemes --- .../sdk/auth-context-consumption/pkp-sign.mdx | 19 ++++- .../e2e/src/tickets/signing-schemes.spec.ts | 3 + .../e2e/src/tickets/signing-schemes.suite.ts | 83 +++++++++++++++++++ .../pkpSign/pkpSign.InputSchema.ts | 1 + .../vNaga/shared/schemas/LitMessageSchema.ts | 31 +++++-- 5 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 packages/e2e/src/tickets/signing-schemes.spec.ts create mode 100644 packages/e2e/src/tickets/signing-schemes.suite.ts diff --git a/docs/sdk/auth-context-consumption/pkp-sign.mdx b/docs/sdk/auth-context-consumption/pkp-sign.mdx index 859a158d2..9858012ee 100644 --- a/docs/sdk/auth-context-consumption/pkp-sign.mdx +++ b/docs/sdk/auth-context-consumption/pkp-sign.mdx @@ -31,6 +31,23 @@ const signatures = await litClient.chain.raw.pkpSign({ }); ``` +### Hashing defaults and bypass + +By default the network hashes ECDSA payloads for you using the canonical function for each chain (Ethereum → keccak256, Bitcoin/Cosmos → SHA-256/SHA-384) before the nodes sign anything. Schnorr/EdDSA schemes receive the raw bytes exactly as you provided them. If you already computed a digest (for example when signing EIP-712 typed data) you can pass it directly and opt out of the SDK hashing step by setting `bypassAutoHashing: true`: + +```ts +const digestBytes = hexToBytes(hashTypedData(typedData)); + +const signature = await litClient.chain.raw.pkpSign({ + chain: 'ethereum', + signingScheme: 'EcdsaK256Sha256', + pubKey: pkpInfo.pubkey, + authContext, + toSign: digestBytes, + bypassAutoHashing: true, +}); +``` + --- # Available signing schemes @@ -66,4 +83,4 @@ const signatures = await litClient.chain.raw.pkpSign({ | `SchnorrRistretto25519Sha512` | Ristretto25519 | | `SchnorrRedJubjubBlake2b512` | Jubjub | | `SchnorrRedDecaf377Blake2b512` | Decaf377 | -| `SchnorrkelSubstrate` | sr25519 | \ No newline at end of file +| `SchnorrkelSubstrate` | sr25519 | diff --git a/packages/e2e/src/tickets/signing-schemes.spec.ts b/packages/e2e/src/tickets/signing-schemes.spec.ts new file mode 100644 index 000000000..aa69130c7 --- /dev/null +++ b/packages/e2e/src/tickets/signing-schemes.spec.ts @@ -0,0 +1,3 @@ +import { registerSigningSchemesTicketSuite } from './signing-schemes.suite'; + +registerSigningSchemesTicketSuite(); diff --git a/packages/e2e/src/tickets/signing-schemes.suite.ts b/packages/e2e/src/tickets/signing-schemes.suite.ts new file mode 100644 index 000000000..ba8664db8 --- /dev/null +++ b/packages/e2e/src/tickets/signing-schemes.suite.ts @@ -0,0 +1,83 @@ +import { LitCurve } from '@lit-protocol/constants'; +import { SigningChainSchema } from '@lit-protocol/schemas'; +import { z } from 'zod'; +import { createEnvVars } from '../helper/createEnvVars'; +import { createTestAccount } from '../helper/createTestAccount'; +import { createTestEnv } from '../helper/createTestEnv'; + +type SigningChain = z.infer; + +type SchemeUnderTest = { + scheme: LitCurve; + chain: SigningChain; +}; + +const SIGNING_MATRIX: SchemeUnderTest[] = [ + // ECDSA variants + { scheme: 'EcdsaK256Sha256', chain: 'ethereum' }, + { scheme: 'EcdsaP256Sha256', chain: 'ethereum' }, + { scheme: 'EcdsaP384Sha384', chain: 'ethereum' }, + // Schnorr over secp256k1 (Bitcoin / Taproot) + { scheme: 'SchnorrK256Sha256', chain: 'bitcoin' }, + { scheme: 'SchnorrK256Taproot', chain: 'bitcoin' }, + // Schnorr over NIST curves + { scheme: 'SchnorrP256Sha256', chain: 'cosmos' }, + { scheme: 'SchnorrP384Sha384', chain: 'cosmos' }, + // EdDSA-style curves + { scheme: 'SchnorrEd25519Sha512', chain: 'solana' }, + { scheme: 'SchnorrEd448Shake256', chain: 'solana' }, + // ZK / privacy-focused curves + { scheme: 'SchnorrRistretto25519Sha512', chain: 'solana' }, + { scheme: 'SchnorrRedJubjubBlake2b512', chain: 'solana' }, + { scheme: 'SchnorrRedDecaf377Blake2b512', chain: 'solana' }, + { scheme: 'SchnorrkelSubstrate', chain: 'solana' }, +]; + +export function registerSigningSchemesTicketSuite() { + describe('pkp signing schemes', () => { + let testEnv: Awaited>; + let signerAccount: Awaited>; + + beforeAll(async () => { + const envVars = createEnvVars(); + testEnv = await createTestEnv(envVars); + signerAccount = await createTestAccount(testEnv, { + label: 'Signing Schemes', + fundAccount: true, + fundLedger: true, + hasEoaAuthContext: true, + hasPKP: true, + fundPKP: true, + fundPKPLedger: true, + }); + }); + + it.each(SIGNING_MATRIX)( + 'should sign using %s', + async ({ scheme, chain }) => { + if (!signerAccount.pkp?.pubkey) { + throw new Error('Signer PKP was not initialized'); + } + if (!signerAccount.eoaAuthContext) { + throw new Error('Signer account is missing an EOA auth context'); + } + + const toSign = new TextEncoder().encode( + `Lit signing e2e test using ${scheme}` + ); + + const signature = await testEnv.litClient.chain.raw.pkpSign({ + authContext: signerAccount.eoaAuthContext, + pubKey: signerAccount.pkp.pubkey, + signingScheme: scheme, + chain, + toSign, + userMaxPrice: 100_000_000_000_000_000n, // 0.1 ETH in wei to clear threshold comfortably + }); + + expect(signature.signature).toBeTruthy(); + expect(signature.sigType).toBe(scheme); + } + ); + }); +} diff --git a/packages/networks/src/networks/vNaga/shared/managers/api-manager/pkpSign/pkpSign.InputSchema.ts b/packages/networks/src/networks/vNaga/shared/managers/api-manager/pkpSign/pkpSign.InputSchema.ts index f0a47da46..4db267ba5 100644 --- a/packages/networks/src/networks/vNaga/shared/managers/api-manager/pkpSign/pkpSign.InputSchema.ts +++ b/packages/networks/src/networks/vNaga/shared/managers/api-manager/pkpSign/pkpSign.InputSchema.ts @@ -21,6 +21,7 @@ export const PKPSignInputSchema = z.object({ toSign: z.any(), authContext: z.union([PKPAuthContextSchema, EoaAuthContextSchema]), userMaxPrice: z.bigint().optional(), + bypassAutoHashing: z.boolean().optional(), }); export const EthereumPKPSignInputSchema = PKPSignInputSchema.omit({ diff --git a/packages/networks/src/networks/vNaga/shared/schemas/LitMessageSchema.ts b/packages/networks/src/networks/vNaga/shared/schemas/LitMessageSchema.ts index 42267b9da..d7554ce37 100644 --- a/packages/networks/src/networks/vNaga/shared/schemas/LitMessageSchema.ts +++ b/packages/networks/src/networks/vNaga/shared/schemas/LitMessageSchema.ts @@ -70,10 +70,15 @@ export const chainHashMapper: ChainHashMapper = { EcdsaP384Sha384: sha384, }, - // @ts-ignore TODO: add support for this - cosmos: undefined, + cosmos: { + EcdsaK256Sha256: sha256, + EcdsaP256Sha256: sha256, + EcdsaP384Sha384: sha384, + }, - // @ts-ignore TODO: add support for this + // Solana signatures use Ed25519 (handled by the FROST branch), + // so we intentionally omit it from the ECDSA mapper. + // @ts-ignore solana: undefined, }; @@ -89,9 +94,23 @@ export const LitMessageSchema = z } if (CURVE_GROUP_BY_CURVE_TYPE[signingScheme] === 'ECDSA') { - const hashedMessage = chainHashMapper[chain][ - signingScheme as DesiredEcdsaSchemes - ](new Uint8Array(toSign)); + const chainHasher = chainHashMapper[chain]; + + if (!chainHasher) { + throw new Error( + `Chain "${chain}" does not support ECDSA signing with Lit yet.` + ); + } + + const hashFn = chainHasher[signingScheme as DesiredEcdsaSchemes]; + + if (!hashFn) { + throw new Error( + `Signing scheme "${signingScheme}" is not enabled for chain "${chain}".` + ); + } + + const hashedMessage = hashFn(new Uint8Array(toSign)); return BytesArraySchema.parse(hashedMessage); } From d2ff969413dad4cb9b3c7ee9b5796b5dbffa20fb Mon Sep 17 00:00:00 2001 From: anson Date: Fri, 7 Nov 2025 16:32:34 +0000 Subject: [PATCH 4/5] chore(release): add changeset --- .changeset/gold-ducks-repeat.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/gold-ducks-repeat.md diff --git a/.changeset/gold-ducks-repeat.md b/.changeset/gold-ducks-repeat.md new file mode 100644 index 000000000..f2fc0c924 --- /dev/null +++ b/.changeset/gold-ducks-repeat.md @@ -0,0 +1,6 @@ +--- +'@lit-protocol/networks': patch +'@lit-protocol/e2e': patch +--- + +PKP signing now auto-hashes Cosmos payloads, exposes a documented bypassAutoHashing option, and ships with a new e2e suite plus docs so builders can rely on every listed curve working out of the box. From beebcdfaa98ac4e86074990778941ea7995cf2c6 Mon Sep 17 00:00:00 2001 From: Anson Date: Fri, 7 Nov 2025 16:51:47 +0000 Subject: [PATCH 5/5] Update docs/sdk/auth-context-consumption/pkp-sign.mdx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Anson --- docs/sdk/auth-context-consumption/pkp-sign.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdk/auth-context-consumption/pkp-sign.mdx b/docs/sdk/auth-context-consumption/pkp-sign.mdx index 9858012ee..523fa78f6 100644 --- a/docs/sdk/auth-context-consumption/pkp-sign.mdx +++ b/docs/sdk/auth-context-consumption/pkp-sign.mdx @@ -33,7 +33,7 @@ const signatures = await litClient.chain.raw.pkpSign({ ### Hashing defaults and bypass -By default the network hashes ECDSA payloads for you using the canonical function for each chain (Ethereum → keccak256, Bitcoin/Cosmos → SHA-256/SHA-384) before the nodes sign anything. Schnorr/EdDSA schemes receive the raw bytes exactly as you provided them. If you already computed a digest (for example when signing EIP-712 typed data) you can pass it directly and opt out of the SDK hashing step by setting `bypassAutoHashing: true`: +By default the SDK hashes ECDSA payloads for you using the canonical function for each chain (Ethereum → keccak256, Bitcoin/Cosmos → SHA-256/SHA-384) before sending to the nodes for signing. Schnorr/EdDSA schemes receive the raw bytes exactly as you provided them. If you already computed a digest (for example when signing EIP-712 typed data) you can pass it directly and opt out of the SDK hashing step by setting `bypassAutoHashing: true`: ```ts const digestBytes = hexToBytes(hashTypedData(typedData));