diff --git a/local-tests/test.ts b/local-tests/test.ts index 431460344b..c66998d30b 100644 --- a/local-tests/test.ts +++ b/local-tests/test.ts @@ -105,6 +105,7 @@ import { testExportWrappedKey } from './tests/wrapped-keys/testExportWrappedKey' import { testSignMessageWithSolanaEncryptedKey } from './tests/wrapped-keys/testSignMessageWithSolanaEncryptedKey'; import { testSignTransactionWithSolanaEncryptedKey } from './tests/wrapped-keys/testSignTransactionWithSolanaEncryptedKey'; import { testBatchGeneratePrivateKeys } from './tests/wrapped-keys/testBatchGeneratePrivateKeys'; +import { testFailBatchGeneratePrivateKeysAtomic } from './tests/wrapped-keys/testFailStoreEncryptedKeyBatchIsAtomic'; import { setLitActionsCodeToLocal } from './tests/wrapped-keys/util'; import { testUseEoaSessionSigsToRequestSingleResponse } from './tests/testUseEoaSessionSigsToRequestSingleResponse'; @@ -151,6 +152,7 @@ setLitActionsCodeToLocal(); testFailEthereumSignTransactionWrappedKeyWithMissingParam, testFailEthereumSignTransactionWrappedKeyWithInvalidParam, testFailEthereumSignTransactionWrappedKeyInvalidDecryption, + testFailBatchGeneratePrivateKeysAtomic, // -- import wrapped keys testFailImportWrappedKeysWithSamePrivateKey, diff --git a/local-tests/tests/wrapped-keys/testBatchGeneratePrivateKeys.ts b/local-tests/tests/wrapped-keys/testBatchGeneratePrivateKeys.ts index 698ef23919..b2c40701bf 100644 --- a/local-tests/tests/wrapped-keys/testBatchGeneratePrivateKeys.ts +++ b/local-tests/tests/wrapped-keys/testBatchGeneratePrivateKeys.ts @@ -7,7 +7,7 @@ import bs58 from 'bs58'; import { ethers } from 'ethers'; import { BatchGeneratePrivateKeysActionResult } from '../../../packages/wrapped-keys/src/lib/types'; -const { batchGeneratePrivateKeys, exportPrivateKey } = api; +const { batchGeneratePrivateKeys } = api; async function verifySolanaSignature( solanaResult: BatchGeneratePrivateKeysActionResult, diff --git a/local-tests/tests/wrapped-keys/testFailEthereumSignTransactionWrappedKeyInvalidDecryption.ts b/local-tests/tests/wrapped-keys/testFailEthereumSignTransactionWrappedKeyInvalidDecryption.ts index 1289957134..0595610d54 100644 --- a/local-tests/tests/wrapped-keys/testFailEthereumSignTransactionWrappedKeyInvalidDecryption.ts +++ b/local-tests/tests/wrapped-keys/testFailEthereumSignTransactionWrappedKeyInvalidDecryption.ts @@ -1,14 +1,13 @@ import { log } from '@lit-protocol/misc'; import { ethers } from 'ethers'; import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; -import { EthereumLitTransaction } from '@lit-protocol/wrapped-keys'; import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs'; -import { getPkpAccessControlCondition } from 'packages/wrapped-keys/src/lib/utils'; import { encryptString } from '@lit-protocol/encryption'; import { LIT_PREFIX } from 'packages/wrapped-keys/src/lib/constants'; import { LIT_ACTION_CID_REPOSITORY } from '../../../packages/wrapped-keys/src/lib/lit-actions-client/constants'; import { getBaseTransactionForNetwork } from './util'; import { GLOBAL_OVERWRITE_IPFS_CODE_BY_NETWORK } from '@lit-protocol/constants'; +import { getPkpAccessControlCondition } from '../../../packages/wrapped-keys/src/lib/api/utils'; /** * Test Commands: diff --git a/local-tests/tests/wrapped-keys/testFailStoreEncryptedKeyBatchIsAtomic.ts b/local-tests/tests/wrapped-keys/testFailStoreEncryptedKeyBatchIsAtomic.ts new file mode 100644 index 0000000000..7716838387 --- /dev/null +++ b/local-tests/tests/wrapped-keys/testFailStoreEncryptedKeyBatchIsAtomic.ts @@ -0,0 +1,130 @@ +import { log } from '@lit-protocol/misc'; +import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; +import { api } from '@lit-protocol/wrapped-keys'; +import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs'; +import { batchGenerateKeysWithLitAction } from '../../../packages/wrapped-keys/src/lib/lit-actions-client'; +import { getLitActionCodeOrCidCommon } from '../../../packages/wrapped-keys/src/lib/lit-actions-client/utils'; +import { + getFirstSessionSig, + getKeyTypeFromNetwork, + getPkpAccessControlCondition, + getPkpAddressFromSessionSig, +} from '../../../packages/wrapped-keys/src/lib/api/utils'; +import { listEncryptedKeyMetadata } from '../../../packages/wrapped-keys/src/lib/api'; + +const { storeEncryptedKeyBatch } = api; + +/** + * Test Commands: + * ✅ NETWORK=datil-dev yarn test:local --filter=testSignMessageWithSolanaEncryptedKey + * ✅ NETWORK=datil-test yarn test:local --filter=testSignMessageWithSolanaEncryptedKey + * ✅ NETWORK=localchain yarn test:local --filter=testSignMessageWithSolanaEncryptedKey + */ +export const testFailBatchGeneratePrivateKeysAtomic = async ( + devEnv: TinnyEnvironment +) => { + const alice = await devEnv.createRandomPerson(); + + try { + const pkpSessionSigsSigning = await getPkpSessionSigs( + devEnv, + alice, + null, + new Date(Date.now() + 1000 * 60 * 10).toISOString() + ); // 10 mins expiry + + const solanaMessageToSign = 'This is a test solana message'; + const evmMessageToSign = 'This is a test evm message'; + + const sessionSig = getFirstSessionSig(pkpSessionSigsSigning); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); + + const allowPkpAddressToDecrypt = getPkpAccessControlCondition(pkpAddress); + + const { litActionCode, litActionIpfsCid } = getLitActionCodeOrCidCommon( + 'batchGenerateEncryptedKeys' + ); + + const actionResults = await batchGenerateKeysWithLitAction({ + litNodeClient: devEnv.litNodeClient, + litActionIpfsCid: litActionCode ? undefined : litActionIpfsCid, + litActionCode: litActionCode ? litActionCode : undefined, + accessControlConditions: [allowPkpAddressToDecrypt], + actions: [ + { + network: 'evm', + signMessageParams: { messageToSign: evmMessageToSign }, + generateKeyParams: { memo: 'Test evm key' }, + }, + { + network: 'solana', + signMessageParams: { messageToSign: solanaMessageToSign }, + generateKeyParams: { memo: 'Test solana key' }, + }, + ], + pkpSessionSigs: pkpSessionSigsSigning, + }); + + const keyParamsBatch = actionResults.map((keyData) => { + const { generateEncryptedPrivateKey, network } = keyData; + return { + ...generateEncryptedPrivateKey, + keyType: getKeyTypeFromNetwork(network), + }; + }); + + // Intentional failure to persist due to missing publicKey + delete keyParamsBatch[0].publicKey; + + try { + await storeEncryptedKeyBatch({ + pkpSessionSigs: pkpSessionSigsSigning, + litNodeClient: devEnv.litNodeClient, + keyBatch: keyParamsBatch, + }); + + throw new Error( + 'storeEncryptedKeyBatch() succeeded but we expected it to fail!' + ); + } catch (err) { + // We expect `storeEncryptedKeyBatch` to fail w/ a specific error + if ( + err.message.includes( + 'storeEncryptedKeyBatch() succeeded but we expected it to fail!' + ) || + !err.message.includes( + 'keyParamsBatch[0]: Missing "publicKey" parameter in request' + ) + ) { + throw err; + } + + try { + const keys = await listEncryptedKeyMetadata({ + litNodeClient: devEnv.litNodeClient, + pkpSessionSigs: pkpSessionSigsSigning, + }); + + console.error( + 'Got a value back we shouldnt have from listEncryptedKeyMetadata()', + keys + ); + + throw new Error( + 'Expected `listEncryptedKeyMetadata() to fail, but it didnt!`' + ); + } catch (err) { + if (err.message.includes('No keys exist for pkpAddress')) { + log('✅ testFailBatchGeneratePrivateKeysAtomic'); + } else { + throw err; + } + } + } + } catch (err) { + console.log(err.message, err, err.stack); + throw err; + } finally { + devEnv.releasePrivateKeyFromUser(alice); + } +}; diff --git a/local-tests/tests/wrapped-keys/testImportWrappedKey.ts b/local-tests/tests/wrapped-keys/testImportWrappedKey.ts index 50bb507423..63a8054383 100644 --- a/local-tests/tests/wrapped-keys/testImportWrappedKey.ts +++ b/local-tests/tests/wrapped-keys/testImportWrappedKey.ts @@ -3,10 +3,8 @@ import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; import { api } from '@lit-protocol/wrapped-keys'; import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs'; import { randomSolanaPrivateKey } from 'local-tests/setup/tinny-utils'; -import { listPrivateKeyMetadata } from '../../../packages/wrapped-keys/src/lib/service-client'; -import { getFirstSessionSig } from '../../../packages/wrapped-keys/src/lib/utils'; -const { importPrivateKey } = api; +const { importPrivateKey, listEncryptedKeyMetadata } = api; /** * Test Commands: @@ -44,9 +42,9 @@ export const testImportWrappedKey = async (devEnv: TinnyEnvironment) => { ); } - const keys = await listPrivateKeyMetadata({ - sessionSig: getFirstSessionSig(pkpSessionSigs), - litNetwork: devEnv.litNodeClient.config.litNetwork, + const keys = await listEncryptedKeyMetadata({ + pkpSessionSigs, + litNodeClient: devEnv.litNodeClient, }); if (keys.length !== 1 || keys[0].id !== id) { diff --git a/packages/wrapped-keys/src/index.ts b/packages/wrapped-keys/src/index.ts index ea0b95d524..88312d4671 100644 --- a/packages/wrapped-keys/src/index.ts +++ b/packages/wrapped-keys/src/index.ts @@ -8,6 +8,7 @@ import { storeEncryptedKey, listEncryptedKeyMetadata, batchGeneratePrivateKeys, + storeEncryptedKeyBatch, } from './lib/api'; import { CHAIN_ETHEREUM, @@ -45,11 +46,13 @@ import type { SignTransactionParamsSupportedEvm, SignTransactionParamsSupportedSolana, StoreEncryptedKeyParams, + StoreEncryptedKeyBatchParams, StoredKeyData, StoredKeyMetadata, ListEncryptedKeyMetadataParams, StoreEncryptedKeyResult, ImportPrivateKeyResult, + StoreEncryptedKeyBatchResult, } from './lib/types'; export const constants = { @@ -70,6 +73,7 @@ export const api = { signMessageWithEncryptedKey, signTransactionWithEncryptedKey, storeEncryptedKey, + storeEncryptedKeyBatch, batchGeneratePrivateKeys, }; @@ -101,6 +105,8 @@ export { SignTransactionWithEncryptedKeyParams, StoreEncryptedKeyParams, StoreEncryptedKeyResult, + StoreEncryptedKeyBatchParams, + StoreEncryptedKeyBatchResult, StoredKeyData, StoredKeyMetadata, SupportedNetworks, diff --git a/packages/wrapped-keys/src/lib/api/batch-generate-private-keys.ts b/packages/wrapped-keys/src/lib/api/batch-generate-private-keys.ts index 5c2313bb50..1d738ca92b 100644 --- a/packages/wrapped-keys/src/lib/api/batch-generate-private-keys.ts +++ b/packages/wrapped-keys/src/lib/api/batch-generate-private-keys.ts @@ -1,17 +1,17 @@ -import { getKeyTypeFromNetwork } from './utils'; +import { + getFirstSessionSig, + getKeyTypeFromNetwork, + getPkpAccessControlCondition, + getPkpAddressFromSessionSig, +} from './utils'; import { batchGenerateKeysWithLitAction } from '../lit-actions-client'; import { getLitActionCodeOrCidCommon } from '../lit-actions-client/utils'; -import { storePrivateKey } from '../service-client'; +import { storePrivateKeyBatch } from '../service-client'; import { BatchGeneratePrivateKeysActionResult, BatchGeneratePrivateKeysParams, BatchGeneratePrivateKeysResult, } from '../types'; -import { - getFirstSessionSig, - getPkpAccessControlCondition, - getPkpAddressFromSessionSig, -} from '../utils'; /** * TODO: Document batch behaviour @@ -41,34 +41,39 @@ export async function batchGeneratePrivateKeys( pkpSessionSigs, }); - const results = await Promise.all( - actionResults.map( - async (result): Promise => { - const { generateEncryptedPrivateKey, network } = result; + const keyParamsBatch = actionResults.map((keyData) => { + const { generateEncryptedPrivateKey, network } = keyData; + return { + ...generateEncryptedPrivateKey, + keyType: getKeyTypeFromNetwork(network), + }; + }); + + const { ids } = await storePrivateKeyBatch({ + sessionSig, + storedKeyMetadataBatch: keyParamsBatch, + litNetwork: litNodeClient.config.litNetwork, + }); - const signature = result.signMessage?.signature; + const results = actionResults.map( + (actionResult, ndx): BatchGeneratePrivateKeysActionResult => { + const { + generateEncryptedPrivateKey: { memo, publicKey }, + } = actionResult; + const id = ids[ndx]; // Result of writes is in same order as provided - const { id } = await storePrivateKey({ - sessionSig, - storedKeyMetadata: { - ...generateEncryptedPrivateKey, - keyType: getKeyTypeFromNetwork(network), - pkpAddress, - }, - litNetwork: litNodeClient.config.litNetwork, - }); + const signature = actionResult.signMessage?.signature; - return { - ...(signature ? { signMessage: { signature } } : {}), - generateEncryptedPrivateKey: { - memo: generateEncryptedPrivateKey.memo, - id, - generatedPublicKey: generateEncryptedPrivateKey.publicKey, - pkpAddress, - }, - }; - } - ) + return { + ...(signature ? { signMessage: { signature } } : {}), + generateEncryptedPrivateKey: { + memo: memo, + id, + generatedPublicKey: publicKey, + pkpAddress, + }, + }; + } ); return { pkpAddress, results }; diff --git a/packages/wrapped-keys/src/lib/api/export-private-key.ts b/packages/wrapped-keys/src/lib/api/export-private-key.ts index 2162fcca66..10c0872b31 100644 --- a/packages/wrapped-keys/src/lib/api/export-private-key.ts +++ b/packages/wrapped-keys/src/lib/api/export-private-key.ts @@ -1,8 +1,12 @@ +import { + getFirstSessionSig, + getPkpAccessControlCondition, + getPkpAddressFromSessionSig, +} from './utils'; import { exportPrivateKeyWithLitAction } from '../lit-actions-client'; import { getLitActionCodeOrCid } from '../lit-actions-client/utils'; import { fetchPrivateKey } from '../service-client'; import { ExportPrivateKeyParams, ExportPrivateKeyResult } from '../types'; -import { getFirstSessionSig, getPkpAccessControlCondition } from '../utils'; /** * Exports a previously persisted private key from the wrapped keys service for direct use by the caller, along with the keys metadata. @@ -19,7 +23,10 @@ export async function exportPrivateKey( const { litNodeClient, network, pkpSessionSigs, id } = params; const sessionSig = getFirstSessionSig(pkpSessionSigs); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); + const storedKeyMetadata = await fetchPrivateKey({ + pkpAddress, id, sessionSig, litNetwork: litNodeClient.config.litNetwork, diff --git a/packages/wrapped-keys/src/lib/api/generate-private-key.ts b/packages/wrapped-keys/src/lib/api/generate-private-key.ts index 2c8216c947..2f4e4b70f4 100644 --- a/packages/wrapped-keys/src/lib/api/generate-private-key.ts +++ b/packages/wrapped-keys/src/lib/api/generate-private-key.ts @@ -1,13 +1,13 @@ -import { getKeyTypeFromNetwork } from './utils'; -import { generateKeyWithLitAction } from '../lit-actions-client'; -import { getLitActionCodeOrCid } from '../lit-actions-client/utils'; -import { storePrivateKey } from '../service-client'; -import { GeneratePrivateKeyParams, GeneratePrivateKeyResult } from '../types'; import { getFirstSessionSig, + getKeyTypeFromNetwork, getPkpAccessControlCondition, getPkpAddressFromSessionSig, -} from '../utils'; +} from './utils'; +import { generateKeyWithLitAction } from '../lit-actions-client'; +import { getLitActionCodeOrCid } from '../lit-actions-client/utils'; +import { storePrivateKey } from '../service-client'; +import { GeneratePrivateKeyParams, GeneratePrivateKeyResult } from '../types'; /** * Generates a random private key inside a Lit Action, and persists the key and its metadata to the wrapped keys service. @@ -55,7 +55,6 @@ export async function generatePrivateKey( publicKey, keyType: getKeyTypeFromNetwork(network), dataToEncryptHash, - pkpAddress, memo, }, litNetwork: litNodeClient.config.litNetwork, diff --git a/packages/wrapped-keys/src/lib/api/get-encrypted-key.ts b/packages/wrapped-keys/src/lib/api/get-encrypted-key.ts index 39bb5dad83..9c54f24aa6 100644 --- a/packages/wrapped-keys/src/lib/api/get-encrypted-key.ts +++ b/packages/wrapped-keys/src/lib/api/get-encrypted-key.ts @@ -1,6 +1,6 @@ +import { getFirstSessionSig, getPkpAddressFromSessionSig } from './utils'; import { fetchPrivateKey } from '../service-client'; import { GetEncryptedKeyDataParams, StoredKeyData } from '../types'; -import { getFirstSessionSig } from '../utils'; /** Get a previously encrypted and persisted private key and its metadata. * Note that this method does _not_ decrypt the private key; only the _encrypted_ key and its metadata will be returned to the caller. @@ -13,9 +13,13 @@ export async function getEncryptedKey( ): Promise { const { pkpSessionSigs, litNodeClient, id } = params; + const sessionSig = getFirstSessionSig(pkpSessionSigs); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); + return fetchPrivateKey({ + pkpAddress, id, - sessionSig: getFirstSessionSig(pkpSessionSigs), + sessionSig, litNetwork: litNodeClient.config.litNetwork, }); } diff --git a/packages/wrapped-keys/src/lib/api/import-private-key.ts b/packages/wrapped-keys/src/lib/api/import-private-key.ts index 567091fc0a..ce1f3eb583 100644 --- a/packages/wrapped-keys/src/lib/api/import-private-key.ts +++ b/packages/wrapped-keys/src/lib/api/import-private-key.ts @@ -1,13 +1,13 @@ import { encryptString } from '@lit-protocol/encryption'; -import { LIT_PREFIX } from '../constants'; -import { storePrivateKey } from '../service-client'; -import { ImportPrivateKeyParams, ImportPrivateKeyResult } from '../types'; import { getFirstSessionSig, getPkpAccessControlCondition, getPkpAddressFromSessionSig, -} from '../utils'; +} from './utils'; +import { LIT_PREFIX } from '../constants'; +import { storePrivateKey } from '../service-client'; +import { ImportPrivateKeyParams, ImportPrivateKeyResult } from '../types'; /** * Import a provided private key into the wrapped keys service backend. @@ -52,7 +52,6 @@ export async function importPrivateKey( publicKey, keyType, dataToEncryptHash, - pkpAddress, memo, }, }); diff --git a/packages/wrapped-keys/src/lib/api/index.ts b/packages/wrapped-keys/src/lib/api/index.ts index d13292a017..2eebfbfc57 100644 --- a/packages/wrapped-keys/src/lib/api/index.ts +++ b/packages/wrapped-keys/src/lib/api/index.ts @@ -7,6 +7,7 @@ import { listEncryptedKeyMetadata } from './list-encrypted-key-metadata'; import { signMessageWithEncryptedKey } from './sign-message-with-encrypted-key'; import { signTransactionWithEncryptedKey } from './sign-transaction-with-encrypted-key'; import { storeEncryptedKey } from './store-encrypted-key'; +import { storeEncryptedKeyBatch } from './store-encrypted-key-batch'; export { listEncryptedKeyMetadata, @@ -16,6 +17,7 @@ export { exportPrivateKey, signMessageWithEncryptedKey, storeEncryptedKey, + storeEncryptedKeyBatch, getEncryptedKey, batchGeneratePrivateKeys, }; diff --git a/packages/wrapped-keys/src/lib/api/list-encrypted-key-metadata.ts b/packages/wrapped-keys/src/lib/api/list-encrypted-key-metadata.ts index 30bc14e6cf..21626c02a9 100644 --- a/packages/wrapped-keys/src/lib/api/list-encrypted-key-metadata.ts +++ b/packages/wrapped-keys/src/lib/api/list-encrypted-key-metadata.ts @@ -1,6 +1,6 @@ +import { getFirstSessionSig, getPkpAddressFromSessionSig } from './utils'; import { listPrivateKeyMetadata } from '../service-client'; import { ListEncryptedKeyMetadataParams, StoredKeyMetadata } from '../types'; -import { getFirstSessionSig } from '../utils'; /** Get list of metadata for previously encrypted and persisted private keys * Note that this method does include the `ciphertext` or `dataToEncryptHash` values necessary to decrypt the keys. @@ -13,9 +13,12 @@ export async function listEncryptedKeyMetadata( params: ListEncryptedKeyMetadataParams ): Promise { const { pkpSessionSigs, litNodeClient } = params; + const sessionSig = getFirstSessionSig(pkpSessionSigs); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); return listPrivateKeyMetadata({ - sessionSig: getFirstSessionSig(pkpSessionSigs), + pkpAddress, + sessionSig, litNetwork: litNodeClient.config.litNetwork, }); } diff --git a/packages/wrapped-keys/src/lib/api/sign-message-with-encrypted-key.ts b/packages/wrapped-keys/src/lib/api/sign-message-with-encrypted-key.ts index d2c5699811..d629306bef 100644 --- a/packages/wrapped-keys/src/lib/api/sign-message-with-encrypted-key.ts +++ b/packages/wrapped-keys/src/lib/api/sign-message-with-encrypted-key.ts @@ -1,8 +1,12 @@ +import { + getFirstSessionSig, + getPkpAccessControlCondition, + getPkpAddressFromSessionSig, +} from './utils'; import { signMessageWithLitAction } from '../lit-actions-client'; import { getLitActionCodeOrCid } from '../lit-actions-client/utils'; import { fetchPrivateKey } from '../service-client'; import { SignMessageWithEncryptedKeyParams } from '../types'; -import { getFirstSessionSig, getPkpAccessControlCondition } from '../utils'; /** * Signs a message inside the Lit Action using the previously persisted wrapped key associated with the current LIT PK. @@ -19,7 +23,10 @@ export async function signMessageWithEncryptedKey( const { litNodeClient, network, pkpSessionSigs, id } = params; const sessionSig = getFirstSessionSig(pkpSessionSigs); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); + const storedKeyMetadata = await fetchPrivateKey({ + pkpAddress, id, sessionSig, litNetwork: litNodeClient.config.litNetwork, diff --git a/packages/wrapped-keys/src/lib/api/sign-transaction-with-encrypted-key.ts b/packages/wrapped-keys/src/lib/api/sign-transaction-with-encrypted-key.ts index 393c89ae54..53cfa8d4e8 100644 --- a/packages/wrapped-keys/src/lib/api/sign-transaction-with-encrypted-key.ts +++ b/packages/wrapped-keys/src/lib/api/sign-transaction-with-encrypted-key.ts @@ -1,8 +1,12 @@ +import { + getFirstSessionSig, + getPkpAccessControlCondition, + getPkpAddressFromSessionSig, +} from './utils'; import { signTransactionWithLitAction } from '../lit-actions-client'; import { getLitActionCodeOrCid } from '../lit-actions-client/utils'; import { fetchPrivateKey } from '../service-client'; import { SignTransactionWithEncryptedKeyParams } from '../types'; -import { getFirstSessionSig, getPkpAccessControlCondition } from '../utils'; /** * Signs a transaction inside the Lit Action using the previously persisted wrapped key associated with the current LIT PK. @@ -18,9 +22,12 @@ export async function signTransactionWithEncryptedKey( params: SignTransactionWithEncryptedKeyParams ): Promise { const { litNodeClient, network, pkpSessionSigs, id } = params; + const sessionSig = getFirstSessionSig(pkpSessionSigs); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); const storedKeyMetadata = await fetchPrivateKey({ + pkpAddress, id, sessionSig, litNetwork: litNodeClient.config.litNetwork, diff --git a/packages/wrapped-keys/src/lib/api/store-encrypted-key-batch.ts b/packages/wrapped-keys/src/lib/api/store-encrypted-key-batch.ts new file mode 100644 index 0000000000..a7fa466a39 --- /dev/null +++ b/packages/wrapped-keys/src/lib/api/store-encrypted-key-batch.ts @@ -0,0 +1,53 @@ +import { getFirstSessionSig, getPkpAddressFromSessionSig } from './utils'; +import { storePrivateKeyBatch } from '../service-client'; +import { StoreKeyBatchParams } from '../service-client/types'; +import { + StoredKeyData, + StoreEncryptedKeyBatchParams, + StoreEncryptedKeyBatchResult, +} from '../types'; + +/** Stores a batch of encrypted private keys and their metadata to the wrapped keys backend service + * + * @param { StoreEncryptedKeyParams } params Parameters required to fetch the encrypted private key metadata + * @returns { Promise } The encrypted private key and its associated metadata + */ +export async function storeEncryptedKeyBatch( + params: StoreEncryptedKeyBatchParams +): Promise { + const { pkpSessionSigs, litNodeClient, keyBatch } = params; + const sessionSig = getFirstSessionSig(pkpSessionSigs); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); + + const storedKeyMetadataBatch: StoreKeyBatchParams['storedKeyMetadataBatch'] = + keyBatch.map( + ({ + keyType, + publicKey, + memo, + dataToEncryptHash, + ciphertext, + }): Pick< + StoredKeyData, + | 'pkpAddress' + | 'publicKey' + | 'keyType' + | 'dataToEncryptHash' + | 'ciphertext' + | 'memo' + > => ({ + pkpAddress, + publicKey, + memo, + dataToEncryptHash, + ciphertext, + keyType, + }) + ); + + return storePrivateKeyBatch({ + storedKeyMetadataBatch, + sessionSig, + litNetwork: litNodeClient.config.litNetwork, + }); +} diff --git a/packages/wrapped-keys/src/lib/api/store-encrypted-key.ts b/packages/wrapped-keys/src/lib/api/store-encrypted-key.ts index b538ce7a77..f37771cdc1 100644 --- a/packages/wrapped-keys/src/lib/api/store-encrypted-key.ts +++ b/packages/wrapped-keys/src/lib/api/store-encrypted-key.ts @@ -1,6 +1,6 @@ +import { getFirstSessionSig, getPkpAddressFromSessionSig } from './utils'; import { storePrivateKey } from '../service-client'; import { StoreEncryptedKeyParams, StoreEncryptedKeyResult } from '../types'; -import { getFirstSessionSig, getPkpAddressFromSessionSig } from '../utils'; /** Stores an encrypted private key and its metadata to the wrapped keys backend service * @@ -12,7 +12,6 @@ export async function storeEncryptedKey( ): Promise { const { pkpSessionSigs, litNodeClient } = params; const sessionSig = getFirstSessionSig(pkpSessionSigs); - const pkpAddress = getPkpAddressFromSessionSig(sessionSig); const { publicKey, keyType, dataToEncryptHash, ciphertext, memo } = params; @@ -22,10 +21,9 @@ export async function storeEncryptedKey( keyType, dataToEncryptHash, ciphertext, - pkpAddress, memo, }, - sessionSig: getFirstSessionSig(pkpSessionSigs), + sessionSig, litNetwork: litNodeClient.config.litNetwork, }); } diff --git a/packages/wrapped-keys/src/lib/utils.spec.ts b/packages/wrapped-keys/src/lib/api/utils.spec.ts similarity index 99% rename from packages/wrapped-keys/src/lib/utils.spec.ts rename to packages/wrapped-keys/src/lib/api/utils.spec.ts index 24dfe55b4f..3b529ff769 100644 --- a/packages/wrapped-keys/src/lib/utils.spec.ts +++ b/packages/wrapped-keys/src/lib/api/utils.spec.ts @@ -4,12 +4,12 @@ import { SessionSigsMap, } from '@lit-protocol/types'; -import { CHAIN_ETHEREUM } from './constants'; import { getFirstSessionSig, getPkpAccessControlCondition, getPkpAddressFromSessionSig, } from './utils'; +import { CHAIN_ETHEREUM } from '../constants'; describe('getFirstSessionSig from sessionSigs record', () => { const sessionSigs: SessionSigsMap = { diff --git a/packages/wrapped-keys/src/lib/api/utils.ts b/packages/wrapped-keys/src/lib/api/utils.ts index f064062a63..1854f29f6b 100644 --- a/packages/wrapped-keys/src/lib/api/utils.ts +++ b/packages/wrapped-keys/src/lib/api/utils.ts @@ -1,4 +1,14 @@ -import { NETWORK_EVM, NETWORK_SOLANA } from '../constants'; +import { ethers } from 'ethers'; + +import { log } from '@lit-protocol/misc'; +import { + AccsDefaultParams, + AuthSig, + SessionKeySignedMessage, + SessionSigsMap, +} from '@lit-protocol/types'; + +import { CHAIN_ETHEREUM, NETWORK_EVM, NETWORK_SOLANA } from '../constants'; import { KeyType, Network } from '../types'; export function getKeyTypeFromNetwork(network: Network): KeyType { @@ -10,3 +20,91 @@ export function getKeyTypeFromNetwork(network: Network): KeyType { throw new Error(`Network not implemented ${network}`); } } + +/** + * + * Extracts the first SessionSig from the SessionSigsMap since we only pass a single SessionSig to the AWS endpoint + * + * @param pkpSessionSigs - The PKP sessionSigs (map) used to associate the PKP with the generated private key + * + * @returns { AuthSig } - The first SessionSig from the map + */ +export function getFirstSessionSig(pkpSessionSigs: SessionSigsMap): AuthSig { + const sessionSigsEntries = Object.entries(pkpSessionSigs); + + if (sessionSigsEntries.length === 0) { + throw new Error( + `Invalid pkpSessionSigs, length zero: ${JSON.stringify(pkpSessionSigs)}` + ); + } + + const [[, sessionSig]] = sessionSigsEntries; + log(`Session Sig being used: ${JSON.stringify(sessionSig)}`); + + return sessionSig; +} + +/** + * + * Extracts the wallet address from an individual SessionSig + * + * @param pkpSessionSig - The first PKP sessionSig from the function getFirstSessionSig + * + * @returns { string } - The wallet address that signed the capabilites AuthSig (BLS) + */ +export function getPkpAddressFromSessionSig(pkpSessionSig: AuthSig): string { + const sessionSignedMessage: SessionKeySignedMessage = JSON.parse( + pkpSessionSig.signedMessage + ); + + const capabilities = sessionSignedMessage.capabilities; + + if (!capabilities || capabilities.length === 0) { + throw new Error( + `Capabilities in the session's signedMessage is empty, but required.` + ); + } + + const delegationAuthSig = capabilities.find(({ algo }) => algo === 'LIT_BLS'); + + if (!delegationAuthSig) { + throw new Error( + 'SessionSig is not from a PKP; no LIT_BLS capabilities found' + ); + } + + const pkpAddress = delegationAuthSig.address; + log(`pkpAddress to permit decryption: ${pkpAddress}`); + + return pkpAddress; +} + +/** + * + * Creates the access control condition used to gate the access for Wrapped Key decryption + * + * @param { string } pkpAddress - The wallet address of the PKP which can decrypt the encrypted Wrapped Key + * + * @returns { AccsDefaultParams } - The access control condition that only allows the PKP address to decrypt + */ +export function getPkpAccessControlCondition( + pkpAddress: string +): AccsDefaultParams { + if (!ethers.utils.isAddress(pkpAddress)) { + throw new Error( + `pkpAddress is not a valid Ethereum Address: ${pkpAddress}` + ); + } + + return { + contractAddress: '', + standardContractType: '', + chain: CHAIN_ETHEREUM, + method: '', + parameters: [':userAddress'], + returnValueTest: { + comparator: '=', + value: pkpAddress, + }, + }; +} diff --git a/packages/wrapped-keys/src/lib/service-client/client.ts b/packages/wrapped-keys/src/lib/service-client/client.ts index 98f4c61e19..319a1ce9b6 100644 --- a/packages/wrapped-keys/src/lib/service-client/client.ts +++ b/packages/wrapped-keys/src/lib/service-client/client.ts @@ -1,11 +1,16 @@ -import { FetchKeyParams, ListKeysParams, StoreKeyParams } from './types'; +import { + FetchKeyParams, + ListKeysParams, + StoreKeyBatchParams, + StoreKeyParams, +} from './types'; import { generateRequestId, getBaseRequestParams, makeRequest } from './utils'; import { StoredKeyData, StoredKeyMetadata, + StoreEncryptedKeyBatchResult, StoreEncryptedKeyResult, } from '../types'; -import { getPkpAddressFromSessionSig } from '../utils'; /** Fetches previously stored private key metadata from the wrapped keys service. * Note that this list will not include `cipherText` or `dataToEncryptHash` necessary to decrypt the keys. @@ -17,7 +22,7 @@ import { getPkpAddressFromSessionSig } from '../utils'; export async function listPrivateKeyMetadata( params: ListKeysParams ): Promise { - const { litNetwork, sessionSig } = params; + const { litNetwork, sessionSig, pkpAddress } = params; const requestId = generateRequestId(); const { baseUrl, initParams } = getBaseRequestParams({ @@ -27,8 +32,6 @@ export async function listPrivateKeyMetadata( requestId, }); - const pkpAddress = getPkpAddressFromSessionSig(sessionSig); - return makeRequest({ url: `${baseUrl}/${pkpAddress}`, init: initParams, @@ -45,7 +48,7 @@ export async function listPrivateKeyMetadata( export async function fetchPrivateKey( params: FetchKeyParams ): Promise { - const { litNetwork, sessionSig, id } = params; + const { litNetwork, sessionSig, id, pkpAddress } = params; const requestId = generateRequestId(); const { baseUrl, initParams } = getBaseRequestParams({ @@ -54,7 +57,6 @@ export async function fetchPrivateKey( method: 'GET', requestId, }); - const pkpAddress = getPkpAddressFromSessionSig(sessionSig); return makeRequest({ url: `${baseUrl}/${pkpAddress}/${id}`, @@ -92,3 +94,33 @@ export async function storePrivateKey( return { pkpAddress, id }; } + +/** Stores a batch of up to 25 private key metadata into the wrapped keys service backend + * + * @param { StoreKeyParams } params Parameters required to store the private key metadata + * @returns { Promise } `true` on successful write to the service. Otherwise, this method throws an error. + */ +export async function storePrivateKeyBatch( + params: StoreKeyBatchParams +): Promise { + const { litNetwork, sessionSig, storedKeyMetadataBatch } = params; + + const requestId = generateRequestId(); + const { baseUrl, initParams } = getBaseRequestParams({ + litNetwork, + sessionSig, + method: 'POST', + requestId, + }); + + const { pkpAddress, ids } = await makeRequest({ + url: `${baseUrl}_batch`, + init: { + ...initParams, + body: JSON.stringify({ keyParamsBatch: storedKeyMetadataBatch }), + }, + requestId, + }); + + return { pkpAddress, ids }; +} diff --git a/packages/wrapped-keys/src/lib/service-client/index.ts b/packages/wrapped-keys/src/lib/service-client/index.ts index 788a8f1fcb..68842e14a1 100644 --- a/packages/wrapped-keys/src/lib/service-client/index.ts +++ b/packages/wrapped-keys/src/lib/service-client/index.ts @@ -1,7 +1,13 @@ import { fetchPrivateKey, storePrivateKey, + storePrivateKeyBatch, listPrivateKeyMetadata, } from './client'; -export { fetchPrivateKey, storePrivateKey, listPrivateKeyMetadata }; +export { + fetchPrivateKey, + storePrivateKey, + storePrivateKeyBatch, + listPrivateKeyMetadata, +}; diff --git a/packages/wrapped-keys/src/lib/service-client/types.ts b/packages/wrapped-keys/src/lib/service-client/types.ts index bff51c9172..4b517612f1 100644 --- a/packages/wrapped-keys/src/lib/service-client/types.ts +++ b/packages/wrapped-keys/src/lib/service-client/types.ts @@ -8,10 +8,11 @@ interface BaseApiParams { } export type FetchKeyParams = BaseApiParams & { + pkpAddress: string; id: string; }; -export type ListKeysParams = BaseApiParams; +export type ListKeysParams = BaseApiParams & { pkpAddress: string }; export type SupportedNetworks = Extract< LIT_NETWORKS_KEYS, @@ -21,15 +22,17 @@ export type SupportedNetworks = Extract< export interface StoreKeyParams extends BaseApiParams { storedKeyMetadata: Pick< StoredKeyData, - | 'pkpAddress' - | 'publicKey' - | 'keyType' - | 'dataToEncryptHash' - | 'ciphertext' - | 'memo' + 'publicKey' | 'keyType' | 'dataToEncryptHash' | 'ciphertext' | 'memo' >; } +export interface StoreKeyBatchParams extends BaseApiParams { + storedKeyMetadataBatch: Pick< + StoredKeyData, + 'publicKey' | 'keyType' | 'dataToEncryptHash' | 'ciphertext' | 'memo' + >[]; +} + export interface BaseRequestParams { sessionSig: AuthSig; method: 'GET' | 'POST'; diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index ab03175543..b3742ff691 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -77,7 +77,7 @@ export interface StoredKeyData extends StoredKeyMetadata { dataToEncryptHash: string; } -/** Fetching a previously persisted key's metadata only requires valid pkpSessionSigs and a LIT Node Client instance configured for the appropriate network. +/** Properties required to persist an encrypted key into the wrapped-keys backend storage service * * @typedef StoreEncryptedKeyParams * @extends BaseApiParams @@ -101,6 +101,31 @@ export interface StoreEncryptedKeyResult { pkpAddress: string; } +/** Properties required to persist a batch of encrypted keys into the wrapped-keys backend storage service + * + * @typedef StoreEncryptedKeyBatchParams + * @extends BaseApiParams + * + */ +export type StoreEncryptedKeyBatchParams = BaseApiParams & { + keyBatch: Pick< + StoredKeyData, + 'publicKey' | 'keyType' | 'dataToEncryptHash' | 'ciphertext' | 'memo' + >[]; +}; + +/** Result of storing a batch of private keys in the wrapped keys backend service + * Includes an array of unique identifiers, which are necessary to get the encrypted ciphertext and dataToEncryptHash in the future + * + * @typedef StoreEncryptedKeyBatchResult + * @property { string } pkpAddress The LIT PKP Address that the key was linked to; this is derived from the provided pkpSessionSigs + * @property { string[] } ids Array of the unique identifiers (UUID V4) of the encrypted private keys in the same order provided + */ +export interface StoreEncryptedKeyBatchResult { + ids: string[]; + pkpAddress: string; +} + /** Exporting a previously persisted key only requires valid pkpSessionSigs and a LIT Node Client instance configured for the appropriate network. * * @typedef ExportPrivateKeyParams diff --git a/packages/wrapped-keys/src/lib/utils.ts b/packages/wrapped-keys/src/lib/utils.ts deleted file mode 100644 index 4a5727d207..0000000000 --- a/packages/wrapped-keys/src/lib/utils.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ethers } from 'ethers'; - -import { log } from '@lit-protocol/misc'; -import { - AccsDefaultParams, - AuthSig, - SessionKeySignedMessage, - SessionSigsMap, -} from '@lit-protocol/types'; - -import { CHAIN_ETHEREUM } from './constants'; - -/** - * - * Extracts the first SessionSig from the SessionSigsMap since we only pass a single SessionSig to the AWS endpoint - * - * @param pkpSessionSigs - The PKP sessionSigs (map) used to associate the PKP with the generated private key - * - * @returns { AuthSig } - The first SessionSig from the map - */ -export function getFirstSessionSig(pkpSessionSigs: SessionSigsMap): AuthSig { - const sessionSigsEntries = Object.entries(pkpSessionSigs); - - if (sessionSigsEntries.length === 0) { - throw new Error( - `Invalid pkpSessionSigs, length zero: ${JSON.stringify(pkpSessionSigs)}` - ); - } - - const [[, sessionSig]] = sessionSigsEntries; - log(`Session Sig being used: ${JSON.stringify(sessionSig)}`); - - return sessionSig; -} - -/** - * - * Extracts the wallet address from an individual SessionSig - * - * @param pkpSessionSig - The first PKP sessionSig from the function getFirstSessionSig - * - * @returns { string } - The wallet address that signed the capabilites AuthSig (BLS) - */ -export function getPkpAddressFromSessionSig(pkpSessionSig: AuthSig): string { - const sessionSignedMessage: SessionKeySignedMessage = JSON.parse( - pkpSessionSig.signedMessage - ); - - const capabilities = sessionSignedMessage.capabilities; - - if (!capabilities || capabilities.length === 0) { - throw new Error( - `Capabilities in the session's signedMessage is empty, but required.` - ); - } - - const delegationAuthSig = capabilities.find(({ algo }) => algo === 'LIT_BLS'); - - if (!delegationAuthSig) { - throw new Error( - 'SessionSig is not from a PKP; no LIT_BLS capabilities found' - ); - } - - const pkpAddress = delegationAuthSig.address; - log(`pkpAddress to permit decryption: ${pkpAddress}`); - - return pkpAddress; -} - -/** - * - * Creates the access control condition used to gate the access for Wrapped Key decryption - * - * @param { string } pkpAddress - The wallet address of the PKP which can decrypt the encrypted Wrapped Key - * - * @returns { AccsDefaultParams } - The access control condition that only allows the PKP address to decrypt - */ -export function getPkpAccessControlCondition( - pkpAddress: string -): AccsDefaultParams { - if (!ethers.utils.isAddress(pkpAddress)) { - throw new Error( - `pkpAddress is not a valid Ethereum Address: ${pkpAddress}` - ); - } - - return { - contractAddress: '', - standardContractType: '', - chain: CHAIN_ETHEREUM, - method: '', - parameters: [':userAddress'], - returnValueTest: { - comparator: '=', - value: pkpAddress, - }, - }; -}