Skip to content

Commit a739bfa

Browse files
committed
feat(tests): add Ethereum typed data signing tests
- Introduced tests for signing typed data with both wrapped and generated Ethereum keys. - Added failure tests for invalid parameters and decryption scenarios. - Updated utility functions to derive Ethereum addresses from generated public keys. - Enhanced existing test suite to include new test cases for Ethereum typed data signing.
1 parent 26abbaf commit a739bfa

7 files changed

+583
-0
lines changed

local-tests/test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ import { testSignMessageWithSolanaEncryptedKey } from './tests/wrapped-keys/test
110110
import { testSignTransactionWithSolanaEncryptedKey } from './tests/wrapped-keys/testSignTransactionWithSolanaEncryptedKey';
111111
import { testBatchGeneratePrivateKeys } from './tests/wrapped-keys/testBatchGeneratePrivateKeys';
112112
import { testFailBatchGeneratePrivateKeysAtomic } from './tests/wrapped-keys/testFailStoreEncryptedKeyBatchIsAtomic';
113+
import { testEthereumSignTypedDataWrappedKey } from './tests/wrapped-keys/testEthereumSignTypedDataWrappedKey';
114+
import { testEthereumSignTypedDataGeneratedKey } from './tests/wrapped-keys/testEthereumSignTypedDataGeneratedKey';
115+
import { testFailEthereumSignTypedDataWrappedKeyWithMissingParam } from './tests/wrapped-keys/testFailEthereumSignTypedDataWrappedKeyWithMissingParam';
116+
import { testFailEthereumSignTypedDataWrappedKeyWithInvalidParam } from './tests/wrapped-keys/testFailEthereumSignTypedDataWrappedKeyWithInvalidParam';
117+
import { testFailEthereumSignTypedDataWrappedKeyInvalidDecryption } from './tests/wrapped-keys/testFailEthereumSignTypedDataWrappedKeyInvalidDecryption';
113118

114119
import { setLitActionsCodeToLocal } from './tests/wrapped-keys/util';
115120
import { testUseEoaSessionSigsToRequestSingleResponse } from './tests/testUseEoaSessionSigsToRequestSingleResponse';
@@ -140,6 +145,10 @@ setLitActionsCodeToLocal();
140145
testEthereumBroadcastTransactionWrappedKey,
141146
testEthereumBroadcastWrappedKeyWithFetchGasParams,
142147

148+
// -- typed data signing
149+
testEthereumSignTypedDataWrappedKey,
150+
testEthereumSignTypedDataGeneratedKey,
151+
143152
// -- generate wrapped keys
144153
testGenerateEthereumWrappedKey,
145154
testGenerateSolanaWrappedKey,
@@ -160,6 +169,11 @@ setLitActionsCodeToLocal();
160169
testFailEthereumSignTransactionWrappedKeyInvalidDecryption,
161170
testFailBatchGeneratePrivateKeysAtomic,
162171

