@cofhe/sdk 0.3.2 → 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.
Files changed (97) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/adapters/{ethers5.test.ts → test/ethers5.test.ts} +2 -2
  3. package/adapters/{ethers6.test.ts → test/ethers6.test.ts} +2 -2
  4. package/adapters/{hardhat.hh2.test.ts → test/hardhat.hh2.test.ts} +2 -2
  5. package/adapters/{index.test.ts → test/index.test.ts} +1 -1
  6. package/adapters/{wagmi.test.ts → test/wagmi.test.ts} +1 -1
  7. package/chains/{chains.test.ts → test/chains.test.ts} +1 -1
  8. package/core/client.ts +15 -5
  9. package/core/clientTypes.ts +7 -5
  10. package/core/consts.ts +9 -0
  11. package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
  12. package/core/decrypt/decryptForTxBuilder.ts +24 -10
  13. package/core/decrypt/decryptForViewBuilder.ts +14 -7
  14. package/core/decrypt/polling.ts +14 -0
  15. package/core/decrypt/tnDecryptUtils.ts +65 -0
  16. package/core/decrypt/{tnDecrypt.ts → tnDecryptV1.ts} +7 -70
  17. package/core/decrypt/tnDecryptV2.ts +483 -0
  18. package/core/decrypt/tnSealOutputV2.ts +245 -104
  19. package/core/decrypt/verifyDecryptResult.ts +65 -0
  20. package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
  21. package/core/encrypt/zkPackProveVerify.ts +10 -19
  22. package/core/fetchKeys.ts +0 -2
  23. package/core/index.ts +9 -1
  24. package/core/keyStore.ts +5 -2
  25. package/core/permits.ts +8 -3
  26. package/core/{client.test.ts → test/client.test.ts} +7 -7
  27. package/core/{config.test.ts → test/config.test.ts} +1 -1
  28. package/core/test/decrypt.test.ts +252 -0
  29. package/core/test/decryptBuilders.test.ts +390 -0
  30. package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
  31. package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
  32. package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
  33. package/core/{permits.test.ts → test/permits.test.ts} +42 -1
  34. package/core/test/pollCallbacks.test.ts +563 -0
  35. package/core/types.ts +21 -0
  36. package/dist/chains.d.cts +2 -2
  37. package/dist/chains.d.ts +2 -2
  38. package/dist/chunk-4FP4V35O.js +13 -0
  39. package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
  40. package/dist/{chunk-LWMRB6SD.js → chunk-S7OKGLFD.js} +615 -198
  41. package/dist/{clientTypes-Y43CKbOz.d.cts → clientTypes-BSbwairE.d.cts} +38 -13
  42. package/dist/{clientTypes-PQha8zes.d.ts → clientTypes-DDmcgZ0a.d.ts} +38 -13
  43. package/dist/core.cjs +691 -235
  44. package/dist/core.d.cts +24 -6
  45. package/dist/core.d.ts +24 -6
  46. package/dist/core.js +3 -2
  47. package/dist/node.cjs +696 -237
  48. package/dist/node.d.cts +3 -3
  49. package/dist/node.d.ts +3 -3
  50. package/dist/node.js +14 -7
  51. package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.cts} +34 -4
  52. package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.ts} +34 -4
  53. package/dist/permits.cjs +66 -29
  54. package/dist/permits.d.cts +18 -13
  55. package/dist/permits.d.ts +18 -13
  56. package/dist/permits.js +2 -1
  57. package/dist/web.cjs +718 -242
  58. package/dist/web.d.cts +8 -4
  59. package/dist/web.d.ts +8 -4
  60. package/dist/web.js +34 -11
  61. package/dist/zkProve.worker.cjs +6 -3
  62. package/dist/zkProve.worker.js +5 -3
  63. package/node/index.ts +13 -4
  64. package/node/test/client.test.ts +25 -0
  65. package/node/test/config.test.ts +16 -0
  66. package/node/test/inherited.test.ts +244 -0
  67. package/node/test/tfheinit.test.ts +56 -0
  68. package/package.json +24 -22
  69. package/permits/permit.ts +31 -5
  70. package/permits/sealing.ts +1 -1
  71. package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
  72. package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
  73. package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
  74. package/permits/{store.test.ts → test/store.test.ts} +2 -2
  75. package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
  76. package/permits/types.ts +1 -1
  77. package/permits/validation.ts +42 -2
  78. package/web/const.ts +2 -0
  79. package/web/index.ts +20 -6
  80. package/web/storage.ts +18 -3
  81. package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
  82. package/web/test/config.web.test.ts +16 -0
  83. package/web/test/inherited.web.test.ts +245 -0
  84. package/web/test/tfheinit.web.test.ts +62 -0
  85. package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
  86. package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
  87. package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
  88. package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
  89. package/web/zkProve.worker.ts +4 -3
  90. package/node/client.test.ts +0 -147
  91. package/node/config.test.ts +0 -68
  92. package/node/encryptInputs.test.ts +0 -155
  93. package/web/config.web.test.ts +0 -69
  94. package/web/encryptInputs.web.test.ts +0 -172
  95. package/web/worker.builder.web.test.ts +0 -148
  96. /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
  97. /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
package/dist/node.cjs CHANGED
@@ -12,25 +12,9 @@ var fs = require('fs');
12
12
  var path = require('path');
13
13
  var nodeTfhe = require('node-tfhe');
14
14
 
15
- function _interopNamespace(e) {
16
- if (e && e.__esModule) return e;
17
- var n = Object.create(null);
18
- if (e) {
19
- Object.keys(e).forEach(function (k) {
20
- if (k !== 'default') {
21
- var d = Object.getOwnPropertyDescriptor(e, k);
22
- Object.defineProperty(n, k, d.get ? d : {
23
- enumerable: true,
24
- get: function () { return e[k]; }
25
- });
26
- }
27
- });
28
- }
29
- n.default = e;
30
- return Object.freeze(n);
31
- }
15
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
32
16
 
33
- var nacl__namespace = /*#__PURE__*/_interopNamespace(nacl);
17
+ var nacl__default = /*#__PURE__*/_interopDefault(nacl);
34
18
 
35
19
  // core/client.ts
36
20
 
@@ -123,6 +107,16 @@ var bigintSafeJsonStringify = (value) => {
123
107
  };
124
108
  var isCofheError = (error) => error instanceof CofheError;
125
109
 
110
+ // core/consts.ts
111
+ var TASK_MANAGER_ADDRESS = "0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9";
112
+ var MOCKS_ZK_VERIFIER_ADDRESS = "0x0000000000000000000000000000000000005001";
113
+ var MOCKS_THRESHOLD_NETWORK_ADDRESS = "0x0000000000000000000000000000000000005002";
114
+ var MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY = "0x6C8D7F768A6BB4AAFE85E8A2F5A9680355239C7E14646ED62B044E39DE154512";
115
+ var MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
116
+ var TFHE_RS_ZK_MAX_BITS = 2048;
117
+ var TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT = BigInt(1 << 30);
118
+ var TFHE_RS_KEY_VERSION = 2;
119
+
126
120
  // core/types.ts
