@cofhe/sdk 0.5.1 → 0.6.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 +34 -0
- package/adapters/test/ethers5.test.ts +1 -1
- package/adapters/test/ethers6.test.ts +1 -1
- package/adapters/test/wagmi.test.ts +1 -1
- package/chains/chains/hardhat.ts +3 -3
- package/core/consts.ts +0 -3
- package/core/decrypt/decryptForTxBuilder.ts +21 -0
- package/core/decrypt/decryptForViewBuilder.ts +19 -0
- package/core/decrypt/submitRetry.ts +126 -0
- package/core/decrypt/tnDecryptV2.ts +48 -53
- package/core/decrypt/tnSealOutputV2.ts +48 -54
- package/core/encrypt/cofheMocksZkVerifySign.ts +2 -2
- package/core/encrypt/encryptInputsBuilder.ts +46 -11
- package/core/encrypt/zkPackProveVerify.ts +3 -3
- package/core/index.ts +13 -1
- package/core/test/decryptBuilders.test.ts +28 -0
- package/core/test/encryptInputsBuilder.test.ts +35 -0
- package/core/test/pollCallbacks.test.ts +226 -0
- package/core/types.ts +65 -5
- package/dist/chains.cjs +3 -3
- package/dist/chains.js +1 -1
- package/dist/{chunk-4FP4V35O.js → chunk-ESMZCFJY.js} +1 -2
- package/dist/{chunk-TBLR7NNE.js → chunk-MTRAXQXC.js} +3 -3
- package/dist/{chunk-S7OKGLFD.js → chunk-PE5V5CCV.js} +288 -153
- package/dist/{chunk-MRCKUMOS.js → chunk-VB62WYPL.js} +1 -1
- package/dist/{clientTypes-BSbwairE.d.cts → clientTypes-BDy1qIBu.d.cts} +78 -11
- package/dist/{clientTypes-DDmcgZ0a.d.ts → clientTypes-CyUvRRzA.d.ts} +78 -11
- package/dist/core.cjs +288 -155
- package/dist/core.d.cts +3 -5
- package/dist/core.d.ts +3 -5
- package/dist/core.js +4 -4
- package/dist/node.cjs +243 -108
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +4 -4
- package/dist/permits.d.cts +10 -6
- package/dist/permits.d.ts +10 -6
- package/dist/permits.js +2 -2
- package/dist/web.cjs +243 -108
- package/dist/web.d.cts +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js +4 -4
- package/dist/zkProve.worker.js +1 -1
- package/package.json +2 -2
- package/permits/store.ts +1 -0
- package/web/test/ssr.test.ts +23 -0
- package/web/test/tfheinit.web.test.ts +81 -5
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type EncryptedItemInputs,
|
|
17
17
|
type TfheInitializer,
|
|
18
18
|
type EncryptStepCallbackContext,
|
|
19
|
+
type HashPlusProofResult,
|
|
19
20
|
} from '../types.js';
|
|
20
21
|
import { cofheMocksCheckEncryptableBits, cofheMocksZkVerifySign } from './cofheMocksZkVerifySign.js';
|
|
21
22
|
import { hardhat } from 'viem/chains';
|
|
@@ -47,10 +48,11 @@ type EncryptInputsBuilderParams<T extends EncryptableItem[]> = BaseBuilderParams
|
|
|
47
48
|
* config, tfhePublicKeyDeserializer, compactPkeCrsDeserializer, and zkBuilderAndCrsGenerator are required to be set in the builder.
|
|
48
49
|
*/
|
|
49
50
|
|
|
50
|
-
export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuilder {
|
|
51
|
+
export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boolean = false> extends BaseBuilder {
|
|
51
52
|
private securityZone: number;
|
|
52
53
|
private stepCallback?: EncryptStepCallbackFunction;
|
|
53
54
|
private inputItems: [...T];
|
|
55
|
+
private hpp: boolean = false;
|
|
54
56
|
|
|
55
57
|
private zkvWalletClient: WalletClient | undefined;
|
|
56
58
|
|
|
@@ -154,7 +156,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
154
156
|
*
|
|
155
157
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
156
158
|
*/
|
|
157
|
-
setAccount(account: string): EncryptInputsBuilder<T> {
|
|
159
|
+
setAccount(account: string): EncryptInputsBuilder<T, HPP> {
|
|
158
160
|
this.account = account;
|
|
159
161
|
return this;
|
|
160
162
|
}
|
|
@@ -177,7 +179,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
177
179
|
*
|
|
178
180
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
179
181
|
*/
|
|
180
|
-
setChainId(chainId: number): EncryptInputsBuilder<T> {
|
|
182
|
+
setChainId(chainId: number): EncryptInputsBuilder<T, HPP> {
|
|
181
183
|
this.chainId = chainId;
|
|
182
184
|
return this;
|
|
183
185
|
}
|
|
@@ -200,7 +202,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
200
202
|
*
|
|
201
203
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
202
204
|
*/
|
|
203
|
-
setSecurityZone(securityZone: number): EncryptInputsBuilder<T> {
|
|
205
|
+
setSecurityZone(securityZone: number): EncryptInputsBuilder<T, HPP> {
|
|
204
206
|
this.securityZone = securityZone;
|
|
205
207
|
return this;
|
|
206
208
|
}
|
|
@@ -209,6 +211,21 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
209
211
|
return this.securityZone;
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Example:
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const encrypted = await encryptInputs([Encryptable.uint128(10n)])
|
|
218
|
+
* .asHashPlusProof()
|
|
219
|
+
* .execute();
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @returns Chainable EncryptInputsBuilder instance that will return a HashPlusProofResult instead of an array of EncryptedItemInputs.
|
|
223
|
+
*/
|
|
224
|
+
asHashPlusProof(): EncryptInputsBuilder<T, true> {
|
|
225
|
+
this.hpp = true;
|
|
226
|
+
return this as unknown as EncryptInputsBuilder<T, true>;
|
|
227
|
+
}
|
|
228
|
+
|
|
212
229
|
/**
|
|
213
230
|
* @param useWorker - Whether to use Web Workers for ZK proof generation.
|
|
214
231
|
*
|
|
@@ -223,7 +240,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
223
240
|
*
|
|
224
241
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
225
242
|
*/
|
|
226
|
-
setUseWorker(useWorker: boolean): EncryptInputsBuilder<T> {
|
|
243
|
+
setUseWorker(useWorker: boolean): EncryptInputsBuilder<T, HPP> {
|
|
227
244
|
this.useWorker = useWorker;
|
|
228
245
|
return this;
|
|
229
246
|
}
|
|
@@ -260,7 +277,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
260
277
|
*
|
|
261
278
|
* @returns The EncryptInputsBuilder instance.
|
|
262
279
|
*/
|
|
263
|
-
onStep(callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T> {
|
|
280
|
+
onStep(callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T, HPP> {
|
|
264
281
|
this.stepCallback = callback;
|
|
265
282
|
return this;
|
|
266
283
|
}
|
|
@@ -541,7 +558,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
541
558
|
const verifyResults = await zkVerify(zkVerifierUrl, proof, this.account, this.securityZone, this.chainId);
|
|
542
559
|
// Add securityZone and utype to the verify results
|
|
543
560
|
const encryptedInputs: EncryptedItemInput[] = verifyResults.map(
|
|
544
|
-
({ ct_hash, signature }: { ct_hash: string; signature: string }, index: number) => ({
|
|
561
|
+
({ ct_hash, signature }: { ct_hash: string; signature: `0x${string}` }, index: number) => ({
|
|
545
562
|
ctHash: BigInt(ct_hash),
|
|
546
563
|
securityZone: this.securityZone,
|
|
547
564
|
utype: this.inputItems[index].utype,
|
|
@@ -554,6 +571,18 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
554
571
|
return encryptedInputs as [...EncryptedItemInputs<T>];
|
|
555
572
|
}
|
|
556
573
|
|
|
574
|
+
private structsToHashPlusProof(inItems: [...EncryptedItemInputs<T>]): HashPlusProofResult<T> {
|
|
575
|
+
let hashes: string[] = [];
|
|
576
|
+
let proof: string = '';
|
|
577
|
+
|
|
578
|
+
for (const item of inItems) {
|
|
579
|
+
hashes.push('0x' + item.ctHash.toString(16).padStart(64, '0'));
|
|
580
|
+
proof += item.signature;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return [...hashes, proof] as unknown as HashPlusProofResult<T>;
|
|
584
|
+
}
|
|
585
|
+
|
|
557
586
|
/**
|
|
558
587
|
* Final step of the encryption process. MUST BE CALLED LAST IN THE CHAIN.
|
|
559
588
|
*
|
|
@@ -573,11 +602,17 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
573
602
|
*
|
|
574
603
|
* @returns The encrypted inputs.
|
|
575
604
|
*/
|
|
576
|
-
async execute(): Promise<[...EncryptedItemInputs<T>]> {
|
|
577
|
-
|
|
578
|
-
|
|
605
|
+
async execute(): Promise<HPP extends true ? HashPlusProofResult<T> : [...EncryptedItemInputs<T>]> {
|
|
606
|
+
type Result = HPP extends true ? HashPlusProofResult<T> : [...EncryptedItemInputs<T>];
|
|
607
|
+
|
|
608
|
+
let items: [...EncryptedItemInputs<T>];
|
|
579
609
|
|
|
610
|
+
// On hardhat chain, interact with MockZkVerifier contract instead of CoFHE
|
|
611
|
+
if (this.chainId === hardhat.id) items = await this.mocksExecute();
|
|
580
612
|
// On other chains, interact with CoFHE coprocessor
|
|
581
|
-
|
|
613
|
+
else items = await this.productionExecute();
|
|
614
|
+
|
|
615
|
+
if (this.hpp) return this.structsToHashPlusProof(items) as unknown as Result;
|
|
616
|
+
return items as unknown as Result;
|
|
582
617
|
}
|
|
583
618
|
}
|
|
@@ -49,7 +49,7 @@ export type VerifyResultRaw = {
|
|
|
49
49
|
|
|
50
50
|
export type VerifyResult = {
|
|
51
51
|
ct_hash: string;
|
|
52
|
-
signature: string
|
|
52
|
+
signature: `0x${string}`;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
export type ZkProvenCiphertextList = {
|
|
@@ -321,6 +321,6 @@ export const zkVerify = async (
|
|
|
321
321
|
}
|
|
322
322
|
};
|
|
323
323
|
|
|
324
|
-
const concatSigRecid = (signature: string, recid: number): string => {
|
|
325
|
-
return signature
|
|
324
|
+
const concatSigRecid = (signature: string, recid: number): `0x${string}` => {
|
|
325
|
+
return `${signature}${(recid + 27).toString(16).padStart(2, '0')}` as `0x${string}`;
|
|
326
326
|
};
|
package/core/index.ts
CHANGED
|
@@ -41,6 +41,19 @@ export type {
|
|
|
41
41
|
EncryptedItemInputs,
|
|
42
42
|
EncryptableToEncryptedItemInputMap,
|
|
43
43
|
FheTypeValue,
|
|
44
|
+
// Hash-plus-proof external types
|
|
45
|
+
ExternalBoolHash,
|
|
46
|
+
ExternalUint8Hash,
|
|
47
|
+
ExternalUint16Hash,
|
|
48
|
+
ExternalUint32Hash,
|
|
49
|
+
ExternalUint64Hash,
|
|
50
|
+
ExternalUint128Hash,
|
|
51
|
+
ExternalAddressHash,
|
|
52
|
+
ExternalHashProof,
|
|
53
|
+
AnyExternalHash,
|
|
54
|
+
EncryptableToExternalHashMap,
|
|
55
|
+
ExternalItemHashes,
|
|
56
|
+
HashPlusProofResult,
|
|
44
57
|
// Decryption types
|
|
45
58
|
UnsealedItem,
|
|
46
59
|
DecryptPollCallbackFunction,
|
|
@@ -96,7 +109,6 @@ export {
|
|
|
96
109
|
MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
|
|
97
110
|
MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY,
|
|
98
111
|
MOCKS_THRESHOLD_NETWORK_ADDRESS,
|
|
99
|
-
TEST_BED_ADDRESS,
|
|
100
112
|
TFHE_RS_ZK_MAX_BITS,
|
|
101
113
|
TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT,
|
|
102
114
|
} from './consts.js';
|
|
@@ -197,6 +197,20 @@ describe('DecryptForTxBuilder', () => {
|
|
|
197
197
|
expect(selected.getChainId()).toBe(99);
|
|
198
198
|
expect(selected.getAccount()).toBe('0xabc');
|
|
199
199
|
});
|
|
200
|
+
|
|
201
|
+
it('should allow configuring 404 retry timeout', () => {
|
|
202
|
+
const builder = createTxBuilder();
|
|
203
|
+
const result = builder.set404RetryTimeout(15_000);
|
|
204
|
+
|
|
205
|
+
expect(result).toBe(builder);
|
|
206
|
+
expect((builder as any).retry404TimeoutMs).toBe(15_000);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should throw for invalid 404 retry timeout', () => {
|
|
210
|
+
const builder = createTxBuilder();
|
|
211
|
+
|
|
212
|
+
expect(() => builder.set404RetryTimeout(-1)).toThrow('set404RetryTimeout(timeoutMs) expects');
|
|
213
|
+
});
|
|
200
214
|
});
|
|
201
215
|
|
|
202
216
|
// --- execute error paths ---
|
|
@@ -326,6 +340,20 @@ describe('DecryptForViewBuilder', () => {
|
|
|
326
340
|
expect(result.getChainId()).toBe(TEST_CHAIN_ID);
|
|
327
341
|
expect(result.getAccount()).toBe(account.address);
|
|
328
342
|
});
|
|
343
|
+
|
|
344
|
+
it('should allow configuring 404 retry timeout', () => {
|
|
345
|
+
const builder = createViewBuilder(FheTypes.Uint32);
|
|
346
|
+
const result = builder.set404RetryTimeout(12_000);
|
|
347
|
+
|
|
348
|
+
expect(result).toBe(builder);
|
|
349
|
+
expect((builder as any).retry404TimeoutMs).toBe(12_000);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should throw for invalid 404 retry timeout', () => {
|
|
353
|
+
const builder = createViewBuilder(FheTypes.Uint32);
|
|
354
|
+
|
|
355
|
+
expect(() => builder.set404RetryTimeout(-1)).toThrow('set404RetryTimeout(timeoutMs) expects');
|
|
356
|
+
});
|
|
329
357
|
});
|
|
330
358
|
|
|
331
359
|
// --- execute error paths ---
|
|
@@ -744,6 +744,41 @@ describe('EncryptInputsBuilder', () => {
|
|
|
744
744
|
});
|
|
745
745
|
});
|
|
746
746
|
|
|
747
|
+
describe('asHashPlusProof', () => {
|
|
748
|
+
it('should return the same builder instance for chaining', () => {
|
|
749
|
+
expect(builder.asHashPlusProof()).toBe(builder);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('execute() returns [hash, proof] for a single input', async () => {
|
|
753
|
+
const result = await builder.asHashPlusProof().execute();
|
|
754
|
+
expect(Array.isArray(result)).toBe(true);
|
|
755
|
+
expect(result).toHaveLength(2); // 1 hash + 1 proof
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it('execute() returns [hash1, hash2, proof] for two inputs', async () => {
|
|
759
|
+
const result = await new EncryptInputsBuilder({
|
|
760
|
+
...createDefaultParams(),
|
|
761
|
+
inputs: [Encryptable.uint128(100n), Encryptable.bool(true)] as [
|
|
762
|
+
ReturnType<typeof Encryptable.uint128>,
|
|
763
|
+
ReturnType<typeof Encryptable.bool>,
|
|
764
|
+
],
|
|
765
|
+
})
|
|
766
|
+
.asHashPlusProof()
|
|
767
|
+
.execute();
|
|
768
|
+
|
|
769
|
+
expect(Array.isArray(result)).toBe(true);
|
|
770
|
+
expect(result).toHaveLength(3); // 2 hashes + 1 proof
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it('should be composable with other builder methods', async () => {
|
|
774
|
+
const overriddenSender = '0x5555555555555555555555555555555555555555';
|
|
775
|
+
const result = await builder.asHashPlusProof().setAccount(overriddenSender).execute();
|
|
776
|
+
|
|
777
|
+
expect(Array.isArray(result)).toBe(true);
|
|
778
|
+
expect(result).toHaveLength(2);
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
747
782
|
describe('setUseWorker and getUseWorker', () => {
|
|
748
783
|
it('should have setUseWorker method', () => {
|
|
749
784
|
expect(builder).toHaveProperty('setUseWorker');
|
|
@@ -188,6 +188,117 @@ describe('decrypt polling callbacks', () => {
|
|
|
188
188
|
);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
+
it('tnDecryptV2 calls onPoll for 404 submit retries', async () => {
|
|
192
|
+
const onPoll = vi.fn();
|
|
193
|
+
|
|
194
|
+
let submitCalls = 0;
|
|
195
|
+
const fetchMock = vi.fn(async (url: string, options?: any) => {
|
|
196
|
+
if (url === `${thresholdNetworkUrl}/v2/decrypt` && options?.method === 'POST') {
|
|
197
|
+
submitCalls += 1;
|
|
198
|
+
|
|
199
|
+
if (submitCalls === 1) {
|
|
200
|
+
return makeMockResponse({
|
|
201
|
+
ok: false,
|
|
202
|
+
status: 404,
|
|
203
|
+
statusText: 'Not Found',
|
|
204
|
+
json: async () => ({ message: 'Not Found' }),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return makeMockResponse({
|
|
209
|
+
ok: true,
|
|
210
|
+
json: async () => ({ request_id: 'req-submit-retry-404' }),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (url === `${thresholdNetworkUrl}/v2/decrypt/req-submit-retry-404` && options?.method === 'GET') {
|
|
215
|
+
return makeMockResponse({
|
|
216
|
+
ok: true,
|
|
217
|
+
json: async () => ({
|
|
218
|
+
request_id: 'req-submit-retry-404',
|
|
219
|
+
status: 'COMPLETED',
|
|
220
|
+
submitted_at: 't',
|
|
221
|
+
is_succeed: true,
|
|
222
|
+
decrypted: [0x01],
|
|
223
|
+
signature: `0x${'01'.repeat(32)}${'02'.repeat(32)}1b`,
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
global.fetch = fetchMock as any;
|
|
232
|
+
|
|
233
|
+
const promise = tnDecryptV2({
|
|
234
|
+
ctHash: 1n,
|
|
235
|
+
chainId: 1,
|
|
236
|
+
permission: null,
|
|
237
|
+
thresholdNetworkUrl,
|
|
238
|
+
onPoll,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
for (let i = 0; i < 25 && onPoll.mock.calls.length < 1; i += 1) {
|
|
242
|
+
await Promise.resolve();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
expect(onPoll).toHaveBeenCalledTimes(1);
|
|
246
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
247
|
+
1,
|
|
248
|
+
expect.objectContaining({
|
|
249
|
+
operation: 'decrypt',
|
|
250
|
+
requestId: '',
|
|
251
|
+
attemptIndex: 0,
|
|
252
|
+
intervalMs: 1000,
|
|
253
|
+
timeoutMs: 5 * 60 * 1000,
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
258
|
+
await promise;
|
|
259
|
+
|
|
260
|
+
expect(onPoll).toHaveBeenCalledTimes(2);
|
|
261
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
262
|
+
2,
|
|
263
|
+
expect.objectContaining({
|
|
264
|
+
operation: 'decrypt',
|
|
265
|
+
requestId: 'req-submit-retry-404',
|
|
266
|
+
attemptIndex: 0,
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('tnDecryptV2 times out 404 submit retries using default timeout', async () => {
|
|
272
|
+
const fetchMock = vi.fn(async (url: string, options?: any) => {
|
|
273
|
+
if (url === `${thresholdNetworkUrl}/v2/decrypt` && options?.method === 'POST') {
|
|
274
|
+
return makeMockResponse({
|
|
275
|
+
ok: false,
|
|
276
|
+
status: 404,
|
|
277
|
+
statusText: 'Not Found',
|
|
278
|
+
json: async () => ({ message: 'Not Found' }),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
global.fetch = fetchMock as any;
|
|
286
|
+
|
|
287
|
+
const promise = tnDecryptV2({
|
|
288
|
+
ctHash: 1n,
|
|
289
|
+
chainId: 1,
|
|
290
|
+
permission: null,
|
|
291
|
+
thresholdNetworkUrl,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const rejection = expect(promise).rejects.toMatchObject({
|
|
295
|
+
message: 'decrypt submit retried 404 responses without receiving request_id for 10000ms',
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await vi.advanceTimersByTimeAsync(11_000);
|
|
299
|
+
await rejection;
|
|
300
|
+
});
|
|
301
|
+
|
|
191
302
|
it('tnDecryptV2 returns immediately when submit responds with cached completed payload', async () => {
|
|
192
303
|
const onPoll = vi.fn();
|
|
193
304
|
|
|
@@ -458,6 +569,121 @@ describe('decrypt polling callbacks', () => {
|
|
|
458
569
|
);
|
|
459
570
|
});
|
|
460
571
|
|
|
572
|
+
it('tnSealOutputV2 calls onPoll for 404 submit retries', async () => {
|
|
573
|
+
const onPoll = vi.fn();
|
|
574
|
+
|
|
575
|
+
let submitCalls = 0;
|
|
576
|
+
const fetchMock = vi.fn(async (url: string, options?: any) => {
|
|
577
|
+
if (url === `${thresholdNetworkUrl}/v2/sealoutput` && options?.method === 'POST') {
|
|
578
|
+
submitCalls += 1;
|
|
579
|
+
|
|
580
|
+
if (submitCalls === 1) {
|
|
581
|
+
return makeMockResponse({
|
|
582
|
+
ok: false,
|
|
583
|
+
status: 404,
|
|
584
|
+
statusText: 'Not Found',
|
|
585
|
+
json: async () => ({ message: 'Not Found' }),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return makeMockResponse({
|
|
590
|
+
ok: true,
|
|
591
|
+
json: async () => ({ request_id: 'req-seal-submit-retry-404' }),
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (url === `${thresholdNetworkUrl}/v2/sealoutput/req-seal-submit-retry-404` && options?.method === 'GET') {
|
|
596
|
+
return makeMockResponse({
|
|
597
|
+
ok: true,
|
|
598
|
+
json: async () => ({
|
|
599
|
+
request_id: 'req-seal-submit-retry-404',
|
|
600
|
+
status: 'COMPLETED',
|
|
601
|
+
submitted_at: 't',
|
|
602
|
+
is_succeed: true,
|
|
603
|
+
sealed: {
|
|
604
|
+
data: [1, 2, 3],
|
|
605
|
+
public_key: [4, 5],
|
|
606
|
+
nonce: [6],
|
|
607
|
+
},
|
|
608
|
+
}),
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
global.fetch = fetchMock as any;
|
|
616
|
+
|
|
617
|
+
const promise = tnSealOutputV2({
|
|
618
|
+
ctHash: 1n,
|
|
619
|
+
chainId: 1,
|
|
620
|
+
permission: {} as any,
|
|
621
|
+
thresholdNetworkUrl,
|
|
622
|
+
onPoll,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
for (let i = 0; i < 25 && onPoll.mock.calls.length < 1; i += 1) {
|
|
626
|
+
await Promise.resolve();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
expect(onPoll).toHaveBeenCalledTimes(1);
|
|
630
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
631
|
+
1,
|
|
632
|
+
expect.objectContaining({
|
|
633
|
+
operation: 'sealoutput',
|
|
634
|
+
requestId: '',
|
|
635
|
+
attemptIndex: 0,
|
|
636
|
+
intervalMs: 1000,
|
|
637
|
+
timeoutMs: 5 * 60 * 1000,
|
|
638
|
+
})
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
642
|
+
await promise;
|
|
643
|
+
|
|
644
|
+
expect(onPoll).toHaveBeenCalledTimes(2);
|
|
645
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
646
|
+
2,
|
|
647
|
+
expect.objectContaining({
|
|
648
|
+
operation: 'sealoutput',
|
|
649
|
+
requestId: 'req-seal-submit-retry-404',
|
|
650
|
+
attemptIndex: 0,
|
|
651
|
+
})
|
|
652
|
+
);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('tnSealOutputV2 uses custom 404 submit retry timeout', async () => {
|
|
656
|
+
const fetchMock = vi.fn(async (url: string, options?: any) => {
|
|
657
|
+
if (url === `${thresholdNetworkUrl}/v2/sealoutput` && options?.method === 'POST') {
|
|
658
|
+
return makeMockResponse({
|
|
659
|
+
ok: false,
|
|
660
|
+
status: 404,
|
|
661
|
+
statusText: 'Not Found',
|
|
662
|
+
json: async () => ({ message: 'Not Found' }),
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
global.fetch = fetchMock as any;
|
|
670
|
+
|
|
671
|
+
const promise = tnSealOutputV2({
|
|
672
|
+
ctHash: 1n,
|
|
673
|
+
chainId: 1,
|
|
674
|
+
permission: {} as any,
|
|
675
|
+
thresholdNetworkUrl,
|
|
676
|
+
retry404TimeoutMs: 2000,
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
const rejection = expect(promise).rejects.toMatchObject({
|
|
680
|
+
message: 'sealOutput submit retried 404 responses without receiving request_id for 2000ms',
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
await vi.advanceTimersByTimeAsync(3000);
|
|
684
|
+
await rejection;
|
|
685
|
+
});
|
|
686
|
+
|
|
461
687
|
it('tnSealOutputV2 returns immediately when submit responds with cached completed payload', async () => {
|
|
462
688
|
const onPoll = vi.fn();
|
|
463
689
|
|
package/core/types.ts
CHANGED
|
@@ -111,16 +111,14 @@ export type EncryptedNumber = {
|
|
|
111
111
|
securityZone: number;
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
-
export type EncryptedItemInput
|
|
114
|
+
export type EncryptedItemInput = {
|
|
115
115
|
ctHash: bigint;
|
|
116
116
|
securityZone: number;
|
|
117
117
|
utype: FheTypes;
|
|
118
|
-
signature:
|
|
118
|
+
signature: `0x${string}`;
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
export function assertCorrectEncryptedItemInput(
|
|
122
|
-
input: EncryptedItemInput
|
|
123
|
-
): asserts input is EncryptedItemInput<`0x${string}`> {
|
|
121
|
+
export function assertCorrectEncryptedItemInput(input: EncryptedItemInput): asserts input is EncryptedItemInput {
|
|
124
122
|
if (!input.signature.startsWith('0x')) throw new Error('Signature must be a hex string starting with 0x');
|
|
125
123
|
}
|
|
126
124
|
|
|
@@ -387,6 +385,68 @@ export type EncryptStepCallbackContext = Record<string, any> & {
|
|
|
387
385
|
};
|
|
388
386
|
export type EncryptStepCallbackFunction = (state: EncryptStep, context?: EncryptStepCallbackContext) => void;
|
|
389
387
|
|
|
388
|
+
// HASH PLUS PROOF ENCRYPTED INPUTS
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Branded bytes32 types for external encrypted inputs (Solidity: externalEbool, externalEuint*, externalEaddress).
|
|
392
|
+
* The readonly `utype` field brands each hash so it can't be accidentally passed to the wrong asE* function.
|
|
393
|
+
*/
|
|
394
|
+
export type ExternalBoolHash = `0x${string}` & { readonly utype: FheTypes.Bool };
|
|
395
|
+
export type ExternalUint8Hash = `0x${string}` & { readonly utype: FheTypes.Uint8 };
|
|
396
|
+
export type ExternalUint16Hash = `0x${string}` & { readonly utype: FheTypes.Uint16 };
|
|
397
|
+
export type ExternalUint32Hash = `0x${string}` & { readonly utype: FheTypes.Uint32 };
|
|
398
|
+
export type ExternalUint64Hash = `0x${string}` & { readonly utype: FheTypes.Uint64 };
|
|
399
|
+
export type ExternalUint128Hash = `0x${string}` & { readonly utype: FheTypes.Uint128 };
|
|
400
|
+
export type ExternalAddressHash = `0x${string}` & { readonly utype: FheTypes.Uint160 };
|
|
401
|
+
|
|
402
|
+
/** Branded bytes proof blob (Solidity: bytes memory proof). */
|
|
403
|
+
export type ExternalHashProof = `0x${string}` & { readonly _kind: 'ExternalHashProof' };
|
|
404
|
+
|
|
405
|
+
/** Union of all External*Hash types — useful for utilities that operate on any hash without caring about the specific FHE type. */
|
|
406
|
+
export type AnyExternalHash =
|
|
407
|
+
| ExternalBoolHash
|
|
408
|
+
| ExternalUint8Hash
|
|
409
|
+
| ExternalUint16Hash
|
|
410
|
+
| ExternalUint32Hash
|
|
411
|
+
| ExternalUint64Hash
|
|
412
|
+
| ExternalUint128Hash
|
|
413
|
+
| ExternalAddressHash;
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Maps a single EncryptableItem to its corresponding External*Hash type.
|
|
417
|
+
* Mirrors EncryptableToEncryptedItemInputMap.
|
|
418
|
+
*/
|
|
419
|
+
export type EncryptableToExternalHashMap<E extends EncryptableItem> = E extends EncryptableBool
|
|
420
|
+
? ExternalBoolHash
|
|
421
|
+
: E extends EncryptableUint8
|
|
422
|
+
? ExternalUint8Hash
|
|
423
|
+
: E extends EncryptableUint16
|
|
424
|
+
? ExternalUint16Hash
|
|
425
|
+
: E extends EncryptableUint32
|
|
426
|
+
? ExternalUint32Hash
|
|
427
|
+
: E extends EncryptableUint64
|
|
428
|
+
? ExternalUint64Hash
|
|
429
|
+
: E extends EncryptableUint128
|
|
430
|
+
? ExternalUint128Hash
|
|
431
|
+
: E extends EncryptableAddress
|
|
432
|
+
? ExternalAddressHash
|
|
433
|
+
: never;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Maps an EncryptableItem[] tuple to a tuple of corresponding External*Hash types,
|
|
437
|
+
* preserving index positions. e.g. [EncryptableBool, EncryptableUint32] → [ExternalBoolHash, ExternalUint32Hash]
|
|
438
|
+
*/
|
|
439
|
+
export type ExternalItemHashes<T extends EncryptableItem[]> = {
|
|
440
|
+
[K in keyof T]: T[K] extends EncryptableItem ? EncryptableToExternalHashMap<T[K]> : never;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Return type of EncryptInputsBuilder.execute() when asHashPlusProof() is set.
|
|
445
|
+
* Tuple of per-input hashes in input order, followed by a single proof blob.
|
|
446
|
+
* e.g. [Encryptable.bool(true), Encryptable.uint32(5)] → [ExternalBoolHash, ExternalUint32Hash, ExternalHashProof]
|
|
447
|
+
*/
|
|
448
|
+
export type HashPlusProofResult<T extends EncryptableItem[]> = [...ExternalItemHashes<T>, ExternalHashProof];
|
|
449
|
+
|
|
390
450
|
// DECRYPT
|
|
391
451
|
|
|
392
452
|
export type DecryptEndpoint = 'decrypt' | 'sealoutput';
|
package/dist/chains.cjs
CHANGED
|
@@ -67,9 +67,9 @@ var hardhat = defineChain({
|
|
|
67
67
|
name: "Hardhat",
|
|
68
68
|
network: "localhost",
|
|
69
69
|
// These are unused in the mock environment
|
|
70
|
-
coFheUrl: "http://
|
|
71
|
-
verifierUrl: "http://
|
|
72
|
-
thresholdNetworkUrl: "http://
|
|
70
|
+
coFheUrl: "http://ignored-in-mock-environment",
|
|
71
|
+
verifierUrl: "http://ignored-in-mock-environment",
|
|
72
|
+
thresholdNetworkUrl: "http://ignored-in-mock-environment",
|
|
73
73
|
environment: "MOCK"
|
|
74
74
|
});
|
|
75
75
|
|
package/dist/chains.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { arbSepolia, baseSepolia, chains, getChainById, getChainByName, hardhat, localcofhe, sepolia } from './chunk-
|
|
1
|
+
export { arbSepolia, baseSepolia, chains, getChainById, getChainByName, hardhat, localcofhe, sepolia } from './chunk-MTRAXQXC.js';
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
var TASK_MANAGER_ADDRESS = "0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9";
|
|
3
3
|
var MOCKS_ZK_VERIFIER_ADDRESS = "0x0000000000000000000000000000000000005001";
|
|
4
4
|
var MOCKS_THRESHOLD_NETWORK_ADDRESS = "0x0000000000000000000000000000000000005002";
|
|
5
|
-
var TEST_BED_ADDRESS = "0x0000000000000000000000000000000000005003";
|
|
6
5
|
var MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY = "0x6C8D7F768A6BB4AAFE85E8A2F5A9680355239C7E14646ED62B044E39DE154512";
|
|
7
6
|
var MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = "0x6E12D8C87503D4287c294f2Fdef96ACd9DFf6bd2";
|
|
8
7
|
var MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
|
|
@@ -10,4 +9,4 @@ var TFHE_RS_ZK_MAX_BITS = 2048;
|
|
|
10
9
|
var TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT = BigInt(1 << 30);
|
|
11
10
|
var TFHE_RS_KEY_VERSION = 2;
|
|
12
11
|
|
|
13
|
-
export { MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY, MOCKS_THRESHOLD_NETWORK_ADDRESS, MOCKS_ZK_VERIFIER_ADDRESS, MOCKS_ZK_VERIFIER_SIGNER_ADDRESS, MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY, TASK_MANAGER_ADDRESS,
|
|
12
|
+
export { MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY, MOCKS_THRESHOLD_NETWORK_ADDRESS, MOCKS_ZK_VERIFIER_ADDRESS, MOCKS_ZK_VERIFIER_SIGNER_ADDRESS, MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY, TASK_MANAGER_ADDRESS, TFHE_RS_KEY_VERSION, TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT, TFHE_RS_ZK_MAX_BITS };
|
|
@@ -65,9 +65,9 @@ var hardhat = defineChain({
|
|
|
65
65
|
name: "Hardhat",
|
|
66
66
|
network: "localhost",
|
|
67
67
|
// These are unused in the mock environment
|
|
68
|
-
coFheUrl: "http://
|
|
69
|
-
verifierUrl: "http://
|
|
70
|
-
thresholdNetworkUrl: "http://
|
|
68
|
+
coFheUrl: "http://ignored-in-mock-environment",
|
|
69
|
+
verifierUrl: "http://ignored-in-mock-environment",
|
|
70
|
+
thresholdNetworkUrl: "http://ignored-in-mock-environment",
|
|
71
71
|
environment: "MOCK"
|
|
72
72
|
});
|
|
73
73
|
|