Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/gold-ducks-repeat.md
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 18 additions & 1 deletion docs/sdk/auth-context-consumption/pkp-sign.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -66,4 +83,4 @@ const signatures = await litClient.chain.raw.pkpSign({
| `SchnorrRistretto25519Sha512` | Ristretto25519 |
| `SchnorrRedJubjubBlake2b512` | Jubjub |
| `SchnorrRedDecaf377Blake2b512` | Decaf377 |
| `SchnorrkelSubstrate` | sr25519 |
| `SchnorrkelSubstrate` | sr25519 |
3 changes: 3 additions & 0 deletions packages/e2e/src/tickets/signing-schemes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { registerSigningSchemesTicketSuite } from './signing-schemes.suite';

registerSigningSchemesTicketSuite();
83 changes: 83 additions & 0 deletions packages/e2e/src/tickets/signing-schemes.suite.ts
Original file line number Diff line number Diff line change
@@ -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<typeof SigningChainSchema>;

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<ReturnType<typeof createTestEnv>>;
let signerAccount: Awaited<ReturnType<typeof createTestAccount>>;

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);
}
);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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);
}

Expand Down
Loading