Skip to content

Commit ab70116

Browse files
committed
feat: ecdsa signing schemes support
1 parent eac65fa commit ab70116

File tree

25 files changed

+434
-100
lines changed

25 files changed

+434
-100
lines changed

local-tests/build.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const createBuildConfig = (entry, outfile, globalName) => ({
3333
platform: 'node',
3434
target: 'esnext',
3535
format: 'esm',
36+
sourcemap: true,
3637
inject: [getPath('./shim.mjs')],
3738
mainFields: ['module', 'main'],
3839
...(globalName ? { globalName } : {}),

local-tests/setup/shiva-client.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type ContractAbis = {
1010
pubkeyRouter: string;
1111
pkpPermissions: string;
1212
pkpHelper: string;
13+
priceFeed: string;
1314
contractResolver: string;
1415
paymentDelegation: string;
1516
};

local-tests/setup/shiva-client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ export class TestnetClient {
116116
PKPHelper: {
117117
abi: JSON.parse(testNetConfig.contractAbis.pkpHelper),
118118
},
119+
PriceFeed: {
120+
abi: JSON.parse(testNetConfig.contractAbis.priceFeed),
121+
},
119122
LITToken: {
120123
abi: JSON.parse(testNetConfig.contractAbis.litToken),
121124
},

local-tests/test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { runInBand, runTestsParallel } from './setup/tinny-operations';
33
// import { testBundleSpeed } from './tests/test-bundle-speed';
44
// import { testExample } from './tests/test-example';
55
import { testUseEoaSessionSigsToExecuteJsSigning } from './tests/testUseEoaSessionSigsToExecuteJsSigning';
6-
import { testUseEoaSessionSigsToPkpSign } from './tests/testUseEoaSessionSigsToPkpSign';
6+
import { testUseEoaSessionSigsToPkpSignK256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignK256';
7+
import { testUseEoaSessionSigsToPkpSignP256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP256';
8+
import { testUseEoaSessionSigsToPkpSignP384 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP384';
79
import { testUsePkpSessionSigsToExecuteJsSigning } from './tests/testUsePkpSessionSigsToExecuteJsSigning';
810
import { testUsePkpSessionSigsToPkpSign } from './tests/testUsePkpSessionSigsToPkpSign';
911
import { testUseValidLitActionCodeGeneratedSessionSigsToPkpSign } from './tests/testUseValidLitActionCodeGeneratedSessionSigsToPkpSign';
@@ -163,7 +165,9 @@ setLitActionsCodeToLocal();
163165

164166
const eoaSessionSigsTests = {
165167
testUseEoaSessionSigsToExecuteJsSigning,
166-
testUseEoaSessionSigsToPkpSign,
168+
testUseEoaSessionSigsToPkpSignK256,
169+
testUseEoaSessionSigsToPkpSignP256,
170+
testUseEoaSessionSigsToPkpSignP384,
167171
testUseEoaSessionSigsToExecuteJsSigningInParallel,
168172
testUseEoaSessionSigsToExecuteJsClaimKeys,
169173
testUseEoaSessionSigsToExecuteJsClaimMultipleKeys,

local-tests/tests.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { testUseEoaSessionSigsToExecuteJsSigning } from './tests/testUseEoaSessionSigsToExecuteJsSigning';
2-
import { testUseEoaSessionSigsToPkpSign } from './tests/testUseEoaSessionSigsToPkpSign';
2+
import { testUseEoaSessionSigsToPkpSignK256 } from './tests/testUseEoaSessionSigsToPkpSignK256';
3+
import { testUseEoaSessionSigsToPkpSignP256 } from './tests/testUseEoaSessionSigsToPkpSignP256';
4+
import { testUseEoaSessionSigsToPkpSignP384 } from './tests/testUseEoaSessionSigsToPkpSignP384';
35
import { testUsePkpSessionSigsToExecuteJsSigning } from './tests/testUsePkpSessionSigsToExecuteJsSigning';
46
import { testUsePkpSessionSigsToPkpSign } from './tests/testUsePkpSessionSigsToPkpSign';
57
import { testUseValidLitActionCodeGeneratedSessionSigsToPkpSign } from './tests/testUseValidLitActionCodeGeneratedSessionSigsToPkpSign';
@@ -103,7 +105,9 @@ import { testFailBatchGeneratePrivateKeysAtomic } from './tests/wrapped-keys/tes
103105
import { testUseEoaSessionSigsToRequestSingleResponse } from './tests/testUseEoaSessionSigsToRequestSingleResponse';
104106

105107
export { testUseEoaSessionSigsToExecuteJsSigning } from './tests/testUseEoaSessionSigsToExecuteJsSigning';
106-
export { testUseEoaSessionSigsToPkpSign } from './tests/testUseEoaSessionSigsToPkpSign';
108+
export { testUseEoaSessionSigsToPkpSignK256 } from './tests/testUseEoaSessionSigsToPkpSignK256';
109+
export { testUseEoaSessionSigsToPkpSignP256 } from './tests/testUseEoaSessionSigsToPkpSignP256';
110+
export { testUseEoaSessionSigsToPkpSignP384 } from './tests/testUseEoaSessionSigsToPkpSignP384';
107111
export { testUsePkpSessionSigsToExecuteJsSigning } from './tests/testUsePkpSessionSigsToExecuteJsSigning';
108112
export { testUsePkpSessionSigsToPkpSign } from './tests/testUsePkpSessionSigsToPkpSign';
109113
export { testUseValidLitActionCodeGeneratedSessionSigsToPkpSign } from './tests/testUseValidLitActionCodeGeneratedSessionSigsToPkpSign';
@@ -251,7 +255,9 @@ const wrappedKeysTests = {
251255

252256
const eoaSessionSigsTests = {
253257
testUseEoaSessionSigsToExecuteJsSigning,
254-
testUseEoaSessionSigsToPkpSign,
258+
testUseEoaSessionSigsToPkpSignK256,
259+
testUseEoaSessionSigsToPkpSignP256,
260+
testUseEoaSessionSigsToPkpSignP384,
255261
testUseEoaSessionSigsToExecuteJsSigningInParallel,
256262
testUseEoaSessionSigsToExecuteJsClaimKeys,
257263
testUseEoaSessionSigsToExecuteJsClaimMultipleKeys,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import EC from 'elliptic';
2+
import { createHash } from 'crypto';
3+
4+
import { log } from '@lit-protocol/misc';
5+
import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs';
6+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
7+
8+
/**
9+
* Test Commands:
10+
* ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSignK256
11+
* ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSignK256
12+
* ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSignK256
13+
*/
14+
export const testUseEoaSessionSigsToPkpSignK256 = async (
15+
devEnv: TinnyEnvironment
16+
) => {
17+
const alice = await devEnv.createRandomPerson();
18+
const messageToSign = [1, 2, 3, 4, 5];
19+
const messageHash = createHash('sha256')
20+
.update(Buffer.from(messageToSign))
21+
.digest();
22+
23+
const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice);
24+
const runWithSessionSigs = await devEnv.litNodeClient.pkpSign({
25+
pubKey: alice.pkp.publicKey,
26+
sessionSigs: eoaSessionSigs,
27+
toSign: messageHash,
28+
signingScheme: 'EcdsaK256Sha256',
29+
});
30+
31+
devEnv.releasePrivateKeyFromUser(alice);
32+
33+
// -- assertions
34+
// r, s, dataSigned, and public key should be present
35+
if (!runWithSessionSigs.r) {
36+
throw new Error(`Expected "r" in runWithSessionSigs`);
37+
}
38+
if (!runWithSessionSigs.s) {
39+
throw new Error(`Expected "s" in runWithSessionSigs`);
40+
}
41+
if (!runWithSessionSigs.dataSigned) {
42+
throw new Error(`Expected "dataSigned" in runWithSessionSigs`);
43+
}
44+
if (!runWithSessionSigs.publicKey) {
45+
throw new Error(`Expected "publicKey" in runWithSessionSigs`);
46+
}
47+
48+
// signature must start with 0x
49+
if (!runWithSessionSigs.signature.startsWith('0x')) {
50+
throw new Error(`Expected "signature" to start with 0x`);
51+
}
52+
53+
// recid must be parseable as a number
54+
if (isNaN(runWithSessionSigs.recid)) {
55+
throw new Error(`Expected "recid" to be parseable as a number`);
56+
}
57+
58+
const ec = new EC.ec('secp256k1');
59+
60+
// Public key derived from message and signature
61+
const recoveredPubKey = ec.recoverPubKey(
62+
messageHash,
63+
runWithSessionSigs,
64+
runWithSessionSigs.recid
65+
);
66+
// Public key returned from nodes
67+
const runWithSessionSigsUncompressedPublicKey = ec
68+
.keyFromPublic(runWithSessionSigs.publicKey, 'hex')
69+
.getPublic(false, 'hex');
70+
71+
if (
72+
runWithSessionSigsUncompressedPublicKey !==
73+
recoveredPubKey.encode('hex', false)
74+
) {
75+
throw new Error(
76+
`Expected recovered public key to match runWithSessionSigsUncompressedPublicKey and recoveredPubKey.encode('hex', false)`
77+
);
78+
}
79+
if (recoveredPubKey.encode('hex', false) !== alice.pkp.publicKey) {
80+
throw new Error(
81+
`Expected recovered public key to match alice.pkp.publicKey`
82+
);
83+
}
84+
85+
log('✅ testUseEoaSessionSigsToPkpSignK256');
86+
};
Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,35 @@
1-
import { ethers } from 'ethers';
1+
import EC from 'elliptic';
2+
import { createHash } from 'crypto';
23

34
import { log } from '@lit-protocol/misc';
45
import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs';
56
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
67

78
/**
89
* Test Commands:
9-
* ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSign
10-
* ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSign
11-
* ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSign
10+
* ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSignP256
11+
* ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSignP256
12+
* ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSignP256
1213
*/
13-
export const testUseEoaSessionSigsToPkpSign = async (
14+
export const testUseEoaSessionSigsToPkpSignP256 = async (
1415
devEnv: TinnyEnvironment
1516
) => {
1617
const alice = await devEnv.createRandomPerson();
18+
const messageToSign = [1, 2, 3, 4, 5];
19+
const messageHash = createHash('sha256')
20+
.update(Buffer.from(messageToSign))
21+
.digest();
1722

1823
const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice);
1924
const runWithSessionSigs = await devEnv.litNodeClient.pkpSign({
20-
toSign: alice.loveLetter,
2125
pubKey: alice.pkp.publicKey,
2226
sessionSigs: eoaSessionSigs,
27+
toSign: messageHash,
28+
signingScheme: 'EcdsaP256Sha256',
2329
});
2430

2531
devEnv.releasePrivateKeyFromUser(alice);
2632

27-
// Expected output:
28-
// {
29-
// r: "25fc0d2fecde8ed801e9fee5ad26f2cf61d82e6f45c8ad1ad1e4798d3b747fd9",
30-
// s: "549fe745b4a09536e6e7108d814cf7e44b93f1d73c41931b8d57d1b101833214",
31-
// recid: 1,
32-
// signature: "0x25fc0d2fecde8ed801e9fee5ad26f2cf61d82e6f45c8ad1ad1e4798d3b747fd9549fe745b4a09536e6e7108d814cf7e44b93f1d73c41931b8d57d1b1018332141c",
33-
// publicKey: "04A3CD53CCF63597D3FFCD1DF1E8236F642C7DF8196F532C8104625635DC55A1EE59ABD2959077432FF635DF2CED36CC153050902B71291C4D4867E7DAAF964049",
34-
// dataSigned: "7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4",
35-
// }
36-
3733
// -- assertions
3834
// r, s, dataSigned, and public key should be present
3935
if (!runWithSessionSigs.r) {
@@ -59,29 +55,33 @@ export const testUseEoaSessionSigsToPkpSign = async (
5955
throw new Error(`Expected "recid" to be parseable as a number`);
6056
}
6157

62-
const signature = ethers.utils.joinSignature({
63-
r: '0x' + runWithSessionSigs.r,
64-
s: '0x' + runWithSessionSigs.s,
65-
recoveryParam: runWithSessionSigs.recid,
66-
});
67-
const recoveredPubKey = ethers.utils.recoverPublicKey(
68-
alice.loveLetter,
69-
signature
70-
);
58+
const ec = new EC.ec('p256');
7159

72-
console.log('recoveredPubKey:', recoveredPubKey);
60+
// Public key derived from message and signature
61+
const recoveredPubKey = ec.recoverPubKey(
62+
messageHash,
63+
runWithSessionSigs,
64+
runWithSessionSigs.recid
65+
);
66+
// Public key returned from nodes
67+
const runWithSessionSigsUncompressedPublicKey = ec
68+
.keyFromPublic(runWithSessionSigs.publicKey, 'hex')
69+
.getPublic(false, 'hex');
7370

74-
// FIXME: Consider adding these assertions back after the v flipping PR is merged
75-
// if (recoveredPubKey !== `0x${runWithSessionSigs.publicKey.toLowerCase()}`) {
76-
// throw new Error(
77-
// `Expected recovered public key to match runWithSessionSigs.publicKey`
78-
// );
79-
// }
80-
// if (recoveredPubKey !== `0x${alice.pkp.publicKey.toLowerCase()}`) {
71+
if (
72+
runWithSessionSigsUncompressedPublicKey !==
73+
recoveredPubKey.encode('hex', false)
74+
) {
75+
throw new Error(
76+
`Expected recovered public key to match runWithSessionSigsUncompressedPublicKey and recoveredPubKey.encode('hex', false)`
77+
);
78+
}
79+
// PKP public key lives in k256, it cannot be directly compared
80+
// if (recoveredPubKey.encode('hex', false) !== alice.pkp.publicKey) {
8181
// throw new Error(
8282
// `Expected recovered public key to match alice.pkp.publicKey`
8383
// );
8484
// }
8585

86-
log('✅ testUseEoaSessionSigsToPkpSign');
86+
log('✅ testUseEoaSessionSigsToPkpSignP256');
8787
};
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import EC from 'elliptic';
2+
import { createHash } from 'crypto';
3+
4+
import { log } from '@lit-protocol/misc';
5+
import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs';
6+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
7+
8+
/**
9+
* Test Commands:
10+
* ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSignP384
11+
* ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSignP384
12+
* ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSignP384
13+
*/
14+
export const testUseEoaSessionSigsToPkpSignP384 = async (
15+
devEnv: TinnyEnvironment
16+
) => {
17+
const alice = await devEnv.createRandomPerson();
18+
const messageToSign = [1, 2, 3, 4, 5];
19+
const messageHash = createHash('sha384')
20+
.update(Buffer.from(messageToSign))
21+
.digest();
22+
23+
const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice);
24+
const runWithSessionSigs = await devEnv.litNodeClient.pkpSign({
25+
pubKey: alice.pkp.publicKey,
26+
sessionSigs: eoaSessionSigs,
27+
toSign: messageHash,
28+
signingScheme: 'EcdsaP384Sha384',
29+
});
30+
31+
devEnv.releasePrivateKeyFromUser(alice);
32+
33+
// -- assertions
34+
// r, s, dataSigned, and public key should be present
35+
if (!runWithSessionSigs.r) {
36+
throw new Error(`Expected "r" in runWithSessionSigs`);
37+
}
38+
if (!runWithSessionSigs.s) {
39+
throw new Error(`Expected "s" in runWithSessionSigs`);
40+
}
41+
if (!runWithSessionSigs.dataSigned) {
42+
throw new Error(`Expected "dataSigned" in runWithSessionSigs`);
43+
}
44+
if (!runWithSessionSigs.publicKey) {
45+
throw new Error(`Expected "publicKey" in runWithSessionSigs`);
46+
}
47+
48+
// signature must start with 0x
49+
if (!runWithSessionSigs.signature.startsWith('0x')) {
50+
throw new Error(`Expected "signature" to start with 0x`);
51+
}
52+
53+
// recid must be parseable as a number
54+
if (isNaN(runWithSessionSigs.recid)) {
55+
throw new Error(`Expected "recid" to be parseable as a number`);
56+
}
57+
58+
const ec = new EC.ec('p384');
59+
60+
// Public key derived from message and signature
61+
const recoveredPubKey = ec.recoverPubKey(
62+
messageHash,
63+
runWithSessionSigs,
64+
runWithSessionSigs.recid
65+
); // Error: The recovery param is more than two bits
66+
// Public key returned from nodes
67+
const runWithSessionSigsUncompressedPublicKey = ec
68+
.keyFromPublic(runWithSessionSigs.publicKey, 'hex')
69+
.getPublic(false, 'hex');
70+
71+
if (
72+
runWithSessionSigsUncompressedPublicKey !==
73+
recoveredPubKey.encode('hex', false)
74+
) {
75+
throw new Error(
76+
`Expected recovered public key to match runWithSessionSigsUncompressedPublicKey and recoveredPubKey.encode('hex', false)`
77+
);
78+
}
79+
// PKP public key lives in k256, it cannot be directly compared
80+
// if (recoveredPubKey.encode('hex', false) !== alice.pkp.publicKey) {
81+
// throw new Error(
82+
// `Expected recovered public key to match alice.pkp.publicKey`
83+
// );
84+
// }
85+
86+
log('✅ testUseEoaSessionSigsToPkpSignP384');
87+
};

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"cross-fetch": "3.1.8",
6262
"date-and-time": "^2.4.1",
6363
"depd": "^2.0.0",
64+
"elliptic": "^6.6.1",
6465
"ethers": "^5.7.1",
6566
"jose": "^4.14.4",
6667
"micromodal": "^0.4.10",
@@ -87,6 +88,7 @@
8788
"@nx/web": "17.3.0",
8889
"@solana/web3.js": "1.95.3",
8990
"@types/depd": "^1.1.36",
91+
"@types/elliptic": "^6.4.18",
9092
"@types/events": "^3.0.3",
9193
"@types/jest": "27.4.1",
9294
"@types/node": "18.19.18",

packages/constants/src/lib/constants/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,8 @@ export const LIT_CURVE = {
12921292
EcdsaCaitSith: 'ECDSA_CAIT_SITH', // Legacy alias of K256
12931293
EcdsaCAITSITHP256: 'EcdsaCaitSithP256',
12941294
EcdsaK256Sha256: 'EcdsaK256Sha256', // same as caitsith
1295+
EcdsaP256Sha256: 'EcdsaP256Sha256',
1296+
EcdsaP384Sha384: 'EcdsaP384Sha384',
12951297
} as const;
12961298

12971299
export type LIT_CURVE_TYPE = keyof typeof LIT_CURVE;

0 commit comments

Comments
 (0)