Skip to content

Commit

Permalink
Merge pull request #534 from LIT-Protocol/feat-update-wrapped-keys-ex…
Browse files Browse the repository at this point in the history
…port

Feat: Update Wrapped keys Export to use Lit Actions
  • Loading branch information
MaximusHaximus authored Jul 15, 2024
2 parents fb77647 + 3d19136 commit 887dea7
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 53 deletions.
1 change: 1 addition & 0 deletions local-tests/tests/wrapped-keys/testExportWrappedKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const testExportWrappedKey = async (devEnv: TinnyEnvironment) => {
const { decryptedPrivateKey } = await exportPrivateKey({
pkpSessionSigs: pkpSessionSigsExport,
litNodeClient: devEnv.litNodeClient,
network: 'solana',
});

if (decryptedPrivateKey !== privateKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ export const testGenerateEthereumWrappedKey = async (

console.log(pkpSessionSigsExport);

// FIXME: Export broken as we can't decrypt data encrypted inside a Lit Action
const { decryptedPrivateKey } = await exportPrivateKey({
pkpSessionSigs: pkpSessionSigsExport,
litNodeClient: devEnv.litNodeClient,
network: 'evm',
});

const wallet = new ethers.Wallet(decryptedPrivateKey);
Expand Down
12 changes: 7 additions & 5 deletions local-tests/tests/wrapped-keys/testGenerateSolanaWrappedKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { api } from '@lit-protocol/wrapped-keys';
import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs';
import nacl from 'tweetnacl';
import bs58 from 'bs58';
import { ethers } from 'ethers';
import { Keypair } from '@solana/web3.js';

const { generatePrivateKey, signMessageWithEncryptedKey, exportPrivateKey } =
api;
Expand Down Expand Up @@ -83,16 +83,18 @@ export const testGenerateSolanaWrappedKey = async (
new Date(Date.now() + 1000 * 60 * 10).toISOString()
); // 10 mins expiry

// FIXME: Export broken as we can't decrypt data encrypted inside a Lit Action
const { decryptedPrivateKey } = await exportPrivateKey({
pkpSessionSigs: pkpSessionSigsExport,
litNodeClient: devEnv.litNodeClient,
network: 'solana',
});

const wallet = new ethers.Wallet(decryptedPrivateKey);
const decryptedPublicKey = wallet.publicKey;
const solanaKeyPair = Keypair.fromSecretKey(
Buffer.from(decryptedPrivateKey, 'hex')
);
const decryptedPublicKey = solanaKeyPair.publicKey;

if (decryptedPublicKey !== generatedPublicKey) {
if (decryptedPublicKey.toString() !== generatedPublicKey) {
throw new Error(
`Decrypted decryptedPublicKey: ${decryptedPublicKey} doesn't match with the original generatedPublicKey: ${generatedPublicKey}`
);
Expand Down
8 changes: 8 additions & 0 deletions packages/wrapped-keys/esbuild.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ const esbuild = require('esbuild');
outdir: './src/lib/litActions/ethereum/dist',
inject: ['./buffer.shim.js'],
});
await esbuild.build({
entryPoints: ['./src/lib/litActions/common/src/exportPrivateKey.js'],
bundle: true,
minify: true,
sourcemap: false,
outdir: './src/lib/litActions/common/dist',
inject: ['./buffer.shim.js'],
});
})();
56 changes: 20 additions & 36 deletions packages/wrapped-keys/src/lib/api/export-private-key.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,39 @@
import { decryptToString } from '@lit-protocol/encryption';
import { exportPrivateKeyWithLitAction } from '../lit-actions-client';

import { CHAIN_ETHEREUM, LIT_PREFIX } from '../constants';
import { fetchPrivateKeyMetadata } from '../service-client';
import { ExportPrivateKeyParams, ExportPrivateKeyResult } from '../types';
import {
getFirstSessionSig,
getPkpAccessControlCondition,
getPkpAddressFromSessionSig,
} from '../utils';
import { getFirstSessionSig, getPkpAccessControlCondition } from '../utils';
import { getLitActionCid } from '../lit-actions-client/utils';

/** Exports a previously persisted private key from the wrapped keys service for direct use by the caller, along with the keys metadata
/**
* Exports a previously persisted private key from the wrapped keys service for direct use by the caller, along with the keys metadata.
* 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
* removes the salt from the decrypted key.
*
* @param { ExportPrivateKeyParams } params Parameters required to export the private key
*
* @returns { Promise<ExportPrivateKeyResult> } - The decrypted private key of the Wrapped Key along with all the associated key info and LIT PKP Address associated with the Wrapped Key
*/
export async function exportPrivateKey(
params: ExportPrivateKeyParams
): Promise<ExportPrivateKeyResult> {
const { pkpSessionSigs, litNodeClient } = params;
const { litNodeClient, network, pkpSessionSigs } = params;

const sessionSig = getFirstSessionSig(pkpSessionSigs);
const pkpAddress = getPkpAddressFromSessionSig(sessionSig);
const allowPkpAddressToDecrypt = getPkpAccessControlCondition(pkpAddress);

const privateKeyMetadata = await fetchPrivateKeyMetadata({
const storedKeyMetadata = await fetchPrivateKeyMetadata({
sessionSig,
litNetwork: litNodeClient.config.litNetwork,
});

const { ciphertext, dataToEncryptHash, ...privateKeyMetadataMinusEncrypted } =
privateKeyMetadata;

const decryptedPrivateKey = await decryptToString(
{
accessControlConditions: [allowPkpAddressToDecrypt],
chain: CHAIN_ETHEREUM,
ciphertext,
dataToEncryptHash,
sessionSigs: pkpSessionSigs,
},
litNodeClient
const allowPkpAddressToDecrypt = getPkpAccessControlCondition(
storedKeyMetadata.pkpAddress
);

// It will be of the form lit_<privateKey>
if (!decryptedPrivateKey.startsWith(LIT_PREFIX)) {
throw new Error(
`PKey was not encrypted with salt; all wrapped keys must be prefixed with '${LIT_PREFIX}'`
);
}

return {
decryptedPrivateKey: decryptedPrivateKey.slice(LIT_PREFIX.length),
...privateKeyMetadataMinusEncrypted,
};
return exportPrivateKeyWithLitAction({
...params,
litActionIpfsCid: getLitActionCid(network, 'exportPrivateKey'),
accessControlConditions: [allowPkpAddressToDecrypt],
pkpSessionSigs,
storedKeyMetadata,
});
}
12 changes: 8 additions & 4 deletions packages/wrapped-keys/src/lib/lit-actions-client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ import { LitCidRepository } from './types';

const LIT_ACTION_CID_REPOSITORY: LitCidRepository = Object.freeze({
signTransaction: Object.freeze({
evm: 'QmdYUhPCCK5hpDWMK1NiDLNLG6RZQy61QE4J7dBm1Y2nbA',
solana: 'QmSi9GL2weCFEP1SMAUw5PDpZRr436Zt3tLUNrSECPA5dT',
evm: 'QmSsgmy1N1zFZ5yNPCY7QWQZwrYRLuYUJF1VDygJc7L26o',
solana: 'QmdkcMmrtqWSQ8VrPr8KwzuzZnAxGJoVDeZP3NKTWCMZCg',
}),
signMessage: Object.freeze({
evm: 'QmTMGcyp77NeppGaqF2DmE1F8GXTSxQYzXCrbE7hNudUWx',
solana: 'QmUxnWS8VU9QwZRdyfwsEtvhJZcvcdrannjokgZoA1sesy',
evm: 'QmWVW51FBH5j3wwaMVy8MR1QyzJgEjuaPh1yqwSGXRCENx',
solana: 'QmSPcfFhLofjhNDd5ZdVbhw53zmbR8oV4C2585Bm7C8izH',
}),
generateEncryptedKey: Object.freeze({
evm: 'QmaoPMSqcze3NW3KSA75ecWSkcmWT1J7kVr8LyJPCKRvHd',
solana: 'QmdRBXYLYvcNHrChmsZ2jFDY8dA99CcSdqHo3p1ES3UThL',
}),
exportPrivateKey: Object.freeze({
evm: 'Qmb5ZAm1EZRL7dYTtyYxkPxx4kBmoCjjzcgdrJH9cKMXxR',
solana: 'Qmb5ZAm1EZRL7dYTtyYxkPxx4kBmoCjjzcgdrJH9cKMXxR',
}),
});

export { LIT_ACTION_CID_REPOSITORY };
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { AccessControlConditions } from '@lit-protocol/types';

import { postLitActionValidation } from './utils';
import { ExportPrivateKeyParams, StoredKeyMetadata } from '../types';

interface SignMessageWithLitActionParams extends ExportPrivateKeyParams {
accessControlConditions: AccessControlConditions;
storedKeyMetadata: StoredKeyMetadata;
litActionIpfsCid: string;
}

export async function exportPrivateKeyWithLitAction(
args: SignMessageWithLitActionParams
) {
const {
accessControlConditions,
litNodeClient,
pkpSessionSigs,
litActionIpfsCid,
storedKeyMetadata,
} = args;

const {
pkpAddress,
ciphertext,
dataToEncryptHash,
...storeKeyMetadataMinusEncryptedAndPkp
} = storedKeyMetadata;
const result = await litNodeClient.executeJs({
sessionSigs: pkpSessionSigs,
ipfsId: litActionIpfsCid,
jsParams: {
pkpAddress,
ciphertext,
dataToEncryptHash,
accessControlConditions,
},
});

const decryptedPrivateKey = postLitActionValidation(result);

return {
decryptedPrivateKey,
pkpAddress,
...storeKeyMetadataMinusEncryptedAndPkp,
};
}
2 changes: 2 additions & 0 deletions packages/wrapped-keys/src/lib/lit-actions-client/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { generateKeyWithLitAction } from './generate-key';
import { signMessageWithLitAction } from './sign-message';
import { signTransactionWithLitAction } from './sign-transaction';
import { exportPrivateKeyWithLitAction } from './export-private-key';

export {
generateKeyWithLitAction,
signTransactionWithLitAction,
signMessageWithLitAction,
exportPrivateKeyWithLitAction,
};
3 changes: 2 additions & 1 deletion packages/wrapped-keys/src/lib/lit-actions-client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Network } from '../types';
export type LitActionType =
| 'signTransaction'
| 'signMessage'
| 'generateEncryptedKey';
| 'generateEncryptedKey'
| 'exportPrivateKey';

export type LitCidRepositoryEntry = Readonly<Record<Network, string>>;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { removeSaltFromDecryptedKey } = require('../../utils');

/**
*
* Exports the private key after decrypting and removing the salt from it.
*
* @jsParam pkpAddress - The Eth address of the PKP which is associated with the Wrapped Key
* @jsParam ciphertext - For the encrypted Wrapped Key
* @jsParam dataToEncryptHash - For the encrypted Wrapped Key
* @jsParam accessControlConditions - The access control condition that allows only the pkpAddress to decrypt the Wrapped Key
*
* @returns { Promise<string> } - Returns a decrypted private key.
*/

(async () => {
let decryptedPrivateKey;
try {
decryptedPrivateKey = await Lit.Actions.decryptToSingleNode({
accessControlConditions,
ciphertext,
dataToEncryptHash,
chain: 'ethereum',
authSig: null,
});
} catch (err) {
const errorMessage =
'Error: When decrypting to a single node- ' + err.message;
Lit.Actions.setResponse({ response: errorMessage });
return;
}

if (!decryptedPrivateKey) {
// Exit the nodes which don't have the decryptedData
return;
}

try {
const privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
Lit.Actions.setResponse({ response: privateKey });
} catch (err) {
Lit.Actions.setResponse({ response: err.message });
}
})();
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ const { removeSaltFromDecryptedKey } = require('../../utils');
return;
}

const privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
let privateKey;
try {
privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
Lit.Actions.setResponse({ response: privateKey });
} catch (err) {
Lit.Actions.setResponse({ response: err.message });
return;
}
const wallet = new ethers.Wallet(privateKey);

try {
const signature = await wallet.signMessage(messageToSign);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ const { removeSaltFromDecryptedKey } = require('../../utils');
return;
}

const privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
let privateKey;
try {
privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
Lit.Actions.setResponse({ response: privateKey });
} catch (err) {
Lit.Actions.setResponse({ response: err.message });
return;
}
const wallet = new ethers.Wallet(privateKey);

let nonce;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ const { removeSaltFromDecryptedKey } = require('../../utils');
return;
}

const privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
let privateKey;
try {
privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
Lit.Actions.setResponse({ response: privateKey });
} catch (err) {
Lit.Actions.setResponse({ response: err.message });
return;
}
const solanaKeyPair = Keypair.fromSecretKey(Buffer.from(privateKey, 'hex'));

let signature;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ const { removeSaltFromDecryptedKey } = require('../../utils');
return;
}

const privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
let privateKey;
try {
privateKey = removeSaltFromDecryptedKey(decryptedPrivateKey);
Lit.Actions.setResponse({ response: privateKey });
} catch (err) {
Lit.Actions.setResponse({ response: err.message });
return;
}

const solanaKeyPair = Keypair.fromSecretKey(
Uint8Array.from(Buffer.from(privateKey, 'hex'))
Expand Down
2 changes: 1 addition & 1 deletion packages/wrapped-keys/src/lib/litActions/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LIT_PREFIX } from '../constants';
export function removeSaltFromDecryptedKey(decryptedPrivateKey) {
if (!decryptedPrivateKey.startsWith(LIT_PREFIX)) {
throw new Error(
`PKey was not encrypted with salt; all wrapped keys must be prefixed with '${LIT_PREFIX}'`
`Error: PKey was not encrypted with salt; all wrapped keys must be prefixed with '${LIT_PREFIX}'`
);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/wrapped-keys/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export type StoreEncryptedKeyMetadataParams = BaseApiParams &
* @extends BaseApiParams
*
*/
export type ExportPrivateKeyParams = BaseApiParams;
export type ExportPrivateKeyParams = BaseApiParams & ApiParamsSupportedNetworks;

/** Includes the decrypted private key and metadata that was stored alongside it in the wrapped keys service
*
Expand Down

0 comments on commit 887dea7

Please sign in to comment.