Skip to content

Anson/propose change for 788 #815

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 134 additions & 1 deletion packages/crypto/src/lib/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,32 @@ import * as ethers from 'ethers';
import { joinSignature } from 'ethers/lib/utils';

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

import { combineEcdsaShares } from './crypto';
const MOCK_SESSION_SIGS = {
'http://127.0.0.1:7470': {
sig: 'fefcd74c2bb2794356a10e62722c2ca4ef47386475ca72865d8dd7cc096fd1715d8b076b29349328e0b13d09f3296768e6b1cbb81e02d2b697b7641984260b01',
derivedVia: 'litSessionSignViaNacl',
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"}`,
address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d',
algo: 'ed25519',
},
'http://127.0.0.1:7471': {
sig: 'ec550ae2addd6fbc399ef26158b9cf8b2a1c52240ee60b62c49c8a782c020d0aae5602090029f5a0024f936f0a747d7c007dfecee44bbe99a5cc12dcef7d3309',
derivedVia: 'litSessionSignViaNacl',
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"}`,
address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d',
algo: 'ed25519',
},
'http://127.0.0.1:7472': {
sig: '5f4d83f7fc425ebfca85e45fed0799b93ac215c5ae1c7d279217ca70c434eb43db6e282f365d4f4dc7d76494548e33e4cb6e5f8901b5f142e447fc9718589f07',
derivedVia: 'litSessionSignViaNacl',
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"}`,
address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d',
algo: 'ed25519',
},
};

describe('combine ECDSA Shares', () => {
it('Should recombine ECDSA signature shares', async () => {
Expand Down Expand Up @@ -67,3 +91,112 @@ describe('combine ECDSA Shares', () => {
expect(recoveredAddr).toEqual(addr);
});
});

describe('walletEncrypt and walletDecrypt', () => {
it('should encrypt and decrypt a message successfully', async () => {
// Generate key pairs using the box functionality
const aliceKeyPair = nacl.box.keyPair();
const bobKeyPair = nacl.box.keyPair();

console.log('aliceKeyPair', aliceKeyPair);
console.log('bobKeyPair', bobKeyPair);

// Message to encrypt
const message = new TextEncoder().encode('This is a secret message');

// Alice encrypts a message for Bob
const encryptedPayload = await walletEncrypt(
aliceKeyPair.secretKey,
bobKeyPair.publicKey,
MOCK_SESSION_SIGS['http://127.0.0.1:7470'],
message
);

console.log('encryptedPayload', encryptedPayload);

// Verify payload structure
expect(encryptedPayload).toHaveProperty('V1');
expect(encryptedPayload.V1).toHaveProperty('verification_key');
expect(encryptedPayload.V1).toHaveProperty('ciphertext_and_tag');
expect(encryptedPayload.V1).toHaveProperty('session_signature');
expect(encryptedPayload.V1).toHaveProperty('random');
expect(encryptedPayload.V1).toHaveProperty('created_at');

// Bob decrypts the message from Alice
const decryptedMessage = await walletDecrypt(
bobKeyPair.secretKey,
encryptedPayload
);

// Verify decryption was successful
expect(decryptedMessage).not.toBeNull();
expect(new TextDecoder().decode(decryptedMessage as Uint8Array)).toBe(
'This is a secret message'
);
});

it('should return null when decryption fails', async () => {
// Generate key pairs
const aliceKeyPair = nacl.box.keyPair();
const bobKeyPair = nacl.box.keyPair();
const eveKeyPair = nacl.box.keyPair(); // Eve is an eavesdropper

// Message to encrypt
const message = new TextEncoder().encode('This is a secret message');

// Alice encrypts a message for Bob
const encryptedPayload = await walletEncrypt(
aliceKeyPair.secretKey,
bobKeyPair.publicKey,
MOCK_SESSION_SIGS['http://127.0.0.1:7470'],
message
);

// Eve tries to decrypt the message with her key (should fail)
const decryptedByEve = await walletDecrypt(
eveKeyPair.secretKey,
encryptedPayload
);

// Verify decryption failed
expect(decryptedByEve).toBeNull();
});

it('should handle tampering with the encrypted payload', async () => {
// Generate key pairs
const aliceKeyPair = nacl.box.keyPair();
const bobKeyPair = nacl.box.keyPair();

// Message to encrypt
const message = new TextEncoder().encode('This is a secret message');

// Alice encrypts a message for Bob
const encryptedPayload = await walletEncrypt(
aliceKeyPair.secretKey,
bobKeyPair.publicKey,
MOCK_SESSION_SIGS['http://127.0.0.1:7470'],
message
);

// Tamper with the ciphertext
const tamperedPayload = {
...encryptedPayload,
V1: {
...encryptedPayload.V1,
ciphertext_and_tag:
encryptedPayload.V1.ciphertext_and_tag.substring(0, 10) +
'ff' +
encryptedPayload.V1.ciphertext_and_tag.substring(12),
},
};

// Bob tries to decrypt the tampered message
const decryptedTamperedMessage = await walletDecrypt(
bobKeyPair.secretKey,
tamperedPayload
);

// Verify decryption failed due to tampering
expect(decryptedTamperedMessage).toBeNull();
});
});
82 changes: 50 additions & 32 deletions packages/crypto/src/lib/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
UnknownError,
UnknownSignatureError,
} from '@lit-protocol/constants';
import { checkType, log } from '@lit-protocol/misc';
import { log } from '@lit-protocol/misc';
import { nacl } from '@lit-protocol/nacl';
import {
AuthSig,
CombinedECDSASignature,
NodeAttestation,
SessionKeyPair,
SigningAccessControlConditionJWTPayload,
SigShare,
WalletEncryptedPayload,
} from '@lit-protocol/types';
Expand All @@ -24,14 +24,14 @@ import {
uint8arrayToString,
} from '@lit-protocol/uint8arrays';
import {
BlsSignatureShareJsonString,
EcdsaVariant,
blsCombine,
blsDecrypt,
blsEncrypt,
BlsSignatureShareJsonString,
blsVerify,
ecdsaCombine,
ecdsaDeriveKey,
EcdsaVariant,
ecdsaVerify,
sevSnpGetVcekUrl,
sevSnpVerify,
Expand Down Expand Up @@ -373,99 +373,117 @@ async function getAmdCert(url: string): Promise<Uint8Array> {
}
}

export const walletEncrypt = async(
myWalletSecretKey: Uint8Array,
export const walletEncrypt = async (
myWalletSecretKey: Uint8Array,
theirWalletPublicKey: Uint8Array,
sessionSig: Uint8Array,
sessionSig: AuthSig,
message: Uint8Array
): Promise<WalletEncryptedPayload> => {
const uint8SessionSig = Buffer.from(JSON.stringify(sessionSig));

const random = new Uint8Array(16);
window.crypto.getRandomValues(random);
crypto.getRandomValues(random);
const dateNow = Date.now();
const createdAt = Math.floor(dateNow / 1000);
const timestamp = Buffer.alloc(8);
timestamp.writeBigUInt64BE(BigInt(createdAt), 0);

const myWalletPublicKey = new Uint8Array(32);
nacl.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);
nacl.lowlevel.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);

// Construct AAD
const sessionSignature = Buffer.from(sessionSig); // Replace with actual session signature
// Construct AAD (Additional Authenticated Data) - data that is authenticated but not encrypted
const sessionSignature = uint8SessionSig; // Replace with actual session signature
const theirPublicKey = Buffer.from(theirWalletPublicKey); // Replace with their public key
const myPublicKey = Buffer.from(myWalletPublicKey); // Replace with your wallet public key

const aad = Buffer.concat([
sessionSignature,
random,
timestamp,
theirPublicKey,
myPublicKey,
]);

const hash = new Uint8Array(64);
nacl.crypto_hash(hash, aad);
nacl.lowlevel.crypto_hash(hash, aad);

const nonce = hash.slice(0, 24);
const ciphertext = nacl.box(message, nonce, theirPublicKey, myWalletSecretKey);
const ciphertext = nacl.box(
message,
nonce,
theirPublicKey,
myWalletSecretKey
);
return {
V1: {
verification_key: uint8ArrayToHex(myWalletPublicKey),
ciphertext_and_tag: uint8ArrayToHex(ciphertext),
session_signature: uint8ArrayToHex(sessionSignature),
random: uint8ArrayToHex(random),
created_at: dateNow.toISOString(),
}
created_at: new Date(dateNow).toISOString(),
},
};
}
};

