@cofhe/sdk 0.1.1 → 0.2.1
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 +22 -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/defineChain.ts +2 -2
- package/chains/index.ts +3 -1
- package/chains/types.ts +3 -3
- package/core/baseBuilder.ts +30 -49
- package/core/client.test.ts +200 -72
- package/core/client.ts +152 -148
- package/core/clientTypes.ts +114 -0
- package/core/config.test.ts +30 -11
- package/core/config.ts +26 -13
- package/core/consts.ts +18 -0
- package/core/decrypt/cofheMocksSealOutput.ts +2 -4
- 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 +15 -16
- 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 +37 -17
- package/core/keyStore.ts +65 -38
- package/core/permits.test.ts +255 -4
- package/core/permits.ts +83 -18
- 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 +18 -8
- package/dist/chains.d.cts +31 -9
- package/dist/chains.d.ts +31 -9
- package/dist/chains.js +1 -1
- package/dist/{chunk-KFGPTJ6X.js → chunk-I5WFEYXX.js} +1768 -1526
- package/dist/{chunk-LU7BMUUT.js → chunk-R3B5TMVX.js} +330 -197
- package/dist/{chunk-GZCQQYVI.js → chunk-TBLR7NNE.js} +18 -9
- package/dist/{types-PhwGgQvs.d.ts → clientTypes-RqkgkV2i.d.ts} +331 -429
- package/dist/{types-bB7wLj0q.d.cts → clientTypes-e4filDzK.d.cts} +331 -429
- package/dist/core.cjs +3000 -2625
- package/dist/core.d.cts +113 -7
- package/dist/core.d.ts +113 -7
- package/dist/core.js +3 -3
- package/dist/node.cjs +2851 -2526
- package/dist/node.d.cts +4 -4
- package/dist/node.d.ts +4 -4
- package/dist/node.js +4 -3
- package/dist/{permit-S9CnI6MF.d.cts → permit-MZ502UBl.d.cts} +54 -41
- package/dist/{permit-S9CnI6MF.d.ts → permit-MZ502UBl.d.ts} +54 -41
- package/dist/permits.cjs +328 -195
- package/dist/permits.d.cts +113 -825
- package/dist/permits.d.ts +113 -825
- package/dist/permits.js +1 -1
- package/dist/types-YiAC4gig.d.cts +33 -0
- package/dist/types-YiAC4gig.d.ts +33 -0
- package/dist/web.cjs +3067 -2527
- package/dist/web.d.cts +22 -6
- package/dist/web.d.ts +22 -6
- 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 +15 -15
- package/permits/index.ts +1 -0
- package/permits/localstorage.test.ts +9 -14
- package/permits/onchain-utils.ts +221 -0
- package/permits/permit.test.ts +76 -27
- package/permits/permit.ts +58 -95
- package/permits/sealing.test.ts +3 -3
- package/permits/sealing.ts +2 -2
- package/permits/store.test.ts +10 -50
- package/permits/store.ts +9 -21
- package/permits/test-utils.ts +11 -3
- package/permits/types.ts +39 -9
- package/permits/utils.ts +0 -5
- package/permits/validation.test.ts +29 -32
- package/permits/validation.ts +114 -176
- 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
- package/dist/types-KImPrEIe.d.cts +0 -48
- package/dist/types-KImPrEIe.d.ts +0 -48
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
|
|
3
|
+
|
|
4
|
+
describe('WorkerManager (Browser)', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
// Clean up worker after each test
|
|
7
|
+
terminateWorker();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('areWorkersAvailable', () => {
|
|
11
|
+
it('should return true in browser environment', () => {
|
|
12
|
+
// In real browser with Playwright, workers should be available
|
|
13
|
+
expect(areWorkersAvailable()).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Worker lifecycle', () => {
|
|
18
|
+
it('should initialize worker successfully', async () => {
|
|
19
|
+
const manager = getWorkerManager();
|
|
20
|
+
expect(manager).toBeDefined();
|
|
21
|
+
|
|
22
|
+
// Worker should be available
|
|
23
|
+
expect(areWorkersAvailable()).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle worker termination', () => {
|
|
27
|
+
const manager = getWorkerManager();
|
|
28
|
+
expect(manager).toBeDefined();
|
|
29
|
+
|
|
30
|
+
terminateWorker();
|
|
31
|
+
|
|
32
|
+
// Should be able to get a new instance
|
|
33
|
+
const newManager = getWorkerManager();
|
|
34
|
+
expect(newManager).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('submitProof', () => {
|
|
39
|
+
it('should reject invalid data with error message', async () => {
|
|
40
|
+
const manager = getWorkerManager();
|
|
41
|
+
|
|
42
|
+
// Submit with invalid data - should fail quickly
|
|
43
|
+
await expect(manager.submitProof('invalid', 'invalid', [], new Uint8Array())).rejects.toThrow();
|
|
44
|
+
}, 35000);
|
|
45
|
+
|
|
46
|
+
it('should reject invalid message type', async () => {
|
|
47
|
+
const manager = getWorkerManager();
|
|
48
|
+
|
|
49
|
+
// Submit with invalid data that will cause worker error
|
|
50
|
+
await expect(
|
|
51
|
+
manager.submitProof(
|
|
52
|
+
'', // empty key
|
|
53
|
+
'', // empty crs
|
|
54
|
+
[{ utype: 'invalid', data: 'test' }],
|
|
55
|
+
new Uint8Array([1, 2, 3])
|
|
56
|
+
)
|
|
57
|
+
).rejects.toThrow();
|
|
58
|
+
}, 35000);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Concurrent requests', () => {
|
|
62
|
+
it('should handle multiple concurrent proof requests', async () => {
|
|
63
|
+
const manager = getWorkerManager();
|
|
64
|
+
|
|
65
|
+
// Submit multiple requests concurrently
|
|
66
|
+
const requests = [
|
|
67
|
+
manager.submitProof('key1', 'crs1', [], new Uint8Array([1])),
|
|
68
|
+
manager.submitProof('key2', 'crs2', [], new Uint8Array([2])),
|
|
69
|
+
manager.submitProof('key3', 'crs3', [], new Uint8Array([3])),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// All should reject (invalid data), but shouldn't crash
|
|
73
|
+
const results = await Promise.allSettled(requests);
|
|
74
|
+
|
|
75
|
+
expect(results).toHaveLength(3);
|
|
76
|
+
results.forEach((result) => {
|
|
77
|
+
expect(result.status).toBe('rejected');
|
|
78
|
+
});
|
|
79
|
+
}, 35000);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('Worker error handling', () => {
|
|
83
|
+
it('should handle worker errors gracefully', async () => {
|
|
84
|
+
const manager = getWorkerManager();
|
|
85
|
+
|
|
86
|
+
// This should cause an error in the worker
|
|
87
|
+
await expect(
|
|
88
|
+
manager.submitProof(
|
|
89
|
+
'malformed-hex',
|
|
90
|
+
'malformed-crs',
|
|
91
|
+
[{ utype: 'uint128', data: 'not-a-number' }],
|
|
92
|
+
new Uint8Array([])
|
|
93
|
+
)
|
|
94
|
+
).rejects.toThrow();
|
|
95
|
+
}, 35000);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Termination during processing', () => {
|
|
99
|
+
it('should reject pending requests on termination', async () => {
|
|
100
|
+
const manager = getWorkerManager();
|
|
101
|
+
|
|
102
|
+
// Start a request (will timeout)
|
|
103
|
+
const proofPromise = manager.submitProof('test', 'test', [], new Uint8Array());
|
|
104
|
+
|
|
105
|
+
// Terminate immediately
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
terminateWorker();
|
|
108
|
+
}, 100);
|
|
109
|
+
|
|
110
|
+
// Request should be rejected
|
|
111
|
+
await expect(proofPromise).rejects.toThrow();
|
|
112
|
+
}, 5000);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Worker for ZK Proof Generation
|
|
3
|
+
* Performs heavy WASM computation off the main thread
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/// <reference lib="webworker" />
|
|
7
|
+
/* eslint-disable no-undef */
|
|
8
|
+
|
|
9
|
+
import type { ZkProveWorkerRequest, ZkProveWorkerResponse } from '../core/encrypt/zkPackProveVerify.js';
|
|
10
|
+
|
|
11
|
+
// TFHE module (will be initialized on first use)
|
|
12
|
+
let tfheModule: any = null;
|
|
13
|
+
let initialized = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize TFHE in worker context
|
|
17
|
+
*/
|
|
18
|
+
async function initTfhe() {
|
|
19
|
+
if (initialized) return;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Dynamic import of tfhe module
|
|
23
|
+
tfheModule = await import('tfhe');
|
|
24
|
+
await tfheModule.default();
|
|
25
|
+
await tfheModule.init_panic_hook();
|
|
26
|
+
initialized = true;
|
|
27
|
+
console.log('[Worker] TFHE initialized');
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('[Worker] Failed to initialize TFHE:', error);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convert hex string to Uint8Array
|
|
36
|
+
*/
|
|
37
|
+
function fromHexString(hexString: string): Uint8Array {
|
|
38
|
+
const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
|
|
39
|
+
const arr = cleanString.replace(/^0x/, '').match(/.{1,2}/g);
|
|
40
|
+
if (!arr) return new Uint8Array();
|
|
41
|
+
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Main message handler
|
|
46
|
+
*/
|
|
47
|
+
self.onmessage = async (event: MessageEvent) => {
|
|
48
|
+
const { id, type, fheKeyHex, crsHex, items, metadata } = event.data as ZkProveWorkerRequest;
|
|
49
|
+
|
|
50
|
+
if (type !== 'zkProve') {
|
|
51
|
+
self.postMessage({
|
|
52
|
+
id,
|
|
53
|
+
type: 'error',
|
|
54
|
+
error: 'Invalid message type',
|
|
55
|
+
} as ZkProveWorkerResponse);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Initialize TFHE if needed
|
|
61
|
+
await initTfhe();
|
|
62
|
+
|
|
63
|
+
if (!tfheModule) {
|
|
64
|
+
throw new Error('TFHE module not initialized');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Deserialize FHE public key and CRS from hex strings
|
|
68
|
+
const fheKeyBytes = fromHexString(fheKeyHex);
|
|
69
|
+
const crsBytes = fromHexString(crsHex);
|
|
70
|
+
|
|
71
|
+
const fheKey = tfheModule.TfheCompactPublicKey.deserialize(fheKeyBytes);
|
|
72
|
+
const crs = tfheModule.CompactPkeCrs.deserialize(crsBytes);
|
|
73
|
+
|
|
74
|
+
// Create builder
|
|
75
|
+
const builder = tfheModule.ProvenCompactCiphertextList.builder(fheKey);
|
|
76
|
+
|
|
77
|
+
// Pack all items (duplicate of zkPack logic)
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
switch (item.utype) {
|
|
80
|
+
case 'bool':
|
|
81
|
+
builder.push_boolean(Boolean(item.data));
|
|
82
|
+
break;
|
|
83
|
+
case 'uint8':
|
|
84
|
+
builder.push_u8(Number(item.data));
|
|
85
|
+
break;
|
|
86
|
+
case 'uint16':
|
|
87
|
+
builder.push_u16(Number(item.data));
|
|
88
|
+
break;
|
|
89
|
+
case 'uint32':
|
|
90
|
+
builder.push_u32(Number(item.data));
|
|
91
|
+
break;
|
|
92
|
+
case 'uint64':
|
|
93
|
+
builder.push_u64(BigInt(item.data));
|
|
94
|
+
break;
|
|
95
|
+
case 'uint128':
|
|
96
|
+
builder.push_u128(BigInt(item.data));
|
|
97
|
+
break;
|
|
98
|
+
case 'uint160':
|
|
99
|
+
builder.push_u160(BigInt(item.data));
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
throw new Error(`Unsupported type: ${item.utype}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// THE HEAVY OPERATION - but in worker thread!
|
|
107
|
+
const metadataBytes = new Uint8Array(metadata);
|
|
108
|
+
const compactList = builder.build_with_proof_packed(crs, metadataBytes, 1);
|
|
109
|
+
|
|
110
|
+
// Serialize result
|
|
111
|
+
const result = compactList.serialize();
|
|
112
|
+
|
|
113
|
+
// Send success response
|
|
114
|
+
self.postMessage({
|
|
115
|
+
id,
|
|
116
|
+
type: 'success',
|
|
117
|
+
result: Array.from(result),
|
|
118
|
+
} as ZkProveWorkerResponse);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// Send error response
|
|
121
|
+
self.postMessage({
|
|
122
|
+
id,
|
|
123
|
+
type: 'error',
|
|
124
|
+
error: error instanceof Error ? error.message : String(error),
|
|
125
|
+
} as ZkProveWorkerResponse);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Signal ready - send proper message format
|
|
130
|
+
self.postMessage({
|
|
131
|
+
id: 'init',
|
|
132
|
+
type: 'ready',
|
|
133
|
+
} as ZkProveWorkerResponse);
|