@cofhe/sdk 0.4.0 → 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 (95) hide show
  1. package/CHANGELOG.md +32 -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 +588 -251
  56. package/dist/web.d.cts +8 -4
  57. package/dist/web.d.ts +8 -4
  58. package/dist/web.js +34 -11
  59. package/dist/zkProve.worker.cjs +6 -3
  60. package/dist/zkProve.worker.js +5 -3
  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 +20 -6
  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 +4 -3
  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
@@ -1,16 +1,31 @@
1
1
  import { type Permission } from '@/permits';
2
2
 
3
3
  import { CofheError, CofheErrorCode } from '../error';
4
+ import { type DecryptPollCallbackFunction } from '../types';
4
5
  import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
6
+ import { computeMinuteRampPollIntervalMs } from './polling.js';
5
7
 
6
8
  // Polling configuration
7
9
  const POLL_INTERVAL_MS = 1000; // 1 second
8
- const POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
10
+ const POLL_MAX_INTERVAL_MS = 10_000; // 10 seconds
11
+ const DECRYPT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes total across submit + poll
12
+ const SUBMIT_RETRY_INTERVAL_MS = 1000; // 1 second
9
13
 
10
14
  type DecryptSubmitResponseV2 = {
11
- request_id: string;
15
+ request_id: string | null;
16
+ status?: string;
17
+ is_succeed?: boolean;
18
+ decrypted?: number[];
19
+ signature?: string;
20
+ encryption_type?: number;
21
+ error_message?: string | null;
22
+ message?: string;
12
23
  };
13
24
 
25
+ type DecryptSubmitResultV2 =
26
+ | { kind: 'request_id'; requestId: string }
27
+ | { kind: 'completed'; decryptedValue: bigint; signature: `0x${string}` };
28
+
14
29
  type DecryptStatusResponseV2 = {
15
30
  request_id: string;
16
31
  status: 'PROCESSING' | 'COMPLETED';
@@ -35,17 +50,75 @@ function assertDecryptSubmitResponseV2(value: unknown): DecryptSubmitResponseV2
35
50
  }
36
51
 
37
52
  const v = value as Record<string, unknown>;
38
- if (typeof v.request_id !== 'string' || v.request_id.trim().length === 0) {
53
+ if (v.request_id !== null && typeof v.request_id !== 'string') {
39
54
  throw new CofheError({
40
55
  code: CofheErrorCode.DecryptFailed,
41
- message: 'decrypt submit response missing request_id',
56
+ message: 'decrypt submit response has invalid request_id',
42
57
  context: {
43
58
  value,
44
59
  },
45
60
  });
46
61
  }
47
62
 
48
- return { request_id: v.request_id };
63
+ return {
64
+ request_id: v.request_id ?? null,
65
+ status: typeof v.status === 'string' ? v.status : undefined,
66
+ is_succeed: typeof v.is_succeed === 'boolean' ? v.is_succeed : undefined,
67
+ decrypted: Array.isArray(v.decrypted) ? (v.decrypted as number[]) : undefined,
68
+ signature: typeof v.signature === 'string' ? v.signature : undefined,
69
+ encryption_type: typeof v.encryption_type === 'number' ? v.encryption_type : undefined,
70
+ error_message: typeof v.error_message === 'string' || v.error_message === null ? v.error_message : undefined,
71
+ message: typeof v.message === 'string' ? v.message : undefined,
72
+ };
73
+ }
74
+
75
+ function parseCompletedDecryptResponseV2(params: {
76
+ value: Pick<DecryptStatusResponseV2, 'decrypted' | 'signature' | 'error_message' | 'is_succeed'>;
77
+ thresholdNetworkUrl: string;
78
+ requestId?: string | null;
79
+ }): { decryptedValue: bigint; signature: `0x${string}` } {
80
+ const { value, thresholdNetworkUrl, requestId } = params;
81
+
82
+ if (value.is_succeed === false) {
83
+ const errorMessage = value.error_message || 'Unknown error';
84
+ throw new CofheError({
85
+ code: CofheErrorCode.DecryptFailed,
86
+ message: `decrypt request failed: ${errorMessage}`,
87
+ context: {
88
+ thresholdNetworkUrl,
89
+ requestId,
90
+ response: value,
91
+ },
92
+ });
93
+ }
94
+
95
+ if (value.error_message) {
96
+ throw new CofheError({
97
+ code: CofheErrorCode.DecryptFailed,
98
+ message: `decrypt request failed: ${value.error_message}`,
99
+ context: {
100
+ thresholdNetworkUrl,
101
+ requestId,
102
+ response: value,
103
+ },
104
+ });
105
+ }
106
+
107
+ if (!Array.isArray(value.decrypted)) {
108
+ throw new CofheError({
109
+ code: CofheErrorCode.DecryptReturnedNull,
110
+ message: 'decrypt completed but response missing <decrypted> byte array',
111
+ context: {
112
+ thresholdNetworkUrl,
113
+ requestId,
114
+ response: value,
115
+ },
116
+ });
117
+ }
118
+
119
+ const decryptedValue = parseDecryptedBytesToBigInt(value.decrypted);
120
+ const signature = normalizeTnSignature(value.signature);
121
+ return { decryptedValue, signature };
49
122
  }
