Skip to content

Commit de97dbe

Browse files
committed
feat(wrapped-keys): add signTypedData functionality with encrypted Ethereum key
- Introduced signTypedDataWithEncryptedEthereumKey function for signing typed data using an encrypted Ethereum key. - Updated API to include signTypedDataWithEncryptedKey. - Enhanced types to support signTypedData message parameters. - Updated constants and code repositories to include new signTypedData actions. - Added tests for signTypedData functionality in the lit actions client.
1 parent f0be7ff commit de97dbe

File tree

15 files changed

+247
-1
lines changed

15 files changed

+247
-1
lines changed

packages/wrapped-keys-lit-actions/esbuild.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ module.exports = {
5656
'./src/lib/self-executing-actions/solana/generateEncryptedSolanaPrivateKey.ts',
5757
'./src/lib/self-executing-actions/ethereum/signTransactionWithEncryptedEthereumKey.ts',
5858
'./src/lib/self-executing-actions/ethereum/signMessageWithEncryptedEthereumKey.ts',
59+
'./src/lib/self-executing-actions/ethereum/signTypedDataWithEncryptedEthereumKey.ts',
5960
'./src/lib/self-executing-actions/ethereum/generateEncryptedEthereumPrivateKey.ts',
6061
'./src/lib/self-executing-actions/common/exportPrivateKey.ts',
6162
'./src/lib/self-executing-actions/common/batchGenerateEncryptedKeys.ts',

packages/wrapped-keys-lit-actions/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as exportPrivateKey from './generated/common/exportPrivateKey';
33
import * as generateEncryptedEthereumPrivateKey from './generated/ethereum/generateEncryptedEthereumPrivateKey';
44
import * as signMessageWithEthereumEncryptedKey from './generated/ethereum/signMessageWithEncryptedEthereumKey';
55
import * as signTransactionWithEthereumEncryptedKey from './generated/ethereum/signTransactionWithEncryptedEthereumKey';
6+
import * as signTypedDataWithEthereumEncryptedKey from './generated/ethereum/signTypedDataWithEncryptedEthereumKey';
67
import * as generateEncryptedSolanaPrivateKey from './generated/solana/generateEncryptedSolanaPrivateKey';
78
import * as signMessageWithSolanaEncryptedKey from './generated/solana/signMessageWithEncryptedSolanaKey';
89
import * as signTransactionWithSolanaEncryptedKey from './generated/solana/signTransactionWithEncryptedSolanaKey';
@@ -22,6 +23,10 @@ const litActionRepository: LitActionCodeRepository = {
2223
evm: signMessageWithEthereumEncryptedKey.code,
2324
solana: signMessageWithSolanaEncryptedKey.code,
2425
},
26+
signTypedData: {
27+
evm: signTypedDataWithEthereumEncryptedKey.code,
28+
solana: '',
29+
},
2530
generateEncryptedKey: {
2631
evm: generateEncryptedEthereumPrivateKey.code,
2732
solana: generateEncryptedSolanaPrivateKey.code,
@@ -47,6 +52,7 @@ export {
4752
generateEncryptedEthereumPrivateKey,
4853
signMessageWithEthereumEncryptedKey,
4954
signTransactionWithEthereumEncryptedKey,
55+
signTypedDataWithEthereumEncryptedKey,
5056
generateEncryptedSolanaPrivateKey,
5157
signMessageWithSolanaEncryptedKey,
5258
signTransactionWithSolanaEncryptedKey,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
interface SignTypedDataParams {
2+
privateKey: string;
3+
messageToSign: {
4+
domain: any;
5+
types: Record<string, any[]>;
6+
value: Record<string, any>;
7+
};
8+
}
9+
10+
interface VerifyMessageSignatureParams {
11+
messageToSign: {
12+
domain: any;
13+
types: Record<string, any[]>;
14+
value: Record<string, any>;
15+
};
16+
signature: string;
17+
}
18+
19+
async function signTypedData({
20+
privateKey,
21+
messageToSign,
22+
}: SignTypedDataParams): Promise<{ signature: string; walletAddress: string }> {
23+
try {
24+
const wallet = new ethers.Wallet(privateKey);
25+
const signature = await wallet._signTypedData(
26+
messageToSign.domain,
27+
messageToSign.types,
28+
messageToSign.value
29+
);
30+
31+
return { signature, walletAddress: wallet.address };
32+
} catch (err: unknown) {
33+
if (err instanceof Error) {
34+
throw new Error(`When signing message - ${err.message}`);
35+
} else {
36+
throw new Error(`An unexpected error occurred: ${err}`);
37+
}
38+
}
39+
}
40+
41+
function verifyMessageSignature({
42+
messageToSign,
43+
signature,
44+
}: VerifyMessageSignatureParams): string {
45+
try {
46+
return ethers.utils.verifyTypedData(
47+
messageToSign.domain,
48+
messageToSign.types,
49+
messageToSign.value,
50+
signature
51+
);
52+
} catch (err: unknown) {
53+
throw new Error(
54+
`When validating signed Ethereum message is valid: ${
55+
(err as Error).message
56+
}`
57+
);
58+
}
59+
}
60+
61+
export async function signTypedDataEthereumKey({
62+
privateKey,
63+
messageToSign,
64+
}: SignTypedDataParams): Promise<string> {
65+
const { signature, walletAddress } = await signTypedData({
66+
privateKey,
67+
messageToSign,
68+
});
69+
70+
const recoveredAddress = verifyMessageSignature({ messageToSign, signature });
71+
72+
if (recoveredAddress !== walletAddress) {
73+
throw new Error(
74+
"Recovered address from verifyMessage doesn't match the wallet address"
75+
);
76+
}
77+
78+
return signature;
79+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { getDecryptedKeyToSingleNode } from '../../internal/common/getDecryptedKeyToSingleNode';
2+
import { signTypedDataEthereumKey } from '../../internal/ethereum/signTypedData';
3+
4+
export interface SignTypedDataWithEncryptedEthereumKeyParams {
5+
accessControlConditions: string;
6+
ciphertext: string;
7+
dataToEncryptHash: string;
8+
messageToSign: {
9+
domain: any;
10+
types: Record<string, any[]>;
11+
value: Record<string, any>;
12+
};
13+
}
14+
15+
/**
16+
* Signs a message with the Ethers wallet which is also decrypted inside the Lit Action.
17+
* @param {string} pkpAddress - The Eth address of the PKP which is associated with the Wrapped Key
18+
* @returns {Promise<string>} - Returns a message signed by the Ethers Wrapped key. Or returns errors if any.
19+
*/
20+
export async function signTypedDataWithEncryptedEthereumKey({
21+
accessControlConditions,
22+
ciphertext,
23+
dataToEncryptHash,
24+
messageToSign,
25+
}: SignTypedDataWithEncryptedEthereumKeyParams): Promise<string> {
26+
const privateKey = await getDecryptedKeyToSingleNode({
27+
accessControlConditions,
28+
ciphertext,
29+
dataToEncryptHash,
30+
});
31+
32+
return signTypedDataEthereumKey({
33+
privateKey,
34+
messageToSign,
35+
});
36+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { litActionHandler } from '../../litActionHandler';
2+
import {
3+
SignTypedDataWithEncryptedEthereumKeyParams,
4+
signTypedDataWithEncryptedEthereumKey,
5+
} from '../../raw-action-functions/ethereum/signTypedDataWithEncryptedEthereumKey';
6+
7+
import type { SignMessageWithEncryptedEthereumKeyParams } from '../../raw-action-functions/ethereum/signMessageWithEncryptedEthereumKey';
8+
9+
// Using local declarations to avoid _every file_ thinking these are always in scope
10+
declare const accessControlConditions: SignMessageWithEncryptedEthereumKeyParams['accessControlConditions'];
11+
declare const ciphertext: SignMessageWithEncryptedEthereumKeyParams['ciphertext'];
12+
declare const dataToEncryptHash: SignMessageWithEncryptedEthereumKeyParams['dataToEncryptHash'];
13+
declare const messageToSign: SignTypedDataWithEncryptedEthereumKeyParams['messageToSign'];
14+
15+
(async () =>
16+
litActionHandler(async () =>
17+
signTypedDataWithEncryptedEthereumKey({
18+
accessControlConditions,
19+
ciphertext,
20+
dataToEncryptHash,
21+
messageToSign,
22+
})
23+
))();

packages/wrapped-keys/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
generatePrivateKey,
66
importPrivateKey,
77
signTransactionWithEncryptedKey,
8+
signTypedDataWithEncryptedKey,
89
storeEncryptedKey,
910
listEncryptedKeyMetadata,
1011
batchGeneratePrivateKeys,
@@ -72,6 +73,7 @@ export const api = {
7273
importPrivateKey,
7374
signMessageWithEncryptedKey,
7475
signTransactionWithEncryptedKey,
76+
signTypedDataWithEncryptedKey,
7577
storeEncryptedKey,
7678
storeEncryptedKeyBatch,
7779
batchGeneratePrivateKeys,

packages/wrapped-keys/src/lib/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { importPrivateKey } from './import-private-key';
66
import { listEncryptedKeyMetadata } from './list-encrypted-key-metadata';
77
import { signMessageWithEncryptedKey } from './sign-message-with-encrypted-key';
88
import { signTransactionWithEncryptedKey } from './sign-transaction-with-encrypted-key';
9+
import { signTypedDataWithEncryptedKey } from './sign-typed-data-with-encrypted-key';
910
import { storeEncryptedKey } from './store-encrypted-key';
1011
import { storeEncryptedKeyBatch } from './store-encrypted-key-batch';
1112

@@ -16,6 +17,7 @@ export {
1617
signTransactionWithEncryptedKey,
1718
exportPrivateKey,
1819
signMessageWithEncryptedKey,
20+
signTypedDataWithEncryptedKey,
1921
storeEncryptedKey,
2022
storeEncryptedKeyBatch,
2123
getEncryptedKey,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
getFirstSessionSig,
3+
getPkpAccessControlCondition,
4+
getPkpAddressFromSessionSig,
5+
} from './utils';
6+
import { signMessageWithLitAction } from '../lit-actions-client';
7+
import { getLitActionCodeOrCid } from '../lit-actions-client/utils';
8+
import { fetchPrivateKey } from '../service-client';
9+
import { SignMessageWithEncryptedKeyParams } from '../types';
10+
11+
/**
12+
* Signs a typed data inside the Lit Action using the previously persisted wrapped key associated with the current LIT PK.
13+
* This method fetches the encrypted key from the wrapped keys service, then executes a Lit Action that decrypts the key inside the LIT action and uses
14+
* the decrypted key to sign the provided typed data
15+
*
16+
* @param { SignMessageWithEncryptedKeyParams } params Parameters to use for signing the message
17+
*
18+
* @returns { Promise<string> } - The signed typed data
19+
*/
20+
export async function signTypedDataWithEncryptedKey(
21+
params: SignMessageWithEncryptedKeyParams
22+
): Promise<string> {
23+
const { litNodeClient, network, pkpSessionSigs, id } = params;
24+
25+
const sessionSig = getFirstSessionSig(pkpSessionSigs);
26+
const pkpAddress = getPkpAddressFromSessionSig(sessionSig);
27+
28+
const storedKeyMetadata = await fetchPrivateKey({
29+
pkpAddress,
30+
id,
31+
sessionSig,
32+
litNetwork: litNodeClient.config.litNetwork,
33+
});
34+
35+
const allowPkpAddressToDecrypt = getPkpAccessControlCondition(
36+
storedKeyMetadata.pkpAddress
37+
);
38+
39+
const { litActionCode, litActionIpfsCid } = getLitActionCodeOrCid(
40+
network,
41+
'signTypedData'
42+
);
43+
44+
return signMessageWithLitAction({
45+
...params,
46+
litActionIpfsCid: litActionCode ? undefined : litActionIpfsCid,
47+
litActionCode: litActionCode ? litActionCode : undefined,
48+
accessControlConditions: [allowPkpAddressToDecrypt],
49+
pkpSessionSigs,
50+
storedKeyMetadata,
51+
});
52+
}

packages/wrapped-keys/src/lib/lit-actions-client/code-repository.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ describe('wrapped keys lit action code repository', () => {
1616
evm: '',
1717
solana: '',
1818
},
19+
signTypedData: {
20+
evm: '',
21+
solana: '',
22+
},
1923
generateEncryptedKey: {
2024
evm: '',
2125
solana: '',
@@ -88,6 +92,10 @@ describe('wrapped keys lit action code repository', () => {
8892
evm: 'test',
8993
solana: 'test',
9094
},
95+
signTypedData: {
96+
evm: 'test',
97+
solana: 'test',
98+
},
9199
generateEncryptedKey: {
92100
evm: 'test',
93101
solana: 'test',
@@ -106,6 +114,10 @@ describe('wrapped keys lit action code repository', () => {
106114
evm: 'test',
107115
solana: 'test',
108116
},
117+
signTypedData: {
118+
evm: 'test',
119+
solana: 'test',
120+
},
109121
generateEncryptedKey: {
110122
evm: 'test',
111123
solana: 'test',
@@ -127,6 +139,10 @@ describe('wrapped keys lit action code repository', () => {
127139
evm: 'test',
128140
solana: 'test',
129141
},
142+
signTypedData: {
143+
evm: 'test',
144+
solana: 'test',
145+
},
130146
generateEncryptedKey: {
131147
evm: 'test',
132148
solana: 'test',
@@ -154,6 +170,10 @@ describe('wrapped keys lit action code repository', () => {
154170
evm: 'eth',
155171
solana: 'test',
156172
},
173+
signTypedData: {
174+
evm: 'eth',
175+
solana: 'test',
176+
},
157177
generateEncryptedKey: {
158178
evm: 'test',
159179
solana: 'test',

packages/wrapped-keys/src/lib/lit-actions-client/code-repository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ const litActionCodeRepository: LitActionCodeRepository = Object.freeze({
2323
evm: '',
2424
solana: '',
2525
}),
26+
signTypedData: Object.seal({
27+
evm: '',
28+
solana: '',
29+
}),
2630
generateEncryptedKey: Object.seal({
2731
evm: '',
2832
solana: '',

0 commit comments

Comments
 (0)