@tinycloud/sdk-services 3.11.0 → 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
@@ -31,8 +31,11 @@ __export(encryption_exports, {
31
31
  ENCRYPTION_SERVICE_SHORT: () => ENCRYPTION_SERVICE_SHORT,
32
32
  ENVELOPE_VERSION: () => ENVELOPE_VERSION,
33
33
  EncryptionService: () => EncryptionService,
34
+ KEY_WRAP_V2_ALG: () => KEY_WRAP_V2_ALG,
34
35
  NETWORK_NAME_PATTERN: () => NETWORK_NAME_PATTERN,
35
36
  NetworkIdError: () => NetworkIdError,
37
+ assertKeyWrapAlgorithm: () => assertKeyWrapAlgorithm,
38
+ assertKeyWrapContext: () => assertKeyWrapContext,
36
39
  base64Decode: () => base64Decode,
37
40
  base64Encode: () => base64Encode,
38
41
  buildCanonicalDecryptRequest: () => buildCanonicalDecryptRequest,
@@ -44,6 +47,7 @@ __export(encryption_exports, {
44
47
  canonicalSignedResponse: () => canonicalSignedResponse,
45
48
  canonicalize: () => canonicalize,
46
49
  checkDecryptInvocationInput: () => checkDecryptInvocationInput,
50
+ decryptColumn: () => decryptColumn,
47
51
  decryptEnvelopeWithKey: () => decryptEnvelopeWithKey,
48
52
  deriveSignedReceiverKey: () => deriveSignedReceiverKey,
49
53
  discoverNetwork: () => discoverNetwork,
@@ -53,11 +57,15 @@ __export(encryption_exports, {
53
57
  generateRandomReceiverKey: () => generateRandomReceiverKey,
54
58
  hexDecode: () => hexDecode,
55
59
  hexEncode: () => hexEncode,
60
+ isKeyWrapV2: () => isKeyWrapV2,
56
61
  isNetworkId: () => isNetworkId,
57
62
  networkDiscoveryKey: () => networkDiscoveryKey,
63
+ openKeyV2: () => openKeyV2,
58
64
  openWrappedKey: () => openWrappedKey,
59
65
  parseEncryptionResource: () => parseEncryptionResource,
60
66
  parseNetworkId: () => parseNetworkId,
67
+ readKeyWrapV2: () => readKeyWrapV2,
68
+ sealKeyV2: () => sealKeyV2,
61
69
  utf8Decode: () => utf8Decode,
62
70
  utf8Encode: () => utf8Encode,
63
71
  validateEnvelope: () => validateEnvelope,
@@ -77,6 +85,10 @@ var ErrorCodes = {
77
85
  /** HTTP 401: a delegation the cited one rests on was revoked. */
78
86
  AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
79
87
  NETWORK_ERROR: "NETWORK_ERROR",
88
+ NODE_NOT_RESPONDING: "NODE_NOT_RESPONDING",
89
+ NODE_IDENTITY_MISMATCH: "NODE_IDENTITY_MISMATCH",
90
+ PARTIAL_NODE_WRITE: "PARTIAL_NODE_WRITE",
91
+ RESOURCE_NOT_ROUTED: "RESOURCE_NOT_ROUTED",
80
92
  TIMEOUT: "TIMEOUT",
81
93
  ABORTED: "ABORTED",
82
94
  INVALID_INPUT: "INVALID_INPUT",
@@ -273,6 +285,34 @@ function networkDiscoveryKey(name) {
273
285
  var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
274
286
  var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
275
287
 
288
+ // src/resourceRouting.ts
289
+ var ROUTING_FAILURE_CODES = {
290
+ NODE_NOT_RESPONDING: true,
291
+ PARTIAL_NODE_WRITE: true,
292
+ NODE_IDENTITY_MISMATCH: true,
293
+ RESOURCE_NOT_ROUTED: true,
294
+ DECRYPTION_DEPENDENCY_UNAVAILABLE: true,
295
+ registry_unavailable: true,
296
+ node_not_found: true,
297
+ node_registry_entry_exists: true
298
+ };
299
+ function isRoutingFailureCode(code) {
300
+ return Object.prototype.hasOwnProperty.call(ROUTING_FAILURE_CODES, code);
301
+ }
302
+ function routingFailure(error) {
303
+ if (typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && typeof error.message === "string" && isRoutingFailureCode(error.code)) {
304
+ const candidate = "meta" in error ? error.meta : void 0;
305
+ let meta = typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : void 0;
306
+ if (error.code === "NODE_NOT_RESPONDING" && "hosts" in error && Array.isArray(error.hosts)) {
307
+ meta = { ...meta, hosts: error.hosts };
308
+ } else if (error.code === "PARTIAL_NODE_WRITE" && "succeeded" in error && Array.isArray(error.succeeded) && "failed" in error && Array.isArray(error.failed)) {
309
+ meta = { ...meta, succeeded: error.succeeded, failed: error.failed };
310
+ }
311
+ return { code: error.code, message: error.message, ...error instanceof Error ? { cause: error } : {}, ...meta ? { meta } : {} };
312
+ }
313
+ return void 0;
314
+ }
315
+
276
316
  // src/errors.ts
277
317
  var RequestTimeoutError = class extends Error {
278
318
  constructor(timeoutMs) {
@@ -315,6 +355,8 @@ var capabilityServices = new Set(
315
355
  import_bootstrap.CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
316
356
  );
317
357
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
358
+ const routing = routingFailure(error);
359
+ if (routing) return { ...routing, service };
318
360
  if (error instanceof Error) {
319
361
  if (isRequestTimeoutError(error)) {
320
362
  return timeoutError(service, error.timeoutMs, error);
@@ -726,9 +768,9 @@ function utf8Encode(s) {
726
768
  function utf8Decode(b) {
727
769
  return new TextDecoder().decode(b);
728
770
  }
729
- function canonicalHashHex(sha256, value) {
771
+ function canonicalHashHex(sha2562, value) {
730
772
  const canonical = canonicalize(value);
731
- return hexEncode(sha256(utf8Encode(canonical)));
773
+ return hexEncode(sha2562(utf8Encode(canonical)));
732
774
  }
733
775
 
734
776
  // src/encryption/types.ts
@@ -762,6 +804,8 @@ function defaultEncryptionMessage(input) {
762
804
  return input.message ?? input.cause.message;
763
805
  case "INVALID_INPUT":
764
806
  return input.message;
807
+ default:
808
+ return input.message ?? input.code;
765
809
  }
766
810
  }
767
811
  function encryptionError(input) {
@@ -826,6 +870,8 @@ async function discoverNetwork(input) {
826
870
  return { ok: true, data: { descriptor: validated.data, source: "node" } };
827
871
  }
828
872
  } catch (err2) {
873
+ const routing = routingFailure(err2);
874
+ if (routing) return { ok: false, error: encryptionError(routing) };
829
875
  }
830
876
  }
831
877
  if (input.wellKnown !== void 0) {
@@ -843,6 +889,8 @@ async function discoverNetwork(input) {
843
889
  };
844
890
  }
845
891
  } catch (err2) {
892
+ const routing = routingFailure(err2);
893
+ if (routing) return { ok: false, error: encryptionError(routing) };
846
894
  }
847
895
  }
848
896
  return {
@@ -932,6 +980,160 @@ function ensureNetworkUsableForDecrypt(descriptor) {
932
980
  };
933
981
  }
934
982
 
983
+ // src/encryption/keyWrap.ts
984
+ var import_aes = require("@noble/ciphers/aes");
985
+ var import_ed25519 = require("@noble/curves/ed25519");
986
+ var import_hkdf = require("@noble/hashes/hkdf");
987
+ var import_sha256 = require("@noble/hashes/sha256");
988
+ var import_utils = require("@noble/hashes/utils");
989
+ var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
990
+ var MAGIC = utf8Encode("TCW2");
991
+ var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
992
+ var MAX_HEADER = 65536;
993
+ function concat(...parts) {
994
+ const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
995
+ let offset = 0;
996
+ for (const part of parts) {
997
+ result.set(part, offset);
998
+ offset += part.length;
999
+ }
1000
+ return result;
1001
+ }
1002
+ function equal(a, b) {
1003
+ if (a.length !== b.length) return false;
1004
+ let difference = 0;
1005
+ for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
1006
+ return difference === 0;
1007
+ }
1008
+ function decode32(value) {
1009
+ if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
1010
+ const decoded = base64Decode(value);
1011
+ if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
1012
+ return decoded;
1013
+ }
1014
+ function isWellFormed(value) {
1015
+ for (let i = 0; i < value.length; i++) {
1016
+ const unit = value.charCodeAt(i);
1017
+ if (unit >= 55296 && unit <= 56319) {
1018
+ const next = value.charCodeAt(++i);
1019
+ if (!(next >= 56320 && next <= 57343)) return false;
1020
+ } else if (unit >= 56320 && unit <= 57343) return false;
1021
+ }
1022
+ return true;
1023
+ }
1024
+ function validateContext(context) {
1025
+ 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))) {
1026
+ throw new Error("Invalid key wrap context");
1027
+ }
1028
+ if (context.networkId !== "") {
1029
+ const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
1030
+ if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
1031
+ throw new Error("Invalid key wrap resource");
1032
+ }
1033
+ }
1034
+ }
1035
+ function isKeyWrapV2(wrapped) {
1036
+ return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
1037
+ }
1038
+ function readKeyWrapV2(wrapped) {
1039
+ if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
1040
+ const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
1041
+ if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
1042
+ const bytes = wrapped.subarray(8, 8 + length);
1043
+ const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1044
+ const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
1045
+ 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") {
1046
+ throw new Error("Invalid v2 key wrap header");
1047
+ }
1048
+ validateContext(header);
1049
+ decode32(header.salt);
1050
+ decode32(header.ephemeralPublicKey);
1051
+ decode32(header.recipientPublicKey);
1052
+ if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
1053
+ const end = 8 + length;
1054
+ return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
1055
+ }
1056
+ function assertKeyWrapContext(header, expected) {
1057
+ validateContext(expected);
1058
+ for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
1059
+ if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
1060
+ }
1061
+ if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
1062
+ }
1063
+ function deriveKey(secret, ephemeral, recipient, salt) {
1064
+ const shared = import_ed25519.x25519.getSharedSecret(secret, ephemeral);
1065
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
1066
+ try {
1067
+ return (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
1068
+ } finally {
1069
+ shared.fill(0);
1070
+ }
1071
+ }
1072
+ function sealKeyV2(recipientPublicKey, plaintext, context) {
1073
+ validateContext(context);
1074
+ if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
1075
+ const secret = (0, import_utils.randomBytes)(32);
1076
+ const ephemeral = import_ed25519.x25519.getPublicKey(secret);
1077
+ const salt = (0, import_utils.randomBytes)(32);
1078
+ const header = {
1079
+ ...context,
1080
+ v: 2,
1081
+ alg: KEY_WRAP_V2_ALG,
1082
+ kdf: "hkdf-sha256",
1083
+ aead: "aes-256-gcm",
1084
+ salt: base64Encode(salt),
1085
+ ephemeralPublicKey: base64Encode(ephemeral),
1086
+ recipientPublicKey: base64Encode(recipientPublicKey)
1087
+ };
1088
+ const bytes = utf8Encode(canonicalize({ ...header }));
1089
+ if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
1090
+ const prefix = new Uint8Array(8);
1091
+ prefix.set(MAGIC);
1092
+ new DataView(prefix.buffer).setUint32(4, bytes.length);
1093
+ const aad = concat(prefix, bytes);
1094
+ const nonce = (0, import_utils.randomBytes)(12);
1095
+ let key;
1096
+ try {
1097
+ const shared = import_ed25519.x25519.getSharedSecret(secret, recipientPublicKey);
1098
+ try {
1099
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
1100
+ key = (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
1101
+ } finally {
1102
+ shared.fill(0);
1103
+ }
1104
+ return concat(aad, nonce, (0, import_aes.gcm)(key, nonce, aad).encrypt(plaintext));
1105
+ } finally {
1106
+ secret.fill(0);
1107
+ key?.fill(0);
1108
+ }
1109
+ }
1110
+ function openKeyV2(recipientPrivateKey, wrapped, expected) {
1111
+ const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
1112
+ assertKeyWrapContext(header, expected);
1113
+ const recipient = import_ed25519.x25519.getPublicKey(recipientPrivateKey);
1114
+ if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
1115
+ const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
1116
+ try {
1117
+ return (0, import_aes.gcm)(key, nonce, aad).decrypt(ciphertext);
1118
+ } finally {
1119
+ key.fill(0);
1120
+ }
1121
+ }
1122
+ function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
1123
+ if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
1124
+ if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
1125
+ if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
1126
+ if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
1127
+ }
1128
+ function decryptColumn(decrypt, key, blob, expectedFormat, strictEncryption = false) {
1129
+ if (expectedFormat === "legacy-plaintext") {
1130
+ if (strictEncryption) throw new Error("Legacy plaintext is disabled");
1131
+ return blob;
1132
+ }
1133
+ if (expectedFormat !== "encrypted" || blob.length < 1 + 12 + 16 || blob[0] !== 1) throw new Error("Invalid encrypted column frame");
1134
+ return decrypt(key, blob.subarray(1));
1135
+ }
1136
+
935
1137
  // src/encryption/envelope.ts
936
1138
  function encryptToNetwork(crypto, input) {
937
1139
  const parsedResource = parseEncryptionResource(
@@ -940,11 +1142,20 @@ function encryptToNetwork(crypto, input) {
940
1142
  if (parsedResource.keyPath !== input.keyPath || input.keyPath?.endsWith("/")) {
941
1143
  throw new NetworkIdError("keyPath must name an exact encryption key");
942
1144
  }
943
- const alg = input.alg ?? DEFAULT_ENCRYPTION_ALG;
1145
+ const alg = input.keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG;
1146
+ if (input.alg !== void 0 && input.alg !== alg) {
1147
+ throw new Error("Algorithm does not match keyWrapV2");
1148
+ }
944
1149
  const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
945
1150
  const symmetricKey = crypto.randomBytes(32);
946
1151
  const ciphertext = crypto.authEncrypt(symmetricKey, input.plaintext, input.aad);
947
- const wrapped = crypto.sealToNetworkKey(input.networkPublicKey, symmetricKey);
1152
+ const wrapped = input.keyWrapV2 ? sealKeyV2(input.networkPublicKey, symmetricKey, {
1153
+ purpose: "network-key",
1154
+ networkId: input.networkId,
1155
+ keyPath: input.keyPath ?? "",
1156
+ keyVersion,
1157
+ metadata: input.metadata ?? {}
1158
+ }) : crypto.sealToNetworkKey(input.networkPublicKey, symmetricKey);
948
1159
  const encryptedSymmetricKey = base64Encode(wrapped);
949
1160
  const encryptedSymmetricKeyHash = canonicalHashHex(
950
1161
  crypto.sha256,
@@ -964,7 +1175,7 @@ function encryptToNetwork(crypto, input) {
964
1175
  };
965
1176
  return { envelope, symmetricKey };
966
1177
  }
967
- function validateEnvelope(crypto, envelope) {
1178
+ function validateEnvelope(crypto, envelope, requireKeyWrapV2 = false) {
968
1179
  if (envelope === null || typeof envelope !== "object") {
969
1180
  return {
970
1181
  ok: false,
@@ -1041,9 +1252,28 @@ function validateEnvelope(crypto, envelope) {
1041
1252
  })
1042
1253
  };
1043
1254
  }
1255
+ try {
1256
+ validateEnvelopeKeyWrap(e, requireKeyWrapV2);
1257
+ } catch {
1258
+ return { ok: false, error: encryptionError({ code: "INVALID_ENVELOPE", message: "Envelope key wrap context is invalid" }) };
1259
+ }
1044
1260
  return { ok: true, data: e };
1045
1261
  }
1262
+ function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
1263
+ const wrapped = base64Decode(envelope.encryptedSymmetricKey);
1264
+ assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
1265
+ if (envelope.alg === KEY_WRAP_V2_ALG) {
1266
+ assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
1267
+ purpose: "network-key",
1268
+ networkId: envelope.networkId,
1269
+ keyPath: envelope.keyPath ?? "",
1270
+ keyVersion: envelope.keyVersion,
1271
+ metadata: envelope.metadata ?? {}
1272
+ });
1273
+ }
1274
+ }
1046
1275
  function decryptEnvelopeWithKey(crypto, envelope, symmetricKey) {
1276
+ validateEnvelopeKeyWrap(envelope);
1047
1277
  const ciphertext = base64Decode(envelope.ciphertext);
1048
1278
  const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
1049
1279
  return crypto.authDecrypt(symmetricKey, ciphertext, aad);
@@ -1333,7 +1563,7 @@ function verifyDecryptResponse(input) {
1333
1563
  })
1334
1564
  };
1335
1565
  }
1336
- if (response.alg !== request.alg) {
1566
+ if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
1337
1567
  return {
1338
1568
  ok: false,
1339
1569
  error: encryptionError({
@@ -1390,7 +1620,7 @@ function verifyDecryptResponse(input) {
1390
1620
  })
1391
1621
  };
1392
1622
  }
1393
- 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) {
1623
+ 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) {
1394
1624
  return {
1395
1625
  ok: false,
1396
1626
  error: encryptionError({
@@ -1413,8 +1643,13 @@ function verifyDecryptResponse(input) {
1413
1643
  }
1414
1644
  return { ok: true, data: response };
1415
1645
  }
1416
- function openWrappedKey(crypto, receiverPrivateKey, response) {
1646
+ function openWrappedKey(crypto, receiverPrivateKey, response, expected) {
1417
1647
  const wrapped = base64Decode(response.wrappedKey);
1648
+ assertKeyWrapAlgorithm(wrapped, response.alg);
1649
+ if (response.alg === KEY_WRAP_V2_ALG) {
1650
+ if (!expected) throw new Error("V2 response requires expected key context");
1651
+ return openKeyV2(receiverPrivateKey, wrapped, expected);
1652
+ }
1418
1653
  return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);
1419
1654
  }
1420
1655
 
@@ -1427,6 +1662,14 @@ function encOk(data) {
1427
1662
  function encErr(error) {
1428
1663
  return { ok: false, error };
1429
1664
  }
1665
+ function decryptionError(input) {
1666
+ const unavailable = input.code === "NODE_NOT_RESPONDING" || input.code === "TRANSPORT_ERROR" && input.cause.name !== "AbortError";
1667
+ return encryptionError(unavailable ? {
1668
+ ...input,
1669
+ code: "DECRYPTION_DEPENDENCY_UNAVAILABLE",
1670
+ message: "The decryption service isn't responding right now. Please try again."
1671
+ } : input);
1672
+ }
1430
1673
  var EncryptionService = class extends BaseService {
1431
1674
  constructor(config) {
1432
1675
  super();
@@ -1468,12 +1711,15 @@ var EncryptionService = class extends BaseService {
1468
1711
  networkPublicKey,
1469
1712
  plaintext,
1470
1713
  ...options?.aad !== void 0 ? { aad: options.aad } : {},
1471
- alg: options?.alg ?? descriptor.alg,
1714
+ keyWrapV2: options?.keyWrapV2 ?? this._config.keyWrapV2 ?? false,
1715
+ ...options?.alg !== void 0 ? { alg: options.alg } : {},
1472
1716
  keyVersion: options?.keyVersion ?? descriptor.keyVersion,
1473
1717
  ...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
1474
1718
  });
1475
1719
  return encOk(result.envelope);
1476
1720
  } catch (error) {
1721
+ const routing = routingFailure(error);
1722
+ if (routing) return encErr(encryptionError(routing));
1477
1723
  return encErr(
1478
1724
  encryptionError({
1479
1725
  code: "TRANSPORT_ERROR",
@@ -1485,7 +1731,7 @@ var EncryptionService = class extends BaseService {
1485
1731
  async decryptEnvelope(envelope, capabilityProof, options) {
1486
1732
  try {
1487
1733
  this.assertActive();
1488
- const validated = validateEnvelope(this.crypto, envelope);
1734
+ const validated = validateEnvelope(this.crypto, envelope, this._config.requireKeyWrapV2 ?? false);
1489
1735
  if (!validated.ok) return validated;
1490
1736
  if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
1491
1737
  return encErr(
@@ -1500,12 +1746,12 @@ var EncryptionService = class extends BaseService {
1500
1746
  descriptor = options.descriptor;
1501
1747
  } else {
1502
1748
  const discovered = await this.discoverNetwork(envelope.networkId);
1503
- if (!discovered.ok) return discovered;
1749
+ if (!discovered.ok) return encErr(decryptionError(discovered.error));
1504
1750
  descriptor = discovered.data;
1505
1751
  }
1506
1752
  const usable = ensureNetworkUsableForDecrypt(descriptor);
1507
1753
  if (!usable.ok) return usable;
1508
- const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
1754
+ const targetNode = this._config.resolveTargetNode ? await this._config.resolveTargetNode(envelope.networkId) : options?.targetNode ?? descriptor.members[0]?.nodeId;
1509
1755
  if (targetNode === void 0) {
1510
1756
  return encErr(
1511
1757
  encryptionError({
@@ -1514,6 +1760,9 @@ var EncryptionService = class extends BaseService {
1514
1760
  })
1515
1761
  );
1516
1762
  }
1763
+ if (this._config.resolveTargetNode && (options?.targetNode !== void 0 && options.targetNode !== targetNode || !descriptor.members.some((member) => member.nodeId === targetNode))) {
1764
+ return encErr(encryptionError({ code: "INVALID_INPUT", message: "Encryption destination does not match the registry" }));
1765
+ }
1517
1766
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
1518
1767
  const receiverPublicKey = base64Encode(receiverKey.publicKey);
1519
1768
  const receiverPublicKeyHash = canonicalHashHex(
@@ -1572,6 +1821,8 @@ var EncryptionService = class extends BaseService {
1572
1821
  });
1573
1822
  this.assertActive();
1574
1823
  } catch (error) {
1824
+ const routing = routingFailure(error);
1825
+ if (routing) return encErr(decryptionError(routing));
1575
1826
  if (error instanceof import_decrypt_transport_response_error.DecryptTransportResponseError) {
1576
1827
  return encErr(
1577
1828
  encryptionError(
@@ -1588,7 +1839,7 @@ var EncryptionService = class extends BaseService {
1588
1839
  );
1589
1840
  }
1590
1841
  return encErr(
1591
- encryptionError({
1842
+ decryptionError({
1592
1843
  code: "TRANSPORT_ERROR",
1593
1844
  cause: toError(error)
1594
1845
  })
@@ -1606,7 +1857,8 @@ var EncryptionService = class extends BaseService {
1606
1857
  const symmetricKey = openWrappedKey(
1607
1858
  this.crypto,
1608
1859
  receiverKey.privateKey,
1609
- verified.data
1860
+ verified.data,
1861
+ { purpose: "receiver-key", networkId: body.networkId, keyPath: body.keyPath ?? "", keyVersion: body.keyVersion, metadata: {} }
1610
1862
  );
1611
1863
  const plaintext = decryptEnvelopeWithKey(
1612
1864
  this.crypto,
@@ -1615,6 +1867,8 @@ var EncryptionService = class extends BaseService {
1615
1867
  );
1616
1868
  return encOk(plaintext);
1617
1869
  } catch (error) {
1870
+ const routing = routingFailure(error);
1871
+ if (routing) return encErr(decryptionError(routing));
1618
1872
  return encErr(
1619
1873
  encryptionError({
1620
1874
  code: "INVALID_RESPONSE",
@@ -1644,8 +1898,11 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
1644
1898
  ENCRYPTION_SERVICE_SHORT,
1645
1899
  ENVELOPE_VERSION,
1646
1900
  EncryptionService,
1901
+ KEY_WRAP_V2_ALG,
1647
1902
  NETWORK_NAME_PATTERN,
1648
1903
  NetworkIdError,
1904
+ assertKeyWrapAlgorithm,
1905
+ assertKeyWrapContext,
1649
1906
  base64Decode,
1650
1907
  base64Encode,
1651
1908
  buildCanonicalDecryptRequest,
@@ -1657,6 +1914,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
1657
1914
  canonicalSignedResponse,
1658
1915
  canonicalize,
1659
1916
  checkDecryptInvocationInput,
1917
+ decryptColumn,
1660
1918
  decryptEnvelopeWithKey,
1661
1919
  deriveSignedReceiverKey,
1662
1920
  discoverNetwork,
@@ -1666,11 +1924,15 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
1666
1924
  generateRandomReceiverKey,
1667
1925
  hexDecode,
1668
1926
  hexEncode,
1927
+ isKeyWrapV2,
1669
1928
  isNetworkId,
1670
1929
  networkDiscoveryKey,
1930
+ openKeyV2,
1671
1931
  openWrappedKey,
1672
1932
  parseEncryptionResource,
1673
1933
  parseNetworkId,
1934
+ readKeyWrapV2,
1935
+ sealKeyV2,
1674
1936
  utf8Decode,
1675
1937
  utf8Encode,
1676
1938
  validateEnvelope,