@tinycloud/sdk-services 3.11.0-beta.2 → 3.12.0-beta.10

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.
Files changed (71) hide show
  1. package/dist/{BaseService-Ma2MwJAd.d.ts → BaseService-BRDG69KR.d.ts} +1 -1
  2. package/dist/{BaseService-O0iyuFLk.d.cts → BaseService-CdBkfPGE.d.cts} +1 -1
  3. package/dist/{IHooksService-4QNODVy6.d.cts → IHooksService-C9p3stsB.d.cts} +1 -1
  4. package/dist/{IHooksService-CvTpJbQD.d.ts → IHooksService-D6eyZat1.d.ts} +1 -1
  5. package/dist/{ISQLService-Aly1CBsW.d.ts → ISQLService-B_4KcGA-.d.ts} +1 -1
  6. package/dist/{ISQLService-BOgWzHLT.d.cts → ISQLService-C6nNpcls.d.cts} +1 -1
  7. package/dist/context.cjs +4 -0
  8. package/dist/context.cjs.map +1 -1
  9. package/dist/context.d.cts +1 -1
  10. package/dist/context.d.ts +1 -1
  11. package/dist/context.js +4 -0
  12. package/dist/context.js.map +1 -1
  13. package/dist/encryption/index.cjs +276 -14
  14. package/dist/encryption/index.cjs.map +1 -1
  15. package/dist/encryption/index.d.cts +26 -28
  16. package/dist/encryption/index.d.ts +26 -28
  17. package/dist/encryption/index.js +268 -14
  18. package/dist/encryption/index.js.map +1 -1
  19. package/dist/encryption/named-keys.cjs +225 -14
  20. package/dist/encryption/named-keys.cjs.map +1 -1
  21. package/dist/encryption/named-keys.d.cts +7 -2
  22. package/dist/encryption/named-keys.d.ts +7 -2
  23. package/dist/encryption/named-keys.js +225 -14
  24. package/dist/encryption/named-keys.js.map +1 -1
  25. package/dist/encryption/resource.cjs.map +1 -1
  26. package/dist/encryption/resource.js.map +1 -1
  27. package/dist/feed/index.cjs +4 -0
  28. package/dist/feed/index.cjs.map +1 -1
  29. package/dist/feed/index.d.cts +3 -3
  30. package/dist/feed/index.d.ts +3 -3
  31. package/dist/feed/index.js +4 -0
  32. package/dist/feed/index.js.map +1 -1
  33. package/dist/index.cjs +509 -18
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +14 -12
  36. package/dist/index.d.ts +14 -12
  37. package/dist/index.js +499 -18
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  40. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  41. package/dist/kv/index.cjs +34 -0
  42. package/dist/kv/index.cjs.map +1 -1
  43. package/dist/kv/index.d.cts +4 -4
  44. package/dist/kv/index.d.ts +4 -4
  45. package/dist/kv/index.js +34 -0
  46. package/dist/kv/index.js.map +1 -1
  47. package/dist/kv/replication/index.cjs +39 -8
  48. package/dist/kv/replication/index.cjs.map +1 -1
  49. package/dist/kv/replication/index.d.cts +3 -3
  50. package/dist/kv/replication/index.d.ts +3 -3
  51. package/dist/kv/replication/index.js +38 -7
  52. package/dist/kv/replication/index.js.map +1 -1
  53. package/dist/{scope-DEgc9f-W.d.cts → scope-DtcbyRKY.d.cts} +1 -1
  54. package/dist/{scope-BZr0QwxX.d.ts → scope-J20PQP3u.d.ts} +1 -1
  55. package/dist/sql/index.cjs +36 -0
  56. package/dist/sql/index.cjs.map +1 -1
  57. package/dist/sql/index.d.cts +4 -4
  58. package/dist/sql/index.d.ts +4 -4
  59. package/dist/sql/index.js +36 -0
  60. package/dist/sql/index.js.map +1 -1
  61. package/dist/telemetry/requestLog.cjs +4 -0
  62. package/dist/telemetry/requestLog.cjs.map +1 -1
  63. package/dist/telemetry/requestLog.d.cts +1 -1
  64. package/dist/telemetry/requestLog.d.ts +1 -1
  65. package/dist/telemetry/requestLog.js +4 -0
  66. package/dist/telemetry/requestLog.js.map +1 -1
  67. package/dist/{types-D4XbIfFJ.d.cts → types-DfqNz9Nu.d.cts} +135 -6
  68. package/dist/{types-CSd0RYir.d.ts → types-DjAP8dve.d.cts} +4 -0
  69. package/dist/{types-CSd0RYir.d.cts → types-DjAP8dve.d.ts} +4 -0
  70. package/dist/{types-Bf91gup6.d.ts → types-Nvi_RckJ.d.ts} +135 -6
  71. package/package.json +4 -1
