@tinycloud/sdk-services 3.11.0 → 3.12.0-beta.16

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 +523 -29
  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 +513 -29
  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 +48 -11
  42. package/dist/kv/index.cjs.map +1 -1
  43. package/dist/kv/index.d.cts +5 -5
  44. package/dist/kv/index.d.ts +5 -5
  45. package/dist/kv/index.js +48 -11
  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
@@ -10,6 +10,10 @@ var ErrorCodes = {
10
10
  /** HTTP 401: a delegation the cited one rests on was revoked. */
11
11
  AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
12
12
  NETWORK_ERROR: "NETWORK_ERROR",
13
+ NODE_NOT_RESPONDING: "NODE_NOT_RESPONDING",
14
+ NODE_IDENTITY_MISMATCH: "NODE_IDENTITY_MISMATCH",
15
+ PARTIAL_NODE_WRITE: "PARTIAL_NODE_WRITE",
16
+ RESOURCE_NOT_ROUTED: "RESOURCE_NOT_ROUTED",
13
17
  TIMEOUT: "TIMEOUT",
14
18
  ABORTED: "ABORTED",
15
19
  INVALID_INPUT: "INVALID_INPUT",
@@ -206,6 +210,34 @@ function networkDiscoveryKey(name) {
206
210
  var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
207
211
  var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
208
212
 
213
+ // src/resourceRouting.ts
214
+ var ROUTING_FAILURE_CODES = {
215
+ NODE_NOT_RESPONDING: true,
216
+ PARTIAL_NODE_WRITE: true,
217
+ NODE_IDENTITY_MISMATCH: true,
218
+ RESOURCE_NOT_ROUTED: true,
219
+ DECRYPTION_DEPENDENCY_UNAVAILABLE: true,
220
+ registry_unavailable: true,
221
+ node_not_found: true,
222
+ node_registry_entry_exists: true
223
+ };
224
+ function isRoutingFailureCode(code) {
225
+ return Object.prototype.hasOwnProperty.call(ROUTING_FAILURE_CODES, code);
226
+ }
227
+ function routingFailure(error) {
228
+ if (typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && typeof error.message === "string" && isRoutingFailureCode(error.code)) {
229
+ const candidate = "meta" in error ? error.meta : void 0;
230
+ let meta = typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : void 0;
231
+ if (error.code === "NODE_NOT_RESPONDING" && "hosts" in error && Array.isArray(error.hosts)) {
232
+ meta = { ...meta, hosts: error.hosts };
233
+ } else if (error.code === "PARTIAL_NODE_WRITE" && "succeeded" in error && Array.isArray(error.succeeded) && "failed" in error && Array.isArray(error.failed)) {
234
+ meta = { ...meta, succeeded: error.succeeded, failed: error.failed };
235
+ }
236
+ return { code: error.code, message: error.message, ...error instanceof Error ? { cause: error } : {}, ...meta ? { meta } : {} };
237
+ }
238
+ return void 0;
239
+ }
240
+
209
241
  // src/errors.ts
210
242
  var RequestTimeoutError = class extends Error {
211
243
  constructor(timeoutMs) {
@@ -248,6 +280,8 @@ var capabilityServices = new Set(
248
280
  CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
249
281
  );
250
282
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
283
+ const routing = routingFailure(error);
284
+ if (routing) return { ...routing, service };
251
285
  if (error instanceof Error) {
252
286
  if (isRequestTimeoutError(error)) {
253
287
  return timeoutError(service, error.timeoutMs, error);
@@ -659,9 +693,9 @@ function utf8Encode(s) {
659
693
  function utf8Decode(b) {
660
694
  return new TextDecoder().decode(b);
661
695
  }
662
- function canonicalHashHex(sha256, value) {
696
+ function canonicalHashHex(sha2562, value) {
663
697
  const canonical = canonicalize(value);
664
- return hexEncode(sha256(utf8Encode(canonical)));
698
+ return hexEncode(sha2562(utf8Encode(canonical)));
665
699
  }
666
700
 
667
701
  // src/encryption/types.ts
@@ -695,6 +729,8 @@ function defaultEncryptionMessage(input) {
695
729
  return input.message ?? input.cause.message;
696
730
  case "INVALID_INPUT":
697
731
  return input.message;
732
+ default:
733
+ return input.message ?? input.code;
698
734
  }
699
735
  }
700
736
  function encryptionError(input) {
@@ -759,6 +795,8 @@ async function discoverNetwork(input) {
759
795
  return { ok: true, data: { descriptor: validated.data, source: "node" } };
760
796
  }
761
797
  } catch (err2) {
798
+ const routing = routingFailure(err2);
799
+ if (routing) return { ok: false, error: encryptionError(routing) };
762
800
  }
763
801
  }
764
802
  if (input.wellKnown !== void 0) {
@@ -776,6 +814,8 @@ async function discoverNetwork(input) {
776
814
  };
777
815
  }
778
816
  } catch (err2) {
817
+ const routing = routingFailure(err2);
818
+ if (routing) return { ok: false, error: encryptionError(routing) };
779
819
  }
780
820
  }
781
821
  return {
@@ -865,6 +905,160 @@ function ensureNetworkUsableForDecrypt(descriptor) {
865
905
  };
866
906
  }
867
907
 
908
+ // src/encryption/keyWrap.ts
909
+ import { gcm } from "@noble/ciphers/aes";
910
+ import { x25519 } from "@noble/curves/ed25519";
911
+ import { hkdf } from "@noble/hashes/hkdf";
912
+ import { sha256 } from "@noble/hashes/sha256";
913
+ import { randomBytes } from "@noble/hashes/utils";
914
+ var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
915
+ var MAGIC = utf8Encode("TCW2");
916
+ var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
917
+ var MAX_HEADER = 65536;
918
+ function concat(...parts) {
919
+ const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
920
+ let offset = 0;
921
+ for (const part of parts) {
922
+ result.set(part, offset);
923
+ offset += part.length;
924
+ }
925
+ return result;
926
+ }
927
+ function equal(a, b) {
928
+ if (a.length !== b.length) return false;
929
+ let difference = 0;
930
+ for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
931
+ return difference === 0;
932
+ }
933
+ function decode32(value) {
934
+ if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
935
+ const decoded = base64Decode(value);
936
+ if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
937
+ return decoded;
938
+ }
939
+ function isWellFormed(value) {
940
+ for (let i = 0; i < value.length; i++) {
941
+ const unit = value.charCodeAt(i);
942
+ if (unit >= 55296 && unit <= 56319) {
943
+ const next = value.charCodeAt(++i);
944
+ if (!(next >= 56320 && next <= 57343)) return false;
945
+ } else if (unit >= 56320 && unit <= 57343) return false;
946
+ }
947
+ return true;
948
+ }
949
+ function validateContext(context) {
950
+ 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))) {
951
+ throw new Error("Invalid key wrap context");
952
+ }
953
+ if (context.networkId !== "") {
954
+ const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
955
+ if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
956
+ throw new Error("Invalid key wrap resource");
957
+ }
958
+ }
959
+ }
960
+ function isKeyWrapV2(wrapped) {
961
+ return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
962
+ }
963
+ function readKeyWrapV2(wrapped) {
964
+ if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
965
+ const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
966
+ if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
967
+ const bytes = wrapped.subarray(8, 8 + length);
968
+ const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
969
+ const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
970
+ 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") {
971
+ throw new Error("Invalid v2 key wrap header");
972
+ }
973
+ validateContext(header);
974
+ decode32(header.salt);
975
+ decode32(header.ephemeralPublicKey);
976
+ decode32(header.recipientPublicKey);
977
+ if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
978
+ const end = 8 + length;
979
+ return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
980
+ }
981
+ function assertKeyWrapContext(header, expected) {
982
+ validateContext(expected);
983
+ for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
984
+ if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
985
+ }
986
+ if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
987
+ }
988
+ function deriveKey(secret, ephemeral, recipient, salt) {
989
+ const shared = x25519.getSharedSecret(secret, ephemeral);
990
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
991
+ try {
992
+ return hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
993
+ } finally {
994
+ shared.fill(0);
995
+ }
996
+ }
997
+ function sealKeyV2(recipientPublicKey, plaintext, context) {
998
+ validateContext(context);
999
+ if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
1000
+ const secret = randomBytes(32);
1001
+ const ephemeral = x25519.getPublicKey(secret);
1002
+ const salt = randomBytes(32);
1003
+ const header = {
1004
+ ...context,
1005
+ v: 2,
1006
+ alg: KEY_WRAP_V2_ALG,
1007
+ kdf: "hkdf-sha256",
1008
+ aead: "aes-256-gcm",
1009
+ salt: base64Encode(salt),
1010
+ ephemeralPublicKey: base64Encode(ephemeral),
1011
+ recipientPublicKey: base64Encode(recipientPublicKey)
1012
+ };
1013
+ const bytes = utf8Encode(canonicalize({ ...header }));
1014
+ if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
1015
+ const prefix = new Uint8Array(8);
1016
+ prefix.set(MAGIC);
1017
+ new DataView(prefix.buffer).setUint32(4, bytes.length);
1018
+ const aad = concat(prefix, bytes);
1019
+ const nonce = randomBytes(12);
1020
+ let key;
1021
+ try {
1022
+ const shared = x25519.getSharedSecret(secret, recipientPublicKey);
1023
+ try {
1024
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
1025
+ key = hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
1026
+ } finally {
1027
+ shared.fill(0);
1028
+ }
1029
+ return concat(aad, nonce, gcm(key, nonce, aad).encrypt(plaintext));
1030
+ } finally {
1031
+ secret.fill(0);
1032
+ key?.fill(0);
1033
+ }
1034
+ }
1035
+ function openKeyV2(recipientPrivateKey, wrapped, expected) {
1036
+ const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
1037
+ assertKeyWrapContext(header, expected);
1038
+ const recipient = x25519.getPublicKey(recipientPrivateKey);
1039
+ if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
1040
+ const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
1041
+ try {
1042
+ return gcm(key, nonce, aad).decrypt(ciphertext);
1043
+ } finally {
1044
+ key.fill(0);
1045
+ }
1046
+ }
1047
+ function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
1048
+ if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
1049
+ if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
1050
+ if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
1051
+ if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
1052
+ }
1053
+ function decryptColumn(decrypt, key, blob, expectedFormat, strictEncryption = false) {
1054
+ if (expectedFormat === "legacy-plaintext") {
1055
+ if (strictEncryption) throw new Error("Legacy plaintext is disabled");
1056
+ return blob;
1057
+ }
1058
+ if (expectedFormat !== "encrypted" || blob.length < 1 + 12 + 16 || blob[0] !== 1) throw new Error("Invalid encrypted column frame");
1059
+ return decrypt(key, blob.subarray(1));
1060
+ }
1061
+
868
1062
  // src/encryption/envelope.ts
