Skip to content

Commit 0f40949

Browse files
authored
Merge pull request #815 from LIT-Protocol/anson/propose-change-for-788
Anson/propose change for 788
2 parents 4a43aa3 + 547c1be commit 0f40949

File tree

2 files changed

+184
-33
lines changed

2 files changed

+184
-33
lines changed

packages/crypto/src/lib/crypto.spec.ts

+134-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,32 @@ import * as ethers from 'ethers';
22
import { joinSignature } from 'ethers/lib/utils';
33

44
import { SigShare } from '@lit-protocol/types';
5+
import { nacl } from '@lit-protocol/nacl';
6+
import { combineEcdsaShares, walletDecrypt, walletEncrypt } from './crypto';
57

6-
import { combineEcdsaShares } from './crypto';
8+
const MOCK_SESSION_SIGS = {
9+
'http://127.0.0.1:7470': {
10+
sig: 'fefcd74c2bb2794356a10e62722c2ca4ef47386475ca72865d8dd7cc096fd1715d8b076b29349328e0b13d09f3296768e6b1cbb81e02d2b697b7641984260b01',
11+
derivedVia: 'litSessionSignViaNacl',
12+
signedMessage: `{"sessionKey":"6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d","resourceAbilityRequests":[{"resource":{"resource":"*","resourcePrefix":"lit-pkp"},"ability":"pkp-signing"},{"resource":{"resource":"*","resourcePrefix":"lit-litaction"},"ability":"lit-action-execution"}],"capabilities":[{"sig":"0x64192b2156f6d60f2c9aed887f5a0c6003afb6cd7a25b94683c74311fe895e8849a178d4edbe9f38cb0d8a3d7d0342b67b521ada92b68f73f0a1e5fa4f33ae751c","derivedVia":"web3.eth.personal.sign","signedMessage":"localhost wants you to sign in with your Ethereum account:\\n0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC\\n\\nThis is a test statement. You can put anything you want here. I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'.\\n\\nURI: lit:session:6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d\\nVersion: 1\\nChain ID: 1\\nNonce: 0x4ee32274a45ab4622d49ea0f9bc3984f2a251e7b88320e23e231673efa564280\\nIssued At: 2025-04-09T14:37:09.339Z\\nExpiration Time: 2025-04-10T14:37:09.337Z\\nResources:\\n- urn:recap:eyJhdHQiOnsibGl0LWxpdGFjdGlvbjovLyoiOnsiVGhyZXNob2xkL0V4ZWN1dGlvbiI6W3t9XX0sImxpdC1wa3A6Ly8qIjp7IlRocmVzaG9sZC9TaWduaW5nIjpbe31dfX0sInByZiI6W119","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}],"issuedAt":"2025-04-09T14:37:09.373Z","expiration":"2025-04-10T14:37:09.337Z","nodeAddress":"http://127.0.0.1:7470","maxPrice":"113427455640312821154458202477256070485"}`,
13+
address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d',
14+
algo: 'ed25519',
15+
},
16+
'http://127.0.0.1:7471': {
17+
sig: 'ec550ae2addd6fbc399ef26158b9cf8b2a1c52240ee60b62c49c8a782c020d0aae5602090029f5a0024f936f0a747d7c007dfecee44bbe99a5cc12dcef7d3309',
18+
derivedVia: 'litSessionSignViaNacl',
19+
signedMessage: `{"sessionKey":"6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d","resourceAbilityRequests":[{"resource":{"resource":"*","resourcePrefix":"lit-pkp"},"ability":"pkp-signing"},{"resource":{"resource":"*","resourcePrefix":"lit-litaction"},"ability":"lit-action-execution"}],"capabilities":[{"sig":"0x64192b2156f6d60f2c9aed887f5a0c6003afb6cd7a25b94683c74311fe895e8849a178d4edbe9f38cb0d8a3d7d0342b67b521ada92b68f73f0a1e5fa4f33ae751c","derivedVia":"web3.eth.personal.sign","signedMessage":"localhost wants you to sign in with your Ethereum account:\\n0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC\\n\\nThis is a test statement. You can put anything you want here. I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'.\\n\\nURI: lit:session:6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d\\nVersion: 1\\nChain ID: 1\\nNonce: 0x4ee32274a45ab4622d49ea0f9bc3984f2a251e7b88320e23e231673efa564280\\nIssued At: 2025-04-09T14:37:09.339Z\\nExpiration Time: 2025-04-10T14:37:09.337Z\\nResources:\\n- urn:recap:eyJhdHQiOnsibGl0LWxpdGFjdGlvbjovLyoiOnsiVGhyZXNob2xkL0V4ZWN1dGlvbiI6W3t9XX0sImxpdC1wa3A6Ly8qIjp7IlRocmVzaG9sZC9TaWduaW5nIjpbe31dfX0sInByZiI6W119","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}],"issuedAt":"2025-04-09T14:37:09.373Z","expiration":"2025-04-10T14:37:09.337Z","nodeAddress":"http://127.0.0.1:7471","maxPrice":"113427455640312821154458202477256070485"}`,
20+
address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d',
21+
algo: 'ed25519',
22+
},
23+
'http://127.0.0.1:7472': {
24+
sig: '5f4d83f7fc425ebfca85e45fed0799b93ac215c5ae1c7d279217ca70c434eb43db6e282f365d4f4dc7d76494548e33e4cb6e5f8901b5f142e447fc9718589f07',
25+
derivedVia: 'litSessionSignViaNacl',
26+
signedMessage: `{"sessionKey":"6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d","resourceAbilityRequests":[{"resource":{"resource":"*","resourcePrefix":"lit-pkp"},"ability":"pkp-signing"},{"resource":{"resource":"*","resourcePrefix":"lit-litaction"},"ability":"lit-action-execution"}],"capabilities":[{"sig":"0x64192b2156f6d60f2c9aed887f5a0c6003afb6cd7a25b94683c74311fe895e8849a178d4edbe9f38cb0d8a3d7d0342b67b521ada92b68f73f0a1e5fa4f33ae751c","derivedVia":"web3.eth.personal.sign","signedMessage":"localhost wants you to sign in with your Ethereum account:\\n0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC\\n\\nThis is a test statement. You can put anything you want here. I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'.\\n\\nURI: lit:session:6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d\\nVersion: 1\\nChain ID: 1\\nNonce: 0x4ee32274a45ab4622d49ea0f9bc3984f2a251e7b88320e23e231673efa564280\\nIssued At: 2025-04-09T14:37:09.339Z\\nExpiration Time: 2025-04-10T14:37:09.337Z\\nResources:\\n- urn:recap:eyJhdHQiOnsibGl0LWxpdGFjdGlvbjovLyoiOnsiVGhyZXNob2xkL0V4ZWN1dGlvbiI6W3t9XX0sImxpdC1wa3A6Ly8qIjp7IlRocmVzaG9sZC9TaWduaW5nIjpbe31dfX0sInByZiI6W119","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}],"issuedAt":"2025-04-09T14:37:09.373Z","expiration":"2025-04-10T14:37:09.337Z","nodeAddress":"http://127.0.0.1:7472","maxPrice":"113427455640312821154458202477256070485"}`,
27+
address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d',
28+
algo: 'ed25519',
29+
},
30+
};
731

