@cofhe/sdk 0.1.1 → 0.2.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 +14 -0
- package/adapters/ethers6.ts +28 -28
- package/adapters/hardhat.ts +0 -1
- package/adapters/index.test.ts +14 -19
- package/adapters/smartWallet.ts +81 -73
- package/adapters/test-utils.ts +45 -45
- package/adapters/types.ts +3 -3
- package/chains/chains/localcofhe.ts +14 -0
- package/chains/chains.test.ts +2 -1
- package/chains/index.ts +3 -1
- package/core/baseBuilder.ts +30 -49
- package/core/client.test.ts +94 -77
- package/core/client.ts +133 -149
- package/core/clientTypes.ts +108 -0
- package/core/config.test.ts +22 -11
- package/core/config.ts +16 -9
- package/core/decrypt/decryptHandleBuilder.ts +51 -45
- package/core/decrypt/{tnSealOutput.ts → tnSealOutputV1.ts} +1 -1
- package/core/decrypt/tnSealOutputV2.ts +298 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +16 -10
- package/core/encrypt/encryptInputsBuilder.test.ts +132 -116
- package/core/encrypt/encryptInputsBuilder.ts +159 -111
- package/core/encrypt/encryptUtils.ts +6 -3
- package/core/encrypt/zkPackProveVerify.ts +70 -8
- package/core/error.ts +0 -2
- package/core/fetchKeys.test.ts +1 -18
- package/core/fetchKeys.ts +0 -26
- package/core/index.ts +29 -17
- package/core/keyStore.ts +65 -38
- package/core/permits.test.ts +253 -1
- package/core/permits.ts +80 -16
- package/core/types.ts +198 -152
- package/core/utils.ts +43 -1
- package/dist/adapters.d.cts +38 -20
- package/dist/adapters.d.ts +38 -20
- package/dist/chains.cjs +14 -1
- package/dist/chains.d.cts +23 -1
- package/dist/chains.d.ts +23 -1
- package/dist/chains.js +1 -1
- package/dist/{chunk-LU7BMUUT.js → chunk-UGBVZNRT.js} +39 -25
- package/dist/{chunk-GZCQQYVI.js → chunk-WEAZ25JO.js} +14 -2
- package/dist/{chunk-KFGPTJ6X.js → chunk-WGCRJCBR.js} +1920 -1692
- package/dist/{types-bB7wLj0q.d.cts → clientTypes-5_1nwtUe.d.cts} +308 -347
- package/dist/{types-PhwGgQvs.d.ts → clientTypes-Es7fyi65.d.ts} +308 -347
- package/dist/core.cjs +2872 -2632
- package/dist/core.d.cts +101 -6
- package/dist/core.d.ts +101 -6
- package/dist/core.js +3 -3
- package/dist/node.cjs +2716 -2520
- package/dist/node.d.cts +3 -3
- package/dist/node.d.ts +3 -3
- package/dist/node.js +4 -3
- package/dist/{permit-S9CnI6MF.d.cts → permit-fUSe6KKq.d.cts} +31 -15
- package/dist/{permit-S9CnI6MF.d.ts → permit-fUSe6KKq.d.ts} +31 -15
- package/dist/permits.cjs +39 -24
- package/dist/permits.d.cts +137 -148
- package/dist/permits.d.ts +137 -148
- package/dist/permits.js +1 -1
- package/dist/web.cjs +2929 -2518
- package/dist/web.d.cts +21 -5
- package/dist/web.d.ts +21 -5
- package/dist/web.js +185 -9
- package/dist/zkProve.worker.cjs +93 -0
- package/dist/zkProve.worker.d.cts +2 -0
- package/dist/zkProve.worker.d.ts +2 -0
- package/dist/zkProve.worker.js +91 -0
- package/node/client.test.ts +20 -25
- package/node/encryptInputs.test.ts +18 -38
- package/node/index.ts +1 -0
- package/package.json +14 -14
- package/permits/index.ts +1 -0
- package/permits/localstorage.test.ts +0 -1
- package/permits/permit.test.ts +25 -22
- package/permits/permit.ts +30 -21
- package/permits/sealing.test.ts +3 -3
- package/permits/sealing.ts +2 -2
- package/permits/store.ts +5 -7
- package/permits/test-utils.ts +1 -1
- package/permits/types.ts +17 -0
- package/permits/utils.ts +0 -1
- package/permits/validation.ts +24 -4
- package/web/client.web.test.ts +20 -25
- package/web/config.web.test.ts +0 -2
- package/web/encryptInputs.web.test.ts +31 -54
- package/web/index.ts +65 -1
- package/web/storage.ts +19 -5
- package/web/worker.builder.web.test.ts +148 -0
- package/web/worker.config.web.test.ts +329 -0
- package/web/worker.output.web.test.ts +84 -0
- package/web/workerManager.test.ts +80 -0
- package/web/workerManager.ts +214 -0
- package/web/workerManager.web.test.ts +114 -0
- package/web/zkProve.worker.ts +133 -0
- package/core/result.test.ts +0 -180
- package/core/result.ts +0 -67
- package/core/test-utils.ts +0 -45
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
|
|
2
|
+
import { Encryptable } from '@/core';
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
5
|
+
import type { PublicClient, WalletClient } from 'viem';
|
|
6
|
+
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
7
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
|
+
import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
9
|
+
import { createCofhesdkClient, createCofhesdkConfig, createCofhesdkClientWithCustomWorker } from './index.js';
|
|
10
|
+
|
|
11
|
+
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
12
|
+
|
|
13
|
+
describe('@cofhe/sdk/web - Worker Configuration Tests', () => {
|
|
14
|
+
let publicClient: PublicClient;
|
|
15
|
+
let walletClient: WalletClient;
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
publicClient = createPublicClient({
|
|
19
|
+
chain: viemArbitrumSepolia,
|
|
20
|
+
transport: http(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
24
|
+
walletClient = createWalletClient({
|
|
25
|
+
chain: viemArbitrumSepolia,
|
|
26
|
+
transport: http(),
|
|
27
|
+
account,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('useWorkers config flag', () => {
|
|
32
|
+
it('should use workers by default (useWorkers: true)', async () => {
|
|
33
|
+
const config = createCofhesdkConfig({
|
|
34
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
35
|
+
// useWorkers defaults to true
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(config.useWorkers).toBe(true);
|
|
39
|
+
|
|
40
|
+
const client = createCofhesdkClient(config);
|
|
41
|
+
await client.connect(publicClient, walletClient);
|
|
42
|
+
|
|
43
|
+
// Track step callbacks to see worker usage
|
|
44
|
+
let proveContext: any;
|
|
45
|
+
const result = await client
|
|
46
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
47
|
+
.setStepCallback((step, context) => {
|
|
48
|
+
if (step === 'prove' && context?.isEnd) {
|
|
49
|
+
proveContext = context;
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.encrypt();
|
|
53
|
+
|
|
54
|
+
expect(result).toBeDefined();
|
|
55
|
+
|
|
56
|
+
// Check that worker was attempted
|
|
57
|
+
expect(proveContext).toBeDefined();
|
|
58
|
+
expect(proveContext.useWorker).toBe(true);
|
|
59
|
+
// Note: Workers may fail to initialize in Playwright test environment
|
|
60
|
+
// due to WASM module loading issues. Verify fallback works correctly.
|
|
61
|
+
expect(proveContext.usedWorker).toBeDefined();
|
|
62
|
+
// The test cannot load tfhe module, so the worker fallback occurs.
|
|
63
|
+
// The real test is to check that usedWorker is true
|
|
64
|
+
// TBD: Find a way to test this in the test environment.
|
|
65
|
+
|
|
66
|
+
// Log if fallback occurred for debugging
|
|
67
|
+
if (!proveContext.usedWorker) {
|
|
68
|
+
console.log('Worker fallback occurred (expected in test env):', proveContext.workerFailedError);
|
|
69
|
+
expect(proveContext.workerFailedError).toBeDefined();
|
|
70
|
+
}
|
|
71
|
+
}, 60000);
|
|
72
|
+
|
|
73
|
+
it('should disable workers when useWorkers: false', async () => {
|
|
74
|
+
const config = createCofhesdkConfig({
|
|
75
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
76
|
+
useWorkers: false,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(config.useWorkers).toBe(false);
|
|
80
|
+
|
|
81
|
+
const client = createCofhesdkClient(config);
|
|
82
|
+
await client.connect(publicClient, walletClient);
|
|
83
|
+
|
|
84
|
+
// Track step callbacks
|
|
85
|
+
let proveContext: any;
|
|
86
|
+
const result = await client
|
|
87
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
88
|
+
.setStepCallback((step, context) => {
|
|
89
|
+
if (step === 'prove' && context?.isEnd) {
|
|
90
|
+
proveContext = context;
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
.encrypt();
|
|
94
|
+
|
|
95
|
+
expect(result).toBeDefined();
|
|
96
|
+
|
|
97
|
+
// Should explicitly NOT use workers
|
|
98
|
+
expect(proveContext).toBeDefined();
|
|
99
|
+
expect(proveContext.useWorker).toBe(false);
|
|
100
|
+
expect(proveContext.usedWorker).toBe(false);
|
|
101
|
+
}, 60000);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('setUseWorker() method', () => {
|
|
105
|
+
it('should override config with setUseWorker(false)', async () => {
|
|
106
|
+
const config = createCofhesdkConfig({
|
|
107
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
108
|
+
useWorkers: true, // Config says true
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const client = createCofhesdkClient(config);
|
|
112
|
+
await client.connect(publicClient, walletClient);
|
|
113
|
+
|
|
114
|
+
// Track step callbacks
|
|
115
|
+
let proveContext: any;
|
|
116
|
+
const result = await client
|
|
117
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
118
|
+
.setUseWorker(false) // Override to false
|
|
119
|
+
.setStepCallback((step, context) => {
|
|
120
|
+
if (step === 'prove' && context?.isEnd) {
|
|
121
|
+
proveContext = context;
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
.encrypt();
|
|
125
|
+
|
|
126
|
+
expect(result).toBeDefined();
|
|
127
|
+
|
|
128
|
+
// Should respect the override
|
|
129
|
+
expect(proveContext.useWorker).toBe(false);
|
|
130
|
+
expect(proveContext.usedWorker).toBe(false);
|
|
131
|
+
}, 60000);
|
|
132
|
+
|
|
133
|
+
it('should override config with setUseWorker(true)', async () => {
|
|
134
|
+
const config = createCofhesdkConfig({
|
|
135
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
136
|
+
useWorkers: false, // Config says false
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const client = createCofhesdkClient(config);
|
|
140
|
+
await client.connect(publicClient, walletClient);
|
|
141
|
+
|
|
142
|
+
// Track step callbacks
|
|
143
|
+
let proveContext: any;
|
|
144
|
+
const result = await client
|
|
145
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
146
|
+
.setUseWorker(true) // Override to true
|
|
147
|
+
.setStepCallback((step, context) => {
|
|
148
|
+
if (step === 'prove' && context?.isEnd) {
|
|
149
|
+
proveContext = context;
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
.encrypt();
|
|
153
|
+
|
|
154
|
+
expect(result).toBeDefined();
|
|
155
|
+
|
|
156
|
+
// Should use worker since we overrode to true
|
|
157
|
+
expect(proveContext.useWorker).toBe(true);
|
|
158
|
+
}, 60000);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('Step callback worker context', () => {
|
|
162
|
+
it('should include worker debug info in prove step', async () => {
|
|
163
|
+
const config = createCofhesdkConfig({
|
|
164
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const client = createCofhesdkClient(config);
|
|
168
|
+
await client.connect(publicClient, walletClient);
|
|
169
|
+
|
|
170
|
+
let proveContext: any;
|
|
171
|
+
const result = await client
|
|
172
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
173
|
+
.setStepCallback((step, context) => {
|
|
174
|
+
if (step === 'prove' && context?.isEnd) {
|
|
175
|
+
proveContext = context;
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
.encrypt();
|
|
179
|
+
|
|
180
|
+
expect(result).toBeDefined();
|
|
181
|
+
|
|
182
|
+
// Verify all worker-related fields are present
|
|
183
|
+
expect(proveContext).toBeDefined();
|
|
184
|
+
expect(proveContext).toHaveProperty('useWorker');
|
|
185
|
+
expect(proveContext).toHaveProperty('usedWorker');
|
|
186
|
+
expect(proveContext).toHaveProperty('isEnd');
|
|
187
|
+
expect(proveContext).toHaveProperty('duration');
|
|
188
|
+
|
|
189
|
+
// If worker failed, should have error message
|
|
190
|
+
if (proveContext.useWorker && !proveContext.usedWorker) {
|
|
191
|
+
expect(proveContext).toHaveProperty('workerFailedError');
|
|
192
|
+
expect(typeof proveContext.workerFailedError).toBe('string');
|
|
193
|
+
}
|
|
194
|
+
}, 60000);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('Worker fallback behavior', () => {
|
|
198
|
+
it('should fallback to main thread when worker fails', async () => {
|
|
199
|
+
const config = createCofhesdkConfig({
|
|
200
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
201
|
+
useWorkers: true,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Create a worker function that ALWAYS fails
|
|
205
|
+
const failingWorkerFn = async () => {
|
|
206
|
+
throw new Error('Worker failed intentionally');
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Inject the failing worker into the client
|
|
210
|
+
const client = createCofhesdkClientWithCustomWorker(config, failingWorkerFn);
|
|
211
|
+
await client.connect(publicClient, walletClient);
|
|
212
|
+
|
|
213
|
+
// Track step callbacks to verify fallback
|
|
214
|
+
let proveContext: any;
|
|
215
|
+
const result = await client
|
|
216
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
217
|
+
.setStepCallback((step, context) => {
|
|
218
|
+
if (step === 'prove' && context?.isEnd) {
|
|
219
|
+
proveContext = context;
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
.encrypt();
|
|
223
|
+
|
|
224
|
+
// Verify encryption succeeded via fallback to main thread
|
|
225
|
+
expect(result).toBeDefined();
|
|
226
|
+
expect(result.length).toBe(1);
|
|
227
|
+
|
|
228
|
+
// Verify worker was attempted but failed, triggering fallback
|
|
229
|
+
expect(proveContext).toBeDefined();
|
|
230
|
+
expect(proveContext.useWorker).toBe(true); // Worker was requested
|
|
231
|
+
expect(proveContext.usedWorker).toBe(false); // But it failed
|
|
232
|
+
expect(proveContext.workerFailedError).toBe('Worker failed intentionally');
|
|
233
|
+
}, 60000);
|
|
234
|
+
|
|
235
|
+
it('should fallback when encrypting multiple values', async () => {
|
|
236
|
+
const config = createCofhesdkConfig({
|
|
237
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
238
|
+
useWorkers: true,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Failing worker with different error message
|
|
242
|
+
const failingWorkerFn = async () => {
|
|
243
|
+
throw new Error('Worker unavailable');
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const client = createCofhesdkClientWithCustomWorker(config, failingWorkerFn);
|
|
247
|
+
await client.connect(publicClient, walletClient);
|
|
248
|
+
|
|
249
|
+
let proveContext: any;
|
|
250
|
+
const result = await client
|
|
251
|
+
.encryptInputs([Encryptable.uint128(100n), Encryptable.uint64(50n), Encryptable.bool(true)])
|
|
252
|
+
.setStepCallback((step, context) => {
|
|
253
|
+
if (step === 'prove' && context?.isEnd) {
|
|
254
|
+
proveContext = context;
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.encrypt();
|
|
258
|
+
|
|
259
|
+
// All values should encrypt successfully via fallback
|
|
260
|
+
expect(result).toBeDefined();
|
|
261
|
+
expect(result.length).toBe(3);
|
|
262
|
+
|
|
263
|
+
// Verify fallback occurred
|
|
264
|
+
expect(proveContext.useWorker).toBe(true);
|
|
265
|
+
expect(proveContext.usedWorker).toBe(false);
|
|
266
|
+
expect(proveContext.workerFailedError).toBe('Worker unavailable');
|
|
267
|
+
}, 60000);
|
|
268
|
+
|
|
269
|
+
it('should handle async worker errors gracefully', async () => {
|
|
270
|
+
const config = createCofhesdkConfig({
|
|
271
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
272
|
+
useWorkers: true,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Worker that fails after a delay
|
|
276
|
+
const asyncFailingWorkerFn = async () => {
|
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
278
|
+
throw new Error('Async worker failure');
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const client = createCofhesdkClientWithCustomWorker(config, asyncFailingWorkerFn);
|
|
282
|
+
await client.connect(publicClient, walletClient);
|
|
283
|
+
|
|
284
|
+
let proveContext: any;
|
|
285
|
+
const result = await client
|
|
286
|
+
.encryptInputs([Encryptable.uint8(42n)])
|
|
287
|
+
.setStepCallback((step, context) => {
|
|
288
|
+
if (step === 'prove' && context?.isEnd) {
|
|
289
|
+
proveContext = context;
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
.encrypt();
|
|
293
|
+
|
|
294
|
+
expect(result).toBeDefined();
|
|
295
|
+
|
|
296
|
+
expect(proveContext.useWorker).toBe(true);
|
|
297
|
+
expect(proveContext.usedWorker).toBe(false);
|
|
298
|
+
expect(proveContext.workerFailedError).toBe('Async worker failure');
|
|
299
|
+
}, 60000);
|
|
300
|
+
|
|
301
|
+
it('should work without worker when explicitly disabled', async () => {
|
|
302
|
+
const config = createCofhesdkConfig({
|
|
303
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
304
|
+
useWorkers: true, // Config says use workers
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const client = createCofhesdkClient(config);
|
|
308
|
+
await client.connect(publicClient, walletClient);
|
|
309
|
+
|
|
310
|
+
let proveContext: any;
|
|
311
|
+
const result = await client
|
|
312
|
+
.encryptInputs([Encryptable.uint8(42n)])
|
|
313
|
+
.setUseWorker(false) // But override to disable worker
|
|
314
|
+
.setStepCallback((step, context) => {
|
|
315
|
+
if (step === 'prove' && context?.isEnd) {
|
|
316
|
+
proveContext = context;
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
.encrypt();
|
|
320
|
+
|
|
321
|
+
expect(result).toBeDefined();
|
|
322
|
+
|
|
323
|
+
// Should NOT attempt worker at all
|
|
324
|
+
expect(proveContext.useWorker).toBe(false);
|
|
325
|
+
expect(proveContext.usedWorker).toBe(false);
|
|
326
|
+
expect(proveContext.workerFailedError).toBeUndefined();
|
|
327
|
+
}, 60000);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
|
|
2
|
+
import { Encryptable } from '@/core';
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
5
|
+
import type { PublicClient, WalletClient } from 'viem';
|
|
6
|
+
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
7
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
|
+
import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
9
|
+
import { createCofhesdkClient, createCofhesdkConfig } from './index.js';
|
|
10
|
+
|
|
11
|
+
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
12
|
+
|
|
13
|
+
describe('@cofhe/sdk/web - Worker vs Main Thread Output Validation', () => {
|
|
14
|
+
let publicClient: PublicClient;
|
|
15
|
+
let walletClient: WalletClient;
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
publicClient = createPublicClient({
|
|
19
|
+
chain: viemArbitrumSepolia,
|
|
20
|
+
transport: http(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
24
|
+
walletClient = createWalletClient({
|
|
25
|
+
chain: viemArbitrumSepolia,
|
|
26
|
+
transport: http(),
|
|
27
|
+
account,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should produce consistent output format regardless of worker usage', async () => {
|
|
32
|
+
// Create two clients - one with workers, one without
|
|
33
|
+
const configWithWorker = createCofhesdkConfig({
|
|
34
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
35
|
+
useWorkers: true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const configWithoutWorker = createCofhesdkConfig({
|
|
39
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
40
|
+
useWorkers: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const clientWithWorker = createCofhesdkClient(configWithWorker);
|
|
44
|
+
const clientWithoutWorker = createCofhesdkClient(configWithoutWorker);
|
|
45
|
+
|
|
46
|
+
await clientWithWorker.connect(publicClient, walletClient);
|
|
47
|
+
await clientWithoutWorker.connect(publicClient, walletClient);
|
|
48
|
+
|
|
49
|
+
const value = Encryptable.uint128(12345n);
|
|
50
|
+
|
|
51
|
+
const [resultWithWorker, resultWithoutWorker] = await Promise.all([
|
|
52
|
+
clientWithWorker.encryptInputs([value]).encrypt(),
|
|
53
|
+
clientWithoutWorker.encryptInputs([value]).encrypt(),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
// Both should succeed
|
|
57
|
+
expect(resultWithWorker).toBeDefined();
|
|
58
|
+
expect(resultWithoutWorker).toBeDefined();
|
|
59
|
+
|
|
60
|
+
// Both should have same structure (but different encrypted values)
|
|
61
|
+
const withWorker = resultWithWorker[0];
|
|
62
|
+
const withoutWorker = resultWithoutWorker[0];
|
|
63
|
+
|
|
64
|
+
expect(withWorker).toHaveProperty('ctHash');
|
|
65
|
+
expect(withWorker).toHaveProperty('signature');
|
|
66
|
+
expect(withWorker).toHaveProperty('utype');
|
|
67
|
+
expect(withWorker).toHaveProperty('securityZone');
|
|
68
|
+
expect(withoutWorker).toHaveProperty('ctHash');
|
|
69
|
+
expect(withoutWorker).toHaveProperty('signature');
|
|
70
|
+
expect(withoutWorker).toHaveProperty('utype');
|
|
71
|
+
expect(withoutWorker).toHaveProperty('securityZone');
|
|
72
|
+
|
|
73
|
+
// Format should be identical
|
|
74
|
+
expect(typeof withWorker.ctHash).toBe('bigint');
|
|
75
|
+
expect(typeof withoutWorker.ctHash).toBe('bigint');
|
|
76
|
+
expect(withWorker.signature.startsWith('0x')).toBe(true);
|
|
77
|
+
expect(withoutWorker.signature.startsWith('0x')).toBe(true);
|
|
78
|
+
expect(typeof withWorker.utype).toBe('number');
|
|
79
|
+
expect(typeof withoutWorker.utype).toBe('number');
|
|
80
|
+
|
|
81
|
+
// Note: The actual encrypted values will differ because of randomness
|
|
82
|
+
// in the encryption process, so we don't check equality
|
|
83
|
+
}, 90000);
|
|
84
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
|
|
3
|
+
|
|
4
|
+
describe('WorkerManager', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
// Clean up worker after each test
|
|
7
|
+
terminateWorker();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('areWorkersAvailable', () => {
|
|
11
|
+
it('should return false in Node.js environment', () => {
|
|
12
|
+
// In Node.js/Vitest, workers aren't available
|
|
13
|
+
expect(areWorkersAvailable()).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('getWorkerManager', () => {
|
|
18
|
+
it('should return a singleton instance', () => {
|
|
19
|
+
const manager1 = getWorkerManager();
|
|
20
|
+
const manager2 = getWorkerManager();
|
|
21
|
+
expect(manager1).toBe(manager2);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should create new instance after termination', () => {
|
|
25
|
+
const manager1 = getWorkerManager();
|
|
26
|
+
terminateWorker();
|
|
27
|
+
const manager2 = getWorkerManager();
|
|
28
|
+
|
|
29
|
+
// After termination, a new instance should be created
|
|
30
|
+
expect(manager1).toBeDefined();
|
|
31
|
+
expect(manager2).toBeDefined();
|
|
32
|
+
// They should NOT be the same instance anymore
|
|
33
|
+
expect(manager1).not.toBe(manager2);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('submitProof', () => {
|
|
38
|
+
it('should throw immediately when workers not available', async () => {
|
|
39
|
+
if (!areWorkersAvailable()) {
|
|
40
|
+
const manager = getWorkerManager();
|
|
41
|
+
|
|
42
|
+
await expect(manager.submitProof('invalid', 'invalid', [], new Uint8Array())).rejects.toThrow(
|
|
43
|
+
'Web Workers not supported'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('terminateWorker', () => {
|
|
50
|
+
it('should clean up worker instance and allow getting a new one', () => {
|
|
51
|
+
const manager1 = getWorkerManager();
|
|
52
|
+
expect(manager1).toBeDefined();
|
|
53
|
+
|
|
54
|
+
// Terminate the worker
|
|
55
|
+
terminateWorker();
|
|
56
|
+
|
|
57
|
+
// After termination, should be able to get a new instance
|
|
58
|
+
const manager2 = getWorkerManager();
|
|
59
|
+
expect(manager2).toBeDefined();
|
|
60
|
+
|
|
61
|
+
// The new instance should be different from the terminated one
|
|
62
|
+
expect(manager2).not.toBe(manager1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should reject pending requests when terminated', async () => {
|
|
66
|
+
if (!areWorkersAvailable()) {
|
|
67
|
+
const manager = getWorkerManager();
|
|
68
|
+
|
|
69
|
+
// Start a request that will fail
|
|
70
|
+
const requestPromise = manager.submitProof('test', 'test', [], new Uint8Array());
|
|
71
|
+
|
|
72
|
+
// Terminate while request is pending
|
|
73
|
+
terminateWorker();
|
|
74
|
+
|
|
75
|
+
// The pending request should be rejected
|
|
76
|
+
await expect(requestPromise).rejects.toThrow();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Manager for ZK Proof Generation
|
|
3
|
+
* Manages worker lifecycle and request/response handling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ZkProveWorkerRequest, ZkProveWorkerResponse } from '@/core';
|
|
7
|
+
|
|
8
|
+
// Declare Worker type for environments where it's not available
|
|
9
|
+
declare const Worker: any;
|
|
10
|
+
|
|
11
|
+
interface PendingRequest {
|
|
12
|
+
resolve: (value: Uint8Array) => void;
|
|
13
|
+
reject: (error: Error) => void;
|
|
14
|
+
timeoutId: ReturnType<typeof setTimeout>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class ZkProveWorkerManager {
|
|
18
|
+
private worker: (typeof Worker extends new (...args: any[]) => infer W ? W : any) | null = null;
|
|
19
|
+
private pendingRequests = new Map<string, PendingRequest>();
|
|
20
|
+
private requestCounter = 0;
|
|
21
|
+
private workerReady = false;
|
|
22
|
+
private initializationPromise: Promise<void> | null = null;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the worker
|
|
26
|
+
*/
|
|
27
|
+
private async initializeWorker(): Promise<void> {
|
|
28
|
+
if (this.worker && this.workerReady) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (this.initializationPromise) {
|
|
33
|
+
return this.initializationPromise;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.initializationPromise = new Promise((resolve, reject) => {
|
|
37
|
+
try {
|
|
38
|
+
// Check if Worker is supported
|
|
39
|
+
if (typeof Worker === 'undefined') {
|
|
40
|
+
reject(new Error('Web Workers not supported'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create worker
|
|
45
|
+
// Note: In production, this will try to load the worker from the same directory
|
|
46
|
+
// The bundler should handle this Worker instantiation
|
|
47
|
+
try {
|
|
48
|
+
this.worker = new Worker(new URL('./zkProve.worker.js', import.meta.url), { type: 'module' }) as any;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// If Worker creation fails, reject immediately
|
|
51
|
+
reject(new Error(`Failed to create worker: ${error}`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Set up message handler
|
|
56
|
+
(this.worker as any).onmessage = (event: any) => {
|
|
57
|
+
const { id, type, result, error } = event.data as ZkProveWorkerResponse;
|
|
58
|
+
|
|
59
|
+
// Handle ready signal
|
|
60
|
+
if (type === 'ready') {
|
|
61
|
+
this.workerReady = true;
|
|
62
|
+
resolve();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Handle proof responses
|
|
67
|
+
const pending = this.pendingRequests.get(id);
|
|
68
|
+
if (!pending) {
|
|
69
|
+
console.warn('[Worker Manager] Received response for unknown request:', id);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Clear timeout
|
|
74
|
+
clearTimeout(pending.timeoutId);
|
|
75
|
+
this.pendingRequests.delete(id);
|
|
76
|
+
|
|
77
|
+
if (type === 'success' && result) {
|
|
78
|
+
pending.resolve(new Uint8Array(result));
|
|
79
|
+
} else if (type === 'error') {
|
|
80
|
+
pending.reject(new Error(error || 'Worker error'));
|
|
81
|
+
} else {
|
|
82
|
+
pending.reject(new Error('Invalid response from worker'));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Set up error handler
|
|
87
|
+
(this.worker as any).onerror = (error: any) => {
|
|
88
|
+
console.error('[Worker Manager] Worker error event:', error);
|
|
89
|
+
console.error('[Worker Manager] Error message:', error.message);
|
|
90
|
+
console.error('[Worker Manager] Error filename:', error.filename);
|
|
91
|
+
console.error('[Worker Manager] Error lineno:', error.lineno);
|
|
92
|
+
|
|
93
|
+
// Reject initialization if not ready yet
|
|
94
|
+
if (!this.workerReady) {
|
|
95
|
+
reject(new Error(`Worker failed to initialize: ${error.message || 'Unknown error'}`));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Reject all pending requests
|
|
99
|
+
this.pendingRequests.forEach(({ reject, timeoutId }) => {
|
|
100
|
+
clearTimeout(timeoutId);
|
|
101
|
+
reject(new Error('Worker encountered an error'));
|
|
102
|
+
});
|
|
103
|
+
this.pendingRequests.clear();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Timeout if worker doesn't signal ready within 5 seconds
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
if (!this.workerReady) {
|
|
109
|
+
reject(new Error('Worker initialization timeout'));
|
|
110
|
+
}
|
|
111
|
+
}, 5000);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
reject(error);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return this.initializationPromise;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Submit a proof generation request to the worker
|
|
122
|
+
*/
|
|
123
|
+
async submitProof(
|
|
124
|
+
fheKeyHex: string,
|
|
125
|
+
crsHex: string,
|
|
126
|
+
items: Array<{ utype: string; data: any }>,
|
|
127
|
+
metadata: Uint8Array
|
|
128
|
+
): Promise<Uint8Array> {
|
|
129
|
+
// Initialize worker if needed
|
|
130
|
+
await this.initializeWorker();
|
|
131
|
+
|
|
132
|
+
// Generate unique request ID
|
|
133
|
+
const id = `zkprove-${Date.now()}-${this.requestCounter++}`;
|
|
134
|
+
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
// Set up timeout (30 seconds)
|
|
137
|
+
const timeoutId = setTimeout(() => {
|
|
138
|
+
this.pendingRequests.delete(id);
|
|
139
|
+
reject(new Error('Worker request timeout (30s)'));
|
|
140
|
+
}, 30000);
|
|
141
|
+
|
|
142
|
+
// Store pending request
|
|
143
|
+
this.pendingRequests.set(id, { resolve, reject, timeoutId });
|
|
144
|
+
|
|
145
|
+
// Send message to worker
|
|
146
|
+
const message: ZkProveWorkerRequest = {
|
|
147
|
+
id,
|
|
148
|
+
type: 'zkProve',
|
|
149
|
+
fheKeyHex,
|
|
150
|
+
crsHex,
|
|
151
|
+
items,
|
|
152
|
+
metadata: Array.from(metadata),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
(this.worker as any).postMessage(message);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Terminate the worker and clean up
|
|
161
|
+
*/
|
|
162
|
+
terminate(): void {
|
|
163
|
+
if (this.worker) {
|
|
164
|
+
(this.worker as any).terminate();
|
|
165
|
+
this.worker = null;
|
|
166
|
+
this.workerReady = false;
|
|
167
|
+
this.initializationPromise = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Reject all pending requests
|
|
171
|
+
this.pendingRequests.forEach(({ reject, timeoutId }) => {
|
|
172
|
+
clearTimeout(timeoutId);
|
|
173
|
+
reject(new Error('Worker terminated'));
|
|
174
|
+
});
|
|
175
|
+
this.pendingRequests.clear();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if worker is available
|
|
180
|
+
*/
|
|
181
|
+
isAvailable(): boolean {
|
|
182
|
+
return typeof Worker !== 'undefined';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Singleton instance
|
|
187
|
+
let workerManager: ZkProveWorkerManager | null = null;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get the worker manager instance
|
|
191
|
+
*/
|
|
192
|
+
export function getWorkerManager(): ZkProveWorkerManager {
|
|
193
|
+
if (!workerManager) {
|
|
194
|
+
workerManager = new ZkProveWorkerManager();
|
|
195
|
+
}
|
|
196
|
+
return workerManager;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Terminate the worker
|
|
201
|
+
*/
|
|
202
|
+
export function terminateWorker(): void {
|
|
203
|
+
if (workerManager) {
|
|
204
|
+
workerManager.terminate();
|
|
205
|
+
workerManager = null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Check if workers are available
|
|
211
|
+
*/
|
|
212
|
+
export function areWorkersAvailable(): boolean {
|
|
213
|
+
return typeof Worker !== 'undefined';
|
|
214
|
+
}
|