@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.
- package/CHANGELOG.md +38 -0
- package/adapters/{ethers5.test.ts → test/ethers5.test.ts} +2 -2
- package/adapters/{ethers6.test.ts → test/ethers6.test.ts} +2 -2
- package/adapters/{hardhat.hh2.test.ts → test/hardhat.hh2.test.ts} +2 -2
- package/adapters/{index.test.ts → test/index.test.ts} +1 -1
- package/adapters/{wagmi.test.ts → test/wagmi.test.ts} +1 -1
- package/chains/{chains.test.ts → test/chains.test.ts} +1 -1
- package/core/client.ts +15 -5
- package/core/clientTypes.ts +7 -5
- package/core/consts.ts +9 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
- package/core/decrypt/decryptForTxBuilder.ts +24 -10
- package/core/decrypt/decryptForViewBuilder.ts +14 -7
- package/core/decrypt/polling.ts +14 -0
- package/core/decrypt/tnDecryptUtils.ts +65 -0
- package/core/decrypt/{tnDecrypt.ts → tnDecryptV1.ts} +7 -70
- package/core/decrypt/tnDecryptV2.ts +483 -0
- package/core/decrypt/tnSealOutputV2.ts +245 -104
- package/core/decrypt/verifyDecryptResult.ts +65 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
- package/core/encrypt/zkPackProveVerify.ts +10 -19
- package/core/fetchKeys.ts +0 -2
- package/core/index.ts +9 -1
- package/core/keyStore.ts +5 -2
- package/core/permits.ts +8 -3
- package/core/{client.test.ts → test/client.test.ts} +7 -7
- package/core/{config.test.ts → test/config.test.ts} +1 -1
- package/core/test/decrypt.test.ts +252 -0
- package/core/test/decryptBuilders.test.ts +390 -0
- package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
- package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
- package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
- package/core/{permits.test.ts → test/permits.test.ts} +42 -1
- package/core/test/pollCallbacks.test.ts +563 -0
- package/core/types.ts +21 -0
- package/dist/chains.d.cts +2 -2
- package/dist/chains.d.ts +2 -2
- package/dist/chunk-4FP4V35O.js +13 -0
- package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
- package/dist/{chunk-LWMRB6SD.js → chunk-S7OKGLFD.js} +615 -198
- package/dist/{clientTypes-Y43CKbOz.d.cts → clientTypes-BSbwairE.d.cts} +38 -13
- package/dist/{clientTypes-PQha8zes.d.ts → clientTypes-DDmcgZ0a.d.ts} +38 -13
- package/dist/core.cjs +691 -235
- package/dist/core.d.cts +24 -6
- package/dist/core.d.ts +24 -6
- package/dist/core.js +3 -2
- package/dist/node.cjs +696 -237
- package/dist/node.d.cts +3 -3
- package/dist/node.d.ts +3 -3
- package/dist/node.js +14 -7
- package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.cts} +34 -4
- package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.ts} +34 -4
- package/dist/permits.cjs +66 -29
- package/dist/permits.d.cts +18 -13
- package/dist/permits.d.ts +18 -13
- package/dist/permits.js +2 -1
- package/dist/web.cjs +718 -242
- package/dist/web.d.cts +8 -4
- package/dist/web.d.ts +8 -4
- package/dist/web.js +34 -11
- package/dist/zkProve.worker.cjs +6 -3
- package/dist/zkProve.worker.js +5 -3
- package/node/index.ts +13 -4
- package/node/test/client.test.ts +25 -0
- package/node/test/config.test.ts +16 -0
- package/node/test/inherited.test.ts +244 -0
- package/node/test/tfheinit.test.ts +56 -0
- package/package.json +24 -22
- package/permits/permit.ts +31 -5
- package/permits/sealing.ts +1 -1
- package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
- package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
- package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
- package/permits/{store.test.ts → test/store.test.ts} +2 -2
- package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
- package/permits/types.ts +1 -1
- package/permits/validation.ts +42 -2
- package/web/const.ts +2 -0
- package/web/index.ts +20 -6
- package/web/storage.ts +18 -3
- package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
- package/web/test/config.web.test.ts +16 -0
- package/web/test/inherited.web.test.ts +245 -0
- package/web/test/tfheinit.web.test.ts +62 -0
- package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
- package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
- package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
- package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
- package/web/zkProve.worker.ts +4 -3
- package/node/client.test.ts +0 -147
- package/node/config.test.ts +0 -68
- package/node/encryptInputs.test.ts +0 -155
- package/web/config.web.test.ts +0 -69
- package/web/encryptInputs.web.test.ts +0 -172
- package/web/worker.builder.web.test.ts +0 -148
- /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
- /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { type Permission } from '@/permits';
|
|
2
|
+
|
|
3
|
+
import { CofheError, CofheErrorCode } from '../error';
|
|
4
|
+
import { type DecryptPollCallbackFunction } from '../types';
|
|
5
|
+
import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
|
|
6
|
+
import { computeMinuteRampPollIntervalMs } from './polling.js';
|
|
7
|
+
|
|
8
|
+
// Polling configuration
|
|
9
|
+
const POLL_INTERVAL_MS = 1000; // 1 second
|
|
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
|
|
13
|
+
|
|
14
|
+
type DecryptSubmitResponseV2 = {
|
|
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;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type DecryptSubmitResultV2 =
|
|
26
|
+
| { kind: 'request_id'; requestId: string }
|
|
27
|
+
| { kind: 'completed'; decryptedValue: bigint; signature: `0x${string}` };
|
|
28
|
+
|
|
29
|
+
type DecryptStatusResponseV2 = {
|
|
30
|
+
request_id: string;
|
|
31
|
+
status: 'PROCESSING' | 'COMPLETED';
|
|
32
|
+
submitted_at: string;
|
|
33
|
+
completed_at?: string;
|
|
34
|
+
is_succeed?: boolean;
|
|
35
|
+
decrypted?: number[];
|
|
36
|
+
signature?: string;
|
|
37
|
+
encryption_type?: number;
|
|
38
|
+
error_message?: string | null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function assertDecryptSubmitResponseV2(value: unknown): DecryptSubmitResponseV2 {
|
|
42
|
+
if (value == null || typeof value !== 'object') {
|
|
43
|
+
throw new CofheError({
|
|
44
|
+
code: CofheErrorCode.DecryptFailed,
|
|
45
|
+
message: 'decrypt submit response must be a JSON object',
|
|
46
|
+
context: {
|
|
47
|
+
value,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const v = value as Record<string, unknown>;
|
|
53
|
+
if (v.request_id !== null && typeof v.request_id !== 'string') {
|
|
54
|
+
throw new CofheError({
|
|
55
|
+
code: CofheErrorCode.DecryptFailed,
|
|
56
|
+
message: 'decrypt submit response has invalid request_id',
|
|
57
|
+
context: {
|
|
58
|
+
value,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
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 };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function assertDecryptStatusResponseV2(value: unknown): DecryptStatusResponseV2 {
|
|
125
|
+
if (value == null || typeof value !== 'object') {
|
|
126
|
+
throw new CofheError({
|
|
127
|
+
code: CofheErrorCode.DecryptFailed,
|
|
128
|
+
message: 'decrypt status response must be a JSON object',
|
|
129
|
+
context: {
|
|
130
|
+
value,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const v = value as Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
const requestId = v.request_id;
|
|
138
|
+
const status = v.status;
|
|
139
|
+
const submittedAt = v.submitted_at;
|
|
140
|
+
|
|
141
|
+
if (typeof requestId !== 'string' || requestId.trim().length === 0) {
|
|
142
|
+
throw new CofheError({
|
|
143
|
+
code: CofheErrorCode.DecryptFailed,
|
|
144
|
+
message: 'decrypt status response missing request_id',
|
|
145
|
+
context: {
|
|
146
|
+
value,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (status !== 'PROCESSING' && status !== 'COMPLETED') {
|
|
152
|
+
throw new CofheError({
|
|
153
|
+
code: CofheErrorCode.DecryptFailed,
|
|
154
|
+
message: 'decrypt status response has invalid status',
|
|
155
|
+
context: {
|
|
156
|
+
value,
|
|
157
|
+
status,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof submittedAt !== 'string' || submittedAt.trim().length === 0) {
|
|
163
|
+
throw new CofheError({
|
|
164
|
+
code: CofheErrorCode.DecryptFailed,
|
|
165
|
+
message: 'decrypt status response missing submitted_at',
|
|
166
|
+
context: {
|
|
167
|
+
value,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return value as DecryptStatusResponseV2;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function submitDecryptRequestV2(
|
|
176
|
+
thresholdNetworkUrl: string,
|
|
177
|
+
ctHash: bigint | string,
|
|
178
|
+
chainId: number,
|
|
179
|
+
permission: Permission | null,
|
|
180
|
+
overallStartTime: number,
|
|
181
|
+
onPoll?: DecryptPollCallbackFunction
|
|
182
|
+
): Promise<DecryptSubmitResultV2> {
|
|
183
|
+
const body: {
|
|
184
|
+
ct_tempkey: string;
|
|
185
|
+
host_chain_id: number;
|
|
186
|
+
permit?: Permission;
|
|
187
|
+
} = {
|
|
188
|
+
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
|
|
189
|
+
host_chain_id: chainId,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (permission) {
|
|
193
|
+
body.permit = permission;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let attemptIndex = 0;
|
|
197
|
+
|
|
198
|
+
for (;;) {
|
|
199
|
+
let response: Response;
|
|
200
|
+
try {
|
|
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
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
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
|
+
}
|
|
314
|
+
|
|
315
|
+
throw new CofheError({
|
|
316
|
+
code: CofheErrorCode.DecryptFailed,
|
|
317
|
+
message: `decrypt submit response missing request_id`,
|
|
318
|
+
context: {
|
|
319
|
+
thresholdNetworkUrl,
|
|
320
|
+
body,
|
|
321
|
+
submitResponse,
|
|
322
|
+
attemptIndex,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function pollDecryptStatusV2(
|
|
329
|
+
thresholdNetworkUrl: string,
|
|
330
|
+
requestId: string,
|
|
331
|
+
overallStartTime: number,
|
|
332
|
+
onPoll?: DecryptPollCallbackFunction
|
|
333
|
+
): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
|
|
334
|
+
let attemptIndex = 0;
|
|
335
|
+
let completed = false;
|
|
336
|
+
|
|
337
|
+
while (!completed) {
|
|
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) {
|
|
353
|
+
throw new CofheError({
|
|
354
|
+
code: CofheErrorCode.DecryptFailed,
|
|
355
|
+
message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
|
|
356
|
+
hint: 'The request may still be processing. Try again later.',
|
|
357
|
+
context: {
|
|
358
|
+
thresholdNetworkUrl,
|
|
359
|
+
requestId,
|
|
360
|
+
timeoutMs: DECRYPT_TIMEOUT_MS,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let response: Response;
|
|
366
|
+
try {
|
|
367
|
+
response = await fetch(`${thresholdNetworkUrl}/v2/decrypt/${requestId}`, {
|
|
368
|
+
method: 'GET',
|
|
369
|
+
headers: {
|
|
370
|
+
'Content-Type': 'application/json',
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
} catch (e) {
|
|
374
|
+
throw new CofheError({
|
|
375
|
+
code: CofheErrorCode.DecryptFailed,
|
|
376
|
+
message: `decrypt status poll failed`,
|
|
377
|
+
hint: 'Ensure the threshold network URL is valid and reachable.',
|
|
378
|
+
cause: e instanceof Error ? e : undefined,
|
|
379
|
+
context: {
|
|
380
|
+
thresholdNetworkUrl,
|
|
381
|
+
requestId,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (response.status === 404) {
|
|
387
|
+
throw new CofheError({
|
|
388
|
+
code: CofheErrorCode.DecryptFailed,
|
|
389
|
+
message: `decrypt request not found: ${requestId}`,
|
|
390
|
+
hint: 'The request may have expired or been invalid.',
|
|
391
|
+
context: {
|
|
392
|
+
thresholdNetworkUrl,
|
|
393
|
+
requestId,
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!response.ok) {
|
|
399
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
400
|
+
try {
|
|
401
|
+
const errorBody = (await response.json()) as Record<string, unknown>;
|
|
402
|
+
const maybeMessage = (errorBody.error_message || errorBody.message) as unknown;
|
|
403
|
+
if (typeof maybeMessage === 'string' && maybeMessage.length > 0) errorMessage = maybeMessage;
|
|
404
|
+
} catch {
|
|
405
|
+
errorMessage = response.statusText || errorMessage;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
throw new CofheError({
|
|
409
|
+
code: CofheErrorCode.DecryptFailed,
|
|
410
|
+
message: `decrypt status poll failed: ${errorMessage}`,
|
|
411
|
+
context: {
|
|
412
|
+
thresholdNetworkUrl,
|
|
413
|
+
requestId,
|
|
414
|
+
status: response.status,
|
|
415
|
+
statusText: response.statusText,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let rawJson: unknown;
|
|
421
|
+
try {
|
|
422
|
+
rawJson = (await response.json()) as unknown;
|
|
423
|
+
} catch (e) {
|
|
424
|
+
throw new CofheError({
|
|
425
|
+
code: CofheErrorCode.DecryptFailed,
|
|
426
|
+
message: `Failed to parse decrypt status response`,
|
|
427
|
+
cause: e instanceof Error ? e : undefined,
|
|
428
|
+
context: {
|
|
429
|
+
thresholdNetworkUrl,
|
|
430
|
+
requestId,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const statusResponse = assertDecryptStatusResponseV2(rawJson);
|
|
436
|
+
|
|
437
|
+
if (statusResponse.status === 'COMPLETED') {
|
|
438
|
+
return parseCompletedDecryptResponseV2({
|
|
439
|
+
value: statusResponse,
|
|
440
|
+
thresholdNetworkUrl,
|
|
441
|
+
requestId,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
446
|
+
attemptIndex += 1;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// This should never be reached, but keeps TS and linters happy.
|
|
450
|
+
throw new CofheError({
|
|
451
|
+
code: CofheErrorCode.DecryptFailed,
|
|
452
|
+
message: 'Polling loop exited unexpectedly',
|
|
453
|
+
context: {
|
|
454
|
+
thresholdNetworkUrl,
|
|
455
|
+
requestId,
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
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);
|
|
483
|
+
}
|