832
describe('combine ECDSA Shares', () => {
933
it('Should recombine ECDSA signature shares', async () => {
@@ -67,3 +91,112 @@ describe('combine ECDSA Shares', () => {
6791
expect(recoveredAddr).toEqual(addr);
6892
});
6993
});
94+
95+
describe('walletEncrypt and walletDecrypt', () => {
96+
it('should encrypt and decrypt a message successfully', async () => {
97+
// Generate key pairs using the box functionality
98+
const aliceKeyPair = nacl.box.keyPair();
99+
const bobKeyPair = nacl.box.keyPair();
100+
101+
console.log('aliceKeyPair', aliceKeyPair);
102+
console.log('bobKeyPair', bobKeyPair);
103+
104+
// Message to encrypt
105+
const message = new TextEncoder().encode('This is a secret message');
106+
107+
// Alice encrypts a message for Bob
108+
const encryptedPayload = await walletEncrypt(
109+
aliceKeyPair.secretKey,
110+
bobKeyPair.publicKey,
111+
MOCK_SESSION_SIGS['http://127.0.0.1:7470'],
112+
message
113+
);
114+
115+
console.log('encryptedPayload', encryptedPayload);
116+
117+
// Verify payload structure
118+
expect(encryptedPayload).toHaveProperty('V1');
119+
expect(encryptedPayload.V1).toHaveProperty('verification_key');
120+
expect(encryptedPayload.V1).toHaveProperty('ciphertext_and_tag');
121+
expect(encryptedPayload.V1).toHaveProperty('session_signature');
122+
expect(encryptedPayload.V1).toHaveProperty('random');
123+
expect(encryptedPayload.V1).toHaveProperty('created_at');
124+
125+
// Bob decrypts the message from Alice
126+
const decryptedMessage = await walletDecrypt(
127+
bobKeyPair.secretKey,
128+
encryptedPayload
129+
);
130+
131+
// Verify decryption was successful
132+
expect(decryptedMessage).not.toBeNull();
133+
expect(new TextDecoder().decode(decryptedMessage as Uint8Array)).toBe(
134+
'This is a secret message'
135+
);
136+
});
137+
138+
it('should return null when decryption fails', async () => {
139+
// Generate key pairs
140+
const aliceKeyPair = nacl.box.keyPair();
141+
const bobKeyPair = nacl.box.keyPair();
142+
const eveKeyPair = nacl.box.keyPair(); // Eve is an eavesdropper
143+
144+
// Message to encrypt
145+
const message = new TextEncoder().encode('This is a secret message');
146+
147+
// Alice encrypts a message for Bob
148+
const encryptedPayload = await walletEncrypt(
149+
aliceKeyPair.secretKey,
150+
bobKeyPair.publicKey,
151+
MOCK_SESSION_SIGS['http://127.0.0.1:7470'],
152+
message
153+
);
154+
155+
// Eve tries to decrypt the message with her key (should fail)
156+
const decryptedByEve = await walletDecrypt(
157+
eveKeyPair.secretKey,
158+
encryptedPayload
159+
);
160+
161+
// Verify decryption failed
162+
expect(decryptedByEve).toBeNull();
163+
});
164+
165+
it('should handle tampering with the encrypted payload', async () => {
166+
// Generate key pairs
167+
const aliceKeyPair = nacl.box.keyPair();
168+
const bobKeyPair = nacl.box.keyPair();
169+
170+
// Message to encrypt
171+
const message = new TextEncoder().encode('This is a secret message');
172+
173+
// Alice encrypts a message for Bob
174+
const encryptedPayload = await walletEncrypt(
175+
aliceKeyPair.secretKey,
176+
bobKeyPair.publicKey,
177+
MOCK_SESSION_SIGS['http://127.0.0.1:7470'],
178+
message
179+
);
180+
181+
// Tamper with the ciphertext
182+
const tamperedPayload = {
183+
...encryptedPayload,
184+
V1: {
185+
...encryptedPayload.V1,
186+
ciphertext_and_tag:
187+
encryptedPayload.V1.ciphertext_and_tag.substring(0, 10) +
188+
'ff' +
189+
encryptedPayload.V1.ciphertext_and_tag.substring(12),
190+
},
191+
};
192+
193+
// Bob tries to decrypt the tampered message
194+
const decryptedTamperedMessage = await walletDecrypt(
195+
bobKeyPair.secretKey,
196+
tamperedPayload
197+
);
198+
199+
// Verify decryption failed due to tampering
200+
expect(decryptedTamperedMessage).toBeNull();
201+
});
202+
});

packages/crypto/src/lib/crypto.ts

+50-32
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import {
99
UnknownError,
1010
UnknownSignatureError,
1111
} from '@lit-protocol/constants';
12-
import { checkType, log } from '@lit-protocol/misc';
12+
import { log } from '@lit-protocol/misc';
1313
import { nacl } from '@lit-protocol/nacl';
1414
import {
15+
AuthSig,
1516
CombinedECDSASignature,
1617
NodeAttestation,
1718
SessionKeyPair,
18-
SigningAccessControlConditionJWTPayload,
1919
SigShare,
2020
WalletEncryptedPayload,
2121
} from '@lit-protocol/types';
@@ -24,14 +24,14 @@ import {
2424
uint8arrayToString,
2525
} from '@lit-protocol/uint8arrays';
2626
import {
27-
BlsSignatureShareJsonString,
28-
EcdsaVariant,
2927
blsCombine,
3028
blsDecrypt,
3129
blsEncrypt,
30+
BlsSignatureShareJsonString,
3231
blsVerify,
3332
ecdsaCombine,
3433
ecdsaDeriveKey,
34+
EcdsaVariant,
3535
ecdsaVerify,
3636
sevSnpGetVcekUrl,
3737
sevSnpVerify,
@@ -373,99 +373,117 @@ async function getAmdCert(url: string): Promise<Uint8Array> {
373373
}
374374
}
375375

376-
export const walletEncrypt = async(
377-
myWalletSecretKey: Uint8Array,
376+
export const walletEncrypt = async (
377+
myWalletSecretKey: Uint8Array,
378378
theirWalletPublicKey: Uint8Array,
379-
sessionSig: Uint8Array,
379+
sessionSig: AuthSig,
380380
message: Uint8Array
381381
): Promise<WalletEncryptedPayload> => {
382+
const uint8SessionSig = Buffer.from(JSON.stringify(sessionSig));
383+
382384
const random = new Uint8Array(16);
383-
window.crypto.getRandomValues(random);
385+
crypto.getRandomValues(random);
384386
const dateNow = Date.now();
385387
const createdAt = Math.floor(dateNow / 1000);
386388
const timestamp = Buffer.alloc(8);
387389
timestamp.writeBigUInt64BE(BigInt(createdAt), 0);
388390

389391
const myWalletPublicKey = new Uint8Array(32);
390-
nacl.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);
392+
nacl.lowlevel.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);
391393

392-
// Construct AAD
393-
const sessionSignature = Buffer.from(sessionSig); // Replace with actual session signature
394+
// Construct AAD (Additional Authenticated Data) - data that is authenticated but not encrypted
395+
const sessionSignature = uint8SessionSig; // Replace with actual session signature
394396
const theirPublicKey = Buffer.from(theirWalletPublicKey); // Replace with their public key
395397
const myPublicKey = Buffer.from(myWalletPublicKey); // Replace with your wallet public key
396-
398+
397399
const aad = Buffer.concat([
398400
sessionSignature,
399401
random,
400402
timestamp,
401403
theirPublicKey,
402404
myPublicKey,
403405
]);
404-
406+
405407
const hash = new Uint8Array(64);
406-
nacl.crypto_hash(hash, aad);
408+
nacl.lowlevel.crypto_hash(hash, aad);
407409

408410
const nonce = hash.slice(0, 24);
409-
const ciphertext = nacl.box(message, nonce, theirPublicKey, myWalletSecretKey);
411+
const ciphertext = nacl.box(
412+
message,
413+
nonce,
414+
theirPublicKey,
415+
myWalletSecretKey
416+
);
410417
return {
411418
V1: {
412419
verification_key: uint8ArrayToHex(myWalletPublicKey),
413420
ciphertext_and_tag: uint8ArrayToHex(ciphertext),
414421
session_signature: uint8ArrayToHex(sessionSignature),
415422
random: uint8ArrayToHex(random),
416-
created_at: dateNow.toISOString(),
417-
}
423+
created_at: new Date(dateNow).toISOString(),
424+
},
418425
};
419-
}
426+
};
420427

