Skip to content

Commit 217d7f6

Browse files
committed
tests(signing): cover hashing + all pkp schemes
1 parent edce647 commit 217d7f6

File tree

5 files changed

+130
-7
lines changed

5 files changed

+130
-7
lines changed

docs/sdk/auth-context-consumption/pkp-sign.mdx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ const signatures = await litClient.chain.raw.pkpSign({
3131
});
3232
```
3333

34+
### Hashing defaults and bypass
35+
36+
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`:
37+
38+
```ts
39+
const digestBytes = hexToBytes(hashTypedData(typedData));
40+
41+
const signature = await litClient.chain.raw.pkpSign({
42+
chain: 'ethereum',
43+
signingScheme: 'EcdsaK256Sha256',
44+
pubKey: pkpInfo.pubkey,
45+
authContext,
46+
toSign: digestBytes,
47+
bypassAutoHashing: true,
48+
});
49+
```
50+
3451
---
3552

3653
# Available signing schemes
@@ -66,4 +83,4 @@ const signatures = await litClient.chain.raw.pkpSign({
6683
| `SchnorrRistretto25519Sha512` | Ristretto25519 |
6784
| `SchnorrRedJubjubBlake2b512` | Jubjub |
6885
| `SchnorrRedDecaf377Blake2b512` | Decaf377 |
69-
| `SchnorrkelSubstrate` | sr25519 |
86+
| `SchnorrkelSubstrate` | sr25519 |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { registerSigningSchemesTicketSuite } from './signing-schemes.suite';
2+
3+
registerSigningSchemesTicketSuite();
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { LitCurve } from '@lit-protocol/constants';
2+
import { SigningChainSchema } from '@lit-protocol/schemas';
3+
import { z } from 'zod';
4+
import { createEnvVars } from '../helper/createEnvVars';
5+
import { createTestAccount } from '../helper/createTestAccount';
6+
import { createTestEnv } from '../helper/createTestEnv';
7+
8+
type SigningChain = z.infer<typeof SigningChainSchema>;
9+
10+
type SchemeUnderTest = {
11+
scheme: LitCurve;
12+
chain: SigningChain;
13+
};
14+
15+
const SIGNING_MATRIX: SchemeUnderTest[] = [
16+
// ECDSA variants
17+
{ scheme: 'EcdsaK256Sha256', chain: 'ethereum' },
18+
{ scheme: 'EcdsaP256Sha256', chain: 'ethereum' },
19+
{ scheme: 'EcdsaP384Sha384', chain: 'ethereum' },
20+
// Schnorr over secp256k1 (Bitcoin / Taproot)
21+
{ scheme: 'SchnorrK256Sha256', chain: 'bitcoin' },
22+
{ scheme: 'SchnorrK256Taproot', chain: 'bitcoin' },
23+
// Schnorr over NIST curves
24+
{ scheme: 'SchnorrP256Sha256', chain: 'cosmos' },
25+
{ scheme: 'SchnorrP384Sha384', chain: 'cosmos' },
26+
// EdDSA-style curves
27+
{ scheme: 'SchnorrEd25519Sha512', chain: 'solana' },
28+
{ scheme: 'SchnorrEd448Shake256', chain: 'solana' },
29+
// ZK / privacy-focused curves
30+
{ scheme: 'SchnorrRistretto25519Sha512', chain: 'solana' },
31+
{ scheme: 'SchnorrRedJubjubBlake2b512', chain: 'solana' },
32+
{ scheme: 'SchnorrRedDecaf377Blake2b512', chain: 'solana' },
33+
{ scheme: 'SchnorrkelSubstrate', chain: 'solana' },
34+
];
35+
36+
export function registerSigningSchemesTicketSuite() {
37+
describe('pkp signing schemes', () => {
38+
let testEnv: Awaited<ReturnType<typeof createTestEnv>>;
39+
let signerAccount: Awaited<ReturnType<typeof createTestAccount>>;
40+
41+
beforeAll(async () => {
42+
const envVars = createEnvVars();
43+
testEnv = await createTestEnv(envVars);
44+
signerAccount = await createTestAccount(testEnv, {
45+
label: 'Signing Schemes',
46+
fundAccount: true,
47+
fundLedger: true,
48+
hasEoaAuthContext: true,
49+
hasPKP: true,
50+
fundPKP: true,
51+
fundPKPLedger: true,
52+
});
53+
});
54+
55+
it.each(SIGNING_MATRIX)(
56+
'should sign using %s',
57+
async ({ scheme, chain }) => {
58+
if (!signerAccount.pkp?.pubkey) {
59+
throw new Error('Signer PKP was not initialized');
60+
}
61+
if (!signerAccount.eoaAuthContext) {
62+
throw new Error('Signer account is missing an EOA auth context');
63+
}
64+
65+
const toSign = new TextEncoder().encode(
66+
`Lit signing e2e test using ${scheme}`
67+
);
68+
69+
const signature = await testEnv.litClient.chain.raw.pkpSign({
70+
authContext: signerAccount.eoaAuthContext,
71+
pubKey: signerAccount.pkp.pubkey,
72+
signingScheme: scheme,
73+
chain,
74+
toSign,
75+
userMaxPrice: 100_000_000_000_000_000n, // 0.1 ETH in wei to clear threshold comfortably
76+
});
77+
78+
expect(signature.signature).toBeTruthy();
79+
expect(signature.sigType).toBe(scheme);
80+
}
81+
);
82+
});
83+
}

packages/networks/src/networks/vNaga/shared/managers/api-manager/pkpSign/pkpSign.InputSchema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const PKPSignInputSchema = z.object({
2121
toSign: z.any(),
2222
authContext: z.union([PKPAuthContextSchema, EoaAuthContextSchema]),
2323
userMaxPrice: z.bigint().optional(),
24+
bypassAutoHashing: z.boolean().optional(),
2425
});
2526

2627
export const EthereumPKPSignInputSchema = PKPSignInputSchema.omit({

packages/networks/src/networks/vNaga/shared/schemas/LitMessageSchema.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,15 @@ export const chainHashMapper: ChainHashMapper = {
7070
EcdsaP384Sha384: sha384,
7171
},
7272

73-
// @ts-ignore TODO: add support for this
74-
cosmos: undefined,
73+
cosmos: {
74+
EcdsaK256Sha256: sha256,
75+
EcdsaP256Sha256: sha256,
76+
EcdsaP384Sha384: sha384,
77+
},
7578

76-
// @ts-ignore TODO: add support for this
79+
// Solana signatures use Ed25519 (handled by the FROST branch),
80+
// so we intentionally omit it from the ECDSA mapper.
81+
// @ts-ignore
7782
solana: undefined,
7883
};
7984

@@ -89,9 +94,23 @@ export const LitMessageSchema = z
8994
}
9095

9196
if (CURVE_GROUP_BY_CURVE_TYPE[signingScheme] === 'ECDSA') {
92-
const hashedMessage = chainHashMapper[chain][
93-
signingScheme as DesiredEcdsaSchemes
94-
](new Uint8Array(toSign));
97+
const chainHasher = chainHashMapper[chain];
98+
99+
if (!chainHasher) {
100+
throw new Error(
101+
`Chain "${chain}" does not support ECDSA signing with Lit yet.`
102+
);
103+
}
104+
105+
const hashFn = chainHasher[signingScheme as DesiredEcdsaSchemes];
106+
107+
if (!hashFn) {
108+
throw new Error(
109+
`Signing scheme "${signingScheme}" is not enabled for chain "${chain}".`
110+
);
111+
}
112+
113+
const hashedMessage = hashFn(new Uint8Array(toSign));
95114
return BytesArraySchema.parse(hashedMessage);
96115
}
97116

0 commit comments

Comments
 (0)