@cofhe/sdk 0.4.0 → 0.5.1

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 (95) 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 +11 -1
  9. package/core/clientTypes.ts +3 -1
  10. package/core/consts.ts +9 -0
  11. package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
  12. package/core/decrypt/decryptForTxBuilder.ts +16 -2
  13. package/core/decrypt/decryptForViewBuilder.ts +14 -7
  14. package/core/decrypt/polling.ts +14 -0
  15. package/core/decrypt/tnDecryptV2.ts +250 -110
  16. package/core/decrypt/tnSealOutputV2.ts +245 -104
  17. package/core/decrypt/verifyDecryptResult.ts +65 -0
  18. package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
  19. package/core/encrypt/zkPackProveVerify.ts +10 -19
  20. package/core/fetchKeys.ts +0 -2
  21. package/core/index.ts +9 -1
  22. package/core/keyStore.ts +5 -2
  23. package/core/permits.ts +5 -0
  24. package/core/{client.test.ts → test/client.test.ts} +7 -7
  25. package/core/{config.test.ts → test/config.test.ts} +1 -1
  26. package/core/test/decrypt.test.ts +252 -0
  27. package/core/test/decryptBuilders.test.ts +390 -0
  28. package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
  29. package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
  30. package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
  31. package/core/{permits.test.ts → test/permits.test.ts} +42 -1
  32. package/core/test/pollCallbacks.test.ts +563 -0
  33. package/core/types.ts +13 -0
  34. package/dist/chains.d.cts +2 -2
  35. package/dist/chains.d.ts +2 -2
  36. package/dist/chunk-4FP4V35O.js +13 -0
  37. package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
  38. package/dist/{chunk-MXND5SVN.js → chunk-S7OKGLFD.js} +485 -207
  39. package/dist/{clientTypes-kkrRdawm.d.ts → clientTypes-BSbwairE.d.cts} +23 -6
  40. package/dist/{clientTypes-ACVWbrXL.d.cts → clientTypes-DDmcgZ0a.d.ts} +23 -6
  41. package/dist/core.cjs +561 -244
  42. package/dist/core.d.cts +24 -6
  43. package/dist/core.d.ts +24 -6
  44. package/dist/core.js +3 -2
  45. package/dist/node.cjs +566 -246
  46. package/dist/node.d.cts +3 -3
  47. package/dist/node.d.ts +3 -3
  48. package/dist/node.js +14 -7
  49. package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.cts} +34 -4
  50. package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.ts} +34 -4
  51. package/dist/permits.cjs +66 -29
  52. package/dist/permits.d.cts +18 -13
  53. package/dist/permits.d.ts +18 -13
  54. package/dist/permits.js +2 -1
  55. package/dist/web.cjs +604 -256
  56. package/dist/web.d.cts +8 -4
  57. package/dist/web.d.ts +8 -4
  58. package/dist/web.js +49 -14
  59. package/dist/zkProve.worker.cjs +72 -64
  60. package/dist/zkProve.worker.js +71 -64
  61. package/node/index.ts +13 -4
  62. package/node/test/client.test.ts +25 -0
  63. package/node/test/config.test.ts +16 -0
  64. package/node/test/inherited.test.ts +244 -0
  65. package/node/test/tfheinit.test.ts +56 -0
  66. package/package.json +24 -22
  67. package/permits/permit.ts +31 -5
  68. package/permits/sealing.ts +1 -1
  69. package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
  70. package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
  71. package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
  72. package/permits/{store.test.ts → test/store.test.ts} +2 -2
  73. package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
  74. package/permits/types.ts +1 -1
  75. package/permits/validation.ts +42 -2
  76. package/web/const.ts +2 -0
  77. package/web/index.ts +40 -11
  78. package/web/storage.ts +18 -3
  79. package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
  80. package/web/test/config.web.test.ts +16 -0
  81. package/web/test/inherited.web.test.ts +245 -0
  82. package/web/test/tfheinit.web.test.ts +62 -0
  83. package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
  84. package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
  85. package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
  86. package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
  87. package/web/zkProve.worker.ts +94 -84
  88. package/node/client.test.ts +0 -147
  89. package/node/config.test.ts +0 -68
  90. package/node/encryptInputs.test.ts +0 -155
  91. package/web/config.web.test.ts +0 -69
  92. package/web/encryptInputs.web.test.ts +0 -172
  93. package/web/worker.builder.web.test.ts +0 -148
  94. /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
  95. /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
2293
2321
  */