export const walletDecrypt = async(
export const walletDecrypt = async (
myWalletSecretKey: Uint8Array,
payload: WalletEncryptedPayload
): Promise<Uint8Array> => {
const dateSent = new Date(payload.V1.created_at)
const createdAt = Math.floor(dateSent / 1000);
const dateSent = new Date(payload.V1.created_at);
const createdAt = Math.floor(dateSent.getTime() / 1000);
const timestamp = Buffer.alloc(8);
timestamp.writeBigUInt64BE(BigInt(createdAt), 0);

const myWalletPublicKey = new Uint8Array(32);
nacl.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);
nacl.lowlevel.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey);

// Construct AAD
const random = Buffer.from(hexToUint8Array(payload.V1.random));
const sessionSignature = Buffer.from(hexToUint8Array(payload.V1.session_signature)); // Replace with actual session signature
const sessionSignature = Buffer.from(
hexToUint8Array(payload.V1.session_signature)
); // Replace with actual session signature
const theirPublicKey = hexToUint8Array(payload.V1.verification_key);
const theirPublicKeyBuffer = Buffer.from(theirPublicKey); // Replace with their public key
const myPublicKey = Buffer.from(myWalletPublicKey); // Replace with your wallet public key

const aad = Buffer.concat([
sessionSignature,
random,
timestamp,
theirPublicKeyBuffer,
myPublicKey,
]);

const hash = new Uint8Array(64);
nacl.crypto_hash(hash, aad);
nacl.lowlevel.crypto_hash(hash, aad);

const nonce = hash.slice(0, 24);
const message = nacl.box.open(payload.V1.ciphertext_and_tag, nonce, theirPublicKey, myWalletSecretKey);

// Convert hex ciphertext back to Uint8Array
const ciphertext = hexToUint8Array(payload.V1.ciphertext_and_tag);

const message = nacl.box.open(
ciphertext,
nonce,
theirPublicKey,
myWalletSecretKey
);
return message;
}
};

function uint8ArrayToHex(array: Uint8Array) {
return Array.from(array)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('');
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('');
}

function hexToUint8Array(hexString: string): Uint8Array {
if (hexString.length % 2 !== 0) {
throw new Error("Hex string must have an even length");
throw new Error('Hex string must have an even length');
}
const bytes = new Uint8Array(hexString.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hexString.slice(i * 2, i * 2 + 2), 16);
bytes[i] = parseInt(hexString.slice(i * 2, i * 2 + 2), 16);
}
return bytes;
}
Expand Down