@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/core.cjs CHANGED
@@ -9,25 +9,9 @@ var middleware = require('zustand/middleware');
9
9
  var immer = require('immer');
10
10
  var nacl = require('tweetnacl');
11
11
 
12
- function _interopNamespace(e) {
13
- if (e && e.__esModule) return e;
14
- var n = Object.create(null);
15
- if (e) {
16
- Object.keys(e).forEach(function (k) {
17
- if (k !== 'default') {
18
- var d = Object.getOwnPropertyDescriptor(e, k);
19
- Object.defineProperty(n, k, d.get ? d : {
20
- enumerable: true,
21
- get: function () { return e[k]; }
22
- });
23
- }
24
- });
25
- }
26
- n.default = e;
27
- return Object.freeze(n);
28
- }
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
29
13
 
30
- var nacl__namespace = /*#__PURE__*/_interopNamespace(nacl);
14
+ var nacl__default = /*#__PURE__*/_interopDefault(nacl);
31
15
 
32
16
  // core/client.ts
33
17
 
@@ -166,6 +150,18 @@ var bigintSafeJsonStringify = (value) => {
166
150
  };
167
151
  var isCofheError = (error) => error instanceof CofheError;
168
152
 
153
+ // core/consts.ts
154
+ var TASK_MANAGER_ADDRESS = "0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9";
155
+ var MOCKS_ZK_VERIFIER_ADDRESS = "0x0000000000000000000000000000000000005001";
156
+ var MOCKS_THRESHOLD_NETWORK_ADDRESS = "0x0000000000000000000000000000000000005002";
157
+ var TEST_BED_ADDRESS = "0x0000000000000000000000000000000000005003";
158
+ var MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY = "0x6C8D7F768A6BB4AAFE85E8A2F5A9680355239C7E14646ED62B044E39DE154512";
159
+ var MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = "0x6E12D8C87503D4287c294f2Fdef96ACd9DFf6bd2";
160
+ var MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
161
+ var TFHE_RS_ZK_MAX_BITS = 2048;
162
+ var TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT = BigInt(1 << 30);
163
+ var TFHE_RS_KEY_VERSION = 2;
164
+
169
165
  // core/types.ts
170
166
  var FheTypes = /* @__PURE__ */ ((FheTypes2) => {
171
167
  FheTypes2[FheTypes2["Bool"] = 0] = "Bool";
@@ -395,7 +391,6 @@ var MAX_UINT32 = 4294967295n;
395
391
  var MAX_UINT64 = 18446744073709551615n;
396
392
  var MAX_UINT128 = 340282366920938463463374607431768211455n;
397
393
  var MAX_UINT160 = 1461501637330902918203684832716283019655932542975n;
398
- var MAX_ENCRYPTABLE_BITS = 2048;
399
394
  var zkPack = (items, builder) => {
400
395
  let totalBits = 0;
401
396
  for (const item of items) {
@@ -459,14 +454,14 @@ var zkPack = (items, builder) => {
459
454
  }
460
455
  }
461
456
  }
462
- if (totalBits > MAX_ENCRYPTABLE_BITS) {
457
+ if (totalBits > TFHE_RS_ZK_MAX_BITS) {
463
458
  throw new CofheError({
464
459
  code: "ZK_PACK_FAILED" /* ZkPackFailed */,
465
- message: `Total bits ${totalBits} exceeds ${MAX_ENCRYPTABLE_BITS}`,
466
- hint: `Ensure that the total bits of the items to encrypt does not exceed ${MAX_ENCRYPTABLE_BITS}`,
460
+ message: `Total bits ${totalBits} exceeds ${TFHE_RS_ZK_MAX_BITS}`,
461
+ hint: `Ensure that the total bits of the items to encrypt does not exceed ${TFHE_RS_ZK_MAX_BITS}`,
467
462
  context: {
468
463
  totalBits,
469
- maxBits: MAX_ENCRYPTABLE_BITS,
464
+ maxBits: TFHE_RS_ZK_MAX_BITS,
470
465
  items
471
466
  }
472
467
  });
@@ -485,7 +480,7 @@ var zkProve = async (builder, crs, metadata) => {
485
480
  1
486
481
  // ZkComputeLoad.Verify
487
482
  );
488
- resolve(compactList.serialize());
483
+ resolve(compactList.safe_serialize(TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT));
489
484
  }, 0);
490
485
  });
491
486
  };
@@ -661,17 +656,6 @@ var MockZkVerifierAbi = [
661
656
  },
662
657
  { type: "error", name: "InvalidInputs", inputs: [] }
663
658
  ];
