@cofhe/sdk 0.7.1 → 1.0.0-alpha.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 +35 -0
- package/acps/acp.ts +35 -7
- package/acps/index.ts +28 -12
- package/acps/onchain-utils.ts +188 -49
- package/acps/registry.ts +231 -0
- package/acps/sealing.ts +9 -3
- package/acps/share-metadata/appKey.ts +8 -0
- package/acps/share-metadata/decode.ts +120 -0
- package/acps/share-metadata/describe.ts +198 -0
- package/acps/share-metadata/encode.ts +147 -0
- package/acps/share-metadata/erc7984.ts +26 -0
- package/acps/share-metadata/index.ts +9 -0
- package/acps/share-metadata/layout.ts +69 -0
- package/acps/share-metadata/presets.ts +116 -0
- package/acps/share-metadata/schema.ts +47 -0
- package/acps/share-metadata/test/codec.test.ts +140 -0
- package/acps/share-metadata/test/describe.test.ts +112 -0
- package/acps/share-metadata/test/fixtures.ts +157 -0
- package/acps/share-metadata/test/live.test.ts +56 -0
- package/acps/share-metadata/test/presets.test.ts +119 -0
- package/acps/share-metadata/test/verify.test.ts +144 -0
- package/acps/share-metadata/verify.ts +155 -0
- package/acps/store.ts +48 -18
- package/acps/test/acp.test.ts +102 -7
- package/acps/test/localstorage.test.ts +28 -1
- package/acps/test/onchainAccessStatus.test.ts +116 -0
- package/acps/test/registry.test.ts +144 -0
- package/acps/test/sealing.test.ts +23 -0
- package/acps/test/store.test.ts +71 -0
- package/acps/test/utils.test.ts +63 -0
- package/acps/test/validation.test.ts +0 -4
- package/acps/types.ts +59 -6
- package/acps/utils.ts +15 -2
- package/acps/validation.ts +3 -1
- package/chains/chains/arbitrum.ts +14 -0
- package/chains/chains/ethereum.ts +14 -0
- package/chains/index.ts +5 -1
- package/chains/test/chains.test.ts +24 -3
- package/core/acps.ts +164 -207
- package/core/client.ts +44 -13
- package/core/clientTypes.ts +50 -7
- package/core/config.ts +33 -1
- package/core/consts.ts +14 -2
- package/core/decrypt/apiError.ts +4 -1
- package/core/decrypt/verifyDecryptResult.ts +8 -4
- package/core/encrypt/cofheMocksZkVerifySign.ts +19 -2
- package/core/encrypt/encryptInputsBuilder.ts +6 -0
- package/core/encrypt/zkPackProveVerify.ts +7 -1
- package/core/error.ts +1 -0
- package/core/fetchKeys.ts +2 -2
- package/core/index.ts +9 -1
- package/core/keyStore.ts +23 -10
- package/core/taskManager.ts +48 -0
- package/core/test/acpDefaults.test.ts +108 -19
- package/core/test/acpStoreAccountCase.test.ts +80 -0
- package/core/test/acps.test.ts +128 -3
- package/core/test/apiError.test.ts +8 -0
- package/core/test/appKeyConfig.test.ts +94 -0
- package/core/test/client.test.ts +41 -0
- package/core/test/config.test.ts +24 -0
- package/core/test/decrypt.test.ts +11 -4
- package/core/test/decryptBuilders.test.ts +110 -2
- package/core/test/encryptInputsBuilder.test.ts +1 -1
- package/core/test/fetchKeys.test.ts +23 -2
- package/core/test/incomingShares.test.ts +84 -0
- package/core/test/keyStore.test.ts +18 -6
- package/core/test/mocksEncryptRange.test.ts +90 -0
- package/core/test/taskManager.test.ts +57 -0
- package/core/test/utils.test.ts +128 -0
- package/core/test/verifyDecryptResult.test.ts +51 -0
- package/core/types.ts +8 -0
- package/core/utils.ts +16 -9
- package/dist/acps.cjs +984 -64
- package/dist/acps.d.cts +589 -10
- package/dist/acps.d.ts +589 -10
- package/dist/acps.js +2 -2
- package/dist/chains.cjs +26 -0
- package/dist/chains.d.cts +45 -1
- package/dist/chains.d.ts +45 -1
- package/dist/chains.js +1 -1
- package/dist/chunk-6ZW76ZKB.js +228 -0
- package/dist/chunk-BQWHPIYU.js +1828 -0
- package/dist/chunk-KSOV5B4B.js +50 -0
- package/dist/{chunk-43USWPEH.js → chunk-OL3DZN7A.js} +159 -523
- package/dist/{chunk-N6IDQRRU.js → chunk-UKQAJK4N.js} +25 -1
- package/dist/chunk-XQ32JG3T.js +174 -0
- package/dist/{clientTypes-CYZjFznO.d.cts → clientTypes-C0A9ji5O.d.cts} +132 -29
- package/dist/{clientTypes-BN3nbzYM.d.ts → clientTypes-CcU5WvdV.d.ts} +132 -29
- package/dist/core.cjs +3269 -2626
- package/dist/core.d.cts +32 -8
- package/dist/core.d.ts +32 -8
- package/dist/core.js +5 -4
- package/dist/node.cjs +3074 -2436
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +20 -19
- package/dist/{acp-Wi6isVQI.d.cts → onchain-utils-D9Z33IP7.d.cts} +166 -12
- package/dist/{acp-Wi6isVQI.d.ts → onchain-utils-D9Z33IP7.d.ts} +166 -12
- package/dist/web.cjs +3204 -2493
- package/dist/web.d.cts +49 -3
- package/dist/web.d.ts +49 -3
- package/dist/web.js +52 -27
- package/dist/zkProve.worker.cjs +79 -18
- package/dist/zkProve.worker.js +24 -24
- package/node/index.ts +1 -10
- package/node/storage.ts +20 -9
- package/node/test/inherited.test.ts +5 -1
- package/node/test/storage-memory-fallback.test.ts +47 -0
- package/node/test/storage-permissions.test.ts +40 -0
- package/package.json +3 -1
- package/web/index.ts +101 -36
- package/web/test/countingZkProveWorker.ts +31 -0
- package/web/test/inherited.web.test.ts +5 -1
- package/web/test/mainThreadPool.web.test.ts +174 -0
- package/web/test/threadPoolBenchHelper.ts +134 -0
- package/web/test/threadPoolOff.web.test.ts +17 -0
- package/web/test/threadPoolOn.web.test.ts +80 -0
- package/web/test/workerInitRace.web.test.ts +79 -0
- package/web/tfheThreadPool.ts +140 -0
- package/web/workerManager.ts +4 -2
- package/web/zkProve.worker.ts +42 -26
- package/dist/chunk-ESMZCFJY.js +0 -12
- package/dist/chunk-Q7CBWGQX.js +0 -1029
package/dist/web.d.cts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { I as IStorage, C as CofheInputConfig, a as CofheConfig, b as CofheClient, E as EncryptableItem } from './clientTypes-
|
|
1
|
+
import { I as IStorage, C as CofheInputConfig, a as CofheConfig, b as CofheClient, E as EncryptableItem } from './clientTypes-C0A9ji5O.cjs';
|
|
2
2
|
import 'viem';
|
|
3
3
|
import './types-C07FK-cL.cjs';
|
|
4
4
|
import 'zod';
|
|
5
|
-
import './
|
|
5
|
+
import './onchain-utils-D9Z33IP7.cjs';
|
|
6
6
|
import 'zustand/vanilla';
|
|
7
7
|
|
|
8
8
|
declare function createSsrStorage(): IStorage;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Worker Manager for ZK Proof Generation
|
|
12
|
+
* Manages worker lifecycle and request/response handling
|
|
13
|
+
*/
|
|
14
|
+
|
|
10
15
|
/**
|
|
11
16
|
* Terminate the worker
|
|
12
17
|
*/
|
|
@@ -16,8 +21,49 @@ declare function terminateWorker(): void;
|
|
|
16
21
|
*/
|
|
17
22
|
declare function areWorkersAvailable(): boolean;
|
|
18
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Rayon thread pool setup for the browser build of tfhe.
|
|
26
|
+
*
|
|
27
|
+
* `tfhe` is built with wasm-bindgen-rayon, so the ZK proof (the
|
|
28
|
+
* `build_with_proof_packed` call, by far the most expensive step of
|
|
29
|
+
* `encryptInputs`) can run across several threads. Those threads are real Web
|
|
30
|
+
* Workers that share the wasm linear memory, and `initThreadPool` hands them
|
|
31
|
+
* that memory over `postMessage`.
|
|
32
|
+
*
|
|
33
|
+
* Sharing memory across threads is only allowed in a cross-origin-isolated
|
|
34
|
+
* context, so the host page must be served with:
|
|
35
|
+
*
|
|
36
|
+
* Cross-Origin-Opener-Policy: same-origin
|
|
37
|
+
* Cross-Origin-Embedder-Policy: require-corp (or `credentialless`)
|
|
38
|
+
*
|
|
39
|
+
* Plenty of apps can't or don't set those headers, and calling
|
|
40
|
+
* `initThreadPool` without them throws ("SharedArrayBuffer transfer requires
|
|
41
|
+
* self.crossOriginIsolated"). So this is strictly an optimisation: we probe for
|
|
42
|
+
* isolation, and when it isn't available we leave tfhe single-threaded rather
|
|
43
|
+
* than failing the encryption. Single-threaded tfhe works in every context.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
type TfheThreadPoolResult = {
|
|
47
|
+
/** Whether a multi-threaded rayon pool is now running */
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
/** Threads requested from `initThreadPool` (1 when single-threaded) */
|
|
50
|
+
threads: number;
|
|
51
|
+
/** Why the pool was not started, when `enabled` is false */
|
|
52
|
+
reason?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
19
55
|
declare const hasDOM: boolean;
|
|
20
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Outcome of the rayon thread pool setup on the main thread, or `null` if the
|
|
59
|
+
* main thread hasn't had to generate a proof yet. `enabled: false` carries a
|
|
60
|
+
* `reason`.
|
|
61
|
+
*
|
|
62
|
+
* Note this reflects the *main thread* only. When workers are enabled (the
|
|
63
|
+
* default) proving happens inside the zkProve worker, which runs its own pool,
|
|
64
|
+
* and the main thread never starts one — so `null` is the normal value there.
|
|
65
|
+
*/
|
|
66
|
+
declare function getTfheThreadPoolStatus(): TfheThreadPoolResult | null;
|
|
21
67
|
/**
|
|
22
68
|
* Creates a CoFHE configuration for web with IndexedDB storage as default
|
|
23
69
|
* @param config - The CoFHE input configuration (fheKeyStorage will default to IndexedDB if not provided)
|
|
@@ -39,4 +85,4 @@ declare function createCofheClient<TConfig extends CofheConfig>(config: TConfig)
|
|
|
39
85
|
*/
|
|
40
86
|
declare function createCofheClientWithCustomWorker(config: CofheConfig, customZkProveWorkerFn: (fheKeyHex: string, crsHex: string, items: EncryptableItem[], metadata: Uint8Array) => Promise<Uint8Array>): CofheClient;
|
|
41
87
|
|
|
42
|
-
export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, hasDOM, terminateWorker };
|
|
88
|
+
export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, getTfheThreadPoolStatus, hasDOM, terminateWorker };
|
package/dist/web.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { I as IStorage, C as CofheInputConfig, a as CofheConfig, b as CofheClient, E as EncryptableItem } from './clientTypes-
|
|
1
|
+
import { I as IStorage, C as CofheInputConfig, a as CofheConfig, b as CofheClient, E as EncryptableItem } from './clientTypes-CcU5WvdV.js';
|
|
2
2
|
import 'viem';
|
|
3
3
|
import './types-C07FK-cL.js';
|
|
4
4
|
import 'zod';
|
|
5
|
-
import './
|
|
5
|
+
import './onchain-utils-D9Z33IP7.js';
|
|
6
6
|
import 'zustand/vanilla';
|
|
7
7
|
|
|
8
8
|
declare function createSsrStorage(): IStorage;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Worker Manager for ZK Proof Generation
|
|
12
|
+
* Manages worker lifecycle and request/response handling
|
|
13
|
+
*/
|
|
14
|
+
|
|
10
15
|
/**
|
|
11
16
|
* Terminate the worker
|
|
12
17
|
*/
|
|
@@ -16,8 +21,49 @@ declare function terminateWorker(): void;
|
|
|
16
21
|
*/
|
|
17
22
|
declare function areWorkersAvailable(): boolean;
|
|
18
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Rayon thread pool setup for the browser build of tfhe.
|
|
26
|
+
*
|
|
27
|
+
* `tfhe` is built with wasm-bindgen-rayon, so the ZK proof (the
|
|
28
|
+
* `build_with_proof_packed` call, by far the most expensive step of
|
|
29
|
+
* `encryptInputs`) can run across several threads. Those threads are real Web
|
|
30
|
+
* Workers that share the wasm linear memory, and `initThreadPool` hands them
|
|
31
|
+
* that memory over `postMessage`.
|
|
32
|
+
*
|
|
33
|
+
* Sharing memory across threads is only allowed in a cross-origin-isolated
|
|
34
|
+
* context, so the host page must be served with:
|
|
35
|
+
*
|
|
36
|
+
* Cross-Origin-Opener-Policy: same-origin
|
|
37
|
+
* Cross-Origin-Embedder-Policy: require-corp (or `credentialless`)
|
|
38
|
+
*
|
|
39
|
+
* Plenty of apps can't or don't set those headers, and calling
|
|
40
|
+
* `initThreadPool` without them throws ("SharedArrayBuffer transfer requires
|
|
41
|
+
* self.crossOriginIsolated"). So this is strictly an optimisation: we probe for
|
|
42
|
+
* isolation, and when it isn't available we leave tfhe single-threaded rather
|
|
43
|
+
* than failing the encryption. Single-threaded tfhe works in every context.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
type TfheThreadPoolResult = {
|
|
47
|
+
/** Whether a multi-threaded rayon pool is now running */
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
/** Threads requested from `initThreadPool` (1 when single-threaded) */
|
|
50
|
+
threads: number;
|
|
51
|
+
/** Why the pool was not started, when `enabled` is false */
|
|
52
|
+
reason?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
19
55
|
declare const hasDOM: boolean;
|
|
20
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Outcome of the rayon thread pool setup on the main thread, or `null` if the
|
|
59
|
+
* main thread hasn't had to generate a proof yet. `enabled: false` carries a
|
|
60
|
+
* `reason`.
|
|
61
|
+
*
|
|
62
|
+
* Note this reflects the *main thread* only. When workers are enabled (the
|
|
63
|
+
* default) proving happens inside the zkProve worker, which runs its own pool,
|
|
64
|
+
* and the main thread never starts one — so `null` is the normal value there.
|
|
65
|
+
*/
|
|
66
|
+
declare function getTfheThreadPoolStatus(): TfheThreadPoolResult | null;
|
|
21
67
|
/**
|
|
22
68
|
* Creates a CoFHE configuration for web with IndexedDB storage as default
|
|
23
69
|
* @param config - The CoFHE input configuration (fheKeyStorage will default to IndexedDB if not provided)
|
|
@@ -39,4 +85,4 @@ declare function createCofheClient<TConfig extends CofheConfig>(config: TConfig)
|
|
|
39
85
|
*/
|
|
40
86
|
declare function createCofheClientWithCustomWorker(config: CofheConfig, customZkProveWorkerFn: (fheKeyHex: string, crsHex: string, items: EncryptableItem[], metadata: Uint8Array) => Promise<Uint8Array>): CofheClient;
|
|
41
87
|
|
|
42
|
-
export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, hasDOM, terminateWorker };
|
|
88
|
+
export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, getTfheThreadPoolStatus, hasDOM, terminateWorker };
|
package/dist/web.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { createCofheConfigBase, createCofheClientBase
|
|
2
|
-
import './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import {
|
|
1
|
+
import { createCofheConfigBase, createCofheClientBase } from './chunk-OL3DZN7A.js';
|
|
2
|
+
import './chunk-UKQAJK4N.js';
|
|
3
|
+
import './chunk-BQWHPIYU.js';
|
|
4
|
+
import { initTfheThreadPool } from './chunk-KSOV5B4B.js';
|
|
5
|
+
import { fheTypeToString, fromHexString } from './chunk-6ZW76ZKB.js';
|
|
6
|
+
import { TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT } from './chunk-XQ32JG3T.js';
|
|
5
7
|
import { constructClient } from 'iframe-shared-storage';
|
|
6
8
|
|
|
7
9
|
// web/const.ts
|
|
@@ -128,7 +130,7 @@ var ZkProveWorkerManager = class {
|
|
|
128
130
|
/**
|
|
129
131
|
* Submit a proof generation request to the worker
|
|
130
132
|
*/
|
|
131
|
-
async submitProof(fheKeyHex, crsHex, items, metadata) {
|
|
133
|
+
async submitProof(fheKeyHex, crsHex, items, metadata, tfheThreads) {
|
|
132
134
|
await this.initializeWorker();
|
|
133
135
|
const id = `zkprove-${Date.now()}-${this.requestCounter++}`;
|
|
134
136
|
return new Promise((resolve, reject) => {
|
|
@@ -143,7 +145,8 @@ var ZkProveWorkerManager = class {
|
|
|
143
145
|
fheKeyHex,
|
|
144
146
|
crsHex,
|
|
145
147
|
items,
|
|
146
|
-
metadata: Array.from(metadata)
|
|
148
|
+
metadata: Array.from(metadata),
|
|
149
|
+
tfheThreads
|
|
147
150
|
};
|
|
148
151
|
this.worker.postMessage(message);
|
|
149
152
|
});
|
|
@@ -191,14 +194,39 @@ function areWorkersAvailable() {
|
|
|
191
194
|
// web/index.ts
|
|
192
195
|
var tfheModule = null;
|
|
193
196
|
var tfheInitialized = false;
|
|
194
|
-
|
|
197
|
+
var tfheInitPromise = null;
|
|
198
|
+
var threadPoolPromise = null;
|
|
199
|
+
var threadPoolResult = null;
|
|
200
|
+
function getTfheThreadPoolStatus() {
|
|
201
|
+
return threadPoolResult;
|
|
202
|
+
}
|
|
203
|
+
var initTfhe = async () => {
|
|
195
204
|
if (tfheInitialized)
|
|
196
205
|
return false;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
if (tfheInitPromise) {
|
|
207
|
+
await tfheInitPromise;
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
tfheInitPromise = (async () => {
|
|
211
|
+
const mod = await import('tfhe');
|
|
212
|
+
await mod.default();
|
|
213
|
+
await mod.init_panic_hook();
|
|
214
|
+
tfheModule = mod;
|
|
215
|
+
tfheInitialized = true;
|
|
216
|
+
})();
|
|
217
|
+
try {
|
|
218
|
+
await tfheInitPromise;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
tfheInitPromise = null;
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
201
223
|
return true;
|
|
224
|
+
};
|
|
225
|
+
function createStartMainThreadPool(tfheThreads) {
|
|
226
|
+
return async () => {
|
|
227
|
+
threadPoolPromise ??= initTfheThreadPool(requireTfhe(), tfheThreads);
|
|
228
|
+
threadPoolResult = await threadPoolPromise;
|
|
229
|
+
};
|
|
202
230
|
}
|
|
203
231
|
function requireTfhe() {
|
|
204
232
|
if (!tfheModule) {
|
|
@@ -206,13 +234,6 @@ function requireTfhe() {
|
|
|
206
234
|
}
|
|
207
235
|
return tfheModule;
|
|
208
236
|
}
|
|
209
|
-
var fromHexString = (hexString) => {
|
|
210
|
-
const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
|
|
211
|
-
const arr = cleanString.replace(/^0x/, "").match(/.{1,2}/g);
|
|
212
|
-
if (!arr)
|
|
213
|
-
return new Uint8Array();
|
|
214
|
-
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
215
|
-
};
|
|
216
237
|
var _deserializeTfhePublicKey = (buff) => {
|
|
217
238
|
return requireTfhe().TfheCompactPublicKey.safe_deserialize(
|
|
218
239
|
fromHexString(buff),
|
|
@@ -234,13 +255,15 @@ var zkBuilderAndCrsGenerator = (fhe, crs) => {
|
|
|
234
255
|
const zkCrs = _deserializeCompactPkeCrs(crs);
|
|
235
256
|
return { zkBuilder, zkCrs };
|
|
236
257
|
};
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
258
|
+
function createZkProveWithWorker(tfheThreads) {
|
|
259
|
+
return async function zkProveWithWorker(fheKeyHex, crsHex, items, metadata) {
|
|
260
|
+
const serializedItems = items.map((item) => ({
|
|
261
|
+
utype: fheTypeToString(item.utype),
|
|
262
|
+
data: typeof item.data === "bigint" ? item.data.toString() : item.data
|
|
263
|
+
}));
|
|
264
|
+
const workerManager2 = getWorkerManager();
|
|
265
|
+
return await workerManager2.submitProof(fheKeyHex, crsHex, serializedItems, metadata, tfheThreads);
|
|
266
|
+
};
|
|
244
267
|
}
|
|
245
268
|
function createCofheConfig(config) {
|
|
246
269
|
return createCofheConfigBase({
|
|
@@ -256,9 +279,10 @@ function createCofheClient(config) {
|
|
|
256
279
|
tfhePublicKeyDeserializer,
|
|
257
280
|
compactPkeCrsDeserializer,
|
|
258
281
|
initTfhe,
|
|
282
|
+
beforeMainThreadProve: createStartMainThreadPool(config.tfheThreads),
|
|
259
283
|
// Always provide the worker function if available - config.useWorkers controls usage
|
|
260
284
|
// areWorkersAvailable will return true if the Worker API is available and false in Node.js
|
|
261
|
-
zkProveWorkerFn: areWorkersAvailable() ?
|
|
285
|
+
zkProveWorkerFn: areWorkersAvailable() ? createZkProveWithWorker(config.tfheThreads) : void 0
|
|
262
286
|
});
|
|
263
287
|
}
|
|
264
288
|
function createCofheClientWithCustomWorker(config, customZkProveWorkerFn) {
|
|
@@ -268,8 +292,9 @@ function createCofheClientWithCustomWorker(config, customZkProveWorkerFn) {
|
|
|
268
292
|
tfhePublicKeyDeserializer,
|
|
269
293
|
compactPkeCrsDeserializer,
|
|
270
294
|
initTfhe,
|
|
295
|
+
beforeMainThreadProve: createStartMainThreadPool(config.tfheThreads),
|
|
271
296
|
zkProveWorkerFn: customZkProveWorkerFn
|
|
272
297
|
});
|
|
273
298
|
}
|
|
274
299
|
|
|
275
|
-
export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, hasDOM, terminateWorker };
|
|
300
|
+
export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, getTfheThreadPoolStatus, hasDOM, terminateWorker };
|
package/dist/zkProve.worker.cjs
CHANGED
|
@@ -1,35 +1,96 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
require('viem');
|
|
4
|
+
|
|
3
5
|
// core/consts.ts
|
|
4
6
|
var TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT = BigInt(1 << 30);
|
|
5
7
|
|
|
6
|
-
// web/
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
// web/tfheThreadPool.ts
|
|
9
|
+
var MAX_AUTO_THREADS = 8;
|
|
10
|
+
function resolveThreadCount(setting = "auto") {
|
|
11
|
+
if (setting === false)
|
|
12
|
+
return 1;
|
|
13
|
+
if (typeof setting === "number") {
|
|
14
|
+
return Number.isFinite(setting) && setting >= 1 ? Math.floor(setting) : 1;
|
|
15
|
+
}
|
|
16
|
+
const cores = globalThis.navigator?.hardwareConcurrency ?? 1;
|
|
17
|
+
if (!Number.isFinite(cores) || cores < 1)
|
|
18
|
+
return 1;
|
|
19
|
+
return Math.min(Math.floor(cores), MAX_AUTO_THREADS);
|
|
20
|
+
}
|
|
21
|
+
function isCrossOriginIsolated() {
|
|
22
|
+
return globalThis.crossOriginIsolated === true;
|
|
23
|
+
}
|
|
24
|
+
async function initTfheThreadPool(mod, setting = "auto") {
|
|
25
|
+
if (setting === false) {
|
|
26
|
+
return { enabled: false, threads: 1, reason: "disabled via config.tfheThreads" };
|
|
27
|
+
}
|
|
28
|
+
if (typeof mod.initThreadPool !== "function") {
|
|
29
|
+
return { enabled: false, threads: 1, reason: "tfhe build has no initThreadPool (single-threaded wasm)" };
|
|
30
|
+
}
|
|
31
|
+
if (typeof Worker === "undefined") {
|
|
32
|
+
return { enabled: false, threads: 1, reason: "Worker is unavailable" };
|
|
33
|
+
}
|
|
34
|
+
if (!isCrossOriginIsolated()) {
|
|
35
|
+
return {
|
|
36
|
+
enabled: false,
|
|
37
|
+
threads: 1,
|
|
38
|
+
reason: "not cross-origin isolated \u2014 serve the page with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp (or credentialless) to enable multi-threaded proving"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const threads = resolveThreadCount(setting);
|
|
42
|
+
if (threads <= 1) {
|
|
43
|
+
return { enabled: false, threads: 1, reason: "resolved to a single thread" };
|
|
44
|
+
}
|
|
12
45
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
await tfheModule.init_panic_hook();
|
|
16
|
-
initialized = true;
|
|
17
|
-
console.log("[Worker] TFHE initialized");
|
|
46
|
+
await mod.initThreadPool(threads);
|
|
47
|
+
return { enabled: true, threads };
|
|
18
48
|
} catch (error) {
|
|
19
|
-
|
|
20
|
-
|
|
49
|
+
return {
|
|
50
|
+
enabled: false,
|
|
51
|
+
threads: 1,
|
|
52
|
+
reason: `initThreadPool failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53
|
+
};
|
|
21
54
|
}
|
|
22
55
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
56
|
+
var HEX_BODY_REGEX = /^[0-9a-fA-F]*$/;
|
|
57
|
+
var fromHexString = (hexString) => {
|
|
58
|
+
const body = hexString.replace(/^0x/, "");
|
|
59
|
+
if (!HEX_BODY_REGEX.test(body)) {
|
|
60
|
+
throw new Error("Invalid hex string: contains non-hexadecimal characters");
|
|
61
|
+
}
|
|
62
|
+
const padded = body.length % 2 === 1 ? `0${body}` : body;
|
|
63
|
+
const arr = padded.match(/.{1,2}/g);
|
|
26
64
|
if (!arr)
|
|
27
65
|
return new Uint8Array();
|
|
28
66
|
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// web/zkProve.worker.ts
|
|
70
|
+
var tfheModule = null;
|
|
71
|
+
var initPromise = null;
|
|
72
|
+
var threadPool = null;
|
|
73
|
+
function initTfhe(tfheThreads = "auto") {
|
|
74
|
+
initPromise ??= (async () => {
|
|
75
|
+
try {
|
|
76
|
+
tfheModule = await import('tfhe');
|
|
77
|
+
await tfheModule.default();
|
|
78
|
+
await tfheModule.init_panic_hook();
|
|
79
|
+
threadPool = await initTfheThreadPool(tfheModule, tfheThreads);
|
|
80
|
+
console.log(
|
|
81
|
+
threadPool.enabled ? `[Worker] TFHE initialized (rayon thread pool: ${threadPool.threads} threads)` : `[Worker] TFHE initialized (single-threaded: ${threadPool.reason})`
|
|
82
|
+
);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("[Worker] Failed to initialize TFHE:", error);
|
|
85
|
+
initPromise = null;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
return initPromise;
|
|
29
90
|
}
|
|
30
91
|
if (typeof self !== "undefined") {
|
|
31
92
|
self.onmessage = async (event) => {
|
|
32
|
-
const { id, type, fheKeyHex, crsHex, items, metadata } = event.data;
|
|
93
|
+
const { id, type, fheKeyHex, crsHex, items, metadata, tfheThreads } = event.data;
|
|
33
94
|
if (type !== "zkProve") {
|
|
34
95
|
self.postMessage({
|
|
35
96
|
id,
|
|
@@ -39,7 +100,7 @@ if (typeof self !== "undefined") {
|
|
|
39
100
|
return;
|
|
40
101
|
}
|
|
41
102
|
try {
|
|
42
|
-
await initTfhe();
|
|
103
|
+
await initTfhe(tfheThreads);
|
|
43
104
|
if (!tfheModule) {
|
|
44
105
|
throw new Error("TFHE module not initialized");
|
|
45
106
|
}
|
package/dist/zkProve.worker.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { initTfheThreadPool } from './chunk-KSOV5B4B.js';
|
|
2
|
+
import { fromHexString } from './chunk-6ZW76ZKB.js';
|
|
3
|
+
import { TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT } from './chunk-XQ32JG3T.js';
|
|
2
4
|
|
|
3
5
|
// web/zkProve.worker.ts
|
|
4
6
|
var tfheModule = null;
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return new Uint8Array();
|
|
25
|
-
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
7
|
+
var initPromise = null;
|
|
8
|
+
var threadPool = null;
|
|
9
|
+
function initTfhe(tfheThreads = "auto") {
|
|
10
|
+
initPromise ??= (async () => {
|
|
11
|
+
try {
|
|
12
|
+
tfheModule = await import('tfhe');
|
|
13
|
+
await tfheModule.default();
|
|
14
|
+
await tfheModule.init_panic_hook();
|
|
15
|
+
threadPool = await initTfheThreadPool(tfheModule, tfheThreads);
|
|
16
|
+
console.log(
|
|
17
|
+
threadPool.enabled ? `[Worker] TFHE initialized (rayon thread pool: ${threadPool.threads} threads)` : `[Worker] TFHE initialized (single-threaded: ${threadPool.reason})`
|
|
18
|
+
);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("[Worker] Failed to initialize TFHE:", error);
|
|
21
|
+
initPromise = null;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
25
|
+
return initPromise;
|
|
26
26
|
}
|
|
27
27
|
if (typeof self !== "undefined") {
|
|
28
28
|
self.onmessage = async (event) => {
|
|
29
|
-
const { id, type, fheKeyHex, crsHex, items, metadata } = event.data;
|
|
29
|
+
const { id, type, fheKeyHex, crsHex, items, metadata, tfheThreads } = event.data;
|
|
30
30
|
if (type !== "zkProve") {
|
|
31
31
|
self.postMessage({
|
|
32
32
|
id,
|
|
@@ -36,7 +36,7 @@ if (typeof self !== "undefined") {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
try {
|
|
39
|
-
await initTfhe();
|
|
39
|
+
await initTfhe(tfheThreads);
|
|
40
40
|
if (!tfheModule) {
|
|
41
41
|
throw new Error("TFHE module not initialized");
|
|
42
42
|
}
|
package/node/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type FheKeyDeserializer,
|
|
11
11
|
TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT,
|
|
12
12
|
} from '@/core';
|
|
13
|
+
import { fromHexString } from '../core/utils.js';
|
|
13
14
|
|
|
14
15
|
// Import node-specific storage (internal use only)
|
|
15
16
|
import { createNodeStorage } from './storage.js';
|
|
@@ -30,16 +31,6 @@ async function initTfhe(): Promise<boolean> {
|
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
/**
|
|
34
|
-
* Utility to convert the hex string key to a Uint8Array for use with tfhe
|
|
35
|
-
*/
|
|
36
|
-
const fromHexString = (hexString: string): Uint8Array => {
|
|
37
|
-
const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
|
|
38
|
-
const arr = cleanString.replace(/^0x/, '').match(/.{1,2}/g);
|
|
39
|
-
if (!arr) return new Uint8Array();
|
|
40
|
-
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
41
|
-
};
|
|
42
|
-
|
|
43
34
|
const _deserializeTfhePublicKey = (buff: string): TfheCompactPublicKey => {
|
|
44
35
|
return TfheCompactPublicKey.safe_deserialize(fromHexString(buff), TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT);
|
|
45
36
|
};
|
package/node/storage.ts
CHANGED
|
@@ -6,7 +6,18 @@ import { promises as fs } from 'fs';
|
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
|
|
8
8
|
// Memory storage fallback
|
|
9
|
-
const memoryStorage: Record<string,
|
|
9
|
+
const memoryStorage: Record<string, unknown> = {};
|
|
10
|
+
|
|
11
|
+
const getStorageDir = () => join(process.env.HOME || process.env.USERPROFILE || '.', '.cofhesdk');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Keep the directory and files private to the current user. `mode` only applies
|
|
15
|
+
* on creation, so chmod also tightens directories/files created by earlier SDK versions.
|
|
16
|
+
*/
|
|
17
|
+
const ensureStorageDir = async (storageDir: string) => {
|
|
18
|
+
await fs.mkdir(storageDir, { recursive: true, mode: 0o700 });
|
|
19
|
+
await fs.chmod(storageDir, 0o700).catch(() => {});
|
|
20
|
+
};
|
|
10
21
|
|
|
11
22
|
/**
|
|
12
23
|
* Creates a node storage implementation using the filesystem
|
|
@@ -16,8 +27,8 @@ export const createNodeStorage = (): IStorage => {
|
|
|
16
27
|
return {
|
|
17
28
|
getItem: async (name: string) => {
|
|
18
29
|
try {
|
|
19
|
-
const storageDir =
|
|
20
|
-
await
|
|
30
|
+
const storageDir = getStorageDir();
|
|
31
|
+
await ensureStorageDir(storageDir);
|
|
21
32
|
const filePath = join(storageDir, `${name}.json`);
|
|
22
33
|
const data = await fs.readFile(filePath, 'utf8').catch(() => null);
|
|
23
34
|
return data ? JSON.parse(data) : null;
|
|
@@ -28,19 +39,19 @@ export const createNodeStorage = (): IStorage => {
|
|
|
28
39
|
},
|
|
29
40
|
setItem: async (name: string, value: any) => {
|
|
30
41
|
try {
|
|
31
|
-
const storageDir =
|
|
32
|
-
await
|
|
42
|
+
const storageDir = getStorageDir();
|
|
43
|
+
await ensureStorageDir(storageDir);
|
|
33
44
|
const filePath = join(storageDir, `${name}.json`);
|
|
34
|
-
await fs.writeFile(filePath, JSON.stringify(value));
|
|
45
|
+
await fs.writeFile(filePath, JSON.stringify(value), { mode: 0o600 });
|
|
46
|
+
await fs.chmod(filePath, 0o600).catch(() => {});
|
|
35
47
|
} catch (e) {
|
|
36
48
|
console.warn('Node.js filesystem modules not available, falling back to memory storage' + e);
|
|
37
|
-
memoryStorage[name] =
|
|
49
|
+
memoryStorage[name] = value;
|
|
38
50
|
}
|
|
39
51
|
},
|
|
40
52
|
removeItem: async (name: string) => {
|
|
41
53
|
try {
|
|
42
|
-
const
|
|
43
|
-
const filePath = join(storageDir, `${name}.json`);
|
|
54
|
+
const filePath = join(getStorageDir(), `${name}.json`);
|
|
44
55
|
await fs.unlink(filePath).catch(() => {});
|
|
45
56
|
} catch (e) {
|
|
46
57
|
console.warn('Node.js filesystem modules not available, falling back to memory storage' + e);
|
|
@@ -16,6 +16,10 @@ import { privateKeyToAccount } from 'viem/accounts';
|
|
|
16
16
|
import { arbitrumSepolia, baseSepolia, sepolia } from 'viem/chains';
|
|
17
17
|
import { createCofheClient, createCofheConfig } from '../index.js';
|
|
18
18
|
|
|
19
|
+
// Live-chain suite: needs the funded TEST_PRIVATE_KEY (transacts against pre-deployed
|
|
20
|
+
// testnet fixtures). Without it — fork PRs, where secrets are unavailable — the suite
|
|
21
|
+
// skips; CI runs it separately with the key (test-live job).
|
|
22
|
+
const LIVE_TESTS = Boolean(TEST_PRIVATE_KEY);
|
|
19
23
|
const DEFAULT_TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
20
24
|
const BOB_PRIVATE_KEY = (TEST_PRIVATE_KEY || DEFAULT_TEST_PRIVATE_KEY) as `0x${string}`;
|
|
21
25
|
const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d';
|
|
@@ -32,7 +36,7 @@ const VIEM_CHAINS: Record<number, Chain> = {
|
|
|
32
36
|
const testViemChain = STAGING_TESTS ? stagingViemChain : arbitrumSepolia;
|
|
33
37
|
const testCofheChain = STAGING_TESTS ? stagingCofhe : cofheArbSepolia;
|
|
34
38
|
|
|
35
|
-
describe('@cofhe/node - Inherited Client Tests', () => {
|
|
39
|
+
describe.skipIf(!LIVE_TESTS)('@cofhe/node - Inherited Client Tests', () => {
|
|
36
40
|
let cofheClient: CofheClient;
|
|
37
41
|
let publicClient: PublicClient;
|
|
38
42
|
let bobWalletClient: WalletClient;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
|
|
6
|
+
import { createNodeStorage } from '../storage.js';
|
|
7
|
+
|
|
8
|
+
describe('createNodeStorage memory fallback', () => {
|
|
9
|
+
let originalHome: string | undefined;
|
|
10
|
+
let testDir: string;
|
|
11
|
+
let invalidHome: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
originalHome = process.env.HOME;
|
|
15
|
+
testDir = await fs.mkdtemp(join(tmpdir(), 'cofhesdk-storage-fallback-'));
|
|
16
|
+
|
|
17
|
+
// HOME points to a regular file, so creating HOME/.cofhesdk fails.
|
|
18
|
+
invalidHome = join(testDir, 'not-a-directory');
|
|
19
|
+
await fs.writeFile(invalidHome, 'file', 'utf8');
|
|
20
|
+
process.env.HOME = invalidHome;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
if (originalHome === undefined) {
|
|
25
|
+
delete process.env.HOME;
|
|
26
|
+
} else {
|
|
27
|
+
process.env.HOME = originalHome;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('round-trips the same value when filesystem storage is unavailable', async () => {
|
|
34
|
+
const storage = createNodeStorage();
|
|
35
|
+
const value = {
|
|
36
|
+
state: {
|
|
37
|
+
fhe: { 1: { 0: 'key' } },
|
|
38
|
+
crs: { 1: 'crs' },
|
|
39
|
+
},
|
|
40
|
+
version: 0,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await storage.setItem('fallback-roundtrip', value);
|
|
44
|
+
|
|
45
|
+
await expect(storage.getItem('fallback-roundtrip')).resolves.toEqual(value);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { createNodeStorage } from '../storage.js';
|
|
6
|
+
|
|
7
|
+
const withTempHome = async (fn: (home: string) => Promise<void>) => {
|
|
8
|
+
const home = await fs.mkdtemp(path.join(os.tmpdir(), 'cofhe-home-'));
|
|
9
|
+
const oldHome = process.env.HOME;
|
|
10
|
+
process.env.HOME = home;
|
|
11
|
+
try {
|
|
12
|
+
await fn(home);
|
|
13
|
+
} finally {
|
|
14
|
+
if (oldHome === undefined) delete process.env.HOME;
|
|
15
|
+
else process.env.HOME = oldHome;
|
|
16
|
+
await fs.rm(home, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('node storage permissions', () => {
|
|
21
|
+
it.skipIf(process.platform === 'win32')('does not make stored data readable by other users', () =>
|
|
22
|
+
withTempHome(async (home) => {
|
|
23
|
+
await createNodeStorage().setItem('t', { value: 1 });
|
|
24
|
+
const st = await fs.stat(path.join(home, '.cofhesdk', 't.json'));
|
|
25
|
+
expect(st.mode & 0o077).toBe(0);
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
it.skipIf(process.platform === 'win32')('tightens permissions of a file created with loose permissions', () =>
|
|
30
|
+
withTempHome(async (home) => {
|
|
31
|
+
const dir = path.join(home, '.cofhesdk');
|
|
32
|
+
await fs.mkdir(dir, { mode: 0o755 });
|
|
33
|
+
const file = path.join(dir, 't.json');
|
|
34
|
+
await fs.writeFile(file, '{}', { mode: 0o644 });
|
|
35
|
+
await createNodeStorage().setItem('t', { value: 1 });
|
|
36
|
+
expect((await fs.stat(file)).mode & 0o077).toBe(0);
|
|
37
|
+
expect((await fs.stat(dir)).mode & 0o077).toBe(0);
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
});
|