421-
export const walletDecrypt = async(
428+
export const walletDecrypt = async (
422429
myWalletSecretKey: Uint8Array,
423430
payload: WalletEncryptedPayload
424431
): Promise<Uint8Array> => {
425-
const dateSent = new Date(payload.V1.created_at)
426-
const createdAt = Math.floor(dateSent / 1000);
432+
const dateSent = new Date(payload.V1.created_at);
433+
const createdAt = Math.floor(dateSent.getTime() / 1000);
427434
const timestamp = Buffer.alloc(8);
428435
timestamp.writeBigUInt64BE(BigInt(createdAt), 0);
429436

430437
const myWalletPublicKey = new Uint8Array(32);
431-
nacl.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);
438+
nacl.lowlevel.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);
432439

433440
// Construct AAD
434441
const random = Buffer.from(hexToUint8Array(payload.V1.random));
435-
const sessionSignature = Buffer.from(hexToUint8Array(payload.V1.session_signature)); // Replace with actual session signature
442+
const sessionSignature = Buffer.from(
443+
hexToUint8Array(payload.V1.session_signature)
444+
); // Replace with actual session signature
436445
const theirPublicKey = hexToUint8Array(payload.V1.verification_key);
437446
const theirPublicKeyBuffer = Buffer.from(theirPublicKey); // Replace with their public key
438447
const myPublicKey = Buffer.from(myWalletPublicKey); // Replace with your wallet public key
439-
448+
440449
const aad = Buffer.concat([
441450
sessionSignature,
442451
random,
443452
timestamp,
444453
theirPublicKeyBuffer,
445454
myPublicKey,
446455
]);
447-
456+
448457
const hash = new Uint8Array(64);
449-
nacl.crypto_hash(hash, aad);
458+
nacl.lowlevel.crypto_hash(hash, aad);
450459

