Skip to content

Commit 835dbf0

Browse files
Merge pull request #675 from LIT-Protocol/LIT-3920-batchGeneratePrivateKeys
LIT-3920 - wrapped-keys - Batch generate private keys & optionally sign messages in a single operation
2 parents aa860a8 + 22747c8 commit 835dbf0

File tree

54 files changed

+1520
-562
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1520
-562
lines changed

local-tests/setup/tinny-environment.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ export class TinnyEnvironment {
181181
return { privateKey: this.processEnvs.PRIVATE_KEYS[index], index }; // Return the key and its index
182182
} else {
183183
console.log('[𐬺🧪 Tinny Environment𐬺] No available keys. Waiting...', {
184-
privateKeys: this.processEnvs.PRIVATE_KEYS,
185184
keysInUse: this.processEnvs.KEY_IN_USE,
186185
}); // Log a message indicating that we are waiting
187186
// Wait for the specified interval before checking again
@@ -447,8 +446,9 @@ export class TinnyEnvironment {
447446
* @throws If there is an error sending the funds.
448447
*/
449448
getFunds = async (walletAddress: string, amount = '0.001') => {
449+
const privateKey = await this.getAvailablePrivateKey();
450+
450451
try {
451-
const privateKey = await this.getAvailablePrivateKey();
452452
const provider = new ethers.providers.JsonRpcBatchProvider(this.rpc);
453453
const wallet = new ethers.Wallet(privateKey.privateKey, provider);
454454

@@ -460,6 +460,9 @@ export class TinnyEnvironment {
460460
await tx.wait();
461461
} catch (e) {
462462
throw new Error(`Failed to send funds to ${walletAddress}: ${e}`);
463+
} finally {
464+
// @ts-expect-error We don't have a user, but this works
465+
this.releasePrivateKeyFromUser({ privateKey });
463466
}
464467
};
465468

local-tests/test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ import { testFailImportWrappedKeysWithExpiredSessionSig } from './tests/wrapped-
104104
import { testExportWrappedKey } from './tests/wrapped-keys/testExportWrappedKey';
105105
import { testSignMessageWithSolanaEncryptedKey } from './tests/wrapped-keys/testSignMessageWithSolanaEncryptedKey';
106106
import { testSignTransactionWithSolanaEncryptedKey } from './tests/wrapped-keys/testSignTransactionWithSolanaEncryptedKey';
107+
import { testBatchGeneratePrivateKeys } from './tests/wrapped-keys/testBatchGeneratePrivateKeys';
108+
109+
import { setLitActionsCodeToLocal } from './tests/wrapped-keys/util';
110+
import { testUseEoaSessionSigsToRequestSingleResponse } from './tests/testUseEoaSessionSigsToRequestSingleResponse';
111+
112+
// Use the current LIT action code to test against
113+
setLitActionsCodeToLocal();
107114

108115
(async () => {
109116
console.log('[𐬺🧪 Tinny𐬺] Running tests...');
@@ -118,6 +125,7 @@ import { testSignTransactionWithSolanaEncryptedKey } from './tests/wrapped-keys/
118125
// --filter=WrappedKey
119126
const wrappedKeysTests = {
120127
// -- valid cases
128+
testBatchGeneratePrivateKeys,
121129
testEthereumSignMessageGeneratedKey,
122130
testEthereumBroadcastTransactionGeneratedKey,
123131
testEthereumSignMessageWrappedKey,
@@ -163,6 +171,7 @@ import { testSignTransactionWithSolanaEncryptedKey } from './tests/wrapped-keys/
163171
testUseEoaSessionSigsToEncryptDecryptString,
164172
testUseEoaSessionSigsToEncryptDecryptFile,
165173
testUseEoaSessionSigsToEncryptDecryptZip,
174+
testUseEoaSessionSigsToRequestSingleResponse,
166175
};
167176

168177
const pkpSessionSigsTests = {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs';
2+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
3+
4+
/**
5+
* Test Commands:
6+
* ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToRequestSingleResponse
7+
* ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToRequestSingleResponse
8+
* ✅ NETWORK=datil yarn test:local --filter=testUseEoaSessionSigsToRequestSingleResponse
9+
*/
10+
export const testUseEoaSessionSigsToRequestSingleResponse = async (
11+
devEnv: TinnyEnvironment
12+
) => {
13+
const alice = await devEnv.createRandomPerson();
14+
15+
try {
16+
const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice);
17+
18+
const res = await devEnv.litNodeClient.executeJs({
19+
sessionSigs: eoaSessionSigs,
20+
code: `(async () => {
21+
console.log('hello world')
22+
})();`,
23+
useSingleNode: true,
24+
});
25+
26+
console.log('res:', res);
27+
28+
// Expected output:
29+
// {
30+
// success: true,
31+
// signedData: {},
32+
// decryptedData: {},
33+
// claimData: {},
34+
// response: "",
35+
// logs: "hello world\n",
36+
// }
37+
38+
// -- assertions
39+
if (res.response) {
40+
throw new Error(`Expected "response" to be falsy`);
41+
}
42+
43+
if (!res.logs) {
44+
throw new Error(`Expected "logs" in res`);
45+
}
46+
47+
if (!res.logs.includes('hello world')) {
48+
throw new Error(`Expected "logs" to include 'hello world'`);
49+
}
50+
51+
if (!res.success) {
52+
throw new Error(`Expected "success" in res`);
53+
}
54+
} finally {
55+
devEnv.releasePrivateKeyFromUser(alice);
56+
}
57+
};
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { log } from '@lit-protocol/misc';
2+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
3+
import { api } from '@lit-protocol/wrapped-keys';
4+
import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs';
5+
import nacl from 'tweetnacl';
6+
import bs58 from 'bs58';
7+
import { ethers } from 'ethers';
8+
import { BatchGeneratePrivateKeysActionResult } from '../../../packages/wrapped-keys/src/lib/types';
9+
10+
const { batchGeneratePrivateKeys, exportPrivateKey } = api;
11+
12+
async function verifySolanaSignature(
13+
solanaResult: BatchGeneratePrivateKeysActionResult,
14+
solanaMessageToSign
15+
) {
16+
const {
17+
signMessage: { signature },
18+
generateEncryptedPrivateKey: { generatedPublicKey },
19+
} = solanaResult;
20+
const signatureIsValidForPublicKey = nacl.sign.detached.verify(
21+
Buffer.from(solanaMessageToSign),
22+
bs58.decode(signature),
23+
bs58.decode(generatedPublicKey)
24+
);
25+
26+
console.log({ signatureIsValidForPublicKey, signature });
27+
if (!signatureIsValidForPublicKey) {
28+
throw new Error(
29+
`signature: ${signature} doesn't validate for the Solana public key: ${generatedPublicKey}`
30+
);
31+
}
32+
}
33+
async function verifyEvmSignature(evmResult, messageToSign) {
34+
function verifyMessageSignature() {
35+
try {
36+
return ethers.utils.verifyMessage(
37+
messageToSign,
38+
evmResult.signMessage.signature
39+
);
40+
} catch (err) {
41+
throw new Error(
42+
`When validating signed Ethereum message is valid: ${err.message}`
43+
);
44+
}
45+
}
46+
47+
const walletAddress = ethers.utils.computeAddress(
48+
evmResult.generateEncryptedPrivateKey.generatedPublicKey
49+
);
50+
51+
const recoveredAddress = verifyMessageSignature();
52+
53+
console.log({
54+
recoveredAddress,
55+
walletAddress,
56+
signature: evmResult.signMessage.signature,
57+
});
58+
if (recoveredAddress !== walletAddress) {
59+
throw new Error(
60+
"Recovered address from verifyMessage doesn't match the wallet address"
61+
);
62+
}
63+
}
64+
65+
/**
66+
* Test Commands:
67+
* ✅ NETWORK=datil-dev yarn test:local --filter=testSignMessageWithSolanaEncryptedKey
68+
* ✅ NETWORK=datil-test yarn test:local --filter=testSignMessageWithSolanaEncryptedKey
69+
* ✅ NETWORK=localchain yarn test:local --filter=testSignMessageWithSolanaEncryptedKey
70+
*/
71+
export const testBatchGeneratePrivateKeys = async (
72+
devEnv: TinnyEnvironment
73+
) => {
74+
const alice = await devEnv.createRandomPerson();
75+
76+
try {
77+
const pkpSessionSigsSigning = await getPkpSessionSigs(
78+
devEnv,
79+
alice,
80+
null,
81+
new Date(Date.now() + 1000 * 60 * 10).toISOString()
82+
); // 10 mins expiry
83+
84+
const solanaMessageToSign = 'This is a test solana message';
85+
const evmMessageToSign = 'This is a test evm message';
86+
const { results } = await batchGeneratePrivateKeys({
87+
pkpSessionSigs: pkpSessionSigsSigning,
88+
actions: [
89+
{
90+
network: 'evm',
91+
signMessageParams: { messageToSign: evmMessageToSign },
92+
generateKeyParams: { memo: 'Test evm key' },
93+
},
94+
{
95+
network: 'solana',
96+
signMessageParams: { messageToSign: solanaMessageToSign },
97+
generateKeyParams: { memo: 'Test solana key' },
98+
},
99+
],
100+
litNodeClient: devEnv.litNodeClient,
101+
});
102+
103+
if (results.length !== 2) {
104+
throw new Error(
105+
`Incorrect # of results; expected 2, got ${results.length}`
106+
);
107+
}
108+
109+
if (
110+
results[0].generateEncryptedPrivateKey.memo !== 'Test evm key' ||
111+
results[1].generateEncryptedPrivateKey.memo !== 'Test solana key'
112+
) {
113+
throw new Error(
114+
'Results not in order sent; expected evm as first result, solana as second'
115+
);
116+
}
117+
118+
if (
119+
!results[0].signMessage.signature ||
120+
!results[1].signMessage.signature
121+
) {
122+
throw new Error('Missing message signature in response');
123+
}
124+
125+
console.log('solana verify sig');
126+
await verifySolanaSignature(results[1], solanaMessageToSign);
127+
128+
console.log('evm verify sig');
129+
await verifyEvmSignature(results[0], evmMessageToSign);
130+
console.log('results', results);
131+
132+
log('✅ testBatchGenerateEncryptedKeys');
133+
} catch (err) {
134+
console.log(err.message, err, err.stack);
135+
throw err;
136+
} finally {
137+
devEnv.releasePrivateKeyFromUser(alice);
138+
}
139+
};

local-tests/tests/wrapped-keys/testFailEthereumSignTransactionWrappedKeyWithInvalidParam.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,7 @@ export const testFailEthereumSignTransactionWrappedKeyWithInvalidParam = async (
7070
id,
7171
});
7272
} catch (e: any) {
73-
if (
74-
e.message.includes(
75-
'Error executing the Signing Lit Action: Error: When signing transaction- invalid hexlify value'
76-
)
77-
) {
73+
if (e.message.includes('invalid hexlify value')) {
7874
console.log('✅ THIS IS EXPECTED: ', e);
7975
console.log(e.message);
8076
console.log(

local-tests/tests/wrapped-keys/testFailEthereumSignTransactionWrappedKeyWithMissingParam.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,7 @@ export const testFailEthereumSignTransactionWrappedKeyWithMissingParam = async (
6666
id,
6767
});
6868
} catch (e: any) {
69-
if (
70-
e.message.includes(
71-
'Error executing the Signing Lit Action: Error: Missing required field: toAddress'
72-
)
73-
) {
69+
if (e.message.includes('Missing required field: toAddress')) {
7470
console.log('✅ THIS IS EXPECTED: ', e);
7571
console.log(e.message);
7672
console.log(

local-tests/tests/wrapped-keys/testSignTransactionWithSolanaEncryptedKey.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
clusterApiUrl,
1212
} from '@solana/web3.js';
1313
import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs';
14+
import { ethers } from 'ethers';
1415

1516
const { importPrivateKey, signTransactionWithEncryptedKey } = api;
1617

@@ -125,7 +126,7 @@ export const testSignTransactionWithSolanaEncryptedKey = async (
125126
// const confirmation = await solanaConnection.confirmTransaction(signedTx);
126127
// console.log(confirmation); // { context: { slot: 321490379 }, value: { err: null } }
127128

128-
const signatureBuffer = Buffer.from(signedTx, 'base64');
129+
const signatureBuffer = Buffer.from(ethers.utils.base58.decode(signedTx));
129130
solanaTransaction.addSignature(solanaKeypair.publicKey, signatureBuffer);
130131

131132
if (!solanaTransaction.verifySignatures()) {

local-tests/tests/wrapped-keys/util.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,50 @@
11
import { LIT_NETWORKS_KEYS } from '@lit-protocol/types';
22
import { LIT_CHAINS } from '@lit-protocol/constants';
33
import { ethers } from 'ethers';
4-
import { EthereumLitTransaction } from '@lit-protocol/wrapped-keys';
4+
import { config } from '@lit-protocol/wrapped-keys';
5+
import {
6+
litActionRepositoryCommon,
7+
litActionRepository,
8+
} from '@lit-protocol/wrapped-keys-lit-actions';
9+
10+
import type {
11+
LitActionCodeRepository,
12+
LitActionCodeRepositoryCommon,
13+
EthereumLitTransaction,
14+
} from '@lit-protocol/wrapped-keys';
15+
16+
const emptyLitActionRepositoryCommon: LitActionCodeRepositoryCommon = {
17+
batchGenerateEncryptedKeys: '',
18+
};
19+
20+
const emptyLitActionRepository: LitActionCodeRepository = {
21+
signTransaction: {
22+
evm: '',
23+
solana: '',
24+
},
25+
signMessage: {
26+
evm: '',
27+
solana: '',
28+
},
29+
generateEncryptedKey: {
30+
evm: '',
31+
solana: '',
32+
},
33+
exportPrivateKey: {
34+
evm: '',
35+
solana: '',
36+
},
37+
};
38+
39+
export function resetLitActionsCode() {
40+
config.setLitActionsCodeCommon(emptyLitActionRepositoryCommon);
41+
config.setLitActionsCode(emptyLitActionRepository);
42+
}
43+
44+
export function setLitActionsCodeToLocal() {
45+
config.setLitActionsCodeCommon(litActionRepositoryCommon);
46+
config.setLitActionsCode(litActionRepository);
47+
}
548

649
export function getChainForNetwork(network: LIT_NETWORKS_KEYS): {
750
chain: string;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
"@walletconnect/utils": "2.9.2",
5757
"@walletconnect/web3wallet": "1.8.8",
5858
"ajv": "^8.12.0",
59-
"axios": "^0.27.2",
6059
"base64url": "^3.0.1",
6160
"bitcoinjs-lib": "^6.1.0",
6261
"bs58": "^5.0.0",
@@ -100,6 +99,7 @@
10099
"@types/secp256k1": "^4.0.6",
101100
"@typescript-eslint/eslint-plugin": "6.21.0",
102101
"@typescript-eslint/parser": "6.21.0",
102+
"axios": "^1.6.0",
103103
"babel-jest": "27.5.1",
104104
"body-parser": "^1.20.2",
105105
"buffer": "^6.0.3",
@@ -120,6 +120,7 @@
120120
"eslint-plugin-react-hooks": "4.6.0",
121121
"ethereum-abi-types-generator": "^1.3.2",
122122
"express": "^4.18.2",
123+
"form-data": "^4.0.0",
123124
"inquirer": "^9.2.21",
124125
"ipfs-unixfs-importer": "12.0.1",
125126
"jest": "27.5.1",

0 commit comments

Comments
 (0)