664
-
665
- // core/consts.ts
666
- var TASK_MANAGER_ADDRESS = "0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9";
667
- var MOCKS_ZK_VERIFIER_ADDRESS = "0x0000000000000000000000000000000000005001";
668
- var MOCKS_THRESHOLD_NETWORK_ADDRESS = "0x0000000000000000000000000000000000005002";
669
- var TEST_BED_ADDRESS = "0x0000000000000000000000000000000000005003";
670
- var MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY = "0x6C8D7F768A6BB4AAFE85E8A2F5A9680355239C7E14646ED62B044E39DE154512";
671
- var MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = "0x6E12D8C87503D4287c294f2Fdef96ACd9DFf6bd2";
672
- var MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
673
-
674
- // core/encrypt/cofheMocksZkVerifySign.ts
675
659
  function createMockZkVerifierSigner() {
676
660
  return viem.createWalletClient({
677
661
  chain: chains.hardhat,
@@ -713,14 +697,14 @@ async function cofheMocksCheckEncryptableBits(items) {
713
697
  }
714
698
  }
715
699
  }
716
- if (totalBits > MAX_ENCRYPTABLE_BITS) {
700
+ if (totalBits > TFHE_RS_ZK_MAX_BITS) {
717
701
  throw new CofheError({
718
702
  code: "ZK_PACK_FAILED" /* ZkPackFailed */,
719
- message: `Total bits ${totalBits} exceeds ${MAX_ENCRYPTABLE_BITS}`,
720
- hint: `Ensure that the total bits of the items to encrypt does not exceed ${MAX_ENCRYPTABLE_BITS}`,
703
+ message: `Total bits ${totalBits} exceeds ${TFHE_RS_ZK_MAX_BITS}`,
704
+ hint: `Ensure that the total bits of the items to encrypt does not exceed ${TFHE_RS_ZK_MAX_BITS}`,
721
705
  context: {
722
706
  totalBits,
723
- maxBits: MAX_ENCRYPTABLE_BITS,
707
+ maxBits: TFHE_RS_ZK_MAX_BITS,
724
708
  items
725
709
  }
726
710
  });
@@ -994,6 +978,7 @@ function getThresholdNetworkUrlOrThrow(config, chainId) {
994
978
  }
995
979
  return url;
996
980
  }
981
+ var KEYSTORE_NAME = `cofhesdk-keys-v${TFHE_RS_KEY_VERSION}`;
997
982
  function isValidPersistedState(state) {
998
983
  if (state && typeof state === "object") {
999
984
  if ("fhe" in state && "crs" in state) {
@@ -1048,7 +1033,7 @@ function createKeysStore(storage) {
1048
1033
  };
1049
1034
  const clearKeysStorage = async () => {
1050
1035
  if (storage) {
1051
- await storage.removeItem("cofhesdk-keys");
1036
+ await storage.removeItem(KEYSTORE_NAME);
1052
1037
  }
1053
1038
  };
1054
1039
  const rehydrateKeysStore = async () => {
@@ -1078,7 +1063,7 @@ function createStoreWithPersit(storage) {
1078
1063
  if (_error)
1079
1064
  throw new Error(`onRehydrateStorage: Error rehydrating keys store: ${_error}`);
1080
1065
  },
1081
- name: "cofhesdk-keys",
1066
+ name: KEYSTORE_NAME,
1082
1067
  storage: middleware.createJSONStorage(() => storage),
1083
1068
  merge: (persistedState, currentState) => {
1084
1069
  const persisted = isValidPersistedState(persistedState) ? persistedState : DEFAULT_KEYS_STORE;
@@ -1803,7 +1788,7 @@ var SealingKey = class _SealingKey {
1803
1788
  const ephemPublicKey = parsedData.public_key instanceof Uint8Array ? parsedData.public_key : new Uint8Array(parsedData.public_key);
1804
1789
  const dataToDecrypt = parsedData.data instanceof Uint8Array ? parsedData.data : new Uint8Array(parsedData.data);
1805
1790
  const privateKeyBytes = fromHexString(this.privateKey);
1806
- const decryptedMessage = nacl__namespace.box.open(dataToDecrypt, nonce, ephemPublicKey, privateKeyBytes);
1791
+ const decryptedMessage = nacl__default.default.box.open(dataToDecrypt, nonce, ephemPublicKey, privateKeyBytes);
1807
1792
  if (!decryptedMessage) {
1808
1793
  throw new Error("Failed to decrypt message");
1809
1794
  }
@@ -1836,9 +1821,9 @@ var SealingKey = class _SealingKey {
1836
1821
  static seal = (value, publicKey) => {
1837
1822
  isString(publicKey);
1838
1823
  isBigIntOrNumber(value);
1839
- const ephemeralKeyPair = nacl__namespace.box.keyPair();
1840
- const nonce = nacl__namespace.randomBytes(nacl__namespace.box.nonceLength);
1841
- const encryptedMessage = nacl__namespace.box(toBeArray(value), nonce, fromHexString(publicKey), ephemeralKeyPair.secretKey);
1824
+ const ephemeralKeyPair = nacl__default.default.box.keyPair();
1825
+ const nonce = nacl__default.default.randomBytes(nacl__default.default.box.nonceLength);
1826
+ const encryptedMessage = nacl__default.default.box(toBeArray(value), nonce, fromHexString(publicKey), ephemeralKeyPair.secretKey);
1842
1827
  return {
1843
1828
  data: encryptedMessage,
1844
1829
  public_key: ephemeralKeyPair.publicKey,
@@ -1847,7 +1832,7 @@ var SealingKey = class _SealingKey {
1847
1832
  };
1848
1833
  };
1849
1834
  var GenerateSealingKey = () => {
1850
- const sodiumKeypair = nacl__namespace.box.keyPair();
1835
+ const sodiumKeypair = nacl__default.default.box.keyPair();
1851
1836
  return new SealingKey(toHexString2(sodiumKeypair.secretKey), toHexString2(sodiumKeypair.publicKey));
1852
1837
  };
1853
1838
  var SerializedSealingPair = zod.z.object({
@@ -2005,9 +1990,9 @@ var ValidationUtils = {
2005
1990
  return false;
2006
1991
  },
2007
1992
  /**
2008
- * Overall validity checker of a permit
1993
+ * Checks that a permit is signed and not expired.
2009
1994
  */
2010
- isValid: (permit) => {
1995
+ isSignedAndNotExpired: (permit) => {
2011
1996
  if (ValidationUtils.isExpired(permit)) {
2012
1997
  return { valid: false, error: "expired" };
2013
1998
  }
@@ -2015,6 +2000,34 @@ var ValidationUtils = {
2015
2000
  return { valid: false, error: "not-signed" };
2016
2001
  }
2017
2002
  return { valid: true, error: null };
2003
+ },
2004
+ /**
2005
+ * Asserts that a permit is signed and not expired.
2006
+ *
2007
+ * Throws `Error` with message:
2008
+ * - `Permit is expired`
2009
+ * - `Permit is not signed`
2010
+ */
2011
+ assertSignedAndNotExpired: (permit) => {
2012
+ const result = ValidationUtils.isSignedAndNotExpired(permit);
2013
+ if (result.valid)
2014
+ return;
2015
+ if (result.error === "expired") {
2016
+ throw new Error("Permit is expired");
2017
+ }
2018
+ if (result.error === "not-signed") {
2019
+ throw new Error("Permit is not signed");
2020
+ }
2021
+ throw new Error("Permit is invalid");
2022
+ },
2023
+ isValid: (permit) => {
2024
+ const schema = permit.type === "self" ? SelfPermitValidator : permit.type === "sharing" ? SharingPermitValidator : permit.type === "recipient" ? ImportPermitValidator : null;
2025
+ if (schema == null)
2026
+ return { valid: false, error: "invalid-schema" };
2027
+ const schemaResult = schema.safeParse(permit);
2028
+ if (!schemaResult.success)
2029
+ return { valid: false, error: "invalid-schema" };
2030
+ return ValidationUtils.isSignedAndNotExpired(permit);
2018
2031
  }
2019
2032
  };
2020
2033
 
@@ -2396,9 +2409,9 @@ var PermitUtils = {
2396
2409
  };
2397
2410
  },
2398
2411
  /**
2399
- * Validate a permit
2412
+ * Validate a permit (schema-level validation)
2400
2413
  */
2401
- validate: (permit) => {
2414
+ validateSchema: (permit) => {
2402
2415
  if (permit.type === "self") {
2403
2416
  return validateSelfPermit(permit);
2404
2417
  } else if (permit.type === "sharing") {
@@ -2409,12 +2422,27 @@ var PermitUtils = {
2409
2422
  throw new Error("Invalid permit type");
2410
2423
  }
2411
2424
  },
2425
+ /**
2426
+ * Validate a permit (holistic validation).
2427
+ *
2428
+ * This validates:
2429
+ * - Permit schema (shape + invariants)
2430
+ * - Permit is signed
2431
+ * - Permit is not expired
2432
+ *
2433
+ * For schema-only validation, use `validateSchema(permit)`.
2434
+ */
2435
+ validate: (permit) => {
2436
+ const validated = PermitUtils.validateSchema(permit);
2437
+ ValidationUtils.assertSignedAndNotExpired(validated);
2438
+ return validated;
2439
+ },
2412
2440
  /**
2413
2441
  * Get the permission object from a permit (for use in contracts)
2414
2442
  */
2415
2443
  getPermission: (permit, skipValidation = false) => {
2416
2444
  if (!skipValidation) {
2417
- PermitUtils.validate(permit);
2445
+ PermitUtils.validateSchema(permit);
2418
2446
  }
2419
2447
  return {
2420
2448
  issuer: permit.issuer,
@@ -2480,8 +2508,17 @@ var PermitUtils = {
2480
2508
  return ValidationUtils.isSigned(permit);
2481
2509
  },
2482
2510
  /**
2483
- * Check if permit is valid
2511
+ * Check if permit is signed and not expired
2484
2512
  */
2513
+ isSignedAndNotExpired: (permit) => {
2514
+ return ValidationUtils.isSignedAndNotExpired(permit);
2515
+ },
2516
+ /**
2517
+ * Assert that permit is signed and not expired
2518
+ */
2519
+ assertSignedAndNotExpired: (permit) => {
2520
+ return ValidationUtils.assertSignedAndNotExpired(permit);
2521
+ },
2485
2522
  isValid: (permit) => {
2486
2523
  return ValidationUtils.isValid(permit);
2487
2524
  },
@@ -2659,19 +2696,22 @@ var importShared = async (options, publicClient, walletClient) => {
2659
2696
  var getHash = (permit) => {
2660
2697
  return PermitUtils.getHash(permit);
2661
2698
  };
2699
+ var exportShared = (permit) => {
2700
+ return PermitUtils.export(permit);
2701
+ };
2662
2702
  var serialize = (permit) => {
2663
2703
  return PermitUtils.serialize(permit);
2664
2704
  };
2665
2705
  var deserialize = (serialized) => {
2666
2706
  return PermitUtils.deserialize(serialized);
2667
2707
  };
2668
- var getPermit2 = async (chainId, account, hash) => {
2708
+ var getPermit2 = (chainId, account, hash) => {
2669
2709
  return permitStore.getPermit(chainId, account, hash);
2670
2710
  };
2671
- var getPermits2 = async (chainId, account) => {
2711
+ var getPermits2 = (chainId, account) => {
2672
2712
  return permitStore.getPermits(chainId, account);
2673
2713
  };
2674
- var getActivePermit2 = async (chainId, account) => {
2714
+ var getActivePermit2 = (chainId, account) => {
2675
2715
  return permitStore.getActivePermit(chainId, account);
2676
2716
  };
2677
2717
  var getActivePermitHash2 = (chainId, account) => {
@@ -2709,6 +2749,7 @@ var permits = {
2709
2749
  getOrCreateSelfPermit,
2710
2750
  getOrCreateSharingPermit,
2711
2751
  getHash,
2752
+ export: exportShared,
2712
2753
  serialize,
2713
2754
  deserialize,
2714
2755
  getPermit: getPermit2,
@@ -2951,9 +2992,19 @@ async function cofheMocksDecryptForView(ctHash, utype, permit, publicClient) {
2951
2992
  return unsealed;
2952
2993
  }
2953
2994
 
2995
+ // core/decrypt/polling.ts
2996
+ function computeMinuteRampPollIntervalMs(elapsedMs, params) {
2997
+ const elapsedSeconds = Math.floor(elapsedMs / 1e3);
2998
+ const intervalSeconds = 1 + Math.floor(elapsedSeconds / 60);
2999
+ const intervalMs = intervalSeconds * 1e3;
3000
+ return Math.min(params.maxIntervalMs, Math.max(params.minIntervalMs, intervalMs));
3001
+ }
3002
+
2954
3003
  // core/decrypt/tnSealOutputV2.ts
2955
3004
  var POLL_INTERVAL_MS = 1e3;
2956
- var POLL_TIMEOUT_MS = 5 * 60 * 1e3;
3005
+ var POLL_MAX_INTERVAL_MS = 1e4;
3006
+ var SEAL_OUTPUT_TIMEOUT_MS = 5 * 60 * 1e3;
3007
+ var SUBMIT_RETRY_INTERVAL_MS = 1e3;
2957
3008
  function numberArrayToUint8Array(arr) {
2958
3009
  return new Uint8Array(arr);
2959
3010
  }
@@ -2970,93 +3021,193 @@ function convertSealedData(sealed) {
2970
3021
  nonce: numberArrayToUint8Array(sealed.nonce)
2971
3022
  };
2972
3023
  }
2973
- async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission) {
2974
- const body = {
2975
- ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2976
- host_chain_id: chainId,
2977
- permit: permission
2978
- };
2979
- let response;
2980
- try {
2981
- response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
2982
- method: "POST",
2983
- headers: {
2984
- "Content-Type": "application/json"
2985
- },
2986
- body: JSON.stringify(body)
2987
- });
2988
- } catch (e) {
2989
- throw new CofheError({
2990
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2991
- message: `sealOutput request failed`,
2992
- hint: "Ensure the threshold network URL is valid and reachable.",
2993
- cause: e instanceof Error ? e : void 0,
2994
- context: {
2995
- thresholdNetworkUrl,
2996
- body
2997
- }
2998
- });
3024
+ function getSealedDataFromSubmitResponse(value) {
3025
+ if (value.sealed)
3026
+ return value.sealed;
3027
+ if (Array.isArray(value.sealed_data) && Array.isArray(value.ephemeral_public_key) && Array.isArray(value.nonce)) {
3028
+ return {
3029
+ data: value.sealed_data,
3030
+ public_key: value.ephemeral_public_key,
3031
+ nonce: value.nonce
3032
+ };
2999
3033
  }
3000
- if (!response.ok) {
3001
- let errorMessage = `HTTP ${response.status}`;
3002
- try {
3003
- const errorBody = await response.json();
3004
- errorMessage = errorBody.error_message || errorBody.message || errorMessage;
3005
- } catch {
3006
- errorMessage = response.statusText || errorMessage;
3007
- }
3034
+ return void 0;
3035
+ }
3036
+ function parseCompletedSealOutputResponse(params) {
3037
+ const { value, thresholdNetworkUrl, requestId } = params;
3038
+ if (value.is_succeed === false) {
3039
+ const errorMessage = value.error_message || "Unknown error";
3008
3040
  throw new CofheError({
3009
3041
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3010
3042
  message: `sealOutput request failed: ${errorMessage}`,
3011
- hint: "Check the threshold network URL and request parameters.",
3012
3043
  context: {
3013
3044
  thresholdNetworkUrl,
3014
- status: response.status,
3015
- statusText: response.statusText,
3016
- body
3045
+ requestId,
3046
+ response: value
3017
3047
  }
3018
3048
  });
3019
3049
  }
3020
- let submitResponse;
3021
- try {
3022
- submitResponse = await response.json();
3023
- } catch (e) {
3050
+ const sealed = "sealed" in value ? value.sealed : getSealedDataFromSubmitResponse(value);
3051
+ if (!sealed) {
3024
3052
  throw new CofheError({
3025
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3026
- message: `Failed to parse sealOutput submit response`,
3027
- cause: e instanceof Error ? e : void 0,
3053
+ code: "SEAL_OUTPUT_RETURNED_NULL" /* SealOutputReturnedNull */,
3054
+ message: `sealOutput request completed but returned no sealed data`,
3028
3055
  context: {
3029
3056
  thresholdNetworkUrl,
3030
- body
3057
+ requestId,
3058
+ response: value
3031
3059
  }
3032
3060
  });
3033
3061
  }
3034
- if (!submitResponse.request_id) {
3062
+ return convertSealedData(sealed);
3063
+ }
3064
+ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
3065
+ const body = {
3066
+ ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
3067
+ host_chain_id: chainId,
3068
+ permit: permission
3069
+ };
3070
+ let attemptIndex = 0;
3071
+ for (; ; ) {
3072
+ let response;
3073
+ try {
3074
+ response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
3075
+ method: "POST",
3076
+ headers: {
3077
+ "Content-Type": "application/json"
3078
+ },
3079
+ body: JSON.stringify(body)
3080
+ });
3081
+ } catch (e) {
3082
+ throw new CofheError({
3083
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3084
+ message: `sealOutput request failed`,
3085
+ hint: "Ensure the threshold network URL is valid and reachable.",
3086
+ cause: e instanceof Error ? e : void 0,
3087
+ context: {
3088
+ thresholdNetworkUrl,
3089
+ body,
3090
+ attemptIndex
3091
+ }
3092
+ });
3093
+ }
3094
+ if (!response.ok) {
3095
+ let errorMessage = `HTTP ${response.status}`;
3096
+ try {
3097
+ const errorBody = await response.json();
3098
+ errorMessage = errorBody.error_message || errorBody.message || errorMessage;
3099
+ } catch {
3100
+ errorMessage = response.statusText || errorMessage;
3101
+ }
3102
+ throw new CofheError({
3103
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3104
+ message: `sealOutput request failed: ${errorMessage}`,
3105
+ hint: "Check the threshold network URL and request parameters.",
3106
+ context: {
3107
+ thresholdNetworkUrl,
3108
+ status: response.status,
3109
+ statusText: response.statusText,
3110
+ body,
3111
+ attemptIndex
3112
+ }
3113
+ });
3114
+ }
3115
+ let submitResponse;
3116
+ if (response.status !== 204) {
3117
+ try {
3118
+ submitResponse = await response.json();
3119
+ } catch (e) {
3120
+ throw new CofheError({
3121
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3122
+ message: `Failed to parse sealOutput submit response`,
3123
+ cause: e instanceof Error ? e : void 0,
3124
+ context: {
3125
+ thresholdNetworkUrl,
3126
+ body,
3127
+ attemptIndex
3128
+ }
3129
+ });
3130
+ }
3131
+ if (getSealedDataFromSubmitResponse(submitResponse)) {
3132
+ return {
3133
+ kind: "completed",
3134
+ sealed: parseCompletedSealOutputResponse({
3135
+ value: submitResponse,
3136
+ thresholdNetworkUrl,
3137
+ requestId: submitResponse.request_id
3138
+ })
3139
+ };
3140
+ }
3141
+ if (submitResponse.request_id) {
3142
+ return { kind: "request_id", requestId: submitResponse.request_id };
3143
+ }
3144
+ }
3145
+ if (response.status === 204) {
3146
+ const elapsedMs = Date.now() - overallStartTime;
3147
+ if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
3148
+ throw new CofheError({
3149
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3150
+ message: `sealOutput submit retried without receiving request_id for ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
3151
+ hint: "The ciphertext may still be propagating. Try again later.",
3152
+ context: {
3153
+ thresholdNetworkUrl,
3154
+ body,
3155
+ attemptIndex,
3156
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
3157
+ submitResponse,
3158
+ status: response.status
3159
+ }
3160
+ });
3161
+ }
3162
+ onPoll?.({
3163
+ operation: "sealoutput",
3164
+ requestId: "",
3165
+ attemptIndex,
3166
+ elapsedMs,
3167
+ intervalMs: SUBMIT_RETRY_INTERVAL_MS,
3168
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
3169
+ });
3170
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
3171
+ attemptIndex += 1;
3172
+ continue;
3173
+ }
3035
3174
  throw new CofheError({
3036
3175
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3037
3176
  message: `sealOutput submit response missing request_id`,
3038
3177
  context: {
3039
3178
  thresholdNetworkUrl,
3040
3179
  body,
3041
- submitResponse
3180
+ submitResponse,
3181
+ attemptIndex
3042
3182
  }
3043
3183
  });
3044
3184
  }
3045
- return submitResponse.request_id;
3046
3185
  }
3047
- async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
3048
- const startTime = Date.now();
3049
- let completed = false;
3050
- while (!completed) {
3051
- if (Date.now() - startTime > POLL_TIMEOUT_MS) {
3186
+ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
3187
+ let attemptIndex = 0;
3188
+ while (true) {
3189
+ const elapsedMs = Date.now() - overallStartTime;
3190
+ const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
3191
+ minIntervalMs: POLL_INTERVAL_MS,
3192
+ maxIntervalMs: POLL_MAX_INTERVAL_MS
3193
+ });
3194
+ onPoll?.({
3195
+ operation: "sealoutput",
3196
+ requestId,
3197
+ attemptIndex,
3198
+ elapsedMs,
3199
+ intervalMs,
3200
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
3201
+ });
3202
+ if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
3052
3203
  throw new CofheError({
3053
3204
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3054
- message: `sealOutput polling timed out after ${POLL_TIMEOUT_MS}ms`,
3205
+ message: `sealOutput polling timed out after ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
3055
3206
  hint: "The request may still be processing. Try again later.",
3056
3207
  context: {
3057
3208
  thresholdNetworkUrl,
3058
3209
  requestId,
3059
- timeoutMs: POLL_TIMEOUT_MS
3210
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
3060
3211
  }
3061
3212
  });
3062
3213
  }
@@ -3125,32 +3276,14 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
3125
3276
  });
3126
3277
  }
3127
3278
  if (statusResponse.status === "COMPLETED") {
3128
- if (statusResponse.is_succeed === false) {
3129
- const errorMessage = statusResponse.error_message || "Unknown error";
3130
- throw new CofheError({
3131
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
3132
- message: `sealOutput request failed: ${errorMessage}`,
3133
- context: {
3134
- thresholdNetworkUrl,
3135
- requestId,
3136
- statusResponse
3137
- }
3138
- });
3139
- }
3140
- if (!statusResponse.sealed) {
3141
- throw new CofheError({
3142
- code: "SEAL_OUTPUT_RETURNED_NULL" /* SealOutputReturnedNull */,
3143
- message: `sealOutput request completed but returned no sealed data`,
3144
- context: {
3145
- thresholdNetworkUrl,
3146
- requestId,
3147
- statusResponse
3148
- }
3149
- });
3150
- }
3151
- return convertSealedData(statusResponse.sealed);
3279
+ return parseCompletedSealOutputResponse({
3280
+ value: statusResponse,
3281
+ thresholdNetworkUrl,
3282
+ requestId
3283
+ });
3152
3284
  }
3153
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
3285
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3286
+ attemptIndex += 1;
3154
3287
  }
3155
3288
  throw new CofheError({
3156
3289
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
@@ -3161,9 +3294,21 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
3161
3294
  }
3162
3295
  });
3163
3296
  }
3164
- async function tnSealOutputV2(ctHash, chainId, permission, thresholdNetworkUrl) {
3165
- const requestId = await submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission);
3166
- return await pollSealOutputStatus(thresholdNetworkUrl, requestId);
3297
+ async function tnSealOutputV2(params) {
3298
+ const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
3299
+ const overallStartTime = Date.now();
3300
+ const submitResult = await submitSealOutputRequest(
3301
+ thresholdNetworkUrl,
3302
+ ctHash,
3303
+ chainId,
3304
+ permission,
3305
+ overallStartTime,
3306
+ onPoll
3307
+ );
3308
+ if (submitResult.kind === "completed") {
3309
+ return submitResult.sealed;
3310
+ }
3311
+ return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
3167
3312
  }
3168
3313
 
3169
3314
  // core/decrypt/decryptForViewBuilder.ts
@@ -3172,6 +3317,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3172
3317
  utype;
3173
3318
  permitHash;
3174
3319
  permit;
3320
+ pollCallback;
3175
3321
  constructor(params) {
3176
3322
  super({
3177
3323
  config: params.config,
@@ -3228,6 +3374,10 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3228
3374
  getAccount() {
3229
3375
  return this.account;
3230
3376
  }
3377
+ onPoll(callback) {
3378
+ this.pollCallback = callback;
3379
+ return this;
3380
+ }
3231
3381
  withPermit(permitOrPermitHash) {
3232
3382
  if (typeof permitOrPermitHash === "string") {
3233
3383
  this.permitHash = permitOrPermitHash;
@@ -3351,7 +3501,13 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3351
3501
  this.assertPublicClient();
3352
3502
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
3353
3503
  const permission = PermitUtils.getPermission(permit, true);
3354
- const sealed = await tnSealOutputV2(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
3504
+ const sealed = await tnSealOutputV2({
3505
+ ctHash: this.ctHash,
3506
+ chainId: this.chainId,
3507
+ permission,
3508
+ thresholdNetworkUrl,
3509
+ onPoll: this.pollCallback
3510
+ });
3355
3511
  return PermitUtils.unseal(permit, sealed);
3356
3512
  }
3357
3513
  /**
@@ -3379,7 +3535,6 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3379
3535
  this.validateUtypeOrThrow();
3380
3536
  const permit = await this.getResolvedPermit();
3381
3537
  PermitUtils.validate(permit);
3382
- PermitUtils.isValid(permit);
3383
3538
  const chainId = permit._signedDomain.chainId;
3384
3539
  let unsealed;
3385
3540
  if (chainId === hardhat2.id) {
@@ -3390,6 +3545,9 @@ var DecryptForViewBuilder = class extends BaseBuilder {
3390
3545
  return convertViaUtype(this.utype, unsealed);
3391
3546
  }
3392
3547
  };
3548
+ var UINT_TYPE_MASK = 0x7fn;
3549
+ var TYPE_BYTE_OFFSET = 8n;
3550
+ var getEncryptionTypeFromCtHash = (ctHash) => Number(ctHash >> TYPE_BYTE_OFFSET & UINT_TYPE_MASK);
3393
3551
  async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
3394
3552
  let allowed;
3395
3553
  let error;
@@ -3427,7 +3585,13 @@ async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
3427
3585
  message: `mocks decryptForTx call failed: ACL Access Denied (NotAllowed)`
3428
3586
  });
3429
3587
  }
3430
- const packed = viem.encodePacked(["uint256", "uint256"], [BigInt(ctHash), decryptedValue]);
3588
+ const chainId = publicClient.chain?.id ?? await publicClient.getChainId();
3589
+ const normalizedCtHash = BigInt(ctHash);
3590
+ const encryptionType = getEncryptionTypeFromCtHash(normalizedCtHash);
3591
+ const packed = viem.encodePacked(
3592
+ ["uint256", "uint32", "uint64", "uint256"],
3593
+ [decryptedValue, encryptionType, BigInt(chainId), normalizedCtHash]
3594
+ );
3431
3595
  const messageHash = viem.keccak256(packed);
3432
3596
  const signature = await accounts.sign({
3433
3597
  hash: messageHash,
@@ -3440,7 +3604,7 @@ async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
3440
3604
  signature
3441
3605
  };
3442
3606
  }
3443
- function normalizeSignature(signature) {
3607
+ function normalizeTnSignature(signature) {
3444
3608
  if (typeof signature !== "string") {
3445
3609
  throw new CofheError({
3446
3610
  code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
@@ -3496,141 +3660,384 @@ function parseDecryptedBytesToBigInt(decrypted) {
3496
3660
  }
3497
3661
  return BigInt(`0x${hex}`);
3498
3662
  }
3499
- function assertTnDecryptResponse(value) {
3663
+
3664
+ // core/decrypt/tnDecryptV2.ts
3665
+ var POLL_INTERVAL_MS2 = 1e3;
3666
+ var POLL_MAX_INTERVAL_MS2 = 1e4;
3667
+ var DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
3668
+ var SUBMIT_RETRY_INTERVAL_MS2 = 1e3;
3669
+ function assertDecryptSubmitResponseV2(value) {
3500
3670
  if (value == null || typeof value !== "object") {
3501
3671
  throw new CofheError({
3502
3672
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3503
- message: "decrypt response must be a JSON object",
3673
+ message: "decrypt submit response must be a JSON object",
3504
3674
  context: {
3505
3675
  value
3506
3676
  }
3507
3677
  });
3508
3678
  }
3509
3679
  const v = value;
3510
- const decrypted = v.decrypted;
3511
- const signature = v.signature;
3512
- const encryptionType = v.encryption_type;
3513
- const errorMessage = v.error_message;
3514
- if (!Array.isArray(decrypted)) {
3680
+ if (v.request_id !== null && typeof v.request_id !== "string") {
3515
3681
  throw new CofheError({
3516
- code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
3517
- message: "decrypt response missing <decrypted> byte array",
3518
- context: { decryptResponse: value }
3682
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3683
+ message: "decrypt submit response has invalid request_id",
3684
+ context: {
3685
+ value
3686
+ }
3519
3687
  });
3520
3688
  }
3521
- if (typeof signature !== "string") {
3689
+ return {
3690
+ request_id: v.request_id ?? null,
3691
+ status: typeof v.status === "string" ? v.status : void 0,
3692
+ is_succeed: typeof v.is_succeed === "boolean" ? v.is_succeed : void 0,
3693
+ decrypted: Array.isArray(v.decrypted) ? v.decrypted : void 0,
3694
+ signature: typeof v.signature === "string" ? v.signature : void 0,
3695
+ encryption_type: typeof v.encryption_type === "number" ? v.encryption_type : void 0,
3696
+ error_message: typeof v.error_message === "string" || v.error_message === null ? v.error_message : void 0,
3697
+ message: typeof v.message === "string" ? v.message : void 0
3698
+ };
3699
+ }
3700
+ function parseCompletedDecryptResponseV2(params) {
3701
+ const { value, thresholdNetworkUrl, requestId } = params;
3702
+ if (value.is_succeed === false) {
3703
+ const errorMessage = value.error_message || "Unknown error";
3522
3704
  throw new CofheError({
3523
- code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
3524
- message: "decrypt response missing <signature> string",
3525
- context: { decryptResponse: value }
3705
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3706
+ message: `decrypt request failed: ${errorMessage}`,
3707
+ context: {
3708
+ thresholdNetworkUrl,
3709
+ requestId,
3710
+ response: value
3711
+ }
3526
3712
  });
3527
3713
  }
3528
- if (typeof encryptionType !== "number") {
3714
+ if (value.error_message) {
3529
3715
  throw new CofheError({
3530
3716
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3531
- message: "decrypt response missing <encryption_type> number",
3532
- context: { decryptResponse: value }
3717
+ message: `decrypt request failed: ${value.error_message}`,
3718
+ context: {
3719
+ thresholdNetworkUrl,
3720
+ requestId,
3721
+ response: value
3722
+ }
3533
3723
  });
3534
3724
  }
3535
- if (!(typeof errorMessage === "string" || errorMessage === null)) {
3725
+ if (!Array.isArray(value.decrypted)) {
3536
3726
  throw new CofheError({
3537
- code: "DECRYPT_FAILED" /* DecryptFailed */,
3538
- message: "decrypt response field <error_message> must be string or null",
3539
- context: { decryptResponse: value }
3727
+ code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
3728
+ message: "decrypt completed but response missing <decrypted> byte array",
3729
+ context: {
3730
+ thresholdNetworkUrl,
3731
+ requestId,
3732
+ response: value
3733
+ }
3540
3734
  });
3541
3735
  }
3542
- return {
3543
- decrypted,
3544
- signature,
3545
- encryption_type: encryptionType,
3546
- error_message: errorMessage
3547
- };
3736
+ const decryptedValue = parseDecryptedBytesToBigInt(value.decrypted);
3737
+ const signature = normalizeTnSignature(value.signature);
3738
+ return { decryptedValue, signature };
3548
3739
  }
3549
- async function tnDecrypt(ctHash, chainId, permission, thresholdNetworkUrl) {
3550
- const body = {
3551
- ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
3552
- host_chain_id: chainId
3553
- };
3554
- if (permission) {
3555
- body.permit = permission;
3556
- }
3557
- let response;
3558
- try {
3559
- response = await fetch(`${thresholdNetworkUrl}/decrypt`, {
3560
- method: "POST",
3561
- headers: {
3562
- "Content-Type": "application/json"
3563
- },
3564
- body: JSON.stringify(body)
3740
+ function assertDecryptStatusResponseV2(value) {
3741
+ if (value == null || typeof value !== "object") {
3742
+ throw new CofheError({
3743
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3744
+ message: "decrypt status response must be a JSON object",
3745
+ context: {
3746
+ value
3747
+ }
3565
3748
  });
3566
- } catch (e) {
3749
+ }
3750
+ const v = value;
3751
+ const requestId = v.request_id;
3752
+ const status = v.status;
3753
+ const submittedAt = v.submitted_at;
3754
+ if (typeof requestId !== "string" || requestId.trim().length === 0) {
3567
3755
  throw new CofheError({
3568
3756
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3569
- message: `decrypt request failed`,
3570
- hint: "Ensure the threshold network URL is valid and reachable.",
3571
- cause: e instanceof Error ? e : void 0,
3757
+ message: "decrypt status response missing request_id",
3572
3758
  context: {
3573
- thresholdNetworkUrl,
3574
- body
3759
+ value
3575
3760
  }
3576
3761
  });
3577
3762
  }
3578
- const responseText = await response.text();
3579
- if (!response.ok) {
3580
- let errorMessage = response.statusText || `HTTP ${response.status}`;
3581
- try {
3582
- const errorBody = JSON.parse(responseText);
3583
- const maybeMessage = errorBody.error_message || errorBody.message;
3584
- if (typeof maybeMessage === "string" && maybeMessage.length > 0)
3585
- errorMessage = maybeMessage;
3586
- } catch {
3587
- const trimmed = responseText.trim();
3588
- if (trimmed.length > 0)
3589
- errorMessage = trimmed;
3590
- }
3763
+ if (status !== "PROCESSING" && status !== "COMPLETED") {
3591
3764
  throw new CofheError({
3592
3765
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3593
- message: `decrypt request failed: ${errorMessage}`,
3594
- hint: "Check the threshold network URL and request parameters.",
3766
+ message: "decrypt status response has invalid status",
3595
3767
  context: {
3596
- thresholdNetworkUrl,
3597
- status: response.status,
3598
- statusText: response.statusText,
3599
- body,
3600
- responseText
3768
+ value,
3769
+ status
3601
3770
  }
3602
3771
  });
3603
3772
  }
3604
- let rawJson;
3605
- try {
3606
- rawJson = JSON.parse(responseText);
3607
- } catch (e) {
3773
+ if (typeof submittedAt !== "string" || submittedAt.trim().length === 0) {
3608
3774
  throw new CofheError({
3609
3775
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3610
- message: `Failed to parse decrypt response`,
3611
- cause: e instanceof Error ? e : void 0,
3776
+ message: "decrypt status response missing submitted_at",
3612
3777
  context: {
3613
- thresholdNetworkUrl,
3614
- body,
3615
- responseText
3778
+ value
3616
3779
  }
3617
3780
  });
3618
3781
  }
3619
- const decryptResponse = assertTnDecryptResponse(rawJson);
3620
- if (decryptResponse.error_message) {
3782
+ return value;
3783
+ }
3784
+ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
3785
+ const body = {
3786
+ ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
3787
+ host_chain_id: chainId
3788
+ };
3789
+ if (permission) {
3790
+ body.permit = permission;
3791
+ }
3792
+ let attemptIndex = 0;
3793
+ for (; ; ) {
3794
+ let response;
3795
+ try {
3796
+ response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
3797
+ method: "POST",
3798
+ headers: {
3799
+ "Content-Type": "application/json"
3800
+ },
3801
+ body: JSON.stringify(body)
3802
+ });
3803
+ } catch (e) {
3804
+ throw new CofheError({
3805
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3806
+ message: `decrypt request failed`,
3807
+ hint: "Ensure the threshold network URL is valid and reachable.",
3808
+ cause: e instanceof Error ? e : void 0,
3809
+ context: {
3810
+ thresholdNetworkUrl,
3811
+ body,
3812
+ attemptIndex
3813
+ }
3814
+ });
3815
+ }
3816
+ if (!response.ok) {
3817
+ let errorMessage = `HTTP ${response.status}`;
3818
+ try {
3819
+ const errorBody = await response.json();
3820
+ const maybeMessage = errorBody.error_message || errorBody.message;
3821
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0)
3822
+ errorMessage = maybeMessage;
3823
+ } catch {
3824
+ errorMessage = response.statusText || errorMessage;
3825
+ }
3826
+ throw new CofheError({
3827
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3828
+ message: `decrypt request failed: ${errorMessage}`,
3829
+ hint: "Check the threshold network URL and request parameters.",
3830
+ context: {
3831
+ thresholdNetworkUrl,
3832
+ status: response.status,
3833
+ statusText: response.statusText,
3834
+ body,
3835
+ attemptIndex
3836
+ }
3837
+ });
3838
+ }
3839
+ let submitResponse;
3840
+ if (response.status !== 204) {
3841
+ let rawJson;
3842
+ try {
3843
+ rawJson = await response.json();
3844
+ } catch (e) {
3845
+ throw new CofheError({
3846
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3847
+ message: `Failed to parse decrypt submit response`,
3848
+ cause: e instanceof Error ? e : void 0,
3849
+ context: {
3850
+ thresholdNetworkUrl,
3851
+ body,
3852
+ attemptIndex
3853
+ }
3854
+ });
3855
+ }
3856
+ submitResponse = assertDecryptSubmitResponseV2(rawJson);
3857
+ if (Array.isArray(submitResponse.decrypted) && typeof submitResponse.signature === "string") {
3858
+ return {
3859
+ kind: "completed",
3860
+ ...parseCompletedDecryptResponseV2({
3861
+ value: submitResponse,
3862
+ thresholdNetworkUrl,
3863
+ requestId: submitResponse.request_id
3864
+ })
3865
+ };
3866
+ }
3867
+ if (submitResponse.request_id) {
3868
+ return { kind: "request_id", requestId: submitResponse.request_id };
3869
+ }
3870
+ }
3871
+ if (response.status === 204) {
3872
+ const elapsedMs = Date.now() - overallStartTime;
3873
+ if (elapsedMs > DECRYPT_TIMEOUT_MS) {
3874
+ throw new CofheError({
3875
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3876
+ message: `decrypt submit retried without receiving request_id for ${DECRYPT_TIMEOUT_MS}ms`,
3877
+ hint: "The ciphertext may still be propagating. Try again later.",
3878
+ context: {
3879
+ thresholdNetworkUrl,
3880
+ body,
3881
+ attemptIndex,
3882
+ timeoutMs: DECRYPT_TIMEOUT_MS,
3883
+ submitResponse,
3884
+ status: response.status
3885
+ }
3886
+ });
3887
+ }
3888
+ onPoll?.({
3889
+ operation: "decrypt",
3890
+ requestId: "",
3891
+ attemptIndex,
3892
+ elapsedMs,
3893
+ intervalMs: SUBMIT_RETRY_INTERVAL_MS2,
3894
+ timeoutMs: DECRYPT_TIMEOUT_MS
3895
+ });
3896
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS2));
3897
+ attemptIndex += 1;
3898
+ continue;
3899
+ }
3621
3900
  throw new CofheError({
3622
3901
  code: "DECRYPT_FAILED" /* DecryptFailed */,
3623
- message: `decrypt request failed: ${decryptResponse.error_message}`,
3902
+ message: `decrypt submit response missing request_id`,
3624
3903
  context: {
3625
3904
  thresholdNetworkUrl,
3626
3905
  body,
3627
- decryptResponse
3906
+ submitResponse,
3907
+ attemptIndex
3628
3908
  }
3629
3909
  });
3630
3910
  }
3631
- const decryptedValue = parseDecryptedBytesToBigInt(decryptResponse.decrypted);
3632
- const signature = normalizeSignature(decryptResponse.signature);
3633
- return { decryptedValue, signature };
3911
+ }
3912
+ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
3913
+ let attemptIndex = 0;
3914
+ while (true) {
3915
+ const elapsedMs = Date.now() - overallStartTime;
3916
+ const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
3917
+ minIntervalMs: POLL_INTERVAL_MS2,
3918
+ maxIntervalMs: POLL_MAX_INTERVAL_MS2
3919
+ });
3920
+ onPoll?.({
3921
+ operation: "decrypt",
3922
+ requestId,
3923
+ attemptIndex,
3924
+ elapsedMs,
3925
+ intervalMs,
3926
+ timeoutMs: DECRYPT_TIMEOUT_MS
3927
+ });
3928
+ if (elapsedMs > DECRYPT_TIMEOUT_MS) {
3929
+ throw new CofheError({
3930
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3931
+ message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
3932
+ hint: "The request may still be processing. Try again later.",
3933
+ context: {
3934
+ thresholdNetworkUrl,
3935
+ requestId,
3936
+ timeoutMs: DECRYPT_TIMEOUT_MS
3937
+ }
3938
+ });
3939
+ }
3940
+ let response;
3941
+ try {
3942
+ response = await fetch(`${thresholdNetworkUrl}/v2/decrypt/${requestId}`, {
3943
+ method: "GET",
3944
+ headers: {
3945
+ "Content-Type": "application/json"
3946
+ }
3947
+ });
3948
+ } catch (e) {
3949
+ throw new CofheError({
3950
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3951
+ message: `decrypt status poll failed`,
3952
+ hint: "Ensure the threshold network URL is valid and reachable.",
3953
+ cause: e instanceof Error ? e : void 0,
3954
+ context: {
3955
+ thresholdNetworkUrl,
3956
+ requestId
3957
+ }
3958
+ });
3959
+ }
3960
+ if (response.status === 404) {
3961
+ throw new CofheError({
3962
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3963
+ message: `decrypt request not found: ${requestId}`,
3964
+ hint: "The request may have expired or been invalid.",
3965
+ context: {
3966
+ thresholdNetworkUrl,
3967
+ requestId
3968
+ }
3969
+ });
3970
+ }
3971
+ if (!response.ok) {
3972
+ let errorMessage = `HTTP ${response.status}`;
3973
+ try {
3974
+ const errorBody = await response.json();
3975
+ const maybeMessage = errorBody.error_message || errorBody.message;
3976
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0)
3977
+ errorMessage = maybeMessage;
3978
+ } catch {
3979
+ errorMessage = response.statusText || errorMessage;
3980
+ }
3981
+ throw new CofheError({
3982
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3983
+ message: `decrypt status poll failed: ${errorMessage}`,
3984
+ context: {
3985
+ thresholdNetworkUrl,
3986
+ requestId,
3987
+ status: response.status,
3988
+ statusText: response.statusText
3989
+ }
3990
+ });
3991
+ }
3992
+ let rawJson;
3993
+ try {
3994
+ rawJson = await response.json();
3995
+ } catch (e) {
3996
+ throw new CofheError({
3997
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
3998
+ message: `Failed to parse decrypt status response`,
3999
+ cause: e instanceof Error ? e : void 0,
4000
+ context: {
4001
+ thresholdNetworkUrl,
4002
+ requestId
4003
+ }
4004
+ });
4005
+ }
4006
+ const statusResponse = assertDecryptStatusResponseV2(rawJson);
4007
+ if (statusResponse.status === "COMPLETED") {
4008
+ return parseCompletedDecryptResponseV2({
4009
+ value: statusResponse,
4010
+ thresholdNetworkUrl,
4011
+ requestId
4012
+ });
4013
+ }
4014
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
4015
+ attemptIndex += 1;
4016
+ }
4017
+ throw new CofheError({
4018
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
4019
+ message: "Polling loop exited unexpectedly",
4020
+ context: {
4021
+ thresholdNetworkUrl,
4022
+ requestId
4023
+ }
4024
+ });
4025
+ }
4026
+ async function tnDecryptV2(params) {
4027
+ const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
4028
+ const overallStartTime = Date.now();
4029
+ const submitResult = await submitDecryptRequestV2(
4030
+ thresholdNetworkUrl,
4031
+ ctHash,
4032
+ chainId,
4033
+ permission,
4034
+ overallStartTime,
4035
+ onPoll
4036
+ );
4037
+ if (submitResult.kind === "completed") {
4038
+ return submitResult;
4039
+ }
4040
+ return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
3634
4041
  }
3635
4042
 
3636
4043
  // core/decrypt/decryptForTxBuilder.ts
@@ -3639,6 +4046,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3639
4046
  permitHash;
3640
4047
  permit;
3641
4048
  permitSelection = "unset";
4049
+ pollCallback;
3642
4050
  constructor(params) {
3643
4051
  super({
3644
4052
  config: params.config,
@@ -3664,6 +4072,10 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3664
4072
  getAccount() {
3665
4073
  return this.account;
3666
4074
  }
4075
+ onPoll(callback) {
4076
+ this.pollCallback = callback;
4077
+ return this;
4078
+ }
3667
4079
  withPermit(permitOrPermitHash) {
3668
4080
  if (this.permitSelection === "with-permit") {
3669
4081
  throw new CofheError({
@@ -3791,7 +4203,13 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3791
4203
  this.assertPublicClient();
3792
4204
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
3793
4205
  const permission = permit ? PermitUtils.getPermission(permit, true) : null;
3794
- const { decryptedValue, signature } = await tnDecrypt(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
4206
+ const { decryptedValue, signature } = await tnDecryptV2({
4207
+ ctHash: this.ctHash,
4208
+ chainId: this.chainId,
4209
+ permission,
4210
+ thresholdNetworkUrl,
4211
+ onPoll: this.pollCallback
4212
+ });
3795
4213
  return {
3796
4214
  ctHash: this.ctHash,
3797
4215
  decryptedValue,
@@ -3809,7 +4227,6 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3809
4227
  const permit = await this.getResolvedPermit();
3810
4228
  if (permit !== null) {
3811
4229
  PermitUtils.validate(permit);
3812
- PermitUtils.isValid(permit);
3813
4230
  const chainId = permit._signedDomain.chainId;
3814
4231
  if (chainId === hardhat2.id) {
3815
4232
  return await this.mocksDecryptForTx(permit);
@@ -3830,6 +4247,35 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3830
4247
  }
3831
4248
  }
3832
4249
  };
4250
+ var decryptResultSignerAbi = viem.parseAbi(["function decryptResultSigner() view returns (address)"]);
4251
+ var UINT_TYPE_MASK2 = 0x7fn;
4252
+ var TYPE_BYTE_OFFSET2 = 8n;
4253
+ var getEncryptionTypeFromCtHash2 = (ctHash) => Number(ctHash >> TYPE_BYTE_OFFSET2 & UINT_TYPE_MASK2);
4254
+ var buildDecryptResultHash = (ctHash, cleartext, chainId) => {
4255
+ const encryptionType = getEncryptionTypeFromCtHash2(ctHash);
4256
+ return viem.keccak256(
4257
+ viem.encodePacked(["uint256", "uint32", "uint64", "uint256"], [cleartext, encryptionType, BigInt(chainId), ctHash])
4258
+ );
4259
+ };
4260
+ async function verifyDecryptResult(handle, cleartext, signature, publicClient) {
4261
+ const chainId = publicClient.chain?.id ?? await publicClient.getChainId();
4262
+ const expectedSigner = await publicClient.readContract({
4263
+ address: TASK_MANAGER_ADDRESS,
4264
+ abi: decryptResultSignerAbi,
4265
+ functionName: "decryptResultSigner",
4266
+ args: []
4267
+ });
4268
+ if (viem.isAddressEqual(expectedSigner, viem.zeroAddress))
4269
+ return true;
4270
+ const ctHash = BigInt(handle);
4271
+ const messageHash = buildDecryptResultHash(ctHash, cleartext, chainId);
4272
+ try {
4273
+ const recovered = await viem.recoverAddress({ hash: messageHash, signature });
4274
+ return viem.isAddressEqual(recovered, expectedSigner);
4275
+ } catch {
4276
+ return false;
4277
+ }
4278
+ }
3833
4279
 
3834
4280
  // core/client.ts
3835
4281
  var InitialConnectStore = {
@@ -3948,6 +4394,11 @@ function createCofheClientBase(opts) {
3948
4394
  requireConnected: _requireConnected
3949
4395
  });
3950
4396
  }
4397
+ function verifyDecryptResult2(handle, cleartext, signature) {
4398
+ _requireConnected();
4399
+ const { publicClient } = connectStore.getState();
4400
+ return verifyDecryptResult(handle, cleartext, signature, publicClient);
4401
+ }
3951
4402
  const _getChainIdAndAccount = (chainId, account) => {
3952
4403
  const state = connectStore.getState();
3953
4404
  const _chainId = chainId ?? state.chainId;
@@ -3999,19 +4450,19 @@ function createCofheClientBase(opts) {
3999
4450
  return permits.getOrCreateSharingPermit(publicClient, walletClient, options, _chainId, _account);
4000
4451
  },
4001
4452
  // Retrieval methods (auto-fill chainId/account)
4002
- getPermit: async (hash, chainId, account) => {
4453
+ getPermit: (hash, chainId, account) => {
4003
4454
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
4004
4455
  return permits.getPermit(_chainId, _account, hash);
4005
4456
  },
4006
- getPermits: async (chainId, account) => {
4457
+ getPermits: (chainId, account) => {
4007
4458
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
4008
4459
  return permits.getPermits(_chainId, _account);
4009
4460
  },
4010
- getActivePermit: async (chainId, account) => {
4461
+ getActivePermit: (chainId, account) => {
4011
4462
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
4012
4463
  return permits.getActivePermit(_chainId, _account);
4013
4464
  },
4014
- getActivePermitHash: async (chainId, account) => {
4465
+ getActivePermitHash: (chainId, account) => {
4015
4466
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
4016
4467
  return permits.getActivePermitHash(_chainId, _account);
4017
4468
  },
@@ -4030,6 +4481,7 @@ function createCofheClientBase(opts) {
4030
4481
  },
4031
4482
  // Utils (no context needed)
4032
4483
  getHash: permits.getHash,
4484
+ export: permits.export,
4033
4485
  serialize: permits.serialize,
4034
4486
  deserialize: permits.deserialize
4035
4487
  };
@@ -4058,6 +4510,7 @@ function createCofheClientBase(opts) {
4058
4510
  */
4059
4511
  decryptHandle: decryptForView,
4060
4512
  decryptForTx,
4513
+ verifyDecryptResult: verifyDecryptResult2,
4061
4514
  permits: clientPermits
4062
4515
  // Add SDK-specific methods below that require connection
4063
4516
  // Example:
@@ -4086,6 +4539,8 @@ exports.MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = MOCKS_ZK_VERIFIER_SIGNER_ADDRESS;
4086
4539
  exports.MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY = MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY;
4087
4540
  exports.TASK_MANAGER_ADDRESS = TASK_MANAGER_ADDRESS;
4088
4541
  exports.TEST_BED_ADDRESS = TEST_BED_ADDRESS;
4542
+ exports.TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT = TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT;
4543
+ exports.TFHE_RS_ZK_MAX_BITS = TFHE_RS_ZK_MAX_BITS;
4089
4544
  exports.assertCorrectEncryptedItemInput = assertCorrectEncryptedItemInput;
4090
4545
  exports.createCofheClientBase = createCofheClientBase;
4091
4546
  exports.createCofheConfigBase = createCofheConfigBase;
@@ -4096,4 +4551,5 @@ exports.getCofheConfigItem = getCofheConfigItem;
4096
4551
  exports.isCofheError = isCofheError;
4097
4552
  exports.isEncryptableItem = isEncryptableItem;
4098
4553
  exports.isLastEncryptionStep = isLastEncryptionStep;
4554
+ exports.verifyDecryptResult = verifyDecryptResult;
4099
4555
  exports.zkProveWithWorker = zkProveWithWorker;