50
123
 
51
124
  function assertDecryptStatusResponseV2(value: unknown): DecryptStatusResponseV2 {
@@ -103,8 +176,10 @@ async function submitDecryptRequestV2(
103
176
  thresholdNetworkUrl: string,
104
177
  ctHash: bigint | string,
105
178
  chainId: number,
106
- permission: Permission | null
107
- ): Promise<string> {
179
+ permission: Permission | null,
180
+ overallStartTime: number,
181
+ onPoll?: DecryptPollCallbackFunction
182
+ ): Promise<DecryptSubmitResultV2> {
108
183
  const body: {
109
184
  ct_tempkey: string;
110
185
  host_chain_id: number;
@@ -118,87 +193,171 @@ async function submitDecryptRequestV2(
118
193
  body.permit = permission;
119
194
  }
120
195
 
121
- let response: Response;
122
- try {
123
- response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
124
- method: 'POST',
125
- headers: {
126
- 'Content-Type': 'application/json',
127
- },
128
- body: JSON.stringify(body),
129
- });
130
- } catch (e) {
131
- throw new CofheError({
132
- code: CofheErrorCode.DecryptFailed,
133
- message: `decrypt request failed`,
134
- hint: 'Ensure the threshold network URL is valid and reachable.',
135
- cause: e instanceof Error ? e : undefined,
136
- context: {
137
- thresholdNetworkUrl,
138
- body,
139
- },
140
- });
141
- }
196
+ let attemptIndex = 0;
142
197
 
143
- if (!response.ok) {
144
- let errorMessage = `HTTP ${response.status}`;
198
+ for (;;) {
199
+ let response: Response;
145
200
  try {
146
- const errorBody = (await response.json()) as Record<string, unknown>;
147
- const maybeMessage = (errorBody.error_message || errorBody.message) as unknown;
148
- if (typeof maybeMessage === 'string' && maybeMessage.length > 0) errorMessage = maybeMessage;
149
- } catch {
150
- errorMessage = response.statusText || errorMessage;
201
+ response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
202
+ method: 'POST',
203
+ headers: {
204
+ 'Content-Type': 'application/json',
205
+ },
206
+ body: JSON.stringify(body),
207
+ });
208
+ } catch (e) {
209
+ throw new CofheError({
210
+ code: CofheErrorCode.DecryptFailed,
211
+ message: `decrypt request failed`,
212
+ hint: 'Ensure the threshold network URL is valid and reachable.',
213
+ cause: e instanceof Error ? e : undefined,
214
+ context: {
215
+ thresholdNetworkUrl,
216
+ body,
217
+ attemptIndex,
218
+ },
219
+ });
151
220
  }
152
221
 
153
- throw new CofheError({
154
- code: CofheErrorCode.DecryptFailed,
155
- message: `decrypt request failed: ${errorMessage}`,
156
- hint: 'Check the threshold network URL and request parameters.',
157
- context: {
158
- thresholdNetworkUrl,
159
- status: response.status,
160
- statusText: response.statusText,
161
- body,
162
- },
163
- });
164
- }
222
+ if (!response.ok) {
223
+ let errorMessage = `HTTP ${response.status}`;
224
+ try {
225
+ const errorBody = (await response.json()) as Record<string, unknown>;
226
+ const maybeMessage = (errorBody.error_message || errorBody.message) as unknown;
227
+ if (typeof maybeMessage === 'string' && maybeMessage.length > 0) errorMessage = maybeMessage;
228
+ } catch {
229
+ errorMessage = response.statusText || errorMessage;
230
+ }
231
+
232
+ throw new CofheError({
233
+ code: CofheErrorCode.DecryptFailed,
234
+ message: `decrypt request failed: ${errorMessage}`,
235
+ hint: 'Check the threshold network URL and request parameters.',
236
+ context: {
237
+ thresholdNetworkUrl,
238
+ status: response.status,
239
+ statusText: response.statusText,
240
+ body,
241
+ attemptIndex,
242
+ },
243
+ });
244
+ }
245
+
246
+ let submitResponse: DecryptSubmitResponseV2 | undefined;
247
+ if (response.status !== 204) {
248
+ let rawJson: unknown;
249
+ try {
250
+ rawJson = (await response.json()) as unknown;
251
+ } catch (e) {
252
+ throw new CofheError({
253
+ code: CofheErrorCode.DecryptFailed,
254
+ message: `Failed to parse decrypt submit response`,
255
+ cause: e instanceof Error ? e : undefined,
256
+ context: {
257
+ thresholdNetworkUrl,
258
+ body,
259
+ attemptIndex,
260
+ },
261
+ });
262
+ }
263
+
264
+ submitResponse = assertDecryptSubmitResponseV2(rawJson);
265
+
266
+ if (Array.isArray(submitResponse.decrypted) && typeof submitResponse.signature === 'string') {
267
+ return {
268
+ kind: 'completed',
269
+ ...parseCompletedDecryptResponseV2({
270
+ value: submitResponse,
271
+ thresholdNetworkUrl,
272
+ requestId: submitResponse.request_id,
273
+ }),
274
+ };
275
+ }
276
+
277
+ if (submitResponse.request_id) {
278
+ return { kind: 'request_id', requestId: submitResponse.request_id };
279
+ }
280
+ }
281
+
282
+ // 204 means backend is aware of ct hash but didn't calculate it yet
283
+ if (response.status === 204) {
284
+ const elapsedMs = Date.now() - overallStartTime;
285
+ if (elapsedMs > DECRYPT_TIMEOUT_MS) {
286
+ throw new CofheError({
287
+ code: CofheErrorCode.DecryptFailed,
288
+ message: `decrypt submit retried without receiving request_id for ${DECRYPT_TIMEOUT_MS}ms`,
289
+ hint: 'The ciphertext may still be propagating. Try again later.',
290
+ context: {
291
+ thresholdNetworkUrl,
292
+ body,
293
+ attemptIndex,
294
+ timeoutMs: DECRYPT_TIMEOUT_MS,
295
+ submitResponse,
296
+ status: response.status,
297
+ },
298
+ });
299
+ }
300
+
301
+ onPoll?.({
302
+ operation: 'decrypt',
303
+ requestId: '',
304
+ attemptIndex,
305
+ elapsedMs,
306
+ intervalMs: SUBMIT_RETRY_INTERVAL_MS,
307
+ timeoutMs: DECRYPT_TIMEOUT_MS,
308
+ });
309
+
310
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
311
+ attemptIndex += 1;
312
+ continue;
313
+ }
165
314
 