127
121
  var FheUintUTypes = [
128
122
  2 /* Uint8 */,
@@ -209,7 +203,6 @@ var MAX_UINT32 = 4294967295n;
209
203
  var MAX_UINT64 = 18446744073709551615n;
210
204
  var MAX_UINT128 = 340282366920938463463374607431768211455n;
211
205
  var MAX_UINT160 = 1461501637330902918203684832716283019655932542975n;
212
- var MAX_ENCRYPTABLE_BITS = 2048;
213
206
  var zkPack = (items, builder) => {
214
207
  let totalBits = 0;
215
208
  for (const item of items) {
@@ -273,14 +266,14 @@ var zkPack = (items, builder) => {
273
266
  }
274
267
  }
275
268
  }
276
- if (totalBits > MAX_ENCRYPTABLE_BITS) {
269
+ if (totalBits > TFHE_RS_ZK_MAX_BITS) {
277
270
  throw new CofheError({
278
271
  code: "ZK_PACK_FAILED" /* ZkPackFailed */,
279
- message: `Total bits ${totalBits} exceeds ${MAX_ENCRYPTABLE_BITS}`,
280
- hint: `Ensure that the total bits of the items to encrypt does not exceed ${MAX_ENCRYPTABLE_BITS}`,
272
+ message: `Total bits ${totalBits} exceeds ${TFHE_RS_ZK_MAX_BITS}`,
273
+ hint: `Ensure that the total bits of the items to encrypt does not exceed ${TFHE_RS_ZK_MAX_BITS}`,
281
274
  context: {
282
275
  totalBits,
283
- maxBits: MAX_ENCRYPTABLE_BITS,
276
+ maxBits: TFHE_RS_ZK_MAX_BITS,
284
277
  items
285
278
  }
286
279
  });
@@ -299,7 +292,7 @@ var zkProve = async (builder, crs, metadata) => {
299
292
  1
300
293
  // ZkComputeLoad.Verify
301
294
  );
302
- resolve(compactList.serialize());
295
+ resolve(compactList.safe_serialize(TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT));
303
296
  }, 0);
304
297
  });
305
298
  };
@@ -475,15 +468,6 @@ var MockZkVerifierAbi = [
475
468
  },
476
469
  { type: "error", name: "InvalidInputs", inputs: [] }
477
470
  ];