172+
// -- typed data signing invalid cases
173+
testFailEthereumSignTypedDataWrappedKeyWithMissingParam,
174+
testFailEthereumSignTypedDataWrappedKeyWithInvalidParam,
175+
testFailEthereumSignTypedDataWrappedKeyInvalidDecryption,
176+
163177
// -- import wrapped keys
164178
testFailImportWrappedKeysWithSamePrivateKey,
165179
testFailImportWrappedKeysWithEoaSessionSig,
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { log } from '@lit-protocol/misc';
2+
import { ethers } from 'ethers';
3+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
4+
import { api } from '@lit-protocol/wrapped-keys';
5+
import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs';
6+
import { deriveAddressFromGeneratedPublicKey } from './util';
7+
8+
const { generatePrivateKey, signTypedDataWithEncryptedKey } = api;
9+
10+
/**
11+
* Test Commands:
12+
* ✅ NETWORK=datil-dev yarn test:local --filter=testEthereumSignTypedDataGeneratedKey
13+
* ✅ NETWORK=datil-test yarn test:local --filter=testEthereumSignTypedDataGeneratedKey
14+
* ✅ NETWORK=custom yarn test:local --filter=testEthereumSignTypedDataGeneratedKey
15+
*/
16+
export const testEthereumSignTypedDataGeneratedKey = async (
17+
devEnv: TinnyEnvironment
18+
) => {
19+
const alice = await devEnv.createRandomPerson();
20+
21+
try {
22+
const pkpSessionSigs = await getPkpSessionSigs(
23+
devEnv,
24+
alice,
25+
null,
26+
new Date(Date.now() + 1000 * 60 * 10).toISOString()
27+
); // 10 mins expiry
28+
29+
const { pkpAddress, id, generatedPublicKey } = await generatePrivateKey({
30+
pkpSessionSigs,
31+
network: 'evm',
32+
litNodeClient: devEnv.litNodeClient,
33+
memo: 'Test key',
34+
});
35+
36+
const alicePkpAddress = alice.authMethodOwnedPkp.ethAddress;
37+
if (pkpAddress !== alicePkpAddress) {
38+
throw new Error(
39+
`Received address: ${pkpAddress} doesn't match Alice's PKP address: ${alicePkpAddress}`
40+
);
41+
}
42+
43+
const aliceWrappedKeyAddress =
44+
deriveAddressFromGeneratedPublicKey(generatedPublicKey);
45+
46+
const pkpSessionSigsSigning = await getPkpSessionSigs(
47+
devEnv,
48+
alice,
49+
null,
50+
new Date(Date.now() + 1000 * 60 * 10).toISOString()
51+
); // 10 mins expiry
52+
53+
// Test EIP-712 typed data with different structure
54+
const typedData = {
55+
domain: {
56+
name: 'TestApp',
57+
version: '2',
58+
chainId: 1,
59+
verifyingContract: '0x1234567890123456789012345678901234567890',
60+
},
61+
types: {
62+
Transaction: [
63+
{ name: 'to', type: 'address' },
64+
{ name: 'value', type: 'uint256' },
65+
{ name: 'data', type: 'bytes' },
66+
{ name: 'operation', type: 'uint8' },
67+
{ name: 'safeTxGas', type: 'uint256' },
68+
{ name: 'baseGas', type: 'uint256' },
69+
{ name: 'gasPrice', type: 'uint256' },
70+
{ name: 'gasToken', type: 'address' },
71+
{ name: 'refundReceiver', type: 'address' },
72+
{ name: 'nonce', type: 'uint256' },
73+
],
74+
},
75+
value: {
76+
to: '0x1234567890123456789012345678901234567890',
77+
value: '0',
78+
data: '0x',
79+
operation: 0,
80+
safeTxGas: 0,
81+
baseGas: 0,
82+
gasPrice: 0,
83+
gasToken: '0x0000000000000000000000000000000000000000',
84+
refundReceiver: '0x0000000000000000000000000000000000000000',
85+
nonce: 0,
86+
},
87+
};
88+
89+
const signature = await signTypedDataWithEncryptedKey({
90+
pkpSessionSigs: pkpSessionSigsSigning,
91+
network: 'evm',
92+
messageToSign: typedData,
93+
litNodeClient: devEnv.litNodeClient,
94+
id,
95+
});
96+
97+
if (!ethers.utils.isHexString(signature)) {
98+
throw new Error(`signature isn't hex: ${signature}`);
99+
}
100+
101+
// Verify the signature is valid by recovering the address
102+
const recoveredAddress = ethers.utils.verifyTypedData(
103+
typedData.domain,
104+
typedData.types,
105+
typedData.value,
106+
signature
107+
);
108+
109+
if (
110+
recoveredAddress.toLowerCase() !== aliceWrappedKeyAddress.toLowerCase()
111+
) {
112+
throw new Error(
113+
`Recovered address: ${recoveredAddress} doesn't match Wrapped Key address: ${aliceWrappedKeyAddress}`
114+
);
115+
}
116+
117+
log('✅ testEthereumSignTypedDataGeneratedKey');
118+
} finally {
119+
devEnv.releasePrivateKeyFromUser(alice);
120+
}
121+
};
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { log } from '@lit-protocol/misc';
2+
import { ethers } from 'ethers';
3+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
4+
import { api } from '@lit-protocol/wrapped-keys';
5+
import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs';
6+
import { deriveAddressFromGeneratedPublicKey } from './util';
7+
8+
const { importPrivateKey, signTypedDataWithEncryptedKey } = api;
9+
10+
/**
11+
* Test Commands:
12+
* ✅ NETWORK=datil-dev yarn test:local --filter=testEthereumSignTypedDataWrappedKey
13+
* ✅ NETWORK=datil-test yarn test:local --filter=testEthereumSignTypedDataWrappedKey
14+
* ✅ NETWORK=custom yarn test:local --filter=testEthereumSignTypedDataWrappedKey
15+
*/
16+
export const testEthereumSignTypedDataWrappedKey = async (
17+
devEnv: TinnyEnvironment
18+
) => {
19+
const alice = await devEnv.createRandomPerson();
20+
21+
try {
22+
const pkpSessionSigs = await getPkpSessionSigs(
23+
devEnv,
24+
alice,
25+
null,
26+
new Date(Date.now() + 1000 * 60 * 10).toISOString()
27+
); // 10 mins expiry
28+
29+
const aliceWrappedKey = ethers.Wallet.createRandom();
30+
const privateKey = aliceWrappedKey.privateKey;
31+
const aliceWrappedKeyAddress = aliceWrappedKey.address;
32+
33+
const { pkpAddress, id } = await importPrivateKey({
34+
pkpSessionSigs,
35+
privateKey,
36+
litNodeClient: devEnv.litNodeClient,
37+
publicKey: aliceWrappedKeyAddress,
38+
keyType: 'K256',
39+
memo: 'Test key',
40+
});
41+
42+
const alicePkpAddress = alice.authMethodOwnedPkp.ethAddress;
43+
if (pkpAddress !== alicePkpAddress) {
44+
throw new Error(
45+
`Received address: ${pkpAddress} doesn't match Alice's PKP address: ${alicePkpAddress}`
46+
);
47+
}
48+
49+
const pkpSessionSigsSigning = await getPkpSessionSigs(
50+
devEnv,
51+
alice,
52+
null,
53+
new Date(Date.now() + 1000 * 60 * 10).toISOString()
54+
); // 10 mins expiry
55+
56+
// Test EIP-712 typed data
57+
const typedData = {
58+
domain: {
59+
name: 'TestApp',
60+
version: '1',
61+
chainId: 1,
62+
verifyingContract: '0x1234567890123456789012345678901234567890',
63+
},
64+
types: {
65+
Person: [
66+
{ name: 'name', type: 'string' },
67+
{ name: 'wallet', type: 'address' },
68+
],
69+
Mail: [
70+
{ name: 'from', type: 'Person' },
71+
{ name: 'to', type: 'Person' },
72+
{ name: 'contents', type: 'string' },
73+
],
74+
},
75+
value: {
76+
from: {
77+
name: 'Alice',
78+
wallet: alice.wallet.address,
79+
},
80+
to: {
81+
name: 'Bob',
82+
wallet: '0x1234567890123456789012345678901234567890',
83+
},
84+
contents: 'Hello, Bob!',
85+
},
86+
};
87+
88+
const signature = await signTypedDataWithEncryptedKey({
89+
pkpSessionSigs: pkpSessionSigsSigning,
90+
network: 'evm',
91+
messageToSign: typedData,
92+
litNodeClient: devEnv.litNodeClient,
93+
id,
94+
});
95+
96+
// console.log('signature');
97+
// console.log(signature);
98+
99+
if (!ethers.utils.isHexString(signature)) {
100+
throw new Error(`signature isn't hex: ${signature}`);
101+
}
102+
103+
// Verify the signature is valid by recovering the address
104+
const recoveredAddress = ethers.utils.verifyTypedData(
105+
typedData.domain,
106+
typedData.types,
107+
typedData.value,
108+
signature
109+
);
110+
111+
if (
112+
recoveredAddress.toLowerCase() !== aliceWrappedKeyAddress.toLowerCase()
113+
) {
114+
throw new Error(
115+
`Recovered address: ${recoveredAddress} doesn't match PKP address: ${pkpAddress}`
116+
);
117+
}
118+
119+
log('✅ testEthereumSignTypedDataWrappedKey');
120+
} finally {
121+
devEnv.releasePrivateKeyFromUser(alice);
122+
}
123+
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { log } from '@lit-protocol/misc';
2+
import { ethers } from 'ethers';
3+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
4+
import { api } from '@lit-protocol/wrapped-keys';
5+
import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs';
6+
7+
const { importPrivateKey, signTypedDataWithEncryptedKey } = api;
8+
9+
/**
10+
* Test Commands:
11+
* ✅ NETWORK=datil-dev yarn test:local --filter=testFailEthereumSignTypedDataWrappedKeyInvalidDecryption
12+
* ✅ NETWORK=datil-test yarn test:local --filter=testFailEthereumSignTypedDataWrappedKeyInvalidDecryption
13+
* ✅ NETWORK=custom yarn test:local --filter=testFailEthereumSignTypedDataWrappedKeyInvalidDecryption
14+
*/
15+
export const testFailEthereumSignTypedDataWrappedKeyInvalidDecryption = async (
16+
devEnv: TinnyEnvironment
17+
) => {
18+
const alice = await devEnv.createRandomPerson();
19+
20+
try {
21+
const pkpSessionSigs = await getPkpSessionSigs(
22+
devEnv,
23+
alice,
24+
null,
25+
new Date(Date.now() + 1000 * 60 * 10).toISOString()
26+
); // 10 mins expiry
27+
28+
const aliceWrappedKey = ethers.Wallet.createRandom();
29+
const privateKey = aliceWrappedKey.privateKey;
30+
const aliceWrappedKeyAddress = aliceWrappedKey.address;
31+
32+
const { pkpAddress, id } = await importPrivateKey({
33+
pkpSessionSigs,
34+
privateKey,
35+
litNodeClient: devEnv.litNodeClient,
36+
publicKey: aliceWrappedKeyAddress,
37+
keyType: 'K256',
38+
memo: 'Test key',
39+
});
40+
41+
const alicePkpAddress = alice.authMethodOwnedPkp.ethAddress;
42+
if (pkpAddress !== alicePkpAddress) {
43+
throw new Error(
44+
`Received address: ${pkpAddress} doesn't match Alice's PKP address: ${alicePkpAddress}`
45+
);
46+
}
47+
48+
// Use a different user's session sigs to try to decrypt the key
49+
const bob = await devEnv.createRandomPerson();
50+
const bobPkpSessionSigs = await getPkpSessionSigs(
51+
devEnv,
52+
bob,
53+
null,
54+
new Date(Date.now() + 1000 * 60 * 10).toISOString()
55+
); // 10 mins expiry
56+
57+
const typedData = {
58+
domain: {
59+
name: 'TestApp',
60+
version: '1',
61+
chainId: 1,
62+
verifyingContract: '0x1234567890123456789012345678901234567890',
63+
},
64+
types: {
65+
Person: [
66+
{ name: 'name', type: 'string' },
67+
{ name: 'wallet', type: 'address' },
68+
],
69+
Mail: [
70+
{ name: 'from', type: 'Person' },
71+
{ name: 'to', type: 'Person' },
72+
{ name: 'contents', type: 'string' },
73+
],
74+
},
75+
value: {
76+
from: {
77+
name: 'Alice',
78+
wallet: alice.wallet.address,
79+
},
80+
to: {
81+
name: 'Bob',
82+
wallet: '0x1234567890123456789012345678901234567890',
83+
},
84+
contents: 'Hello, Bob!',
85+
},
86+
};
87+
88+
try {
89+
const _res = await signTypedDataWithEncryptedKey({
90+
pkpSessionSigs: bobPkpSessionSigs, // Using Bob's session sigs to try to decrypt Alice's key
91+
network: 'evm',
92+
messageToSign: typedData,
93+
litNodeClient: devEnv.litNodeClient,
94+
id, // Alice's key ID
95+
});
96+
} catch (e: any) {
97+
if (e.message.includes('Could not find')) {
98+
console.log('✅ THIS IS EXPECTED: ', e);
99+
console.log(e.message);
100+
console.log(
101+
'✅ testFailEthereumSignTypedDataWrappedKeyInvalidDecryption is expected to have an error'
102+
);
103+
} else {
104+
console.log('ERROR', e.message);
105+
throw e;
106+
}
107+
}
108+
109+
log('✅ testFailEthereumSignTypedDataWrappedKeyInvalidDecryption');
110+
} finally {
111+
devEnv.releasePrivateKeyFromUser(alice);
112+
}
113+
};

0 commit comments

Comments
 (0)