@@ -1,6 +1,6 @@
1
- import { E as EncryptionCrypto, I as InlineEncryptedEnvelope, J as Json } from '../types-Bf91gup6.js';
1
+ import { E as EncryptionCrypto, I as InlineEncryptedEnvelope, J as Json } from '../types-Nvi_RckJ.js';
2
2
  import { ParsedKeyResource } from './resource.js';
3
- import '../types-CSd0RYir.js';
3
+ import '../types-DjAP8dve.js';
4
4
 
5
5
  /**
6
6
  * Named-key client: key admin, descriptor reads, `ensureKey` and key-scoped
@@ -103,6 +103,10 @@ interface NamedKeyInvocationInput {
103
103
  interface NamedKeyClientConfig {
104
104
  host: string;
105
105
  crypto: EncryptionCrypto;
106
+ /** Write TCW2 wraps; disabled by default. */
107
+ keyWrapV2?: boolean;
108
+ /** Require TCW2 incoming network-key wraps, not signed receiver responses. Disabled by default. */
109
+ requireKeyWrapV2?: boolean;
106
110
  fetch: typeof fetch;
107
111
  /** Network used when a call does not name one. */
108
112
  defaultNetworkId: () => string;
@@ -130,6 +134,7 @@ interface KeyCallOptions {
130
134
  networkId?: string;
131
135
  }
132
136
  interface KeyEncryptOptions extends KeyCallOptions {
137
+ keyWrapV2?: boolean;
133
138
  aad?: Uint8Array;
134
139
  metadata?: Record<string, string>;
135
140
  }
@@ -85,9 +85,9 @@ function base64Decode(s) {
85
85
  function utf8Encode(s) {
86
86
  return new TextEncoder().encode(s);
87
87
  }
88
- function canonicalHashHex(sha256, value) {
88
+ function canonicalHashHex(sha2562, value) {
89
89
  const canonical = canonicalize(value);
90
- return hexEncode(sha256(utf8Encode(canonical)));
90
+ return hexEncode(sha2562(utf8Encode(canonical)));
91
91
  }
92
92
 
93
93
  // src/encryption/networkId.ts
@@ -135,6 +135,26 @@ function parseNetworkId(networkId) {
135
135
  }
136
136
  return { networkId, ownerDid, name };
137
137
  }
138
+ function parseEncryptionResource(resource) {
139
+ const networkSeparator = resource.lastIndexOf(":");
140
+ const nameAndPath = resource.slice(networkSeparator + 1);
141
+ const pathSeparator = nameAndPath.indexOf("/");
142
+ const name = pathSeparator === -1 ? nameAndPath : nameAndPath.slice(0, pathSeparator);
143
+ const networkId = pathSeparator === -1 ? resource : `${resource.slice(0, networkSeparator + 1)}${name}`;
144
+ const parsed = parseNetworkId(networkId);
145
+ if (pathSeparator === -1) return parsed;
146
+ const keyPath = nameAndPath.slice(pathSeparator + 1);
147
+ const segments = keyPath.split("/");
148
+ const prefix = keyPath.endsWith("/");
149
+ if (keyPath.length === 0 || /[\u0000-\u001f\u007f\\]/.test(keyPath) || segments.some(
150
+ (segment, index) => segment === "." || segment === ".." || segment.length === 0 && !(prefix && index === segments.length - 1)
151
+ )) {
152
+ throw new NetworkIdError(
153
+ `encryption resource has an invalid keyPath (got ${JSON.stringify(resource)})`
154
+ );
155
+ }
156
+ return { ...parsed, keyPath };
157
+ }
138
158
  var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
139
159
 
140
160
  // src/encryption/receiverKey.ts
@@ -170,6 +190,8 @@ function defaultEncryptionMessage(input) {
170
190
  return input.message ?? input.cause.message;
171
191
  case "INVALID_INPUT":
172
192
  return input.message;
193
+ default:
194
+ return input.message ?? input.code;
173
195
  }
174
196
  }
175
197
  function encryptionError(input) {
@@ -180,6 +202,152 @@ function encryptionError(input) {
180
202
  };
181
203
  }
182
204
 