166
- let rawJson: unknown;
167
- try {
168
- rawJson = (await response.json()) as unknown;
169
- } catch (e) {
170
315
  throw new CofheError({
171
316
  code: CofheErrorCode.DecryptFailed,
172
- message: `Failed to parse decrypt submit response`,
173
- cause: e instanceof Error ? e : undefined,
317
+ message: `decrypt submit response missing request_id`,
174
318
  context: {
175
319
  thresholdNetworkUrl,
176
320
  body,
321
+ submitResponse,
322
+ attemptIndex,
177
323
  },
178
324
  });
179
325
  }
180
-
181
- const submitResponse = assertDecryptSubmitResponseV2(rawJson);
182
- return submitResponse.request_id;
183
326
  }
184
327
 
185
328
  async function pollDecryptStatusV2(
186
329
  thresholdNetworkUrl: string,
187
- requestId: string
330
+ requestId: string,
331
+ overallStartTime: number,
332
+ onPoll?: DecryptPollCallbackFunction
188
333
  ): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
189
- const startTime = Date.now();
334
+ let attemptIndex = 0;
190
335
  let completed = false;
191
336
 
192
337
  while (!completed) {
193
- if (Date.now() - startTime > POLL_TIMEOUT_MS) {
338
+ const elapsedMs = Date.now() - overallStartTime;
339
+ const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
340
+ minIntervalMs: POLL_INTERVAL_MS,
341
+ maxIntervalMs: POLL_MAX_INTERVAL_MS,
342
+ });
343
+ onPoll?.({
344
+ operation: 'decrypt',
345
+ requestId,
346
+ attemptIndex,
347
+ elapsedMs,
348
+ intervalMs,
349
+ timeoutMs: DECRYPT_TIMEOUT_MS,
350
+ });
351
+
352
+ if (elapsedMs > DECRYPT_TIMEOUT_MS) {
194
353
  throw new CofheError({
195
354
  code: CofheErrorCode.DecryptFailed,
196
- message: `decrypt polling timed out after ${POLL_TIMEOUT_MS}ms`,
355
+ message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
197
356
  hint: 'The request may still be processing. Try again later.',
198
357
  context: {
199
358
  thresholdNetworkUrl,
200
359
  requestId,
201
- timeoutMs: POLL_TIMEOUT_MS,
360
+ timeoutMs: DECRYPT_TIMEOUT_MS,
202
361
  },
203
362
  });
204
363
  }
