@icp-sdk/vetkeys 0.5.0-beta.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
- import { T as h, E as f, D as g, a as v } from "./index-BUXhtQhx.mjs";
2
- import { A as m } from "./actor-4fotMNaR.mjs";
1
+ import { T as v, E as f, D as g, a as m } from "./index-BUXhtQhx.mjs";
2
+ import { A as w } from "./actor-CrGCNm9W.mjs";
3
3
  function y(r) {
4
4
  return new Promise((e, t) => {
5
5
  r.oncomplete = r.onsuccess = () => e(r.result), r.onabort = r.onerror = () => t(r.error);
6
6
  });
7
7
  }
8
- function w(r, e) {
8
+ function h(r, e) {
9
9
  let t;
10
10
  const n = () => {
11
11
  if (t)
@@ -18,17 +18,54 @@ function w(r, e) {
18
18
  };
19
19
  return (a, i) => n().then((s) => i(s.transaction(e, a).objectStore(e)));
20
20
  }
21
- let u;
22
- function d() {
23
- return u || (u = w("keyval-store", "keyval")), u;
21
+ let _;
22
+ function p() {
23
+ return _ || (_ = h("keyval-store", "keyval")), _;
24
24
  }
25
- function k(r, e = d()) {
25
+ function k(r, e = p()) {
26
26
  return e("readonly", (t) => y(t.get(r)));
27
27
  }
28
- function E(r, e, t = d()) {
28
+ function E(r, e, t = p()) {
29
29
  return t("readwrite", (n) => (n.put(e, r), y(n.transaction)));
30
30
  }
31
- const V = ({ IDL: r }) => {
31
+ function V(r = p()) {
32
+ return r("readwrite", (e) => (e.clear(), y(e.transaction)));
33
+ }
34
+ class K {
35
+ #e = /* @__PURE__ */ new Map();
36
+ get(e) {
37
+ return Promise.resolve(this.#e.get(e));
38
+ }
39
+ set(e, t) {
40
+ return this.#e.set(e, t), Promise.resolve();
41
+ }
42
+ clear() {
43
+ return this.#e.clear(), Promise.resolve();
44
+ }
45
+ }
46
+ class C {
47
+ #e;
48
+ /**
49
+ * @param dbName - IndexedDB database name. Defaults to `"ic-vetkeys"`. This
50
+ * is the isolation knob: give each identity its own database name (e.g.
51
+ * `` `vetkeys-${principal}` ``) so one identity's persisted keys are never
52
+ * served to another. The object store name is fixed, because `idb-keyval`
53
+ * supports only a single object store per database.
54
+ */
55
+ constructor(e = "ic-vetkeys") {
56
+ this.#e = h(e, "derived-key-material");
57
+ }
58
+ async get(e) {
59
+ return k(e, this.#e);
60
+ }
61
+ async set(e, t) {
62
+ await E(e, t, this.#e);
63
+ }
64
+ async clear() {
65
+ await V(this.#e);
66
+ }
67
+ }
68
+ const A = ({ IDL: r }) => {
32
69
  const e = r.Record({ inner: r.Vec(r.Nat8) }), t = r.Variant({
33
70
  Read: r.Null,
34
71
  ReadWrite: r.Null,
@@ -47,7 +84,7 @@ const V = ({ IDL: r }) => {
47
84
  }), o = r.Variant({
48
85
  Ok: r.Opt(t),
49
86
  Err: r.Text
50
- }), _ = r.Variant({ Ok: r.Vec(e), Err: r.Text });
87
+ }), u = r.Variant({ Ok: r.Vec(e), Err: r.Text });
51
88
  return r.Service({
52
89
  get_accessible_shared_map_names: r.Func(
53
90
  [],
@@ -112,7 +149,7 @@ const V = ({ IDL: r }) => {
112
149
  [a],
113
150
  []
114
151
  ),
115
- remove_map_values: r.Func([r.Principal, e], [_], []),
152
+ remove_map_values: r.Func([r.Principal, e], [u], []),
116
153
  remove_user: r.Func(
117
154
  [r.Principal, e, r.Principal],
118
155
  [o],
@@ -125,10 +162,10 @@ const V = ({ IDL: r }) => {
125
162
  )
126
163
  });
127
164
  };
128
- class U {
165
+ class R {
129
166
  actor;
130
167
  constructor(e, t) {
131
- this.actor = m.createActor(V, {
168
+ this.actor = w.createActor(A, {
132
169
  agent: e,
133
170
  canisterId: t
134
171
  });
@@ -184,7 +221,7 @@ class U {
184
221
  return this.actor.remove_user(e, t, n);
185
222
  }
186
223
  }
187
- class F {
224
+ class O {
188
225
  /**
189
226
  * The client instance for interacting with the EncryptedMaps canister.
190
227
  */
@@ -193,18 +230,44 @@ class F {
193
230
  * The cached verification key for validating encrypted VetKeys.
194
231
  */
195
232
  verificationKey = void 0;
233
+ /**
234
+ * Cache backing the per-map derived key material handles.
235
+ */
236
+ #e;
196
237
  /**
197
238
  * Creates a new instance of the EncryptedMaps client.
198
239
  *
240
+ * @param canisterClient - The client used to talk to the EncryptedMaps canister.
241
+ * @param options - Optional configuration.
242
+ * @param options.cache - Strategy for caching derived key material. Defaults to
243
+ * {@link InMemoryDerivedKeyMaterialCache} (in-memory only, never persisted).
244
+ * Pass {@link IndexedDbDerivedKeyMaterialCache} to persist across page reloads —
245
+ * see the security note in the class description.
246
+ *
199
247
  * @example
200
248
  * ```ts
201
249
  * import { EncryptedMaps } from "@icp-sdk/vetkeys/encrypted_maps";
202
250
  *
203
251
  * const encryptedMaps = new EncryptedMaps(encryptedMapsClientInstance);
204
252
  * ```
253
+ *
254
+ * @example Opt into persistent caching, namespaced per identity
255
+ * ```ts
256
+ * import {
257
+ * EncryptedMaps,
258
+ * IndexedDbDerivedKeyMaterialCache,
259
+ * } from "@icp-sdk/vetkeys/encrypted_maps";
260
+ *
261
+ * const encryptedMaps = new EncryptedMaps(encryptedMapsClientInstance, {
262
+ * // Namespace the store per identity so one identity's cached keys are
263
+ * // never served to another on the same origin.
264
+ * cache: new IndexedDbDerivedKeyMaterialCache(`vetkeys-${myPrincipal}`),
265
+ * });
266
+ * // Remember to call encryptedMaps.clearCache() on logout / identity change.
267
+ * ```
205
268
  */
206
- constructor(e) {
207
- this.canisterClient = e;
269
+ constructor(e, t) {
270
+ this.canisterClient = e, this.#e = t?.cache ?? new K();
208
271
  }
209
272
  /**
210
273
  * Retrieves a list of maps that were shared with the user and the user still has access to.
@@ -240,13 +303,13 @@ class F {
240
303
  for (const [n, a] of e) {
241
304
  const i = Uint8Array.from(n[1].inner), s = [];
242
305
  for (const [l, o] of a) {
243
- const _ = Uint8Array.from(l.inner), p = await this.decryptFor(
306
+ const u = Uint8Array.from(l.inner), d = await this.decryptFor(
244
307
  n[0],
245
308
  i,
246
- _,
309
+ u,
247
310
  Uint8Array.from(o.inner)
248
311
  );
249
- s.push([_, p]);
312
+ s.push([u, d]);
250
313
  }
251
314
  t.push([
252
315
  [n[0], Uint8Array.from(n[1].inner)],
@@ -268,13 +331,13 @@ class F {
268
331
  s,
269
332
  l
270
333
  ] of n.keyvals) {
271
- const o = Uint8Array.from(s.inner), _ = await this.decryptFor(
334
+ const o = Uint8Array.from(s.inner), u = await this.decryptFor(
272
335
  n.map_owner,
273
336
  a,
274
337
  o,
275
338
  Uint8Array.from(l.inner)
276
339
  );
277
- i.push([o, _]);
340
+ i.push([o, u]);
278
341
  }
279
342
  t.push({
280
343
  accessControl: n.access_control,
@@ -342,13 +405,13 @@ class F {
342
405
  ]);
343
406
  const i = new Array();
344
407
  for (const [s, l] of n.Ok) {
345
- const o = Uint8Array.from(s.inner), _ = await this.decryptFor(
408
+ const o = Uint8Array.from(s.inner), u = await this.decryptFor(
346
409
  e,
347
410
  t,
348
411
  o,
349
412
  Uint8Array.from(l.inner)
350
413
  );
351
- i.push([o, _]);
414
+ i.push([o, u]);
352
415
  }
353
416
  return i;
354
417
  }
@@ -567,7 +630,7 @@ class F {
567
630
  * @throws Error if the operation fails
568
631
  */
569
632
  async getDerivedKeyMaterial(e, t) {
570
- const n = h.random(), a = await this.canisterClient.get_encrypted_vetkey(
633
+ const n = v.random(), a = await this.canisterClient.get_encrypted_vetkey(
571
634
  e,
572
635
  c(t),
573
636
  c(n.publicKeyBytes())
@@ -579,10 +642,10 @@ class F {
579
642
  e.toUint8Array().length,
580
643
  ...e.toUint8Array(),
581
644
  ...t
582
- ]), o = f.deserialize(i), _ = g.deserialize(s);
645
+ ]), o = f.deserialize(i), u = g.deserialize(s);
583
646
  return await o.decryptAndVerify(
584
647
  n,
585
- _,
648
+ u,
586
649
  l
587
650
  ).asDerivedKeyMaterial();
588
651
  }
@@ -623,28 +686,50 @@ class F {
623
686
  * @returns Promise resolving to the derived key material
624
687
  */
625
688
  async getDerivedKeyMaterialOrFetchIfNeeded(e, t) {
626
- const n = new Uint8Array(t), a = await k([
627
- e.toString(),
628
- n
629
- ]);
689
+ const n = M(e, t), a = await this.#e.get(n);
630
690
  if (a)
631
- return await v.fromCryptoKey(
691
+ return await m.fromCryptoKey(
632
692
  a
633
693
  );
634
694
  const i = await this.getDerivedKeyMaterial(
635
695
  e,
636
696
  t
637
697
  );
638
- return await E(
639
- [e.toString(), n],
698
+ return await this.#e.set(
699
+ n,
640
700
  i.getCryptoKey()
641
701
  ), i;
642
702
  }
703
+ /**
704
+ * Clears all cached derived key material.
705
+ *
706
+ * Strongly recommended on logout or whenever the authenticated identity
707
+ * changes: the cache belongs to a single identity, so clearing it (or
708
+ * discarding the `EncryptedMaps` instance) prevents one identity's key
709
+ * material from being served to another, and drops the still-usable
710
+ * decryption capability that otherwise lingers. This matters most with
711
+ * {@link IndexedDbDerivedKeyMaterialCache}, where cached key handles persist
712
+ * across sessions until cleared.
713
+ */
714
+ async clearCache() {
715
+ await this.#e.clear();
716
+ }
717
+ }
718
+ function M(r, e) {
719
+ return `${r.toString()}|${F(e)}`;
720
+ }
721
+ function F(r) {
722
+ let e = "";
723
+ for (const t of r)
724
+ e += t.toString(16).padStart(2, "0");
725
+ return e;
643
726
  }
644
727
  function c(r) {
645
728
  return { inner: r };
646
729
  }
647
730
  export {
648
- U as DefaultEncryptedMapsClient,
649
- F as EncryptedMaps
731
+ R as DefaultEncryptedMapsClient,
732
+ O as EncryptedMaps,
733
+ K as InMemoryDerivedKeyMaterialCache,
734
+ C as IndexedDbDerivedKeyMaterialCache
650
735
  };
@@ -1,5 +1,5 @@
1
1
  import { T as u, D as _, E as l } from "./index-BUXhtQhx.mjs";
2
- import { A as h } from "./actor-4fotMNaR.mjs";
2
+ import { A as h } from "./actor-CrGCNm9W.mjs";
3
3
  const g = ({ IDL: e }) => {
4
4
  const r = e.Record({ inner: e.Vec(e.Nat8) }), t = e.Variant({ Ok: r, Err: e.Text }), s = e.Variant({
5
5
  Read: e.Null,
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @module @icp-sdk/vetkeys/encrypted_maps
3
+ *
4
+ * @description Caching strategies for derived key material. See
5
+ * {@link DerivedKeyMaterialCache}.
6
+ */
7
+ /**
8
+ * Strategy for caching the per-map derived key material handles used by
9
+ * {@link EncryptedMaps}.
10
+ *
11
+ * Deriving key material requires a canister round-trip and threshold
12
+ * cryptography, so it is cached and reused. The cached value is a
13
+ * non-extractable {@link CryptoKey} handle: its raw bytes can never be read
14
+ * back (`crypto.subtle.exportKey` throws), but the handle can still be *used*
15
+ * to decrypt. Where that handle lives therefore matters for security — see the
16
+ * provided implementations.
17
+ *
18
+ * Cache entries are keyed by an opaque string derived from the map owner and
19
+ * map name. Derived key material is per map, not per caller, so the key does
20
+ * not encode the identity. Isolating one identity's cached keys from another's
21
+ * on the same origin is therefore a property of the cache instance: use a fresh
22
+ * cache per identity (the in-memory default is naturally per-instance), or give
23
+ * a persistent cache a per-identity namespace (see
24
+ * {@link IndexedDbDerivedKeyMaterialCache}).
25
+ */
26
+ export interface DerivedKeyMaterialCache {
27
+ /**
28
+ * Returns the cached key handle for the given key, or `undefined` on a miss.
29
+ */
30
+ get(key: string): Promise<CryptoKey | undefined>;
31
+ /**
32
+ * Stores a key handle under the given key.
33
+ */
34
+ set(key: string, value: CryptoKey): Promise<void>;
35
+ /**
36
+ * Removes every cached key handle.
37
+ *
38
+ * Call this on logout or whenever the authenticated identity changes to
39
+ * avoid leaving usable decryption capability behind.
40
+ */
41
+ clear(): Promise<void>;
42
+ }
43
+ /**
44
+ * Default {@link DerivedKeyMaterialCache} that keeps key handles in memory only.
45
+ *
46
+ * Nothing is written to disk, so the cache is discarded when the page is
47
+ * reloaded or the tab is closed and there is no at-rest exposure. The trade-off
48
+ * is one extra key derivation per map per page load.
49
+ */
50
+ export declare class InMemoryDerivedKeyMaterialCache implements DerivedKeyMaterialCache {
51
+ #private;
52
+ get(key: string): Promise<CryptoKey | undefined>;
53
+ set(key: string, value: CryptoKey): Promise<void>;
54
+ clear(): Promise<void>;
55
+ }
56
+ /**
57
+ * Opt-in {@link DerivedKeyMaterialCache} that persists key handles in IndexedDB.
58
+ *
59
+ * Key handles survive page reloads, avoiding repeated key derivation, but this
60
+ * is a deliberate security trade-off: the persisted handle is non-extractable
61
+ * (its raw bytes cannot be stolen), yet any same-origin code — e.g. via XSS, a
62
+ * malicious extension, or a shared browser profile — can read the handle and
63
+ * use it to decrypt the user's data without an authenticated session, for as
64
+ * long as it remains stored.
65
+ *
66
+ * Prefer {@link InMemoryDerivedKeyMaterialCache} (the default) unless you need
67
+ * cross-reload persistence and accept this exposure. When using this cache, be
68
+ * sure to call {@link EncryptedMaps.clearCache} on logout or identity change.
69
+ *
70
+ * Because the cache key does not encode the identity, **give the store a
71
+ * per-identity namespace** to keep one identity's persisted keys from being
72
+ * served to another on the same origin — e.g. include the caller's principal in
73
+ * the database name:
74
+ *
75
+ * ```ts
76
+ * new IndexedDbDerivedKeyMaterialCache(`vetkeys-${principal}`);
77
+ * ```
78
+ *
79
+ * A dedicated IndexedDB store is used, so {@link clear} only removes entries
80
+ * written by this cache and never touches other application data.
81
+ */
82
+ export declare class IndexedDbDerivedKeyMaterialCache implements DerivedKeyMaterialCache {
83
+ #private;
84
+ /**
85
+ * @param dbName - IndexedDB database name. Defaults to `"ic-vetkeys"`. This
86
+ * is the isolation knob: give each identity its own database name (e.g.
87
+ * `` `vetkeys-${principal}` ``) so one identity's persisted keys are never
88
+ * served to another. The object store name is fixed, because `idb-keyval`
89
+ * supports only a single object store per database.
90
+ */
91
+ constructor(dbName?: string);
92
+ get(key: string): Promise<CryptoKey | undefined>;
93
+ set(key: string, value: CryptoKey): Promise<void>;
94
+ clear(): Promise<void>;
95
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,9 @@
1
1
  import { Principal } from '@icp-sdk/core/principal';
2
2
  import { DerivedKeyMaterial } from '../utils/utils';
3
+ import { DerivedKeyMaterialCache } from './cache';
3
4
  import { AccessRights, ByteBuf } from '../declarations/ic_vetkeys_manager_canister/ic_vetkeys_manager_canister.did.js';
4
5
  export { DefaultEncryptedMapsClient } from './encrypted_maps_canister';
6
+ export { type DerivedKeyMaterialCache, InMemoryDerivedKeyMaterialCache, IndexedDbDerivedKeyMaterialCache, } from './cache';
5
7
  export type { AccessRights, ByteBuf, } from '../declarations/ic_vetkeys_manager_canister/ic_vetkeys_manager_canister.did.js';
6
8
  /**
7
9
  * The **EncryptedMaps** frontend library facilitates interaction with an [**EncryptedMaps-enabled canister**](https://docs.rs/ic-vetkeys/latest/ic_vetkeys/encrypted_maps/struct.EncryptedMaps.html) on the **Internet Computer (ICP)**.
@@ -19,9 +21,23 @@ export type { AccessRights, ByteBuf, } from '../declarations/ic_vetkeys_manager_
19
21
  *
20
22
  * - **Access Rights** should be carefully managed to prevent unauthorized access.
21
23
  * - VetKeys should be decrypted **only in trusted environments** such as user browsers to prevent leaks.
24
+ * - Derived key material is cached **in memory by default** ({@link InMemoryDerivedKeyMaterialCache}),
25
+ * so it never touches disk and is discarded on page reload. Persisting it across
26
+ * reloads with {@link IndexedDbDerivedKeyMaterialCache} is an explicit, opt-in trade-off:
27
+ * the persisted handle is non-extractable, but any same-origin code can use it to decrypt
28
+ * without an authenticated session for as long as it is stored.
29
+ * - The cache belongs to a single authenticated identity. Identity lifecycle is the
30
+ * caller's responsibility, so to avoid serving one identity's key material to another
31
+ * on the same origin:
32
+ * - with the in-memory default, use a **fresh `EncryptedMaps` instance per identity**
33
+ * (or call {@link EncryptedMaps.clearCache} on logout / identity change);
34
+ * - with {@link IndexedDbDerivedKeyMaterialCache}, give the store a **per-identity
35
+ * namespace** (e.g. include the caller's principal in the database name) so entries
36
+ * are physically separated, and call {@link EncryptedMaps.clearCache} on logout.
22
37
  *
23
38
  */
24
39
  export declare class EncryptedMaps {
40
+ #private;
25
41
  /**
26
42
  * The client instance for interacting with the EncryptedMaps canister.
27
43
  */
@@ -33,14 +49,38 @@ export declare class EncryptedMaps {
33
49
  /**
34
50
  * Creates a new instance of the EncryptedMaps client.
35
51
  *
52
+ * @param canisterClient - The client used to talk to the EncryptedMaps canister.
53
+ * @param options - Optional configuration.
54
+ * @param options.cache - Strategy for caching derived key material. Defaults to
55
+ * {@link InMemoryDerivedKeyMaterialCache} (in-memory only, never persisted).
56
+ * Pass {@link IndexedDbDerivedKeyMaterialCache} to persist across page reloads —
57
+ * see the security note in the class description.
58
+ *
36
59
  * @example
37
60
  * ```ts
38
61
  * import { EncryptedMaps } from "@icp-sdk/vetkeys/encrypted_maps";
39
62
  *
40
63
  * const encryptedMaps = new EncryptedMaps(encryptedMapsClientInstance);
41
64
  * ```
65
+ *
66
+ * @example Opt into persistent caching, namespaced per identity
67
+ * ```ts
68
+ * import {
69
+ * EncryptedMaps,
70
+ * IndexedDbDerivedKeyMaterialCache,
71
+ * } from "@icp-sdk/vetkeys/encrypted_maps";
72
+ *
73
+ * const encryptedMaps = new EncryptedMaps(encryptedMapsClientInstance, {
74
+ * // Namespace the store per identity so one identity's cached keys are
75
+ * // never served to another on the same origin.
76
+ * cache: new IndexedDbDerivedKeyMaterialCache(`vetkeys-${myPrincipal}`),
77
+ * });
78
+ * // Remember to call encryptedMaps.clearCache() on logout / identity change.
79
+ * ```
42
80
  */
43
- constructor(canisterClient: EncryptedMapsClient);
81
+ constructor(canisterClient: EncryptedMapsClient, options?: {
82
+ cache?: DerivedKeyMaterialCache;
83
+ });
44
84
  /**
45
85
  * Retrieves a list of maps that were shared with the user and the user still has access to.
46
86
  *
@@ -255,6 +295,18 @@ export declare class EncryptedMaps {
255
295
  * @returns Promise resolving to the derived key material
256
296
  */
257
297
  getDerivedKeyMaterialOrFetchIfNeeded(mapOwner: Principal, mapName: Uint8Array): Promise<DerivedKeyMaterial>;
298
+ /**
299
+ * Clears all cached derived key material.
300
+ *
301
+ * Strongly recommended on logout or whenever the authenticated identity
302
+ * changes: the cache belongs to a single identity, so clearing it (or
303
+ * discarding the `EncryptedMaps` instance) prevents one identity's key
304
+ * material from being served to another, and drops the still-usable
305
+ * decryption capability that otherwise lingers. This matters most with
306
+ * {@link IndexedDbDerivedKeyMaterialCache}, where cached key handles persist
307
+ * across sessions until cleared.
308
+ */
309
+ clearCache(): Promise<void>;
258
310
  }
259
311
  /**
260
312
  * Interface for map data structure.
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@icp-sdk/vetkeys",
3
- "version": "0.5.0-beta.0",
4
- "packageManager": "pnpm@10.10.0",
3
+ "version": "0.5.0",
5
4
  "author": "DFINITY Stiftung",
6
5
  "description": "JavaScript and TypeScript library to use Internet Computer vetKeys",
7
6
  "homepage": "https://internetcomputer.org/docs/building-apps/network-features/vetkeys/introduction",
@@ -58,7 +57,7 @@
58
57
  "module": "dist/lib/index.es.js",
59
58
  "typings": "dist/types/index.d.ts",
60
59
  "dependencies": {
61
- "@icp-sdk/core": "^5.2.1",
60
+ "@icp-sdk/core": "^5.4.0",
62
61
  "idb-keyval": "^6.2.1"
63
62
  },
64
63
  "devDependencies": {
@@ -77,7 +76,7 @@
77
76
  "typescript-eslint": "^8.59.0",
78
77
  "vite": "^7.0.8",
79
78
  "vite-plugin-dts": "^4.5.3",
80
- "vitest": "^3.0.5"
79
+ "vitest": "^3.2.6"
81
80
  },
82
81
  "scripts": {
83
82
  "build": "tsc && vite build",