205
+ // src/encryption/keyWrap.ts
206
+ import { gcm } from "@noble/ciphers/aes";
207
+ import { x25519 } from "@noble/curves/ed25519";
208
+ import { hkdf } from "@noble/hashes/hkdf";
209
+ import { sha256 } from "@noble/hashes/sha256";
210
+ import { randomBytes } from "@noble/hashes/utils";
211
+ var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
212
+ var MAGIC = utf8Encode("TCW2");
213
+ var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
214
+ var MAX_HEADER = 65536;
215
+ function concat(...parts) {
216
+ const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
217
+ let offset = 0;
218
+ for (const part of parts) {
219
+ result.set(part, offset);
220
+ offset += part.length;
221
+ }
222
+ return result;
223
+ }
224
+ function equal(a, b) {
225
+ if (a.length !== b.length) return false;
226
+ let difference = 0;
227
+ for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
228
+ return difference === 0;
229
+ }
230
+ function decode32(value) {
231
+ if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
232
+ const decoded = base64Decode(value);
233
+ if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
234
+ return decoded;
235
+ }
236
+ function isWellFormed(value) {
237
+ for (let i = 0; i < value.length; i++) {
238
+ const unit = value.charCodeAt(i);
239
+ if (unit >= 55296 && unit <= 56319) {
240
+ const next = value.charCodeAt(++i);
241
+ if (!(next >= 56320 && next <= 57343)) return false;
242
+ } else if (unit >= 56320 && unit <= 57343) return false;
243
+ }
244
+ return true;
245
+ }
246
+ function validateContext(context) {
247
+ if (context.purpose !== "network-key" && context.purpose !== "receiver-key" || typeof context.networkId !== "string" || typeof context.keyPath !== "string" || !isWellFormed(context.networkId) || !isWellFormed(context.keyPath) || !Number.isSafeInteger(context.keyVersion) || context.keyVersion <= 0 || !context.metadata || typeof context.metadata !== "object" || Array.isArray(context.metadata) || Object.entries(context.metadata).some(([key, value]) => !isWellFormed(key) || typeof value !== "string" || !isWellFormed(value))) {
248
+ throw new Error("Invalid key wrap context");
249
+ }
250
+ if (context.networkId !== "") {
251
+ const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
252
+ if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
253
+ throw new Error("Invalid key wrap resource");
254
+ }
255
+ }
256
+ }
257
+ function isKeyWrapV2(wrapped) {
258
+ return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
259
+ }
260
+ function readKeyWrapV2(wrapped) {
261
+ if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
262
+ const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
263
+ if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
264
+ const bytes = wrapped.subarray(8, 8 + length);
265
+ const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
266
+ const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
267
+ if (!header || typeof header !== "object" || Array.isArray(header) || Object.keys(header).sort().join(",") !== fields.join(",") || header.v !== 2 || header.alg !== KEY_WRAP_V2_ALG || header.kdf !== "hkdf-sha256" || header.aead !== "aes-256-gcm") {
268
+ throw new Error("Invalid v2 key wrap header");
269
+ }
270
+ validateContext(header);
271
+ decode32(header.salt);
272
+ decode32(header.ephemeralPublicKey);
273
+ decode32(header.recipientPublicKey);
274
+ if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
275
+ const end = 8 + length;
276
+ return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
277
+ }
278
+ function assertKeyWrapContext(header, expected) {
279
+ validateContext(expected);
280
+ for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
281
+ if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
282
+ }
283
+ if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
284
+ }
285
+ function deriveKey(secret, ephemeral, recipient, salt) {
286
+ const shared = x25519.getSharedSecret(secret, ephemeral);
287
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
288
+ try {
289
+ return hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
290
+ } finally {
291
+ shared.fill(0);
292
+ }
293
+ }
294
+ function sealKeyV2(recipientPublicKey, plaintext, context) {
295
+ validateContext(context);
296
+ if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
297
+ const secret = randomBytes(32);
298
+ const ephemeral = x25519.getPublicKey(secret);
299
+ const salt = randomBytes(32);
300
+ const header = {
301
+ ...context,
302
+ v: 2,
303
+ alg: KEY_WRAP_V2_ALG,
304
+ kdf: "hkdf-sha256",
305
+ aead: "aes-256-gcm",
306
+ salt: base64Encode(salt),
307
+ ephemeralPublicKey: base64Encode(ephemeral),
308
+ recipientPublicKey: base64Encode(recipientPublicKey)
309
+ };
310
+ const bytes = utf8Encode(canonicalize({ ...header }));
311
+ if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
312
+ const prefix = new Uint8Array(8);
313
+ prefix.set(MAGIC);
314
+ new DataView(prefix.buffer).setUint32(4, bytes.length);
315
+ const aad = concat(prefix, bytes);
316
+ const nonce = randomBytes(12);
317
+ let key;
318
+ try {
319
+ const shared = x25519.getSharedSecret(secret, recipientPublicKey);
320
+ try {
321
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
322
+ key = hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
323
+ } finally {
324
+ shared.fill(0);
325
+ }
326
+ return concat(aad, nonce, gcm(key, nonce, aad).encrypt(plaintext));
327
+ } finally {
328
+ secret.fill(0);
329
+ key?.fill(0);
330
+ }
331
+ }
332
+ function openKeyV2(recipientPrivateKey, wrapped, expected) {
333
+ const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
334
+ assertKeyWrapContext(header, expected);
335
+ const recipient = x25519.getPublicKey(recipientPrivateKey);
336
+ if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
337
+ const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
338
+ try {
339
+ return gcm(key, nonce, aad).decrypt(ciphertext);
340
+ } finally {
341
+ key.fill(0);
342
+ }
343
+ }
344
+ function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
345
+ if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
346
+ if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
347
+ if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
348
+ if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
349
+ }
350
+
183
351
  // src/encryption/response.ts