2322
+ isSignedAndNotExpired: (permit) => {
2323
+ return ValidationUtils.isSignedAndNotExpired(permit);
2324
+ },
2325
+ /**
2326
+ * Assert that permit is signed and not expired
2327
+ */
2328
+ assertSignedAndNotExpired: (permit) => {
2329
+ return ValidationUtils.assertSignedAndNotExpired(permit);
2330
+ },
2294
2331
  isValid: (permit) => {
2295
2332
  return ValidationUtils.isValid(permit);
2296
2333
  },
@@ -2468,6 +2505,9 @@ 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
  };
@@ -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,
@@ -3308,7 +3472,9 @@ function parseDecryptedBytesToBigInt(decrypted) {
3308
3472
 
3309
3473
  // core/decrypt/tnDecryptV2.ts
3310
3474
  var POLL_INTERVAL_MS2 = 1e3;
3311
- var POLL_TIMEOUT_MS2 = 5 * 60 * 1e3;
3475
+ var POLL_MAX_INTERVAL_MS2 = 1e4;
3476
+ var DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
3477
+ var SUBMIT_RETRY_INTERVAL_MS2 = 1e3;
3312
3478
  function assertDecryptSubmitResponseV2(value) {
3313
3479
  if (value == null || typeof value !== "object") {
3314
3480
  throw new CofheError({
@@ -3320,16 +3486,65 @@ function assertDecryptSubmitResponseV2(value) {
3320
3486
  });
3321
3487
  }
3322
3488
  const v = value;
3323
- if (typeof v.request_id !== "string" || v.request_id.trim().length === 0) {
3489
+ if (v.request_id !== null && typeof v.request_id !== "string") {
3324
3490
  throw new CofheError({
3325
3491
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3326
- message: "decrypt submit response missing request_id",
3492
+ message: "decrypt submit response has invalid request_id",
3327
3493
  context: {
3328
3494
  value
3329
3495
  }
3330
3496
  });
3331
3497
  }
3332
- return { request_id: v.request_id };
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";
3513
+ throw new CofheError({
3514
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3515
+ message: `decrypt request failed: ${errorMessage}`,
3516
+ context: {
3517
+ thresholdNetworkUrl,
3518
+ requestId,
3519
+ response: value
3520
+ }
3521
+ });
3522
+ }
3523
+ if (value.error_message) {
3524
+ throw new CofheError({
3525
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3526
+ message: `decrypt request failed: ${value.error_message}`,
3527
+ context: {
3528
+ thresholdNetworkUrl,
3529
+ requestId,
3530
+ response: value
3531
+ }
3532
+ });
3533
+ }
3534
+ if (!Array.isArray(value.decrypted)) {
3535
+ throw new CofheError({
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
+ }
3543
+ });
3544
+ }
3545
+ const decryptedValue = parseDecryptedBytesToBigInt(value.decrypted);
3546
+ const signature = normalizeTnSignature(value.signature);
3547
+ return { decryptedValue, signature };
3333
3548
  }
3334
3549
  function assertDecryptStatusResponseV2(value) {
3335
3550
  if (value == null || typeof value !== "object") {
@@ -3375,7 +3590,7 @@ function assertDecryptStatusResponseV2(value) {
3375
3590
  }
3376
3591
  return value;
3377
3592
  }
3378
- async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission) {
3593
+ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
3379
3594
  const body = {
3380
3595
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
3381
3596
  host_chain_id: chainId
@@ -3383,79 +3598,151 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, perm
3383
3598
  if (permission) {
3384
3599
  body.permit = permission;
3385
3600
  }
3386
- let response;
3387
- try {
3388
- response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
3389
- method: "POST",
3390
- headers: {
3391
- "Content-Type": "application/json"
3392
- },
3393
- body: JSON.stringify(body)
3394
- });
3395
- } catch (e) {
3396
- throw new CofheError({
3397
- code: "DECRYPT_FAILED" /* DecryptFailed */,
3398
- message: `decrypt request failed`,
3399
- hint: "Ensure the threshold network URL is valid and reachable.",
3400
- cause: e instanceof Error ? e : void 0,
3401
- context: {
3402
- thresholdNetworkUrl,
3403
- body
3404
- }
3405
- });
3406
- }
3407
- if (!response.ok) {
3408
- let errorMessage = `HTTP ${response.status}`;
3601
+ let attemptIndex = 0;
3602
+ for (; ; ) {
3603
+ let response;
3409
3604
  try {
3410
- const errorBody = await response.json();
3411
- const maybeMessage = errorBody.error_message || errorBody.message;
3412
- if (typeof maybeMessage === "string" && maybeMessage.length > 0)
3413
- errorMessage = maybeMessage;
3414
- } catch {
3415
- errorMessage = response.statusText || errorMessage;
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
+ });
3416
3624
  }
3417
- throw new CofheError({
3418
- code: "DECRYPT_FAILED" /* DecryptFailed */,
3419
- message: `decrypt request failed: ${errorMessage}`,
3420
- hint: "Check the threshold network URL and request parameters.",
3421
- context: {
3422
- thresholdNetworkUrl,
3423
- status: response.status,
3424
- statusText: response.statusText,
3425
- body
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;
3426
3634
  }
3427
- });
3428
- }
3429
- let rawJson;
3430
- try {
3431
- rawJson = await response.json();
3432
- } catch (e) {
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
+ }
3433
3709
  throw new CofheError({
3434
3710
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3435
- message: `Failed to parse decrypt submit response`,
3436
- cause: e instanceof Error ? e : void 0,
3711
+ message: `decrypt submit response missing request_id`,
3437
3712
  context: {
3438
3713
  thresholdNetworkUrl,
3439
- body
3714
+ body,
3715
+ submitResponse,
3716
+ attemptIndex
3440
3717
  }
3441
3718
  });
