Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 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));

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