478
-
479
- // core/consts.ts
480
- var TASK_MANAGER_ADDRESS = "0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9";
481
- var MOCKS_ZK_VERIFIER_ADDRESS = "0x0000000000000000000000000000000000005001";
482
- var MOCKS_THRESHOLD_NETWORK_ADDRESS = "0x0000000000000000000000000000000000005002";
483
- var MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY = "0x6C8D7F768A6BB4AAFE85E8A2F5A9680355239C7E14646ED62B044E39DE154512";
484
- var MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
485
-
486
- // core/encrypt/cofheMocksZkVerifySign.ts
487
471
  function createMockZkVerifierSigner() {
488
472
  return viem.createWalletClient({
489
473
  chain: chains.hardhat,
@@ -525,14 +509,14 @@ async function cofheMocksCheckEncryptableBits(items) {
525
509
  }
526
510
  }
527
511
  }
528
- if (totalBits > MAX_ENCRYPTABLE_BITS) {
512
+ if (totalBits > TFHE_RS_ZK_MAX_BITS) {
529
513
  throw new CofheError({
530
514
  code: "ZK_PACK_FAILED" /* ZkPackFailed */,
531
- message: `Total bits ${totalBits} exceeds ${MAX_ENCRYPTABLE_BITS}`,
532
- hint: `Ensure that the total bits of the items to encrypt does not exceed ${MAX_ENCRYPTABLE_BITS}`,
515
+ message: `Total bits ${totalBits} exceeds ${TFHE_RS_ZK_MAX_BITS}`,
516
+ hint: `Ensure that the total bits of the items to encrypt does not exceed ${TFHE_RS_ZK_MAX_BITS}`,
533
517
  context: {
534
518
  totalBits,
535
- maxBits: MAX_ENCRYPTABLE_BITS,
519
+ maxBits: TFHE_RS_ZK_MAX_BITS,
536
520
  items
537
521
  }
538
522
  });
@@ -803,6 +787,7 @@ function getThresholdNetworkUrlOrThrow(config, chainId) {
803
787
  }
804
788
  return url;
805
789
  }
790
+ var KEYSTORE_NAME = `cofhesdk-keys-v${TFHE_RS_KEY_VERSION}`;
806
791
  function isValidPersistedState(state) {
807
792
  if (state && typeof state === "object") {
808
793
  if ("fhe" in state && "crs" in state) {
@@ -857,7 +842,7 @@ function createKeysStore(storage) {
857
842
  };
858
843
  const clearKeysStorage = async () => {
859
844
  if (storage) {
860
- await storage.removeItem("cofhesdk-keys");
845
+ await storage.removeItem(KEYSTORE_NAME);
861
846
  }
862
847
  };
863
848
  const rehydrateKeysStore = async () => {
@@ -887,7 +872,7 @@ function createStoreWithPersit(storage) {
887
872
  if (_error)
888
873
  throw new Error(`onRehydrateStorage: Error rehydrating keys store: ${_error}`);
889
874
  },
890
- name: "cofhesdk-keys",
875
+ name: KEYSTORE_NAME,
891
876
  storage: middleware.createJSONStorage(() => storage),
892
877
  merge: (persistedState, currentState) => {
893
878
  const persisted = isValidPersistedState(persistedState) ? persistedState : DEFAULT_KEYS_STORE;
@@ -1612,7 +1597,7 @@ var SealingKey = class _SealingKey {
1612
1597
  const ephemPublicKey = parsedData.public_key instanceof Uint8Array ? parsedData.public_key : new Uint8Array(parsedData.public_key);
1613
1598
  const dataToDecrypt = parsedData.data instanceof Uint8Array ? parsedData.data : new Uint8Array(parsedData.data);
1614
1599
  const privateKeyBytes = fromHexString(this.privateKey);
1615
- const decryptedMessage = nacl__namespace.box.open(dataToDecrypt, nonce, ephemPublicKey, privateKeyBytes);
1600
+ const decryptedMessage = nacl__default.default.box.open(dataToDecrypt, nonce, ephemPublicKey, privateKeyBytes);
1616
1601
  if (!decryptedMessage) {
1617
1602
  throw new Error("Failed to decrypt message");
1618
1603
  }
@@ -1645,9 +1630,9 @@ var SealingKey = class _SealingKey {
1645
1630
  static seal = (value, publicKey) => {
1646
1631
  isString(publicKey);
1647
1632
  isBigIntOrNumber(value);
1648
- const ephemeralKeyPair = nacl__namespace.box.keyPair();
1649
- const nonce = nacl__namespace.randomBytes(nacl__namespace.box.nonceLength);
1650
- const encryptedMessage = nacl__namespace.box(toBeArray(value), nonce, fromHexString(publicKey), ephemeralKeyPair.secretKey);
1633
+ const ephemeralKeyPair = nacl__default.default.box.keyPair();
1634
+ const nonce = nacl__default.default.randomBytes(nacl__default.default.box.nonceLength);
1635
+ const encryptedMessage = nacl__default.default.box(toBeArray(value), nonce, fromHexString(publicKey), ephemeralKeyPair.secretKey);
1651
1636
  return {
1652
1637
  data: encryptedMessage,
1653
1638
  public_key: ephemeralKeyPair.publicKey,
@@ -1656,7 +1641,7 @@ var SealingKey = class _SealingKey {
1656
1641
  };
1657
1642
  };
1658
1643
  var GenerateSealingKey = () => {
1659
- const sodiumKeypair = nacl__namespace.box.keyPair();
1644
+ const sodiumKeypair = nacl__default.default.box.keyPair();
1660
1645
  return new SealingKey(toHexString2(sodiumKeypair.secretKey), toHexString2(sodiumKeypair.publicKey));
1661
1646
  };
1662
1647
  var SerializedSealingPair = zod.z.object({
@@ -1814,9 +1799,9 @@ var ValidationUtils = {
1814
1799
  return false;
1815
1800
  },
1816
1801
  /**
1817
- * Overall validity checker of a permit
1802
+ * Checks that a permit is signed and not expired.
1818
1803
  */
1819
- isValid: (permit) => {
1804
+ isSignedAndNotExpired: (permit) => {
1820
1805
  if (ValidationUtils.isExpired(permit)) {
1821
1806
  return { valid: false, error: "expired" };
1822
1807
  }
@@ -1824,6 +1809,34 @@ var ValidationUtils = {
1824
1809
  return { valid: false, error: "not-signed" };
1825
1810
  }
1826
1811
  return { valid: true, error: null };
1812
+ },
1813
+ /**
1814
+ * Asserts that a permit is signed and not expired.
1815
+ *
1816
+ * Throws `Error` with message:
1817
+ * - `Permit is expired`
1818
+ * - `Permit is not signed`
1819
+ */
1820
+ assertSignedAndNotExpired: (permit) => {
1821
+ const result = ValidationUtils.isSignedAndNotExpired(permit);
1822
+ if (result.valid)
1823
+ return;
1824
+ if (result.error === "expired") {
1825
+ throw new Error("Permit is expired");
1826
+ }
1827
+ if (result.error === "not-signed") {
1828
+ throw new Error("Permit is not signed");
1829
+ }
1830
+ throw new Error("Permit is invalid");
1831
+ },
1832
+ isValid: (permit) => {
1833
+ const schema = permit.type === "self" ? SelfPermitValidator : permit.type === "sharing" ? SharingPermitValidator : permit.type === "recipient" ? ImportPermitValidator : null;
1834
+ if (schema == null)
1835
+ return { valid: false, error: "invalid-schema" };
1836
+ const schemaResult = schema.safeParse(permit);
1837
+ if (!schemaResult.success)
1838
+ return { valid: false, error: "invalid-schema" };
1839
+ return ValidationUtils.isSignedAndNotExpired(permit);
1827
1840
  }
1828
1841
  };
1829
1842
 
@@ -2205,9 +2218,9 @@ var PermitUtils = {
2205
2218
  };
2206
2219
  },
2207
2220
  /**
2208
- * Validate a permit
2221
+ * Validate a permit (schema-level validation)
2209
2222
  */
2210
- validate: (permit) => {
2223
+ validateSchema: (permit) => {
2211
2224
  if (permit.type === "self") {
2212
2225
  return validateSelfPermit(permit);
2213
2226
  } else if (permit.type === "sharing") {
@@ -2218,12 +2231,27 @@ var PermitUtils = {
2218
2231
  throw new Error("Invalid permit type");
2219
2232
  }
2220
2233
  },
2234
+ /**
2235
+ * Validate a permit (holistic validation).
2236
+ *
2237
+ * This validates:
2238
+ * - Permit schema (shape + invariants)
2239
+ * - Permit is signed
2240
+ * - Permit is not expired
2241
+ *
2242
+ * For schema-only validation, use `validateSchema(permit)`.
2243
+ */
2244
+ validate: (permit) => {
2245
+ const validated = PermitUtils.validateSchema(permit);
2246
+ ValidationUtils.assertSignedAndNotExpired(validated);
2247
+ return validated;
2248
+ },
2221
2249
  /**
2222
2250
  * Get the permission object from a permit (for use in contracts)
2223
2251
  */
2224
2252
  getPermission: (permit, skipValidation = false) => {
2225
2253
  if (!skipValidation) {
2226
- PermitUtils.validate(permit);
2254
+ PermitUtils.validateSchema(permit);
2227
2255
  }
2228
2256
  return {
2229
2257
  issuer: permit.issuer,
@@ -2289,8 +2317,17 @@ var PermitUtils = {
2289
2317
  return ValidationUtils.isSigned(permit);
2290
2318
  },
2291
2319
  /**
2292
- * Check if permit is valid
2320
+ * Check if permit is signed and not expired
2321
+ */
2322
+ isSignedAndNotExpired: (permit) => {
2323
+ return ValidationUtils.isSignedAndNotExpired(permit);
2324
+ },
2325
+ /**
2326
+ * Assert that permit is signed and not expired
2293
2327
  */
2328
+ assertSignedAndNotExpired: (permit) => {
2329
+ return ValidationUtils.assertSignedAndNotExpired(permit);
2330
+ },
2294
2331
  isValid: (permit) => {
2295
2332
  return ValidationUtils.isValid(permit);
2296
2333
  },
@@ -2468,19 +2505,22 @@ var importShared = async (options, publicClient, walletClient) => {
2468
2505
  var getHash = (permit) => {
2469
2506
  return PermitUtils.getHash(permit);
2470
2507
  };
2508
+ var exportShared = (permit) => {
2509
+ return PermitUtils.export(permit);
2510
+ };
2471
2511
  var serialize = (permit) => {
2472
2512
  return PermitUtils.serialize(permit);
2473
2513
  };
2474
2514
  var deserialize = (serialized) => {
2475
2515
  return PermitUtils.deserialize(serialized);
2476
2516
  };
2477
- var getPermit2 = async (chainId, account, hash) => {
2517
+ var getPermit2 = (chainId, account, hash) => {
2478
2518
  return permitStore.getPermit(chainId, account, hash);
2479
2519
  };
2480
- var getPermits2 = async (chainId, account) => {
2520
+ var getPermits2 = (chainId, account) => {
2481
2521
  return permitStore.getPermits(chainId, account);
2482
2522
  };
2483
- var getActivePermit2 = async (chainId, account) => {
2523
+ var getActivePermit2 = (chainId, account) => {
2484
2524
  return permitStore.getActivePermit(chainId, account);
2485
2525
  };
2486
2526
  var getActivePermitHash2 = (chainId, account) => {
@@ -2518,6 +2558,7 @@ var permits = {
2518
2558
  getOrCreateSelfPermit,
2519
2559
  getOrCreateSharingPermit,
2520
2560
  getHash,
2561
+ export: exportShared,
2521
2562
  serialize,
2522
2563
  deserialize,
2523
2564
  getPermit: getPermit2,
@@ -2760,9 +2801,19 @@ async function cofheMocksDecryptForView(ctHash, utype, permit, publicClient) {
2760
2801
  return unsealed;
2761
2802
  }
2762
2803
 
2804
+ // core/decrypt/polling.ts
2805
+ function computeMinuteRampPollIntervalMs(elapsedMs, params) {
2806
+ const elapsedSeconds = Math.floor(elapsedMs / 1e3);
2807
+ const intervalSeconds = 1 + Math.floor(elapsedSeconds / 60);
2808
+ const intervalMs = intervalSeconds * 1e3;
2809
+ return Math.min(params.maxIntervalMs, Math.max(params.minIntervalMs, intervalMs));
2810
+ }
2811
+
2763
2812
  // core/decrypt/tnSealOutputV2.ts
2764
2813
  var POLL_INTERVAL_MS = 1e3;
2765
- var POLL_TIMEOUT_MS = 5 * 60 * 1e3;
2814
+ var POLL_MAX_INTERVAL_MS = 1e4;
2815
+ var SEAL_OUTPUT_TIMEOUT_MS = 5 * 60 * 1e3;
2816
+ var SUBMIT_RETRY_INTERVAL_MS = 1e3;
2766
2817
  function numberArrayToUint8Array(arr) {
2767
2818
  return new Uint8Array(arr);
2768
2819
  }
@@ -2779,93 +2830,193 @@ function convertSealedData(sealed) {
2779
2830
  nonce: numberArrayToUint8Array(sealed.nonce)
2780
2831
  };
2781
2832
  }
2782
- async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission) {
2783
- const body = {
2784
- ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2785
- host_chain_id: chainId,
2786
- permit: permission
2787
- };
2788
- let response;
2789
- try {
2790
- response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
2791
- method: "POST",
2792
- headers: {
2793
- "Content-Type": "application/json"
2794
- },
2795
- body: JSON.stringify(body)
2796
- });
2797
- } catch (e) {
2798
- throw new CofheError({
2799
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2800
- message: `sealOutput request failed`,
2801
- hint: "Ensure the threshold network URL is valid and reachable.",
2802
- cause: e instanceof Error ? e : void 0,
2803
- context: {
2804
- thresholdNetworkUrl,
2805
- body
2806
- }
2807
- });
2833
+ function getSealedDataFromSubmitResponse(value) {
2834
+ if (value.sealed)
2835
+ return value.sealed;
2836
+ if (Array.isArray(value.sealed_data) && Array.isArray(value.ephemeral_public_key) && Array.isArray(value.nonce)) {
2837
+ return {
2838
+ data: value.sealed_data,
2839
+ public_key: value.ephemeral_public_key,
2840
+ nonce: value.nonce
2841
+ };
2808
2842
  }
2809
- if (!response.ok) {
2810
- let errorMessage = `HTTP ${response.status}`;
2811
- try {
2812
- const errorBody = await response.json();
2813
- errorMessage = errorBody.error_message || errorBody.message || errorMessage;
2814
- } catch {
2815
- errorMessage = response.statusText || errorMessage;
2816
- }
2843
+ return void 0;
2844
+ }
2845
+ function parseCompletedSealOutputResponse(params) {
2846
+ const { value, thresholdNetworkUrl, requestId } = params;
2847
+ if (value.is_succeed === false) {
2848
+ const errorMessage = value.error_message || "Unknown error";
2817
2849
  throw new CofheError({
2818
2850
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2819
2851
  message: `sealOutput request failed: ${errorMessage}`,
2820
- hint: "Check the threshold network URL and request parameters.",
2821
2852
  context: {
2822
2853
  thresholdNetworkUrl,
2823
- status: response.status,
2824
- statusText: response.statusText,
2825
- body
2854
+ requestId,
2855
+ response: value
2826
2856
  }
2827
2857
  });
2828
2858
  }
2829
- let submitResponse;
2830
- try {
2831
- submitResponse = await response.json();
2832
- } catch (e) {
2859
+ const sealed = "sealed" in value ? value.sealed : getSealedDataFromSubmitResponse(value);
2860
+ if (!sealed) {
2833
2861
  throw new CofheError({
2834
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2835
- message: `Failed to parse sealOutput submit response`,
2836
- cause: e instanceof Error ? e : void 0,
2862
+ code: "SEAL_OUTPUT_RETURNED_NULL" /* SealOutputReturnedNull */,
2863
+ message: `sealOutput request completed but returned no sealed data`,
2837
2864
  context: {
2838
2865
  thresholdNetworkUrl,
2839
- body
2866
+ requestId,
2867
+ response: value
2840
2868
  }
2841
2869
  });
2842
2870
  }
2843
- if (!submitResponse.request_id) {
2871
+ return convertSealedData(sealed);
2872
+ }
2873
+ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
2874
+ const body = {
2875
+ ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2876
+ host_chain_id: chainId,
2877
+ permit: permission
2878
+ };
2879
+ let attemptIndex = 0;
2880
+ for (; ; ) {
2881
+ let response;
2882
+ try {
2883
+ response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
2884
+ method: "POST",
2885
+ headers: {
2886
+ "Content-Type": "application/json"
2887
+ },
2888
+ body: JSON.stringify(body)
2889
+ });
2890
+ } catch (e) {
2891
+ throw new CofheError({
2892
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2893
+ message: `sealOutput request failed`,
2894
+ hint: "Ensure the threshold network URL is valid and reachable.",
2895
+ cause: e instanceof Error ? e : void 0,
2896
+ context: {
2897
+ thresholdNetworkUrl,
2898
+ body,
2899
+ attemptIndex
2900
+ }
2901
+ });
2902
+ }
2903
+ if (!response.ok) {
2904
+ let errorMessage = `HTTP ${response.status}`;
2905
+ try {
2906
+ const errorBody = await response.json();
2907
+ errorMessage = errorBody.error_message || errorBody.message || errorMessage;
2908
+ } catch {
2909
+ errorMessage = response.statusText || errorMessage;
2910
+ }
2911
+ throw new CofheError({
2912
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2913
+ message: `sealOutput request failed: ${errorMessage}`,
2914
+ hint: "Check the threshold network URL and request parameters.",
2915
+ context: {
2916
+ thresholdNetworkUrl,
2917
+ status: response.status,
2918
+ statusText: response.statusText,
2919
+ body,
2920
+ attemptIndex
2921
+ }
2922
+ });
2923
+ }
2924
+ let submitResponse;
2925
+ if (response.status !== 204) {
2926
+ try {
2927
+ submitResponse = await response.json();
2928
+ } catch (e) {
2929
+ throw new CofheError({
2930
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2931
+ message: `Failed to parse sealOutput submit response`,
2932
+ cause: e instanceof Error ? e : void 0,
2933
+ context: {
2934
+ thresholdNetworkUrl,
2935
+ body,
2936
+ attemptIndex
2937
+ }
2938
+ });
2939
+ }
2940
+ if (getSealedDataFromSubmitResponse(submitResponse)) {
2941
+ return {
2942
+ kind: "completed",
2943
+ sealed: parseCompletedSealOutputResponse({
2944
+ value: submitResponse,
2945
+ thresholdNetworkUrl,
2946
+ requestId: submitResponse.request_id
2947
+ })
2948
+ };
2949
+ }
2950
+ if (submitResponse.request_id) {
2951
+ return { kind: "request_id", requestId: submitResponse.request_id };
2952
+ }
2953
+ }
2954
+ if (response.status === 204) {
2955
+ const elapsedMs = Date.now() - overallStartTime;
2956
+ if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
2957
+ throw new CofheError({
2958
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2959
+ message: `sealOutput submit retried without receiving request_id for ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
2960
+ hint: "The ciphertext may still be propagating. Try again later.",
2961
+ context: {
2962
+ thresholdNetworkUrl,
2963
+ body,
2964
+ attemptIndex,
2965
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
2966
+ submitResponse,
2967
+ status: response.status
2968
+ }
2969
+ });
2970
+ }
2971
+ onPoll?.({
2972
+ operation: "sealoutput",
2973
+ requestId: "",
2974
+ attemptIndex,
2975
+ elapsedMs,
2976
+ intervalMs: SUBMIT_RETRY_INTERVAL_MS,
2977
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2978
+ });
2979
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
2980
+ attemptIndex += 1;
2981
+ continue;
2982
+ }
2844
2983
  throw new CofheError({
2845
2984
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2846
2985
  message: `sealOutput submit response missing request_id`,
2847
2986
  context: {
2848
2987
  thresholdNetworkUrl,
2849
2988
  body,
2850
- submitResponse
2989
+ submitResponse,
2990
+ attemptIndex
2851
2991
  }
2852
2992
  });
2853
2993
  }
2854
- return submitResponse.request_id;
2855
2994
  }
2856
- async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
2857
- const startTime = Date.now();
2858
- let completed = false;
2859
- while (!completed) {
2860
- if (Date.now() - startTime > POLL_TIMEOUT_MS) {
2995
+ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
2996
+ let attemptIndex = 0;
2997
+ while (true) {
2998
+ const elapsedMs = Date.now() - overallStartTime;
2999
+ const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
3000
+ minIntervalMs: POLL_INTERVAL_MS,
3001
+ maxIntervalMs: POLL_MAX_INTERVAL_MS
3002
+ });
3003
+ onPoll?.({
3004
+ operation: "sealoutput",
3005
+ requestId,
3006
+ attemptIndex,
3007
+ elapsedMs,
3008
+ intervalMs,
3009
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
3010
+ });
3011
+ if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
2861
3012
  throw new CofheError({
2862
3013
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2863
- message: `sealOutput polling timed out after ${POLL_TIMEOUT_MS}ms`,
3014
+ message: `sealOutput polling timed out after ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
2864
3015
  hint: "The request may still be processing. Try again later.",
2865
3016
  context: {
2866
3017
  thresholdNetworkUrl,
2867
3018
  requestId,
2868
- timeoutMs: POLL_TIMEOUT_MS
3019
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2869
3020
  }
2870
3021
  });
2871
3022
  }
@@ -2934,32 +3085,14 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
2934
3085
  });
2935
3086
  }
2936
3087
  if (statusResponse.status === "COMPLETED") {
2937
- if (statusResponse.is_succeed === false) {
2938
- const errorMessage = statusResponse.error_message || "Unknown error";
2939
- throw new CofheError({
2940
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2941
- message: `sealOutput request failed: ${errorMessage}`,
2942
- context: {
2943
- thresholdNetworkUrl,
2944
- requestId,
2945
- statusResponse
2946
- }
2947
- });
2948
- }
2949
- if (!statusResponse.sealed) {
2950
- throw new CofheError({
2951
- code: "SEAL_OUTPUT_RETURNED_NULL" /* SealOutputReturnedNull */,
2952
- message: `sealOutput request completed but returned no sealed data`,
2953
- context: {
2954
- thresholdNetworkUrl,
2955
- requestId,
2956
- statusResponse
2957
- }
2958
- });
2959
- }
2960
- return convertSealedData(statusResponse.sealed);
3088
+ return parseCompletedSealOutputResponse({
3089
+ value: statusResponse,
3090
+ thresholdNetworkUrl,
3091
+ requestId
3092
+ });
2961
3093
  }
2962
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
3094
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3095
+ attemptIndex += 1;
2963
3096
  }
2964
3097
  throw new CofheError({
2965
3098
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
@@ -2970,9 +3103,21 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
2970
3103
  }
2971
3104
  });
2972
3105
  }
2973
- async function tnSealOutputV2(ctHash, chainId, permission, thresholdNetworkUrl) {
2974
- const requestId = await submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission);
2975
- return await pollSealOutputStatus(thresholdNetworkUrl, requestId);
3106
+ async function tnSealOutputV2(params) {
3107
+ const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
3108
+ const overallStartTime = Date.now();
3109
+ const submitResult = await submitSealOutputRequest(
3110
+ thresholdNetworkUrl,
3111
+ ctHash,
3112
+ chainId,
3113
+ permission,
3114
+ overallStartTime,
3115
+ onPoll
3116
+ );
3117
+ if (submitResult.kind === "completed") {
3118
+ return submitResult.sealed;
3119
+ }
3120
+ return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
2976
3121
  }
2977
3122
 
2978
3123
  // core/decrypt/decryptForViewBuilder.ts
@@ -2981,6 +3126,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2981
3126
  utype;
2982
3127
  permitHash;
2983
3128
  permit;
3129
+ pollCallback;
2984
3130
  constructor(params) {
2985
3131
  super({
2986
3132
  config: params.config,
@@ -3037,6 +3183,10 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3037
3183
  getAccount() {
3038
3184
  return this.account;
3039
3185
  }
3186
+ onPoll(callback) {
3187
+ this.pollCallback = callback;
3188
+ return this;
3189
+ }
3040
3190
  withPermit(permitOrPermitHash) {
3041
3191
  if (typeof permitOrPermitHash === "string") {
3042
3192
  this.permitHash = permitOrPermitHash;
@@ -3160,7 +3310,13 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3160
3310
  this.assertPublicClient();
3161
3311
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
3162
3312
  const permission = PermitUtils.getPermission(permit, true);
3163
- const sealed = await tnSealOutputV2(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
3313
+ const sealed = await tnSealOutputV2({
3314
+ ctHash: this.ctHash,
3315
+ chainId: this.chainId,
3316
+ permission,
3317
+ thresholdNetworkUrl,
3318
+ onPoll: this.pollCallback
3319
+ });
3164
3320
  return PermitUtils.unseal(permit, sealed);
3165
3321
  }
3166
3322
  /**
@@ -3188,7 +3344,6 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3188
3344
  this.validateUtypeOrThrow();
3189
3345
  const permit = await this.getResolvedPermit();
3190
3346
  PermitUtils.validate(permit);
3191
- PermitUtils.isValid(permit);
3192
3347
  const chainId = permit._signedDomain.chainId;
3193
3348
  let unsealed;
3194
3349
  if (chainId === hardhat2.id) {
@@ -3199,6 +3354,9 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3199
3354
  return convertViaUtype(this.utype, unsealed);
3200
3355
  }
3201
3356
  };
3357
+ var UINT_TYPE_MASK = 0x7fn;
3358
+ var TYPE_BYTE_OFFSET = 8n;
3359
+ var getEncryptionTypeFromCtHash = (ctHash) => Number(ctHash >> TYPE_BYTE_OFFSET & UINT_TYPE_MASK);
3202
3360
  async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
3203
3361
  let allowed;
3204
3362
  let error;
@@ -3236,7 +3394,13 @@ async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
3236
3394
  message: `mocks decryptForTx call failed: ACL Access Denied (NotAllowed)`
3237
3395
  });
3238
3396
  }
3239
- const packed = viem.encodePacked(["uint256", "uint256"], [BigInt(ctHash), decryptedValue]);
3397
+ const chainId = publicClient.chain?.id ?? await publicClient.getChainId();
3398
+ const normalizedCtHash = BigInt(ctHash);
3399
+ const encryptionType = getEncryptionTypeFromCtHash(normalizedCtHash);
3400
+ const packed = viem.encodePacked(
3401
+ ["uint256", "uint32", "uint64", "uint256"],
3402
+ [decryptedValue, encryptionType, BigInt(chainId), normalizedCtHash]
3403
+ );
3240
3404
  const messageHash = viem.keccak256(packed);
3241
3405
  const signature = await accounts.sign({
3242
3406
  hash: messageHash,
@@ -3249,7 +3413,7 @@ async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
3249
3413
  signature
3250
3414
  };
3251
3415
  }
3252
- function normalizeSignature(signature) {
3416
+ function normalizeTnSignature(signature) {
3253
3417
  if (typeof signature !== "string") {
3254
3418
  throw new CofheError({
3255
3419
  code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
@@ -3305,141 +3469,384 @@ function parseDecryptedBytesToBigInt(decrypted) {
3305
3469
  }
3306
3470
  return BigInt(`0x${hex}`);
3307
3471
  }
3308
- function assertTnDecryptResponse(value) {
3472
+
3473
+ // core/decrypt/tnDecryptV2.ts
3474
+ var POLL_INTERVAL_MS2 = 1e3;
3475
+ var POLL_MAX_INTERVAL_MS2 = 1e4;
3476
+ var DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
3477
+ var SUBMIT_RETRY_INTERVAL_MS2 = 1e3;
3478
+ function assertDecryptSubmitResponseV2(value) {
3309
3479
  if (value == null || typeof value !== "object") {
3310
3480
  throw new CofheError({
3311
3481
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3312
- message: "decrypt response must be a JSON object",
3482
+ message: "decrypt submit response must be a JSON object",
3313
3483
  context: {
3314
3484
  value
3315
3485
  }
3316
3486
  });
3317
3487
  }
3318
3488
  const v = value;
3319
- const decrypted = v.decrypted;
3320
- const signature = v.signature;
3321
- const encryptionType = v.encryption_type;
3322
- const errorMessage = v.error_message;
3323
- if (!Array.isArray(decrypted)) {
3489
+ if (v.request_id !== null && typeof v.request_id !== "string") {
3324
3490
  throw new CofheError({
3325
- code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
3326
- message: "decrypt response missing <decrypted> byte array",
3327
- context: { decryptResponse: value }
3491
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3492
+ message: "decrypt submit response has invalid request_id",
3493
+ context: {
3494
+ value
3495
+ }
3328
3496
  });
3329
3497
  }
3330
- if (typeof signature !== "string") {
3498
+ return {
3499
+ request_id: v.request_id ?? null,
3500
+ status: typeof v.status === "string" ? v.status : void 0,
3501
+ is_succeed: typeof v.is_succeed === "boolean" ? v.is_succeed : void 0,
3502
+ decrypted: Array.isArray(v.decrypted) ? v.decrypted : void 0,
3503
+ signature: typeof v.signature === "string" ? v.signature : void 0,
3504
+ encryption_type: typeof v.encryption_type === "number" ? v.encryption_type : void 0,
3505
+ error_message: typeof v.error_message === "string" || v.error_message === null ? v.error_message : void 0,
3506
+ message: typeof v.message === "string" ? v.message : void 0
3507
+ };
3508
+ }
3509
+ function parseCompletedDecryptResponseV2(params) {
3510
+ const { value, thresholdNetworkUrl, requestId } = params;
3511
+ if (value.is_succeed === false) {
3512
+ const errorMessage = value.error_message || "Unknown error";
3331
3513
  throw new CofheError({
3332
- code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
3333
- message: "decrypt response missing <signature> string",
3334
- context: { decryptResponse: value }
3514
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3515
+ message: `decrypt request failed: ${errorMessage}`,
3516
+ context: {
3517
+ thresholdNetworkUrl,
3518
+ requestId,
3519
+ response: value
3520
+ }
3335
3521
  });
3336
3522
  }
3337
- if (typeof encryptionType !== "number") {
3523
+ if (value.error_message) {
3338
3524
  throw new CofheError({
3339
3525
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3340
- message: "decrypt response missing <encryption_type> number",
3341
- context: { decryptResponse: value }
3526
+ message: `decrypt request failed: ${value.error_message}`,
3527
+ context: {
3528
+ thresholdNetworkUrl,
3529
+ requestId,
3530
+ response: value
3531
+ }
3342
3532
  });
3343
3533
  }
3344
- if (!(typeof errorMessage === "string" || errorMessage === null)) {
3534
+ if (!Array.isArray(value.decrypted)) {
3345
3535
  throw new CofheError({
3346
- code: "DECRYPT_FAILED" /* DecryptFailed */,
3347
- message: "decrypt response field <error_message> must be string or null",
3348
- context: { decryptResponse: value }
3536
+ code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
3537
+ message: "decrypt completed but response missing <decrypted> byte array",
3538
+ context: {
3539
+ thresholdNetworkUrl,
3540
+ requestId,
3541
+ response: value
3542
+ }
3349
3543
  });
3350
3544
  }
3351
- return {
3352
- decrypted,
3353
- signature,
3354
- encryption_type: encryptionType,
3355
- error_message: errorMessage
3356
- };
3545
+ const decryptedValue = parseDecryptedBytesToBigInt(value.decrypted);
3546
+ const signature = normalizeTnSignature(value.signature);
3547
+ return { decryptedValue, signature };
3357
3548
  }
3358
- async function tnDecrypt(ctHash, chainId, permission, thresholdNetworkUrl) {
3359
- const body = {
3360
- ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
3361
- host_chain_id: chainId
3362
- };
3363
- if (permission) {
3364
- body.permit = permission;
3365
- }
3366
- let response;
3367
- try {
3368
- response = await fetch(`${thresholdNetworkUrl}/decrypt`, {
3369
- method: "POST",
3370
- headers: {
3371
- "Content-Type": "application/json"
3372
- },
3373
- body: JSON.stringify(body)
3549
+ function assertDecryptStatusResponseV2(value) {
3550
+ if (value == null || typeof value !== "object") {
3551
+ throw new CofheError({
3552
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3553
+ message: "decrypt status response must be a JSON object",
3554
+ context: {
3555
+ value
3556
+ }
3374
3557
  });
3375
- } catch (e) {
3558
+ }
3559
+ const v = value;
3560
+ const requestId = v.request_id;
3561
+ const status = v.status;
3562
+ const submittedAt = v.submitted_at;
3563
+ if (typeof requestId !== "string" || requestId.trim().length === 0) {
3376
3564
  throw new CofheError({
3377
3565
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3378
- message: `decrypt request failed`,
3379
- hint: "Ensure the threshold network URL is valid and reachable.",
3380
- cause: e instanceof Error ? e : void 0,
3566
+ message: "decrypt status response missing request_id",
3381
3567
  context: {
3382
- thresholdNetworkUrl,
3383
- body
3568
+ value
3384
3569
  }
3385
3570
  });
3386
3571
  }
3387
- const responseText = await response.text();
3388
- if (!response.ok) {
3389
- let errorMessage = response.statusText || `HTTP ${response.status}`;
3390
- try {
3391
- const errorBody = JSON.parse(responseText);
3392
- const maybeMessage = errorBody.error_message || errorBody.message;
3393
- if (typeof maybeMessage === "string" && maybeMessage.length > 0)
3394
- errorMessage = maybeMessage;
3395
- } catch {
3396
- const trimmed = responseText.trim();
3397
- if (trimmed.length > 0)
3398
- errorMessage = trimmed;
3399
- }
3572
+ if (status !== "PROCESSING" && status !== "COMPLETED") {
3400
3573
  throw new CofheError({
3401
3574
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3402
- message: `decrypt request failed: ${errorMessage}`,
3403
- hint: "Check the threshold network URL and request parameters.",
3575
+ message: "decrypt status response has invalid status",
3404
3576
  context: {
3405
- thresholdNetworkUrl,
3406
- status: response.status,
3407
- statusText: response.statusText,
3408
- body,
3409
- responseText
3577
+ value,
3578
+ status
3410
3579
  }
3411
3580
  });
3412
3581
  }
3413
- let rawJson;
3414
- try {
3415
- rawJson = JSON.parse(responseText);
3416
- } catch (e) {
3582
+ if (typeof submittedAt !== "string" || submittedAt.trim().length === 0) {
3417
3583
  throw new CofheError({
3418
3584
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3419
- message: `Failed to parse decrypt response`,
3420
- cause: e instanceof Error ? e : void 0,
3585
+ message: "decrypt status response missing submitted_at",
3421
3586
  context: {
3422
- thresholdNetworkUrl,
3423
- body,
3424
- responseText
3587
+ value
3425
3588
  }
3426
3589
  });
3427
3590
  }
3428
- const decryptResponse = assertTnDecryptResponse(rawJson);
3429
- if (decryptResponse.error_message) {
3591
+ return value;
3592
+ }
3593
+ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
3594
+ const body = {
3595
+ ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
3596
+ host_chain_id: chainId
3597
+ };
3598
+ if (permission) {
3599
+ body.permit = permission;
3600
+ }
3601
+ let attemptIndex = 0;
3602
+ for (; ; ) {
3603
+ let response;
3604
+ try {
3605
+ response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
3606
+ method: "POST",
3607
+ headers: {
3608
+ "Content-Type": "application/json"
3609
+ },
3610
+ body: JSON.stringify(body)
3611
+ });
3612
+ } catch (e) {
3613
+ throw new CofheError({
3614
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3615
+ message: `decrypt request failed`,
3616
+ hint: "Ensure the threshold network URL is valid and reachable.",
3617
+ cause: e instanceof Error ? e : void 0,
3618
+ context: {
3619
+ thresholdNetworkUrl,
3620
+ body,
3621
+ attemptIndex
3622
+ }
3623
+ });
3624
+ }
3625
+ if (!response.ok) {
3626
+ let errorMessage = `HTTP ${response.status}`;
3627
+ try {
3628
+ const errorBody = await response.json();
3629
+ const maybeMessage = errorBody.error_message || errorBody.message;
3630
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0)
3631
+ errorMessage = maybeMessage;
3632
+ } catch {
3633
+ errorMessage = response.statusText || errorMessage;
3634
+ }
3635
+ throw new CofheError({
3636
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3637
+ message: `decrypt request failed: ${errorMessage}`,
3638
+ hint: "Check the threshold network URL and request parameters.",
3639
+ context: {
3640
+ thresholdNetworkUrl,
3641
+ status: response.status,
3642
+ statusText: response.statusText,
3643
+ body,
3644
+ attemptIndex
3645
+ }
3646
+ });
3647
+ }
3648
+ let submitResponse;
3649
+ if (response.status !== 204) {
3650
+ let rawJson;
3651
+ try {
3652
+ rawJson = await response.json();
3653
+ } catch (e) {
3654
+ throw new CofheError({
3655
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3656
+ message: `Failed to parse decrypt submit response`,
3657
+ cause: e instanceof Error ? e : void 0,
3658
+ context: {
3659
+ thresholdNetworkUrl,
3660
+ body,
3661
+ attemptIndex
3662
+ }
3663
+ });
3664
+ }
3665
+ submitResponse = assertDecryptSubmitResponseV2(rawJson);
3666
+ if (Array.isArray(submitResponse.decrypted) && typeof submitResponse.signature === "string") {
3667
+ return {
3668
+ kind: "completed",
3669
+ ...parseCompletedDecryptResponseV2({
3670
+ value: submitResponse,
3671
+ thresholdNetworkUrl,
3672
+ requestId: submitResponse.request_id
3673
+ })
3674
+ };
3675
+ }
3676
+ if (submitResponse.request_id) {
3677
+ return { kind: "request_id", requestId: submitResponse.request_id };
3678
+ }
3679
+ }
3680
+ if (response.status === 204) {
3681
+ const elapsedMs = Date.now() - overallStartTime;
3682
+ if (elapsedMs > DECRYPT_TIMEOUT_MS) {
3683
+ throw new CofheError({
3684
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3685
+ message: `decrypt submit retried without receiving request_id for ${DECRYPT_TIMEOUT_MS}ms`,
3686
+ hint: "The ciphertext may still be propagating. Try again later.",
3687
+ context: {
3688
+ thresholdNetworkUrl,
3689
+ body,
3690
+ attemptIndex,
3691
+ timeoutMs: DECRYPT_TIMEOUT_MS,
3692
+ submitResponse,
3693
+ status: response.status
3694
+ }
3695
+ });
3696
+ }
3697
+ onPoll?.({
3698
+ operation: "decrypt",
3699
+ requestId: "",
3700
+ attemptIndex,
3701
+ elapsedMs,
3702
+ intervalMs: SUBMIT_RETRY_INTERVAL_MS2,
3703
+ timeoutMs: DECRYPT_TIMEOUT_MS
3704
+ });
3705
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS2));
3706
+ attemptIndex += 1;
3707
+ continue;
3708
+ }
3430
3709
  throw new CofheError({
3431
3710
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3432
- message: `decrypt request failed: ${decryptResponse.error_message}`,
3711
+ message: `decrypt submit response missing request_id`,
3433
3712
  context: {
3434
3713
  thresholdNetworkUrl,
3435
3714
  body,
3436
- decryptResponse
3715
+ submitResponse,
3716
+ attemptIndex
3437
3717
  }
3438
3718
  });
3439
3719
  }
3440
- const decryptedValue = parseDecryptedBytesToBigInt(decryptResponse.decrypted);
3441
- const signature = normalizeSignature(decryptResponse.signature);
3442
- return { decryptedValue, signature };
3720
+ }
3721
+ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
3722
+ let attemptIndex = 0;
3723
+ while (true) {
3724
+ const elapsedMs = Date.now() - overallStartTime;
3725
+ const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
3726
+ minIntervalMs: POLL_INTERVAL_MS2,
3727
+ maxIntervalMs: POLL_MAX_INTERVAL_MS2
3728
+ });
3729
+ onPoll?.({
3730
+ operation: "decrypt",
3731
+ requestId,
3732
+ attemptIndex,
3733
+ elapsedMs,
3734
+ intervalMs,
3735
+ timeoutMs: DECRYPT_TIMEOUT_MS
3736
+ });
3737
+ if (elapsedMs > DECRYPT_TIMEOUT_MS) {
3738
+ throw new CofheError({
3739
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3740
+ message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
3741
+ hint: "The request may still be processing. Try again later.",
3742
+ context: {
3743
+ thresholdNetworkUrl,
3744
+ requestId,
3745
+ timeoutMs: DECRYPT_TIMEOUT_MS
3746
+ }
3747
+ });
3748
+ }
3749
+ let response;
3750
+ try {
3751
+ response = await fetch(`${thresholdNetworkUrl}/v2/decrypt/${requestId}`, {
3752
+ method: "GET",
3753
+ headers: {
3754
+ "Content-Type": "application/json"
3755
+ }
3756
+ });
3757
+ } catch (e) {
3758
+ throw new CofheError({
3759
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3760
+ message: `decrypt status poll failed`,
3761
+ hint: "Ensure the threshold network URL is valid and reachable.",
3762
+ cause: e instanceof Error ? e : void 0,
3763
+ context: {
3764
+ thresholdNetworkUrl,
3765
+ requestId
3766
+ }
3767
+ });
3768
+ }
3769
+ if (response.status === 404) {
3770
+ throw new CofheError({
3771
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3772
+ message: `decrypt request not found: ${requestId}`,
3773
+ hint: "The request may have expired or been invalid.",
3774
+ context: {
3775
+ thresholdNetworkUrl,
3776
+ requestId
3777
+ }
3778
+ });
3779
+ }
3780
+ if (!response.ok) {
3781
+ let errorMessage = `HTTP ${response.status}`;
3782
+ try {
3783
+ const errorBody = await response.json();
3784
+ const maybeMessage = errorBody.error_message || errorBody.message;
3785
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0)
3786
+ errorMessage = maybeMessage;
3787
+ } catch {
3788
+ errorMessage = response.statusText || errorMessage;
3789
+ }
3790
+ throw new CofheError({
3791
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3792
+ message: `decrypt status poll failed: ${errorMessage}`,
3793
+ context: {
3794
+ thresholdNetworkUrl,
3795
+ requestId,
3796
+ status: response.status,
3797
+ statusText: response.statusText
3798
+ }
3799
+ });
3800
+ }
3801
+ let rawJson;
3802
+ try {
3803
+ rawJson = await response.json();
3804
+ } catch (e) {
3805
+ throw new CofheError({
3806
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3807
+ message: `Failed to parse decrypt status response`,
3808
+ cause: e instanceof Error ? e : void 0,
3809
+ context: {
3810
+ thresholdNetworkUrl,
3811
+ requestId
3812
+ }
3813
+ });
3814
+ }
3815
+ const statusResponse = assertDecryptStatusResponseV2(rawJson);
3816
+ if (statusResponse.status === "COMPLETED") {
3817
+ return parseCompletedDecryptResponseV2({
3818
+ value: statusResponse,
3819
+ thresholdNetworkUrl,
3820
+ requestId
3821
+ });
3822
+ }
3823
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3824
+ attemptIndex += 1;
3825
+ }
3826
+ throw new CofheError({
3827
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3828
+ message: "Polling loop exited unexpectedly",
3829
+ context: {
3830
+ thresholdNetworkUrl,
3831
+ requestId
3832
+ }
3833
+ });
3834
+ }
3835
+ async function tnDecryptV2(params) {
3836
+ const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
3837
+ const overallStartTime = Date.now();
3838
+ const submitResult = await submitDecryptRequestV2(
3839
+ thresholdNetworkUrl,
3840
+ ctHash,
3841
+ chainId,
3842
+ permission,
3843
+ overallStartTime,
3844
+ onPoll
3845
+ );
3846
+ if (submitResult.kind === "completed") {
3847
+ return submitResult;
3848
+ }
3849
+ return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
3443
3850
  }
3444
3851
 
3445
3852
  // core/decrypt/decryptForTxBuilder.ts
@@ -3448,6 +3855,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3448
3855
  permitHash;
3449
3856
  permit;
3450
3857
  permitSelection = "unset";
3858
+ pollCallback;
3451
3859
  constructor(params) {
3452
3860
  super({
3453
3861
  config: params.config,
@@ -3473,6 +3881,10 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3473
3881
  getAccount() {
3474
3882
  return this.account;
3475
3883
  }
3884
+ onPoll(callback) {
3885
+ this.pollCallback = callback;
3886
+ return this;
3887
+ }
3476
3888
  withPermit(permitOrPermitHash) {
3477
3889
  if (this.permitSelection === "with-permit") {
3478
3890
  throw new CofheError({
@@ -3600,7 +4012,13 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3600
4012
  this.assertPublicClient();
3601
4013
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
3602
4014
  const permission = permit ? PermitUtils.getPermission(permit, true) : null;
3603
- const { decryptedValue, signature } = await tnDecrypt(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
4015
+ const { decryptedValue, signature } = await tnDecryptV2({
4016
+ ctHash: this.ctHash,
4017
+ chainId: this.chainId,
4018
+ permission,
4019
+ thresholdNetworkUrl,
4020
+ onPoll: this.pollCallback
4021
+ });
3604
4022
  return {
3605
4023
  ctHash: this.ctHash,
3606
4024
  decryptedValue,
@@ -3618,7 +4036,6 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3618
4036
  const permit = await this.getResolvedPermit();
3619
4037
  if (permit !== null) {
3620
4038
  PermitUtils.validate(permit);
3621
- PermitUtils.isValid(permit);
3622
4039
  const chainId = permit._signedDomain.chainId;
3623
4040
  if (chainId === hardhat2.id) {
3624
4041
  return await this.mocksDecryptForTx(permit);
@@ -3639,6 +4056,35 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3639
4056
  }
3640
4057
  }
3641
4058
  };
4059
+ var decryptResultSignerAbi = viem.parseAbi(["function decryptResultSigner() view returns (address)"]);
4060
+ var UINT_TYPE_MASK2 = 0x7fn;
4061
+ var TYPE_BYTE_OFFSET2 = 8n;
4062
+ var getEncryptionTypeFromCtHash2 = (ctHash) => Number(ctHash >> TYPE_BYTE_OFFSET2 & UINT_TYPE_MASK2);
4063
+ var buildDecryptResultHash = (ctHash, cleartext, chainId) => {
4064
+ const encryptionType = getEncryptionTypeFromCtHash2(ctHash);
4065
+ return viem.keccak256(
4066
+ viem.encodePacked(["uint256", "uint32", "uint64", "uint256"], [cleartext, encryptionType, BigInt(chainId), ctHash])
4067
+ );
4068
+ };
4069
+ async function verifyDecryptResult(handle, cleartext, signature, publicClient) {
4070
+ const chainId = publicClient.chain?.id ?? await publicClient.getChainId();
4071
+ const expectedSigner = await publicClient.readContract({
4072
+ address: TASK_MANAGER_ADDRESS,
4073
+ abi: decryptResultSignerAbi,
4074
+ functionName: "decryptResultSigner",
4075
+ args: []
4076
+ });
4077
+ if (viem.isAddressEqual(expectedSigner, viem.zeroAddress))
4078
+ return true;
4079
+ const ctHash = BigInt(handle);
4080
+ const messageHash = buildDecryptResultHash(ctHash, cleartext, chainId);
4081
+ try {
4082
+ const recovered = await viem.recoverAddress({ hash: messageHash, signature });
4083
+ return viem.isAddressEqual(recovered, expectedSigner);
4084
+ } catch {
4085
+ return false;
4086
+ }
4087
+ }
3642
4088
 
3643
4089
  // core/client.ts
3644
4090
  var InitialConnectStore = {
@@ -3757,6 +4203,11 @@ function createCofheClientBase(opts) {
3757
4203
  requireConnected: _requireConnected
3758
4204
  });
3759
4205
  }
4206
+ function verifyDecryptResult2(handle, cleartext, signature) {
4207
+ _requireConnected();
4208
+ const { publicClient } = connectStore.getState();
4209
+ return verifyDecryptResult(handle, cleartext, signature, publicClient);
4210
+ }
3760
4211
  const _getChainIdAndAccount = (chainId, account) => {
3761
4212
  const state = connectStore.getState();
3762
4213
  const _chainId = chainId ?? state.chainId;
@@ -3808,19 +4259,19 @@ function createCofheClientBase(opts) {
3808
4259
  return permits.getOrCreateSharingPermit(publicClient, walletClient, options, _chainId, _account);
3809
4260
  },
3810
4261
  // Retrieval methods (auto-fill chainId/account)
3811
- getPermit: async (hash, chainId, account) => {
4262
+ getPermit: (hash, chainId, account) => {
3812
4263
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3813
4264
  return permits.getPermit(_chainId, _account, hash);
3814
4265
  },
3815
- getPermits: async (chainId, account) => {
4266
+ getPermits: (chainId, account) => {
3816
4267
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3817
4268
  return permits.getPermits(_chainId, _account);
3818
4269
  },
3819
- getActivePermit: async (chainId, account) => {
4270
+ getActivePermit: (chainId, account) => {
3820
4271
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3821
4272
  return permits.getActivePermit(_chainId, _account);
3822
4273
  },
3823
- getActivePermitHash: async (chainId, account) => {
4274
+ getActivePermitHash: (chainId, account) => {
3824
4275
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3825
4276
  return permits.getActivePermitHash(_chainId, _account);
3826
4277
  },
@@ -3839,6 +4290,7 @@ function createCofheClientBase(opts) {
3839
4290
  },
3840
4291
  // Utils (no context needed)
3841
4292
  getHash: permits.getHash,
4293
+ export: permits.export,
3842
4294
  serialize: permits.serialize,
3843
4295
  deserialize: permits.deserialize
3844
4296
  };
@@ -3867,6 +4319,7 @@ function createCofheClientBase(opts) {
3867
4319
  */
3868
4320
  decryptHandle: decryptForView,
3869
4321
  decryptForTx,
4322
+ verifyDecryptResult: verifyDecryptResult2,
3870
4323
  permits: clientPermits
3871
4324
  // Add SDK-specific methods below that require connection
3872
4325
  // Example:
@@ -3930,16 +4383,22 @@ var fromHexString2 = (hexString) => {
3930
4383
  return new Uint8Array();
3931
4384
  return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
3932
4385
  };
4386
+ var _deserializeTfhePublicKey = (buff) => {
4387
+ return nodeTfhe.TfheCompactPublicKey.safe_deserialize(fromHexString2(buff), TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT);
4388
+ };
4389
+ var _deserializeCompactPkeCrs = (buff) => {
4390
+ return nodeTfhe.CompactPkeCrs.safe_deserialize(fromHexString2(buff), TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT);
4391
+ };
3933
4392
  var tfhePublicKeyDeserializer = (buff) => {
3934
- nodeTfhe.TfheCompactPublicKey.deserialize(fromHexString2(buff));
4393
+ _deserializeTfhePublicKey(buff);
3935
4394
  };
3936
4395
  var compactPkeCrsDeserializer = (buff) => {
3937
- nodeTfhe.CompactPkeCrs.deserialize(fromHexString2(buff));
4396
+ _deserializeCompactPkeCrs(buff);
3938
4397
  };
3939
4398
  var zkBuilderAndCrsGenerator = (fhe, crs) => {
3940
- const fhePublicKey = nodeTfhe.TfheCompactPublicKey.deserialize(fromHexString2(fhe));
4399
+ const fhePublicKey = _deserializeTfhePublicKey(fhe);
3941
4400
  const zkBuilder = nodeTfhe.ProvenCompactCiphertextList.builder(fhePublicKey);
3942
- const zkCrs = nodeTfhe.CompactPkeCrs.deserialize(fromHexString2(crs));
4401
+ const zkCrs = _deserializeCompactPkeCrs(crs);
3943
4402
  return { zkBuilder, zkCrs };
3944
4403
  };
3945
4404
  function createCofheConfig(config) {