3442
3719
  }
3443
- const submitResponse = assertDecryptSubmitResponseV2(rawJson);
3444
- return submitResponse.request_id;
3445
3720
  }
3446
- async function pollDecryptStatusV2(thresholdNetworkUrl, requestId) {
3447
- const startTime = Date.now();
3448
- let completed = false;
3449
- while (!completed) {
3450
- if (Date.now() - startTime > POLL_TIMEOUT_MS2) {
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) {
3451
3738
  throw new CofheError({
3452
3739
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3453
- message: `decrypt polling timed out after ${POLL_TIMEOUT_MS2}ms`,
3740
+ message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
3454
3741
  hint: "The request may still be processing. Try again later.",
3455
3742
  context: {
3456
3743
  thresholdNetworkUrl,
3457
3744
  requestId,
3458
- timeoutMs: POLL_TIMEOUT_MS2
3745
+ timeoutMs: DECRYPT_TIMEOUT_MS
3459
3746
  }
3460
3747
  });
3461
3748
  }
@@ -3527,45 +3814,14 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId) {
3527
3814
  }
3528
3815
  const statusResponse = assertDecryptStatusResponseV2(rawJson);
3529
3816
  if (statusResponse.status === "COMPLETED") {
3530
- if (statusResponse.is_succeed === false) {
3531
- const errorMessage = statusResponse.error_message || "Unknown error";
3532
- throw new CofheError({
3533
- code: "DECRYPT_FAILED" /* DecryptFailed */,
3534
- message: `decrypt request failed: ${errorMessage}`,
3535
- context: {
3536
- thresholdNetworkUrl,
3537
- requestId,
3538
- statusResponse
3539
- }
3540
- });
3541
- }
3542
- if (statusResponse.error_message) {
3543
- throw new CofheError({
3544
- code: "DECRYPT_FAILED" /* DecryptFailed */,
3545
- message: `decrypt request failed: ${statusResponse.error_message}`,
3546
- context: {
3547
- thresholdNetworkUrl,
3548
- requestId,
3549
- statusResponse
3550
- }
3551
- });
3552
- }
3553
- if (!Array.isArray(statusResponse.decrypted)) {
3554
- throw new CofheError({
3555
- code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
3556
- message: "decrypt completed but response missing <decrypted> byte array",
3557
- context: {
3558
- thresholdNetworkUrl,
3559
- requestId,
3560
- statusResponse
3561
- }
3562
- });
3563
- }
3564
- const decryptedValue = parseDecryptedBytesToBigInt(statusResponse.decrypted);
3565
- const signature = normalizeTnSignature(statusResponse.signature);
3566
- return { decryptedValue, signature };
3817
+ return parseCompletedDecryptResponseV2({
3818
+ value: statusResponse,
3819
+ thresholdNetworkUrl,
3820
+ requestId
3821
+ });
3567
3822
  }
3568
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS2));
3823
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3824
+ attemptIndex += 1;
3569
3825
  }
3570
3826
  throw new CofheError({
3571
3827
  code: "DECRYPT_FAILED" /* DecryptFailed */,
@@ -3576,9 +3832,21 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId) {
3576
3832
  }
3577
3833
  });
3578
3834
  }
3579
- async function tnDecryptV2(ctHash, chainId, permission, thresholdNetworkUrl) {
3580
- const requestId = await submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission);
3581
- return await pollDecryptStatusV2(thresholdNetworkUrl, requestId);
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);
3582
3850
  }
3583
3851
 
