From adea77ae092315ac2e2fc1b3a53888ff7c1a34e6 Mon Sep 17 00:00:00 2001 From: anson Date: Tue, 9 Dec 2025 22:10:12 +0000 Subject: [PATCH 01/19] feat(wrapped-keys): add versioned key update support and client APIs --- .../src/test-helpers/executeJs/wrappedKeys.ts | 50 +++++++++++++++++++ packages/wrapped-keys/src/index.ts | 5 ++ .../src/lib/api/get-encrypted-key.ts | 3 +- packages/wrapped-keys/src/lib/api/index.ts | 2 + .../src/lib/api/update-encrypted-key.ts | 35 +++++++++++++ .../src/lib/service-client/client.ts | 49 +++++++++++++++++- .../src/lib/service-client/index.ts | 2 + .../src/lib/service-client/types.ts | 11 +++- packages/wrapped-keys/src/lib/types.ts | 34 +++++++++++++ 9 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 packages/wrapped-keys/src/lib/api/update-encrypted-key.ts diff --git a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts index a61c52e5e9..5a8211c335 100644 --- a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts +++ b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts @@ -341,6 +341,56 @@ export const registerWrappedKeysTests = () => { expect(storedKey.dataToEncryptHash).toBeTruthy(); }); + test('updateEncryptedKey rotates ciphertext and returns version history', async () => { + const pkpSessionSigs = await TestHelper.createPkpSessionSigs({ + testEnv, + alice, + delegationAuthSig: aliceDelegationAuthSig, + }); + + const initialPayload = TestHelper.createStorePayload( + TestHelper.randomMemo('update-before') + ); + + const { id } = await wrappedKeysApi.storeEncryptedKey({ + pkpSessionSigs, + litClient: testEnv.litClient, + ...initialPayload, + }); + + const newCiphertext = TestHelper.randomCiphertext(); + const newMemo = TestHelper.randomMemo('update-after'); + + const updateResult = await wrappedKeysApi.updateEncryptedKey({ + pkpSessionSigs, + litClient: testEnv.litClient, + id, + ciphertext: newCiphertext, + memo: newMemo, + }); + + expect(updateResult.id).toBe(id); + expect(updateResult.pkpAddress).toBe(alice.pkp!.ethAddress); + expect(updateResult.updatedAt).toBeTruthy(); + + const fetched = await wrappedKeysApi.getEncryptedKey({ + pkpSessionSigs, + litClient: testEnv.litClient, + id, + includeVersions: true, + }); + + expect(fetched.ciphertext).toBe(newCiphertext); + expect(fetched.memo).toBe(newMemo); + expect(fetched.updatedAt).toBeTruthy(); + expect(fetched.versions).toBeDefined(); + expect(fetched.versions?.length).toBe(1); + expect(fetched.versions?.[0].ciphertext).toBe( + initialPayload.ciphertext + ); + expect(fetched.versions?.[0].memo).toBe(initialPayload.memo); + }); + test('importPrivateKey persists an externally generated key', async () => { const pkpSessionSigs = await TestHelper.createPkpSessionSigs({ testEnv, diff --git a/packages/wrapped-keys/src/index.ts b/packages/wrapped-keys/src/index.ts index f94419f6f1..213a10f959 100644 --- a/packages/wrapped-keys/src/index.ts +++ b/packages/wrapped-keys/src/index.ts @@ -9,6 +9,7 @@ import { signTransactionWithEncryptedKey, storeEncryptedKey, storeEncryptedKeyBatch, + updateEncryptedKey, } from './lib/api'; import { CHAIN_ETHEREUM, @@ -43,6 +44,7 @@ export const api = { storeEncryptedKey, storeEncryptedKeyBatch, batchGeneratePrivateKeys, + updateEncryptedKey, }; export const config = { @@ -76,6 +78,9 @@ export type { StoreEncryptedKeyResult, StoredKeyData, StoredKeyMetadata, + UpdateEncryptedKeyParams, + UpdateEncryptedKeyResult, + WrappedKeyVersion, } from './lib/types'; export type { 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 a8d21fc654..a87fa8873a 100644 --- a/packages/wrapped-keys/src/lib/api/get-encrypted-key.ts +++ b/packages/wrapped-keys/src/lib/api/get-encrypted-key.ts @@ -15,7 +15,7 @@ import { GetEncryptedKeyDataParams, StoredKeyData } from '../types'; export async function getEncryptedKey( params: GetEncryptedKeyDataParams ): Promise { - const { pkpSessionSigs, litClient, id } = params; + const { pkpSessionSigs, litClient, id, includeVersions } = params; const sessionSig = getFirstSessionSig(pkpSessionSigs); const pkpAddress = getPkpAddressFromSessionSig(sessionSig); @@ -26,5 +26,6 @@ export async function getEncryptedKey( id, sessionSig, litNetwork, + includeVersions, }); } diff --git a/packages/wrapped-keys/src/lib/api/index.ts b/packages/wrapped-keys/src/lib/api/index.ts index 2eebfbfc57..7c2d314d62 100644 --- a/packages/wrapped-keys/src/lib/api/index.ts +++ b/packages/wrapped-keys/src/lib/api/index.ts @@ -8,6 +8,7 @@ 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'; +import { updateEncryptedKey } from './update-encrypted-key'; export { listEncryptedKeyMetadata, @@ -20,4 +21,5 @@ export { storeEncryptedKeyBatch, getEncryptedKey, batchGeneratePrivateKeys, + updateEncryptedKey, }; diff --git a/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts b/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts new file mode 100644 index 0000000000..3663a55dfa --- /dev/null +++ b/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts @@ -0,0 +1,35 @@ +import { updatePrivateKey } from '../service-client'; +import { UpdateEncryptedKeyParams, UpdateEncryptedKeyResult } from '../types'; +import { + getFirstSessionSig, + getLitNetworkFromClient, + getPkpAddressFromSessionSig, +} from './utils'; + +/** Update an existing wrapped key and append the previous state to versions. */ +export async function updateEncryptedKey( + params: UpdateEncryptedKeyParams +): Promise { + const { + pkpSessionSigs, + litClient, + id, + ciphertext, + evmContractConditions, + memo, + } = params; + + const sessionSig = getFirstSessionSig(pkpSessionSigs); + const pkpAddress = getPkpAddressFromSessionSig(sessionSig); + const litNetwork = getLitNetworkFromClient(litClient); + + return updatePrivateKey({ + pkpAddress, + id, + sessionSig, + ciphertext, + evmContractConditions, + memo, + litNetwork, + }); +} diff --git a/packages/wrapped-keys/src/lib/service-client/client.ts b/packages/wrapped-keys/src/lib/service-client/client.ts index 319a1ce9b6..966a65bc43 100644 --- a/packages/wrapped-keys/src/lib/service-client/client.ts +++ b/packages/wrapped-keys/src/lib/service-client/client.ts @@ -3,6 +3,7 @@ import { ListKeysParams, StoreKeyBatchParams, StoreKeyParams, + UpdateKeyParams, } from './types'; import { generateRequestId, getBaseRequestParams, makeRequest } from './utils'; import { @@ -10,6 +11,7 @@ import { StoredKeyMetadata, StoreEncryptedKeyBatchResult, StoreEncryptedKeyResult, + UpdateEncryptedKeyResult, } from '../types'; /** Fetches previously stored private key metadata from the wrapped keys service. @@ -48,7 +50,7 @@ export async function listPrivateKeyMetadata( export async function fetchPrivateKey( params: FetchKeyParams ): Promise { - const { litNetwork, sessionSig, id, pkpAddress } = params; + const { litNetwork, sessionSig, id, pkpAddress, includeVersions } = params; const requestId = generateRequestId(); const { baseUrl, initParams } = getBaseRequestParams({ @@ -58,8 +60,9 @@ export async function fetchPrivateKey( requestId, }); + const query = includeVersions ? '?includeVersions=true' : ''; return makeRequest({ - url: `${baseUrl}/${pkpAddress}/${id}`, + url: `${baseUrl}/${pkpAddress}/${id}${query}`, init: initParams, requestId, }); @@ -124,3 +127,45 @@ export async function storePrivateKeyBatch( return { pkpAddress, ids }; } + +/** Updates an existing wrapped key and appends prior state to versions. + * + * @param { UpdateKeyParams } params Parameters required to update the private key metadata + * @returns { Promise } id/pkpAddress/updatedAt on successful update + */ +export async function updatePrivateKey( + params: UpdateKeyParams +): Promise { + const { + litNetwork, + sessionSig, + pkpAddress, + id, + ciphertext, + evmContractConditions, + memo, + } = params; + + const requestId = generateRequestId(); + const { baseUrl, initParams } = getBaseRequestParams({ + litNetwork, + sessionSig, + method: 'PUT', + requestId, + }); + + return makeRequest({ + url: `${baseUrl}/${pkpAddress}/${id}`, + init: { + ...initParams, + body: JSON.stringify({ + ciphertext, + ...(evmContractConditions !== undefined + ? { evmContractConditions } + : {}), + ...(memo !== undefined ? { memo } : {}), + }), + }, + requestId, + }); +} diff --git a/packages/wrapped-keys/src/lib/service-client/index.ts b/packages/wrapped-keys/src/lib/service-client/index.ts index 68842e14a1..a64c6b0a88 100644 --- a/packages/wrapped-keys/src/lib/service-client/index.ts +++ b/packages/wrapped-keys/src/lib/service-client/index.ts @@ -3,6 +3,7 @@ import { storePrivateKey, storePrivateKeyBatch, listPrivateKeyMetadata, + updatePrivateKey, } from './client'; export { @@ -10,4 +11,5 @@ export { storePrivateKey, storePrivateKeyBatch, listPrivateKeyMetadata, + updatePrivateKey, }; diff --git a/packages/wrapped-keys/src/lib/service-client/types.ts b/packages/wrapped-keys/src/lib/service-client/types.ts index 1e7291ef4b..3efbb198d1 100644 --- a/packages/wrapped-keys/src/lib/service-client/types.ts +++ b/packages/wrapped-keys/src/lib/service-client/types.ts @@ -11,6 +11,7 @@ interface BaseApiParams { export type FetchKeyParams = BaseApiParams & { pkpAddress: string; id: string; + includeVersions?: boolean; }; export type ListKeysParams = BaseApiParams & { pkpAddress: string }; @@ -34,9 +35,17 @@ export interface StoreKeyBatchParams extends BaseApiParams { >[]; } +export interface UpdateKeyParams extends BaseApiParams { + pkpAddress: string; + id: string; + ciphertext: string; + evmContractConditions?: string; + memo?: string; +} + export interface BaseRequestParams { sessionSig: AuthSig; - method: 'GET' | 'POST'; + method: 'GET' | 'POST' | 'PUT'; litNetwork: LIT_NETWORKS_KEYS; requestId: string; } diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index 2351975c1a..682bd12a37 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -47,6 +47,7 @@ export type ListEncryptedKeyMetadataParams = BaseApiParams; */ export type GetEncryptedKeyDataParams = BaseApiParams & { id: string; + includeVersions?: boolean; }; /** Metadata for a key that has been stored, encrypted, on the wrapped keys backend service @@ -68,6 +69,8 @@ export interface StoredKeyMetadata { litNetwork: LIT_NETWORKS_KEYS; memo: string; id: string; + updatedAt?: string; + versions?: WrappedKeyVersion[]; } /** Complete encrypted private key data, including the `ciphertext` and `dataToEncryptHash` necessary to decrypt the key @@ -81,6 +84,22 @@ export interface StoredKeyData extends StoredKeyMetadata { dataToEncryptHash: string; } +/** Represents a historical version of a wrapped key after an update operation */ +export interface WrappedKeyVersion + extends Pick< + StoredKeyData, + | 'ciphertext' + | 'dataToEncryptHash' + | 'keyType' + | 'litNetwork' + | 'memo' + | 'publicKey' + > { + id: string; + evmContractConditions?: string; + updatedAt: string; +} + /** Properties required to persist an encrypted key into the wrapped-keys backend storage service * * @typedef StoreEncryptedKeyParams @@ -130,6 +149,21 @@ export interface StoreEncryptedKeyBatchResult { pkpAddress: string; } +/** Properties required to update an existing encrypted key in the wrapped-keys backend storage service */ +export type UpdateEncryptedKeyParams = BaseApiParams & { + id: string; + ciphertext: string; + evmContractConditions?: string; + memo?: string; +}; + +/** Result of updating a private key in the wrapped keys backend service */ +export interface UpdateEncryptedKeyResult { + id: string; + pkpAddress: string; + updatedAt: string; +} + /** Exporting a previously persisted key only requires valid pkpSessionSigs and a LIT Node Client instance configured for the appropriate network. * * @typedef ExportPrivateKeyParams From 3a644ecb62f74b992d12bffa2d591fba98012732 Mon Sep 17 00:00:00 2001 From: anson Date: Tue, 9 Dec 2025 22:12:46 +0000 Subject: [PATCH 02/19] chore(version): add changeset --- .changeset/young-pianos-tickle.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/young-pianos-tickle.md diff --git a/.changeset/young-pianos-tickle.md b/.changeset/young-pianos-tickle.md new file mode 100644 index 0000000000..7de212ed57 --- /dev/null +++ b/.changeset/young-pianos-tickle.md @@ -0,0 +1,6 @@ +--- +'@lit-protocol/wrapped-keys': minor +'@lit-protocol/e2e': minor +--- + +Wrapped-keys now supports updating ciphertext/ACCs via a new PUT endpoint, returns version history when requested. From e381b1f3d1b2f934ee9862fb9d4640652213551f Mon Sep 17 00:00:00 2001 From: Anson Date: Wed, 10 Dec 2025 16:35:11 +0000 Subject: [PATCH 03/19] Update packages/wrapped-keys/src/lib/api/update-encrypted-key.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Anson --- .../src/lib/api/update-encrypted-key.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts b/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts index 3663a55dfa..56a0158355 100644 --- a/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts +++ b/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts @@ -6,7 +6,18 @@ import { getPkpAddressFromSessionSig, } from './utils'; -/** Update an existing wrapped key and append the previous state to versions. */ +/** + * Updates an existing wrapped key and appends the previous state to versions. + * + * @param {UpdateEncryptedKeyParams} params - The parameters required to update the encrypted private key. + * @param {Record} params.pkpSessionSigs - The session signatures for the PKP. + * @param {any} params.litClient - The Lit Protocol client instance. + * @param {string} params.id - The unique identifier of the wrapped key to update. + * @param {string} params.ciphertext - The new encrypted private key ciphertext. + * @param {any} params.evmContractConditions - The EVM contract conditions for access control. + * @param {string} [params.memo] - An optional memo to associate with the update. + * @returns {Promise} An object containing the id, pkpAddress, and updatedAt timestamp of the updated key. + */ export async function updateEncryptedKey( params: UpdateEncryptedKeyParams ): Promise { From 4ec70bdede059ff755863ec030ec2860c4bd9002 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:35:34 +0000 Subject: [PATCH 04/19] Initial plan From f6a6538a10a38d94fdfb69b574e5fb596e308a24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:35:45 +0000 Subject: [PATCH 05/19] Initial plan From b987f1d1a96607e7e11625118564d62e9e199e33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:35:52 +0000 Subject: [PATCH 06/19] Initial plan From 47ca9e9ee02dc31e26c8f8fd725676a112f81042 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:35:57 +0000 Subject: [PATCH 07/19] Initial plan From 8a56af297a176f9637d764ddae4a397e653f9546 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:37:32 +0000 Subject: [PATCH 08/19] docs(wrapped-keys): add detailed JSDoc for UpdateEncryptedKeyResult Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- packages/wrapped-keys/src/lib/types.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index 682bd12a37..19f81b83b6 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -157,7 +157,13 @@ export type UpdateEncryptedKeyParams = BaseApiParams & { memo?: string; }; -/** Result of updating a private key in the wrapped keys backend service */ +/** Result of updating a private key in the wrapped keys backend service + * + * @typedef UpdateEncryptedKeyResult + * @property { string } id The unique identifier (UUID V4) of the encrypted private key that was updated + * @property { string } pkpAddress The LIT PKP Address that the key was linked to; this is derived from the provided pkpSessionSigs + * @property { string } updatedAt ISO 8601 timestamp of when the key was updated + */ export interface UpdateEncryptedKeyResult { id: string; pkpAddress: string; From 04395fd19c1310246db8b1af642f5553a6cbcc79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:38:53 +0000 Subject: [PATCH 09/19] docs: add JSDoc for updatedAt and versions properties in StoredKeyMetadata Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- packages/wrapped-keys/src/lib/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index 682bd12a37..c8cc64c831 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -61,6 +61,8 @@ export type GetEncryptedKeyDataParams = BaseApiParams & { * @property { string } memo A (typically) user-provided descriptor for the encrypted private key * @property { string } id The unique identifier (UUID V4) of the encrypted private key * @property { LIT_NETWORKS_KEYS } litNetwork The LIT network that the client who stored the key was connected to + * @property { string } [updatedAt] ISO 8601 timestamp of when the key was last updated + * @property { WrappedKeyVersion[] } [versions] Array of historical versions of this key after update operations */ export interface StoredKeyMetadata { publicKey: string; From 14ae7a8e92d6a46209ab51d66961c7893cf68e1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:38:59 +0000 Subject: [PATCH 10/19] Add detailed JSDoc documentation to WrappedKeyVersion interface Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- packages/wrapped-keys/src/lib/types.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index 682bd12a37..25551c4e20 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -84,7 +84,19 @@ export interface StoredKeyData extends StoredKeyMetadata { dataToEncryptHash: string; } -/** Represents a historical version of a wrapped key after an update operation */ +/** Represents a historical version of a wrapped key after an update operation + * + * @typedef WrappedKeyVersion + * @property { string } id The unique identifier (UUID V4) of this key version + * @property { string } ciphertext The base64 encoded, salted & encrypted private key at this version + * @property { string } dataToEncryptHash SHA-256 of the ciphertext for this version + * @property { string } keyType The type of key that was encrypted -- e.g. ed25519, K256, etc. + * @property { LIT_NETWORKS_KEYS } litNetwork The LIT network that the client was connected to + * @property { string } memo The descriptor for the encrypted private key at this version + * @property { string } publicKey The public key of the encrypted private key + * @property { string } [evmContractConditions] Optional EVM contract conditions for access control at this version + * @property { string } updatedAt ISO 8601 timestamp of when this version was created + */ export interface WrappedKeyVersion extends Pick< StoredKeyData, From 3553f15852532183bcfad964c5b13ab164c68ddd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:39:03 +0000 Subject: [PATCH 11/19] docs: add JSDoc for includeVersions property in GetEncryptedKeyDataParams Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- packages/wrapped-keys/src/lib/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index 682bd12a37..d730615399 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -44,6 +44,7 @@ export type ListEncryptedKeyMetadataParams = BaseApiParams; * @extends BaseApiParams * * @property { string } id The unique identifier (UUID V4) of the encrypted private key + * @property { boolean } [includeVersions] Optional flag to include version history in the response */ export type GetEncryptedKeyDataParams = BaseApiParams & { id: string; From e58308c76b87b35d291467fcb30122e626621987 Mon Sep 17 00:00:00 2001 From: anson Date: Wed, 10 Dec 2025 16:52:04 +0000 Subject: [PATCH 12/19] doc: add wrapped keys doc --- docs/docs.json | 3 +- .../functions/getEncryptedKey.mdx | 7 +- .../functions/updateEncryptedKey.mdx | 65 +++++++++++++++++++ packages/wrapped-keys/src/lib/types.ts | 2 +- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 docs/sdk/sdk-reference/wrapped-keys/functions/updateEncryptedKey.mdx diff --git a/docs/docs.json b/docs/docs.json index fa5be34198..7890c39d02 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -133,6 +133,7 @@ "sdk/sdk-reference/wrapped-keys/functions/exportPrivateKey", "sdk/sdk-reference/wrapped-keys/functions/importPrivateKey", "sdk/sdk-reference/wrapped-keys/functions/getEncryptedKey", + "sdk/sdk-reference/wrapped-keys/functions/updateEncryptedKey", "sdk/sdk-reference/wrapped-keys/functions/listEncryptedKeyMetadata", "sdk/sdk-reference/wrapped-keys/functions/storeEncryptedKey", "sdk/sdk-reference/wrapped-keys/functions/storeEncryptedKeyBatch", @@ -246,4 +247,4 @@ "discord": "https://litgateway.com/discord" } } -} \ No newline at end of file +} diff --git a/docs/sdk/sdk-reference/wrapped-keys/functions/getEncryptedKey.mdx b/docs/sdk/sdk-reference/wrapped-keys/functions/getEncryptedKey.mdx index e923152d79..fd5bc0e8e7 100644 --- a/docs/sdk/sdk-reference/wrapped-keys/functions/getEncryptedKey.mdx +++ b/docs/sdk/sdk-reference/wrapped-keys/functions/getEncryptedKey.mdx @@ -22,6 +22,10 @@ Fetches the encrypted ciphertext and metadata for a stored wrapped key without d Identifier of the wrapped key to retrieve. + + Optional flag to return the `versions` array of prior states. + + Optional price ceiling for the Lit Action. @@ -29,7 +33,7 @@ Fetches the encrypted ciphertext and metadata for a stored wrapped key without d ## Returns - Includes ciphertext, `dataToEncryptHash`, memo, key type, public key, and PKP address. + Includes ciphertext, `dataToEncryptHash`, memo, key type, public key, PKP address, and (when requested) `versions` + `updatedAt`. ## Example @@ -39,5 +43,6 @@ const storedKey = await wrappedKeysApi.getEncryptedKey({ pkpSessionSigs, litClient, id, + includeVersions: true, // optional }); ``` diff --git a/docs/sdk/sdk-reference/wrapped-keys/functions/updateEncryptedKey.mdx b/docs/sdk/sdk-reference/wrapped-keys/functions/updateEncryptedKey.mdx new file mode 100644 index 0000000000..90ef948b9d --- /dev/null +++ b/docs/sdk/sdk-reference/wrapped-keys/functions/updateEncryptedKey.mdx @@ -0,0 +1,65 @@ +--- +title: updateEncryptedKey +--- + +# Function + +> **updateEncryptedKey**(`params`) + +Re-encrypts an existing wrapped key and appends the previous state to the `versions` array. + +## Parameters + + + Session signatures identifying the PKP that owns the wrapped key. + + + + Lit client instance used to talk to the wrapped-keys service. + + + + Identifier of the wrapped key to update. + + + + New base64-encrypted ciphertext to store. + + + + Optional updated memo for the wrapped key. + + + + Optional updated ACCs for EVM contract conditions. + + + + Optional price ceiling for the Lit Action. + + +## Returns + + + Returns `id`, `pkpAddress`, and `updatedAt` of the updated wrapped key. + + +## Example + +```ts +const res = await wrappedKeysApi.updateEncryptedKey({ + pkpSessionSigs, + litClient, + id, + ciphertext: 'base64-new', + memo: 'rotated memo', +}); + +const withHistory = await wrappedKeysApi.getEncryptedKey({ + pkpSessionSigs, + litClient, + id, + includeVersions: true, +}); +console.log(withHistory.versions); // [{ ciphertext: 'old', ... }] +``` diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index 361c723557..3773c75b3b 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -173,7 +173,7 @@ export type UpdateEncryptedKeyParams = BaseApiParams & { }; /** Result of updating a private key in the wrapped keys backend service - * + * * @typedef UpdateEncryptedKeyResult * @property { string } id The unique identifier (UUID V4) of the encrypted private key that was updated * @property { string } pkpAddress The LIT PKP Address that the key was linked to; this is derived from the provided pkpSessionSigs From 79fe914c9680d1a515854f7c3fe197e0c0faf5fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:00:16 +0000 Subject: [PATCH 13/19] Initial plan From fb3817ea0b3d8a475dfc09c562924540c1c35abe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:00:20 +0000 Subject: [PATCH 14/19] Initial plan From a43b1221457ae4cb0c31b6a7e7e169b4844d4dd8 Mon Sep 17 00:00:00 2001 From: Anson Date: Wed, 10 Dec 2025 17:00:36 +0000 Subject: [PATCH 15/19] Update packages/wrapped-keys/src/lib/api/update-encrypted-key.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Anson --- .../wrapped-keys/src/lib/api/update-encrypted-key.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts b/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts index 56a0158355..217c498a8a 100644 --- a/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts +++ b/packages/wrapped-keys/src/lib/api/update-encrypted-key.ts @@ -9,14 +9,8 @@ import { /** * Updates an existing wrapped key and appends the previous state to versions. * - * @param {UpdateEncryptedKeyParams} params - The parameters required to update the encrypted private key. - * @param {Record} params.pkpSessionSigs - The session signatures for the PKP. - * @param {any} params.litClient - The Lit Protocol client instance. - * @param {string} params.id - The unique identifier of the wrapped key to update. - * @param {string} params.ciphertext - The new encrypted private key ciphertext. - * @param {any} params.evmContractConditions - The EVM contract conditions for access control. - * @param {string} [params.memo] - An optional memo to associate with the update. - * @returns {Promise} An object containing the id, pkpAddress, and updatedAt timestamp of the updated key. + * @param { UpdateEncryptedKeyParams } params Parameters required to update the encrypted private key + * @returns { Promise } An object containing the id, pkpAddress, and updatedAt timestamp of the updated key */ export async function updateEncryptedKey( params: UpdateEncryptedKeyParams From 505e2cf07d1cd8c03ed05996722379b6adf75885 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:03:20 +0000 Subject: [PATCH 16/19] docs: add detailed JSDoc documentation to UpdateEncryptedKeyParams Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- packages/wrapped-keys/src/lib/types.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/wrapped-keys/src/lib/types.ts b/packages/wrapped-keys/src/lib/types.ts index 3773c75b3b..bfe1a12bf0 100644 --- a/packages/wrapped-keys/src/lib/types.ts +++ b/packages/wrapped-keys/src/lib/types.ts @@ -164,7 +164,16 @@ export interface StoreEncryptedKeyBatchResult { pkpAddress: string; } -/** Properties required to update an existing encrypted key in the wrapped-keys backend storage service */ +/** Properties required to update an existing encrypted key in the wrapped-keys backend storage service + * + * @typedef UpdateEncryptedKeyParams + * @extends BaseApiParams + * + * @property { string } id The unique identifier (UUID V4) of the encrypted private key to update + * @property { string } ciphertext The new base64 encoded, salted & encrypted private key + * @property { string } [evmContractConditions] Optional EVM contract conditions for access control + * @property { string } [memo] Optional descriptor for the encrypted private key + */ export type UpdateEncryptedKeyParams = BaseApiParams & { id: string; ciphertext: string; From 582bbc25771900c86d1ba8bbd7dd6d43ef620190 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:07:15 +0000 Subject: [PATCH 17/19] Add test for updateEncryptedKey with evmContractConditions Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- .../src/test-helpers/executeJs/wrappedKeys.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts index 5a8211c335..55a3af8ba5 100644 --- a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts +++ b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts @@ -57,6 +57,21 @@ namespace TestHelper { export const randomHash = (input: string) => createHash('sha256').update(input).digest('hex'); + export const createEvmContractConditions = (address: string) => + JSON.stringify([ + { + contractAddress: '', + standardContractType: '', + chain: EVM_CHAIN, + method: '', + parameters: [':userAddress'], + returnValueTest: { + comparator: '=', + value: address, + }, + }, + ]); + export const createStorePayload = (memo = randomMemo('store')) => { const ciphertext = randomCiphertext(); return { @@ -391,6 +406,61 @@ export const registerWrappedKeysTests = () => { expect(fetched.versions?.[0].memo).toBe(initialPayload.memo); }); + test('updateEncryptedKey with evmContractConditions stores conditions in version history', async () => { + const pkpSessionSigs = await TestHelper.createPkpSessionSigs({ + testEnv, + alice, + delegationAuthSig: aliceDelegationAuthSig, + }); + + const initialPayload = TestHelper.createStorePayload( + TestHelper.randomMemo('update-evm-before') + ); + + const { id } = await wrappedKeysApi.storeEncryptedKey({ + pkpSessionSigs, + litClient: testEnv.litClient, + ...initialPayload, + }); + + const newCiphertext = TestHelper.randomCiphertext(); + const newMemo = TestHelper.randomMemo('update-evm-after'); + const evmContractConditions = TestHelper.createEvmContractConditions( + alice.pkp!.ethAddress + ); + + const updateResult = await wrappedKeysApi.updateEncryptedKey({ + pkpSessionSigs, + litClient: testEnv.litClient, + id, + ciphertext: newCiphertext, + memo: newMemo, + evmContractConditions, + }); + + expect(updateResult.id).toBe(id); + expect(updateResult.pkpAddress).toBe(alice.pkp!.ethAddress); + expect(updateResult.updatedAt).toBeTruthy(); + + const fetched = await wrappedKeysApi.getEncryptedKey({ + pkpSessionSigs, + litClient: testEnv.litClient, + id, + includeVersions: true, + }); + + expect(fetched.ciphertext).toBe(newCiphertext); + expect(fetched.memo).toBe(newMemo); + expect(fetched.updatedAt).toBeTruthy(); + expect(fetched.versions).toBeDefined(); + expect(fetched.versions?.length).toBe(1); + expect(fetched.versions?.[0].ciphertext).toBe( + initialPayload.ciphertext + ); + expect(fetched.versions?.[0].memo).toBe(initialPayload.memo); + expect(fetched.versions?.[0].evmContractConditions).toBeUndefined(); + }); + test('importPrivateKey persists an externally generated key', async () => { const pkpSessionSigs = await TestHelper.createPkpSessionSigs({ testEnv, From 7b182131f7e2b1ccd56e98c8eb266a223fab1514 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:09:00 +0000 Subject: [PATCH 18/19] Use realistic EVM contract conditions in test helper Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts index 55a3af8ba5..bde8e96062 100644 --- a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts +++ b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts @@ -60,14 +60,14 @@ namespace TestHelper { export const createEvmContractConditions = (address: string) => JSON.stringify([ { - contractAddress: '', - standardContractType: '', + contractAddress: ZERO_ADDRESS, + standardContractType: 'ERC20', chain: EVM_CHAIN, - method: '', + method: 'balanceOf', parameters: [':userAddress'], returnValueTest: { - comparator: '=', - value: address, + comparator: '>=', + value: '0', }, }, ]); From be45d0f402c715c9780aa3abcaa86b4b9053532a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:10:49 +0000 Subject: [PATCH 19/19] Remove unused address parameter from createEvmContractConditions Co-authored-by: Ansonhkg <4049673+Ansonhkg@users.noreply.github.com> --- packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts index bde8e96062..eabfe70fd2 100644 --- a/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts +++ b/packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts @@ -57,7 +57,7 @@ namespace TestHelper { export const randomHash = (input: string) => createHash('sha256').update(input).digest('hex'); - export const createEvmContractConditions = (address: string) => + export const createEvmContractConditions = () => JSON.stringify([ { contractAddress: ZERO_ADDRESS, @@ -425,9 +425,7 @@ export const registerWrappedKeysTests = () => { const newCiphertext = TestHelper.randomCiphertext(); const newMemo = TestHelper.randomMemo('update-evm-after'); - const evmContractConditions = TestHelper.createEvmContractConditions( - alice.pkp!.ethAddress - ); + const evmContractConditions = TestHelper.createEvmContractConditions(); const updateResult = await wrappedKeysApi.updateEncryptedKey({ pkpSessionSigs,