451460
const nonce = hash.slice(0, 24);
452-
const message = nacl.box.open(payload.V1.ciphertext_and_tag, nonce, theirPublicKey, myWalletSecretKey);
461+
462+
// Convert hex ciphertext back to Uint8Array
463+
const ciphertext = hexToUint8Array(payload.V1.ciphertext_and_tag);
464+
465+
const message = nacl.box.open(
466+
ciphertext,
467+
nonce,
468+
theirPublicKey,
469+
myWalletSecretKey
470+
);
453471
return message;
454-
}
472+
};
455473

456474
function uint8ArrayToHex(array: Uint8Array) {
457475
return Array.from(array)
458-
.map(byte => byte.toString(16).padStart(2, '0'))
459-
.join('');
476+
.map((byte) => byte.toString(16).padStart(2, '0'))
477+
.join('');
460478
}
461479

462480
function hexToUint8Array(hexString: string): Uint8Array {
463481
if (hexString.length % 2 !== 0) {
464-
throw new Error("Hex string must have an even length");
482+
throw new Error('Hex string must have an even length');
465483
}
466484
const bytes = new Uint8Array(hexString.length / 2);
467485
for (let i = 0; i < bytes.length; i++) {
468-
bytes[i] = parseInt(hexString.slice(i * 2, i * 2 + 2), 16);
486+
bytes[i] = parseInt(hexString.slice(i * 2, i * 2 + 2), 16);
469487
}
470488
return bytes;
471489
}

0 commit comments

Comments
 (0)