3584
3852
  // core/decrypt/decryptForTxBuilder.ts
@@ -3587,6 +3855,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3587
3855
  permitHash;
3588
3856
  permit;
3589
3857
  permitSelection = "unset";
3858
+ pollCallback;
3590
3859
  constructor(params) {
3591
3860
  super({
3592
3861
  config: params.config,
@@ -3612,6 +3881,10 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3612
3881
  getAccount() {
3613
3882
  return this.account;
3614
3883
  }
3884
+ onPoll(callback) {
3885
+ this.pollCallback = callback;
3886
+ return this;
3887
+ }
3615
3888
  withPermit(permitOrPermitHash) {
3616
3889
  if (this.permitSelection === "with-permit") {
3617
3890
  throw new CofheError({
@@ -3739,7 +4012,13 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3739
4012
  this.assertPublicClient();
3740
4013
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
3741
4014
  const permission = permit ? PermitUtils.getPermission(permit, true) : null;
3742
- const { decryptedValue, signature } = await tnDecryptV2(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
+ });
3743
4022
  return {
3744
4023
  ctHash: this.ctHash,
3745
4024
  decryptedValue,
@@ -3757,7 +4036,6 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3757
4036
  const permit = await this.getResolvedPermit();
3758
4037
  if (permit !== null) {
3759
4038
  PermitUtils.validate(permit);
3760
- PermitUtils.isValid(permit);
3761
4039
  const chainId = permit._signedDomain.chainId;
3762
4040
  if (chainId === hardhat2.id) {
3763
4041
  return await this.mocksDecryptForTx(permit);
@@ -3778,6 +4056,35 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3778
4056
  }
3779
4057
  }
3780
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
+ }
3781
4088
 
3782
4089
  // core/client.ts
3783
4090
  var InitialConnectStore = {
@@ -3896,6 +4203,11 @@ function createCofheClientBase(opts) {
3896
4203
  requireConnected: _requireConnected
3897
4204
  });
3898
4205
  }
4206
+ function verifyDecryptResult2(handle, cleartext, signature) {
4207
+ _requireConnected();
4208
+ const { publicClient } = connectStore.getState();
4209
+ return verifyDecryptResult(handle, cleartext, signature, publicClient);
4210
+ }
3899
4211
  const _getChainIdAndAccount = (chainId, account) => {
3900
4212
  const state = connectStore.getState();
3901
4213
  const _chainId = chainId ?? state.chainId;
@@ -3978,6 +4290,7 @@ function createCofheClientBase(opts) {
3978
4290
  },
3979
4291
  // Utils (no context needed)
3980
4292
  getHash: permits.getHash,
4293
+ export: permits.export,
3981
4294
  serialize: permits.serialize,
3982
4295
  deserialize: permits.deserialize
3983
4296
  };
@@ -4006,6 +4319,7 @@ function createCofheClientBase(opts) {
4006
4319
  */
4007
4320
  decryptHandle: decryptForView,
4008
4321
  decryptForTx,
4322
+ verifyDecryptResult: verifyDecryptResult2,
4009
4323
  permits: clientPermits
4010
4324
  // Add SDK-specific methods below that require connection
4011
4325
  // Example:
@@ -4069,16 +4383,22 @@ var fromHexString2 = (hexString) => {
4069
4383
  return new Uint8Array();
4070
4384
  return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
4071
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
+ };
4072
4392
  var tfhePublicKeyDeserializer = (buff) => {
4073
- nodeTfhe.TfheCompactPublicKey.deserialize(fromHexString2(buff));
4393
+ _deserializeTfhePublicKey(buff);
4074
4394
  };
4075
4395
  var compactPkeCrsDeserializer = (buff) => {
4076
- nodeTfhe.CompactPkeCrs.deserialize(fromHexString2(buff));
4396
+ _deserializeCompactPkeCrs(buff);
4077
4397
  };
4078
4398
  var zkBuilderAndCrsGenerator = (fhe, crs) => {
4079
- const fhePublicKey = nodeTfhe.TfheCompactPublicKey.deserialize(fromHexString2(fhe));
4399
+ const fhePublicKey = _deserializeTfhePublicKey(fhe);
4080
4400
  const zkBuilder = nodeTfhe.ProvenCompactCiphertextList.builder(fhePublicKey);
4081
- const zkCrs = nodeTfhe.CompactPkeCrs.deserialize(fromHexString2(crs));
4401
+ const zkCrs = _deserializeCompactPkeCrs(crs);
4082
4402
  return { zkBuilder, zkCrs };
4083
4403
  };
4084
4404
  function createCofheConfig(config) {