Skip to content

Commit adea77a

Browse files
committed
feat(wrapped-keys): add versioned key update support and client APIs
1 parent eb219e5 commit adea77a

File tree

9 files changed

+187
-4
lines changed

9 files changed

+187
-4
lines changed

packages/e2e/src/test-helpers/executeJs/wrappedKeys.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,56 @@ export const registerWrappedKeysTests = () => {
341341
expect(storedKey.dataToEncryptHash).toBeTruthy();
342342
});
343343

344+
test('updateEncryptedKey rotates ciphertext and returns version history', async () => {
345+
const pkpSessionSigs = await TestHelper.createPkpSessionSigs({
346+
testEnv,
347+
alice,
348+
delegationAuthSig: aliceDelegationAuthSig,
349+
});
350+
351+
const initialPayload = TestHelper.createStorePayload(
352+
TestHelper.randomMemo('update-before')
353+
);
354+
355+
const { id } = await wrappedKeysApi.storeEncryptedKey({
356+
pkpSessionSigs,
357+
litClient: testEnv.litClient,
358+
...initialPayload,
359+
});
360+
361+
const newCiphertext = TestHelper.randomCiphertext();
362+
const newMemo = TestHelper.randomMemo('update-after');
363+
364+
const updateResult = await wrappedKeysApi.updateEncryptedKey({
365+
pkpSessionSigs,
366+
litClient: testEnv.litClient,
367+
id,
368+
ciphertext: newCiphertext,
369+
memo: newMemo,
370+
});
371+
372+
expect(updateResult.id).toBe(id);
373+
expect(updateResult.pkpAddress).toBe(alice.pkp!.ethAddress);
374+
expect(updateResult.updatedAt).toBeTruthy();
375+
376+
const fetched = await wrappedKeysApi.getEncryptedKey({
377+
pkpSessionSigs,
378+
litClient: testEnv.litClient,
379+
id,
380+
includeVersions: true,
381+
});
382+
383+
expect(fetched.ciphertext).toBe(newCiphertext);
384+
expect(fetched.memo).toBe(newMemo);
385+
expect(fetched.updatedAt).toBeTruthy();
386+
expect(fetched.versions).toBeDefined();
387+
expect(fetched.versions?.length).toBe(1);
388+
expect(fetched.versions?.[0].ciphertext).toBe(
389+
initialPayload.ciphertext
390+
);
391+
expect(fetched.versions?.[0].memo).toBe(initialPayload.memo);
392+
});
393+
344394
test('importPrivateKey persists an externally generated key', async () => {
345395
const pkpSessionSigs = await TestHelper.createPkpSessionSigs({
346396
testEnv,

packages/wrapped-keys/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
signTransactionWithEncryptedKey,
1010
storeEncryptedKey,
1111
storeEncryptedKeyBatch,
12+
updateEncryptedKey,
1213
} from './lib/api';
1314
import {
1415
CHAIN_ETHEREUM,
@@ -43,6 +44,7 @@ export const api = {
4344
storeEncryptedKey,
4445
storeEncryptedKeyBatch,
4546
batchGeneratePrivateKeys,
47+
updateEncryptedKey,
4648
};
4749

4850
export const config = {
@@ -76,6 +78,9 @@ export type {
7678
StoreEncryptedKeyResult,
7779
StoredKeyData,
7880
StoredKeyMetadata,
81+
UpdateEncryptedKeyParams,
82+
UpdateEncryptedKeyResult,
83+
WrappedKeyVersion,
7984
} from './lib/types';
8085

8186
export type {

packages/wrapped-keys/src/lib/api/get-encrypted-key.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { GetEncryptedKeyDataParams, StoredKeyData } from '../types';
1515
export async function getEncryptedKey(
1616
params: GetEncryptedKeyDataParams
1717
): Promise<StoredKeyData> {
18-
const { pkpSessionSigs, litClient, id } = params;
18+
const { pkpSessionSigs, litClient, id, includeVersions } = params;
1919

2020
const sessionSig = getFirstSessionSig(pkpSessionSigs);
2121
const pkpAddress = getPkpAddressFromSessionSig(sessionSig);
@@ -26,5 +26,6 @@ export async function getEncryptedKey(
2626
id,
2727
sessionSig,
2828
litNetwork,
29+
includeVersions,
2930
});
3031
}

packages/wrapped-keys/src/lib/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { signMessageWithEncryptedKey } from './sign-message-with-encrypted-key';
88
import { signTransactionWithEncryptedKey } from './sign-transaction-with-encrypted-key';
99
import { storeEncryptedKey } from './store-encrypted-key';
1010
import { storeEncryptedKeyBatch } from './store-encrypted-key-batch';
11+
import { updateEncryptedKey } from './update-encrypted-key';
1112

1213
export {
1314
listEncryptedKeyMetadata,
@@ -20,4 +21,5 @@ export {
2021
storeEncryptedKeyBatch,
2122
getEncryptedKey,
2223
batchGeneratePrivateKeys,
24+
updateEncryptedKey,
2325
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { updatePrivateKey } from '../service-client';
2+
import { UpdateEncryptedKeyParams, UpdateEncryptedKeyResult } from '../types';
3+
import {
4+
getFirstSessionSig,
5+
getLitNetworkFromClient,
6+
getPkpAddressFromSessionSig,
7+
} from './utils';
8+
9+
/** Update an existing wrapped key and append the previous state to versions. */
10+
export async function updateEncryptedKey(
11+
params: UpdateEncryptedKeyParams
12+
): Promise<UpdateEncryptedKeyResult> {
13+
const {
14+
pkpSessionSigs,
15+
litClient,
16+
id,
17+
ciphertext,
18+
evmContractConditions,
19+
memo,
20+
} = params;
21+
22+
const sessionSig = getFirstSessionSig(pkpSessionSigs);
23+
const pkpAddress = getPkpAddressFromSessionSig(sessionSig);
24+
const litNetwork = getLitNetworkFromClient(litClient);
25+
26+
return updatePrivateKey({
27+
pkpAddress,
28+
id,
29+
sessionSig,
30+
ciphertext,
31+
evmContractConditions,
32+
memo,
33+
litNetwork,
34+
});
35+
}

packages/wrapped-keys/src/lib/service-client/client.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {
33
ListKeysParams,
44
StoreKeyBatchParams,
55
StoreKeyParams,
6+
UpdateKeyParams,
67
} from './types';
78
import { generateRequestId, getBaseRequestParams, makeRequest } from './utils';
89
import {
910
StoredKeyData,
1011
StoredKeyMetadata,
1112
StoreEncryptedKeyBatchResult,
1213
StoreEncryptedKeyResult,
14+
UpdateEncryptedKeyResult,
1315
} from '../types';
1416

1517
/** Fetches previously stored private key metadata from the wrapped keys service.
@@ -48,7 +50,7 @@ export async function listPrivateKeyMetadata(
4850
export async function fetchPrivateKey(
4951
params: FetchKeyParams
5052
): Promise<StoredKeyData> {
51-
const { litNetwork, sessionSig, id, pkpAddress } = params;
53+
const { litNetwork, sessionSig, id, pkpAddress, includeVersions } = params;
5254

5355
const requestId = generateRequestId();
5456
const { baseUrl, initParams } = getBaseRequestParams({
@@ -58,8 +60,9 @@ export async function fetchPrivateKey(
5860
requestId,
5961
});
6062

63+
const query = includeVersions ? '?includeVersions=true' : '';
6164
return makeRequest<StoredKeyData>({
62-
url: `${baseUrl}/${pkpAddress}/${id}`,
65+
url: `${baseUrl}/${pkpAddress}/${id}${query}`,
6366
init: initParams,
6467
requestId,
6568
});
@@ -124,3 +127,45 @@ export async function storePrivateKeyBatch(
124127

125128
return { pkpAddress, ids };
126129
}
130+
131+
/** Updates an existing wrapped key and appends prior state to versions.
132+
*
133+
* @param { UpdateKeyParams } params Parameters required to update the private key metadata
134+
* @returns { Promise<UpdateEncryptedKeyResult> } id/pkpAddress/updatedAt on successful update
135+
*/
136+
export async function updatePrivateKey(
137+
params: UpdateKeyParams
138+
): Promise<UpdateEncryptedKeyResult> {
139+
const {
140+
litNetwork,
141+
sessionSig,
142+
pkpAddress,
143+
id,
144+
ciphertext,
145+
evmContractConditions,
146+
memo,
147+
} = params;
148+
149+
const requestId = generateRequestId();
150+
const { baseUrl, initParams } = getBaseRequestParams({
151+
litNetwork,
152+
sessionSig,
153+
method: 'PUT',
154+
requestId,
155+
});
156+
157+
return makeRequest<UpdateEncryptedKeyResult>({
158+
url: `${baseUrl}/${pkpAddress}/${id}`,
159+
init: {
160+
...initParams,
161+
body: JSON.stringify({
162+
ciphertext,
163+
...(evmContractConditions !== undefined
164+
? { evmContractConditions }
165+
: {}),
166+
...(memo !== undefined ? { memo } : {}),
167+
}),
168+
},
169+
requestId,
170+
});
171+
}

packages/wrapped-keys/src/lib/service-client/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {
33
storePrivateKey,
44
storePrivateKeyBatch,
55
listPrivateKeyMetadata,
6+
updatePrivateKey,
67
} from './client';
78

89
export {
910
fetchPrivateKey,
1011
storePrivateKey,
1112
storePrivateKeyBatch,
1213
listPrivateKeyMetadata,
14+
updatePrivateKey,
1315
};

packages/wrapped-keys/src/lib/service-client/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface BaseApiParams {
1111
export type FetchKeyParams = BaseApiParams & {
1212
pkpAddress: string;
1313
id: string;
14+
includeVersions?: boolean;
1415
};
1516

1617
export type ListKeysParams = BaseApiParams & { pkpAddress: string };
@@ -34,9 +35,17 @@ export interface StoreKeyBatchParams extends BaseApiParams {
3435
>[];
3536
}
3637

38+
export interface UpdateKeyParams extends BaseApiParams {
39+
pkpAddress: string;
40+
id: string;
41+
ciphertext: string;
42+
evmContractConditions?: string;
43+
memo?: string;
44+
}
45+
3746
export interface BaseRequestParams {
3847
sessionSig: AuthSig;
39-
method: 'GET' | 'POST';
48+
method: 'GET' | 'POST' | 'PUT';
4049
litNetwork: LIT_NETWORKS_KEYS;
4150
requestId: string;
4251
}

packages/wrapped-keys/src/lib/types.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type ListEncryptedKeyMetadataParams = BaseApiParams;
4747
*/
4848
export type GetEncryptedKeyDataParams = BaseApiParams & {
4949
id: string;
50+
includeVersions?: boolean;
5051
};
5152

5253
/** Metadata for a key that has been stored, encrypted, on the wrapped keys backend service
@@ -68,6 +69,8 @@ export interface StoredKeyMetadata {
6869
litNetwork: LIT_NETWORKS_KEYS;
6970
memo: string;
7071
id: string;
72+
updatedAt?: string;
73+
versions?: WrappedKeyVersion[];
7174
}
7275

7376
/** Complete encrypted private key data, including the `ciphertext` and `dataToEncryptHash` necessary to decrypt the key
@@ -81,6 +84,22 @@ export interface StoredKeyData extends StoredKeyMetadata {
8184
dataToEncryptHash: string;
8285
}
8386

87+
/** Represents a historical version of a wrapped key after an update operation */
88+
export interface WrappedKeyVersion
89+
extends Pick<
90+
StoredKeyData,
91+
| 'ciphertext'
92+
| 'dataToEncryptHash'
93+
| 'keyType'
94+
| 'litNetwork'
95+
| 'memo'
96+
| 'publicKey'
97+
> {
98+
id: string;
99+
evmContractConditions?: string;
100+
updatedAt: string;
101+
}
102+
84103
/** Properties required to persist an encrypted key into the wrapped-keys backend storage service
85104
*
86105
* @typedef StoreEncryptedKeyParams
@@ -130,6 +149,21 @@ export interface StoreEncryptedKeyBatchResult {
130149
pkpAddress: string;
131150
}
132151

152+
/** Properties required to update an existing encrypted key in the wrapped-keys backend storage service */
153+
export type UpdateEncryptedKeyParams = BaseApiParams & {
154+
id: string;
155+
ciphertext: string;
156+
evmContractConditions?: string;
157+
memo?: string;
158+
};
159+
160+
/** Result of updating a private key in the wrapped keys backend service */
161+
export interface UpdateEncryptedKeyResult {
162+
id: string;
163+
pkpAddress: string;
164+
updatedAt: string;
165+
}
166+
133167
/** Exporting a previously persisted key only requires valid pkpSessionSigs and a LIT Node Client instance configured for the appropriate network.
134168
*
135169
* @typedef ExportPrivateKeyParams

0 commit comments

Comments
 (0)