184
352
  function canonicalSignedResponse(response) {
185
353
  const { nodeSignature: _drop, ...rest } = response;
@@ -223,7 +391,7 @@ function verifyDecryptResponse(input) {
223
391
  })
224
392
  };
225
393
  }
226
- if (response.alg !== request.alg) {
394
+ if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
227
395
  return {
228
396
  ok: false,
229
397
  error: encryptionError({
@@ -280,7 +448,7 @@ function verifyDecryptResponse(input) {
280
448
  })
281
449
  };
282
450
  }
283
- if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== response.alg || facts.keyVersion !== response.keyVersion) {
451
+ if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== request.alg || facts.keyVersion !== response.keyVersion) {
284
452
  return {
285
453
  ok: false,
286
454
  error: encryptionError({
@@ -303,11 +471,31 @@ function verifyDecryptResponse(input) {
303
471
  }
304
472
  return { ok: true, data: response };
305
473
  }
306
- function openWrappedKey(crypto, receiverPrivateKey, response) {
474
+ function openWrappedKey(crypto, receiverPrivateKey, response, expected) {
307
475
  const wrapped = base64Decode(response.wrappedKey);
476
+ assertKeyWrapAlgorithm(wrapped, response.alg);
477
+ if (response.alg === KEY_WRAP_V2_ALG) {
478
+ if (!expected) throw new Error("V2 response requires expected key context");
479
+ return openKeyV2(receiverPrivateKey, wrapped, expected);
480
+ }
308
481
  return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);
309
482
  }
310
483
 
484
+ // src/encryption/envelope.ts
485
+ function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
486
+ const wrapped = base64Decode(envelope.encryptedSymmetricKey);
487
+ assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
488
+ if (envelope.alg === KEY_WRAP_V2_ALG) {
489
+ assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
490
+ purpose: "network-key",
491
+ networkId: envelope.networkId,
492
+ keyPath: envelope.keyPath ?? "",
493
+ keyVersion: envelope.keyVersion,
494
+ metadata: envelope.metadata ?? {}
495
+ });
496
+ }
497
+ }
498
+
311
499
  // src/encryption/resource.ts
312
500
  import { pkhDid } from "@tinycloud/bootstrap";
313
501
  var SEGMENT_SOURCE = "[a-z0-9][a-z0-9._-]{0,63}";
@@ -390,7 +578,7 @@ function assertPath(path, what) {
390
578
  );
391
579
  }
392
580
  }