869
1063
  function encryptToNetwork(crypto, input) {
870
1064
  const parsedResource = parseEncryptionResource(
@@ -873,11 +1067,20 @@ function encryptToNetwork(crypto, input) {
873
1067
  if (parsedResource.keyPath !== input.keyPath || input.keyPath?.endsWith("/")) {
874
1068
  throw new NetworkIdError("keyPath must name an exact encryption key");
875
1069
  }
876
- const alg = input.alg ?? DEFAULT_ENCRYPTION_ALG;
1070
+ const alg = input.keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG;
1071
+ if (input.alg !== void 0 && input.alg !== alg) {
1072
+ throw new Error("Algorithm does not match keyWrapV2");
1073
+ }
877
1074
  const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
878
1075
  const symmetricKey = crypto.randomBytes(32);
879
1076
  const ciphertext = crypto.authEncrypt(symmetricKey, input.plaintext, input.aad);
880
- const wrapped = crypto.sealToNetworkKey(input.networkPublicKey, symmetricKey);
1077
+ const wrapped = input.keyWrapV2 ? sealKeyV2(input.networkPublicKey, symmetricKey, {
1078
+ purpose: "network-key",
1079
+ networkId: input.networkId,
1080
+ keyPath: input.keyPath ?? "",
1081
+ keyVersion,
1082
+ metadata: input.metadata ?? {}
1083
+ }) : crypto.sealToNetworkKey(input.networkPublicKey, symmetricKey);
881
1084
  const encryptedSymmetricKey = base64Encode(wrapped);
882
1085
  const encryptedSymmetricKeyHash = canonicalHashHex(
883
1086
  crypto.sha256,
@@ -897,7 +1100,7 @@ function encryptToNetwork(crypto, input) {
897
1100
  };
898
1101
  return { envelope, symmetricKey };
899
1102
  }
900
- function validateEnvelope(crypto, envelope) {
1103
+ function validateEnvelope(crypto, envelope, requireKeyWrapV2 = false) {
901
1104
  if (envelope === null || typeof envelope !== "object") {
902
1105
  return {
903
1106
  ok: false,
@@ -974,9 +1177,28 @@ function validateEnvelope(crypto, envelope) {
974
1177
  })
975
1178
  };
976
1179
  }
1180
+ try {
1181
+ validateEnvelopeKeyWrap(e, requireKeyWrapV2);
1182
+ } catch {
1183
+ return { ok: false, error: encryptionError({ code: "INVALID_ENVELOPE", message: "Envelope key wrap context is invalid" }) };
1184
+ }
977
1185
  return { ok: true, data: e };
978
1186
  }
1187
+ function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
1188
+ const wrapped = base64Decode(envelope.encryptedSymmetricKey);
1189
+ assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
1190
+ if (envelope.alg === KEY_WRAP_V2_ALG) {
1191
+ assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
1192
+ purpose: "network-key",
1193
+ networkId: envelope.networkId,
1194
+ keyPath: envelope.keyPath ?? "",
1195
+ keyVersion: envelope.keyVersion,
1196
+ metadata: envelope.metadata ?? {}
1197
+ });
1198
+ }
1199
+ }
979
1200
  function decryptEnvelopeWithKey(crypto, envelope, symmetricKey) {
1201
+ validateEnvelopeKeyWrap(envelope);
980
1202
  const ciphertext = base64Decode(envelope.ciphertext);
981
1203
  const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
982
1204
  return crypto.authDecrypt(symmetricKey, ciphertext, aad);
@@ -1266,7 +1488,7 @@ function verifyDecryptResponse(input) {
1266
1488
  })