@@ -276,49 +435,15 @@ async function pollDecryptStatusV2(
276
435
  const statusResponse = assertDecryptStatusResponseV2(rawJson);
277
436
 
278
437
  if (statusResponse.status === 'COMPLETED') {
279
- if (statusResponse.is_succeed === false) {
280
- const errorMessage = statusResponse.error_message || 'Unknown error';
281
- throw new CofheError({
282
- code: CofheErrorCode.DecryptFailed,
283
- message: `decrypt request failed: ${errorMessage}`,
284
- context: {
285
- thresholdNetworkUrl,
286
- requestId,
287
- statusResponse,
288
- },
289
- });
290
- }
291
-
292
- if (statusResponse.error_message) {
293
- throw new CofheError({
294
- code: CofheErrorCode.DecryptFailed,
295
- message: `decrypt request failed: ${statusResponse.error_message}`,
296
- context: {
297
- thresholdNetworkUrl,
298
- requestId,
299
- statusResponse,
300
- },
301
- });
302
- }
303
-
304
- if (!Array.isArray(statusResponse.decrypted)) {
305
- throw new CofheError({
306
- code: CofheErrorCode.DecryptReturnedNull,
307
- message: 'decrypt completed but response missing <decrypted> byte array',
308
- context: {
309
- thresholdNetworkUrl,
310
- requestId,
311
- statusResponse,
312
- },
313
- });
314
- }
315
-
316
- const decryptedValue = parseDecryptedBytesToBigInt(statusResponse.decrypted);
317
- const signature = normalizeTnSignature(statusResponse.signature);
318
- return { decryptedValue, signature };
438
+ return parseCompletedDecryptResponseV2({
439
+ value: statusResponse,
440
+ thresholdNetworkUrl,
441
+ requestId,
442
+ });
319
443
  }
320
444
 
321
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
445
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
446
+ attemptIndex += 1;
322
447
  }
323
448
 
324
449
  // This should never be reached, but keeps TS and linters happy.
@@ -332,12 +457,27 @@ async function pollDecryptStatusV2(
332
457
  });
333
458
  }
334
459
 
335
- export async function tnDecryptV2(
336
- ctHash: bigint | string,
337
- chainId: number,
338
- permission: Permission | null,
339
- thresholdNetworkUrl: string
340
- ): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
341
- const requestId = await submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission);
342
- return await pollDecryptStatusV2(thresholdNetworkUrl, requestId);
460
+ export async function tnDecryptV2(params: {
461
+ ctHash: bigint | string;
462
+ chainId: number;
463
+ permission: Permission | null;
464
+ thresholdNetworkUrl: string;
465
+ onPoll?: DecryptPollCallbackFunction;
466
+ }): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
467
+ const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
468
+ const overallStartTime = Date.now();
469
+ const submitResult = await submitDecryptRequestV2(
470
+ thresholdNetworkUrl,
471
+ ctHash,
472
+ chainId,
473
+ permission,
474
+ overallStartTime,
475
+ onPoll
476
+ );
477
+
478
+ if (submitResult.kind === 'completed') {
479
+ return submitResult;
480
+ }
481
+
482
+ return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
343
483
  }