393
- function parseEncryptionResource(uri) {
581
+ function parseEncryptionResource2(uri) {
394
582
  if (typeof uri !== "string" || !uri.startsWith(ENCRYPTION_NETWORK_URN_PREFIX)) {
395
583
  throw new EncryptionResourceError(
396
584
  "not_encryption_urn",
@@ -451,7 +639,7 @@ function canonicalNetworkId(networkId) {
451
639
  return parsed.networkId;
452
640
  }
453
641
  function parseBuilt(resource, networkId) {
454
- if (parseEncryptionResource(resource).networkId !== networkId) {
642
+ if (parseEncryptionResource2(resource).networkId !== networkId) {
455
643
  throw new EncryptionResourceError(
456
644
  "malformed",
457
645
  `built resource does not round-trip (got ${JSON.stringify(resource)})`
@@ -550,7 +738,7 @@ function mapNodeKeyError(status, text, context) {
550
738
  function requireKeyTarget(resource) {
551
739
  let parsed;
552
740
  try {
553
- parsed = parseEncryptionResource(resource);
741
+ parsed = parseEncryptionResource2(resource);
554
742
  } catch (error) {
555
743
  throw new NamedKeyError(
556
744
  "INVALID_KEY_TARGET",
@@ -852,16 +1040,21 @@ var NamedKeyClient = class {
852
1040
  const { crypto } = this.config;
853
1041
  const symmetricKey = crypto.randomBytes(32);
854
1042
  const ciphertext = crypto.authEncrypt(symmetricKey, plaintext, options?.aad);
855
- const wrapped = crypto.sealToNetworkKey(
856
- base64Decode(descriptor.publicEncryptionKey),
857
- symmetricKey
858
- );
1043
+ const keyWrapV2 = options?.keyWrapV2 ?? this.config.keyWrapV2 ?? false;
1044
+ const publicKey = base64Decode(descriptor.publicEncryptionKey);
1045
+ const wrapped = keyWrapV2 ? sealKeyV2(publicKey, symmetricKey, {
1046
+ purpose: "network-key",
1047
+ networkId: descriptor.networkId,
1048
+ keyPath: descriptor.keyPath,
1049
+ keyVersion: descriptor.keyVersion,
1050
+ metadata: options?.metadata ?? {}
1051
+ }) : crypto.sealToNetworkKey(publicKey, symmetricKey);
859
1052
  const encryptedSymmetricKey = base64Encode(wrapped);
860
1053
  return {
861
1054
  v: NAMED_KEY_ENVELOPE_VERSION,
862
1055
  networkId: descriptor.networkId,
863
1056
  keyPath: descriptor.keyPath,
864
- alg: descriptor.alg ?? DEFAULT_ENCRYPTION_ALG,
1057
+ alg: keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG,
865
1058
  keyVersion: descriptor.keyVersion,
866
1059
  encryptedSymmetricKey,
867
1060
  encryptedSymmetricKeyHash: canonicalHashHex(crypto.sha256, encryptedSymmetricKey),
@@ -906,6 +1099,13 @@ var NamedKeyClient = class {
906
1099
  return results;
907
1100
  }
908
1101
  async decryptRoot(envelope) {
1102
+ if (this.config.requireKeyWrapV2) {
1103
+ try {
1104
+ validateEnvelopeKeyWrap(envelope, true);
1105
+ } catch (error) {
1106
+ throw new NamedKeyError("INVALID_ENVELOPE", "Envelope key wrap context is invalid", { cause: error });
1107
+ }
1108
+ }
909
1109
  if (this.config.decryptRoot === void 0) {
910
1110
  throw new NamedKeyError("DECRYPT_FAILED", "no root-key decrypt path is configured");
911
1111
  }
@@ -942,6 +1142,11 @@ var NamedKeyClient = class {
942
1142
  "envelope.encryptedSymmetricKeyHash does not match encryptedSymmetricKey"
943
1143
  );
944
1144
  }
1145
+ try {
1146
+ validateEnvelopeKeyWrap(e, this.config.requireKeyWrapV2 ?? false);
1147
+ } catch (error) {
1148
+ throw new NamedKeyError("INVALID_ENVELOPE", "Envelope key wrap context is invalid", { cause: error });
1149
+ }
945
1150
  return e;
946
1151
  }
947
1152
  async decryptKey(envelope) {
@@ -1003,7 +1208,13 @@ var NamedKeyClient = class {
1003
1208
  if (!verified.ok) {
1004
1209
  throw new NamedKeyError("INVALID_RESPONSE", safeEcho(verified.error.message), { cause: verified.error });
1005
1210
  }
1006
- const symmetricKey = openWrappedKey(crypto, receiverKey.privateKey, verified.data);
1211
+ const symmetricKey = openWrappedKey(crypto, receiverKey.privateKey, verified.data, {
1212
+ purpose: "receiver-key",
1213
+ networkId: body.networkId,
1214
+ keyPath: body.keyPath,
1215
+ keyVersion: body.keyVersion,
1216
+ metadata: {}
1217
+ });
1007
1218
  try {
1008
1219
  return crypto.authDecrypt(
1009
1220
  symmetricKey,