1267
1489
  };
1268
1490
  }
1269
- if (response.alg !== request.alg) {
1491
+ if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
1270
1492
  return {
1271
1493
  ok: false,
1272
1494
  error: encryptionError({
@@ -1323,7 +1545,7 @@ function verifyDecryptResponse(input) {
1323
1545
  })
1324
1546
  };
1325
1547
  }
1326
- 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) {
1548
+ 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) {
1327
1549
  return {
1328
1550
  ok: false,
1329
1551
  error: encryptionError({
@@ -1346,8 +1568,13 @@ function verifyDecryptResponse(input) {
1346
1568
  }
1347
1569
  return { ok: true, data: response };
1348
1570
  }
1349
- function openWrappedKey(crypto, receiverPrivateKey, response) {
1571
+ function openWrappedKey(crypto, receiverPrivateKey, response, expected) {
1350
1572
  const wrapped = base64Decode(response.wrappedKey);
1573
+ assertKeyWrapAlgorithm(wrapped, response.alg);
1574
+ if (response.alg === KEY_WRAP_V2_ALG) {
1575
+ if (!expected) throw new Error("V2 response requires expected key context");
1576
+ return openKeyV2(receiverPrivateKey, wrapped, expected);
1577
+ }
1351
1578
  return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);
1352
1579
  }
1353
1580
 
@@ -1360,6 +1587,14 @@ function encOk(data) {
1360
1587
  function encErr(error) {
1361
1588
  return { ok: false, error };
1362
1589
  }
1590
+ function decryptionError(input) {
1591
+ const unavailable = input.code === "NODE_NOT_RESPONDING" || input.code === "TRANSPORT_ERROR" && input.cause.name !== "AbortError";
1592
+ return encryptionError(unavailable ? {
1593
+ ...input,
1594
+ code: "DECRYPTION_DEPENDENCY_UNAVAILABLE",
1595
+ message: "The decryption service isn't responding right now. Please try again."
1596
+ } : input);
1597
+ }
1363
1598
  var EncryptionService = class extends BaseService {
1364
1599
  constructor(config) {
1365
1600
  super();
@@ -1401,12 +1636,15 @@ var EncryptionService = class extends BaseService {
1401
1636
  networkPublicKey,
1402
1637
  plaintext,
1403
1638
  ...options?.aad !== void 0 ? { aad: options.aad } : {},
1404
- alg: options?.alg ?? descriptor.alg,
1639
+ keyWrapV2: options?.keyWrapV2 ?? this._config.keyWrapV2 ?? false,
1640
+ ...options?.alg !== void 0 ? { alg: options.alg } : {},
1405
1641
  keyVersion: options?.keyVersion ?? descriptor.keyVersion,
1406
1642
  ...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
1407
1643
  });
1408
1644
  return encOk(result.envelope);
1409
1645
  } catch (error) {
1646
+ const routing = routingFailure(error);
1647
+ if (routing) return encErr(encryptionError(routing));
1410
1648
  return encErr(
1411
1649
  encryptionError({
1412
1650
  code: "TRANSPORT_ERROR",
@@ -1418,7 +1656,7 @@ var EncryptionService = class extends BaseService {
1418
1656
  async decryptEnvelope(envelope, capabilityProof, options) {
1419
1657
  try {
1420
1658
  this.assertActive();
1421
- const validated = validateEnvelope(this.crypto, envelope);
1659
+ const validated = validateEnvelope(this.crypto, envelope, this._config.requireKeyWrapV2 ?? false);
1422
1660
  if (!validated.ok) return validated;
1423
1661
  if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
1424
1662
  return encErr(
@@ -1433,12 +1671,12 @@ var EncryptionService = class extends BaseService {
1433
1671
  descriptor = options.descriptor;
1434
1672
  } else {
1435
1673
  const discovered = await this.discoverNetwork(envelope.networkId);
1436
- if (!discovered.ok) return discovered;
1674
+ if (!discovered.ok) return encErr(decryptionError(discovered.error));
1437
1675
  descriptor = discovered.data;
1438
1676
  }
1439
1677
  const usable = ensureNetworkUsableForDecrypt(descriptor);
1440
1678
  if (!usable.ok) return usable;
1441
- const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
1679
+ const targetNode = this._config.resolveTargetNode ? await this._config.resolveTargetNode(envelope.networkId) : options?.targetNode ?? descriptor.members[0]?.nodeId;
1442
1680
  if (targetNode === void 0) {
1443
1681
  return encErr(
1444
1682
  encryptionError({
@@ -1447,6 +1685,9 @@ var EncryptionService = class extends BaseService {
1447
1685
  })
1448
1686
  );
1449
1687
  }
1688
+ if (this._config.resolveTargetNode && (options?.targetNode !== void 0 && options.targetNode !== targetNode || !descriptor.members.some((member) => member.nodeId === targetNode))) {
1689
+ return encErr(encryptionError({ code: "INVALID_INPUT", message: "Encryption destination does not match the registry" }));
1690
+ }
1450
1691
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
1451
1692
  const receiverPublicKey = base64Encode(receiverKey.publicKey);
1452
1693
  const receiverPublicKeyHash = canonicalHashHex(
@@ -1505,6 +1746,8 @@ var EncryptionService = class extends BaseService {
1505
1746
  });
1506
1747
  this.assertActive();
1507
1748
  } catch (error) {
1749
+ const routing = routingFailure(error);
1750
+ if (routing) return encErr(decryptionError(routing));
1508
1751
  if (error instanceof DecryptTransportResponseError) {
1509
1752
  return encErr(
1510
1753
  encryptionError(
@@ -1521,7 +1764,7 @@ var EncryptionService = class extends BaseService {
1521
1764
  );
1522
1765
  }
1523
1766
  return encErr(
1524
- encryptionError({
1767
+ decryptionError({
1525
1768
  code: "TRANSPORT_ERROR",
1526
1769
  cause: toError(error)
1527
1770
  })
@@ -1539,7 +1782,8 @@ var EncryptionService = class extends BaseService {
1539
1782
  const symmetricKey = openWrappedKey(
1540
1783
  this.crypto,
1541
1784
  receiverKey.privateKey,
1542
- verified.data
1785
+ verified.data,
1786
+ { purpose: "receiver-key", networkId: body.networkId, keyPath: body.keyPath ?? "", keyVersion: body.keyVersion, metadata: {} }
1543
1787
  );
1544
1788
  const plaintext = decryptEnvelopeWithKey(
1545
1789
  this.crypto,
@@ -1548,6 +1792,8 @@ var EncryptionService = class extends BaseService {
1548
1792
  );
1549
1793
  return encOk(plaintext);
1550
1794
  } catch (error) {
1795
+ const routing = routingFailure(error);
1796
+ if (routing) return encErr(decryptionError(routing));
1551
1797
  return encErr(
1552
1798
  encryptionError({
1553
1799
  code: "INVALID_RESPONSE",
@@ -1576,8 +1822,11 @@ export {
1576
1822
  ENCRYPTION_SERVICE_SHORT,
1577
1823
  ENVELOPE_VERSION,
1578
1824
  EncryptionService,
1825
+ KEY_WRAP_V2_ALG,
1579
1826
  NETWORK_NAME_PATTERN,
1580
1827
  NetworkIdError,
1828
+ assertKeyWrapAlgorithm,
1829
+ assertKeyWrapContext,
1581
1830
  base64Decode,
1582
1831
  base64Encode,
1583
1832
  buildCanonicalDecryptRequest,
@@ -1589,6 +1838,7 @@ export {
1589
1838
  canonicalSignedResponse,
1590
1839
  canonicalize,
1591
1840
  checkDecryptInvocationInput,
1841
+ decryptColumn,
1592
1842
  decryptEnvelopeWithKey,
1593
1843
  deriveSignedReceiverKey,
1594
1844
  discoverNetwork,
@@ -1598,11 +1848,15 @@ export {
1598
1848
  generateRandomReceiverKey,
1599
1849
  hexDecode,
1600
1850
  hexEncode,
1851
+ isKeyWrapV2,
1601
1852
  isNetworkId,
1602
1853
  networkDiscoveryKey,
1854
+ openKeyV2,
1603
1855
  openWrappedKey,
1604
1856
  parseEncryptionResource,
1605
1857
  parseNetworkId,
1858
+ readKeyWrapV2,
1859
+ sealKeyV2,
1606
1860
  utf8Decode,
1607
1861
  utf8Encode,
1608
1862
  validateEnvelope,