@cofhe/sdk 0.7.0 → 0.8.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/acps/acp.ts +10 -1
  3. package/acps/index.ts +11 -0
  4. package/acps/onchain-utils.ts +188 -49
  5. package/acps/registry.ts +73 -0
  6. package/acps/test/onchainAccessStatus.test.ts +116 -0
  7. package/acps/types.ts +16 -0
  8. package/chains/chains/arbitrum.ts +14 -0
  9. package/chains/chains/ethereum.ts +14 -0
  10. package/chains/index.ts +5 -1
  11. package/chains/test/chains.test.ts +24 -3
  12. package/core/acps.ts +51 -143
  13. package/core/client.ts +13 -8
  14. package/core/clientTypes.ts +29 -4
  15. package/core/config.ts +25 -1
  16. package/core/consts.ts +14 -2
  17. package/core/decrypt/verifyDecryptResult.ts +4 -3
  18. package/core/encrypt/encryptInputsBuilder.ts +6 -0
  19. package/core/encrypt/zkPackProveVerify.ts +7 -1
  20. package/core/error.ts +1 -0
  21. package/core/fetchKeys.ts +2 -2
  22. package/core/index.ts +9 -1
  23. package/core/keyStore.ts +23 -10
  24. package/core/taskManager.ts +48 -0
  25. package/core/test/acps.test.ts +91 -1
  26. package/core/test/client.test.ts +41 -0
  27. package/core/test/config.test.ts +16 -0
  28. package/core/test/decrypt.test.ts +11 -4
  29. package/core/test/encryptInputsBuilder.test.ts +1 -1
  30. package/core/test/fetchKeys.test.ts +23 -2
  31. package/core/test/keyStore.test.ts +18 -6
  32. package/core/test/taskManager.test.ts +57 -0
  33. package/core/types.ts +8 -0
  34. package/dist/acps.cjs +314 -35
  35. package/dist/acps.d.cts +251 -4
  36. package/dist/acps.d.ts +251 -4
  37. package/dist/acps.js +2 -2
  38. package/dist/chains.cjs +26 -0
  39. package/dist/chains.d.cts +45 -1
  40. package/dist/chains.d.ts +45 -1
  41. package/dist/chains.js +1 -1
  42. package/dist/{chunk-43USWPEH.js → chunk-GGI7Y6AW.js} +59 -264
  43. package/dist/chunk-KSOV5B4B.js +50 -0
  44. package/dist/{chunk-ESMZCFJY.js → chunk-NMKMCAI3.js} +4 -2
  45. package/dist/{chunk-Q7CBWGQX.js → chunk-UJYOBHYA.js} +374 -37
  46. package/dist/{chunk-N6IDQRRU.js → chunk-UKQAJK4N.js} +25 -1
  47. package/dist/{clientTypes-BN3nbzYM.d.ts → clientTypes-5YPPR16m.d.ts} +77 -14
  48. package/dist/{clientTypes-CYZjFznO.d.cts → clientTypes-D3aIXmUp.d.cts} +77 -14
  49. package/dist/core.cjs +271 -136
  50. package/dist/core.d.cts +29 -8
  51. package/dist/core.d.ts +29 -8
  52. package/dist/core.js +4 -4
  53. package/dist/node.cjs +266 -136
  54. package/dist/node.d.cts +2 -2
  55. package/dist/node.d.ts +2 -2
  56. package/dist/node.js +5 -5
  57. package/dist/{acp-Wi6isVQI.d.cts → onchain-utils-CUAK8glx.d.cts} +31 -1
  58. package/dist/{acp-Wi6isVQI.d.ts → onchain-utils-CUAK8glx.d.ts} +31 -1
  59. package/dist/web.cjs +360 -150
  60. package/dist/web.d.cts +49 -3
  61. package/dist/web.d.ts +49 -3
  62. package/dist/web.js +51 -20
  63. package/dist/zkProve.worker.cjs +69 -15
  64. package/dist/zkProve.worker.js +23 -17
  65. package/node/storage.ts +2 -2
  66. package/node/test/inherited.test.ts +5 -1
  67. package/node/test/storage-memory-fallback.test.ts +47 -0
  68. package/package.json +5 -3
  69. package/web/index.ts +100 -26
  70. package/web/test/countingZkProveWorker.ts +31 -0
  71. package/web/test/inherited.web.test.ts +5 -1
  72. package/web/test/mainThreadPool.web.test.ts +174 -0
  73. package/web/test/threadPoolBenchHelper.ts +140 -0
  74. package/web/test/threadPoolOff.web.test.ts +17 -0
  75. package/web/test/threadPoolOn.web.test.ts +80 -0
  76. package/web/test/workerInitRace.web.test.ts +79 -0
  77. package/web/tfheThreadPool.ts +140 -0
  78. package/web/workerManager.ts +4 -2
  79. package/web/zkProve.worker.ts +42 -17
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-CYZjFznO.cjs';
1
+ import { I as IStorage, C as CofheInputConfig, a as CofheConfig, b as CofheClient, E as EncryptableItem } from './clientTypes-D3aIXmUp.cjs';
2
2
  import 'viem';
3
3
  import './types-C07FK-cL.cjs';
4
4
  import 'zod';
5
- import './acp-Wi6isVQI.cjs';
5
+ import './onchain-utils-CUAK8glx.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-BN3nbzYM.js';
1
+ import { I as IStorage, C as CofheInputConfig, a as CofheConfig, b as CofheClient, E as EncryptableItem } from './clientTypes-5YPPR16m.js';
2
2
  import 'viem';
3
3
  import './types-C07FK-cL.js';
4
4
  import 'zod';
5
- import './acp-Wi6isVQI.js';
5
+ import './onchain-utils-CUAK8glx.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,8 @@
1
- import { createCofheConfigBase, createCofheClientBase, fheTypeToString } from './chunk-43USWPEH.js';
2
- import './chunk-N6IDQRRU.js';
3
- import './chunk-Q7CBWGQX.js';
4
- import { TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT } from './chunk-ESMZCFJY.js';
1
+ import { createCofheConfigBase, createCofheClientBase, fheTypeToString } from './chunk-GGI7Y6AW.js';
2
+ import './chunk-UKQAJK4N.js';
3
+ import './chunk-UJYOBHYA.js';
4
+ import { initTfheThreadPool } from './chunk-KSOV5B4B.js';
5
+ import { TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT } from './chunk-NMKMCAI3.js';
5
6
  import { constructClient } from 'iframe-shared-storage';
6
7
 
7
8
  // web/const.ts
@@ -128,7 +129,7 @@ var ZkProveWorkerManager = class {
128
129
  /**
129
130
  * Submit a proof generation request to the worker
130
131
  */
131
- async submitProof(fheKeyHex, crsHex, items, metadata) {
132
+ async submitProof(fheKeyHex, crsHex, items, metadata, tfheThreads) {
132
133
  await this.initializeWorker();
133
134
  const id = `zkprove-${Date.now()}-${this.requestCounter++}`;
134
135
  return new Promise((resolve, reject) => {
@@ -143,7 +144,8 @@ var ZkProveWorkerManager = class {
143
144
  fheKeyHex,
144
145
  crsHex,
145
146
  items,
146
- metadata: Array.from(metadata)
147
+ metadata: Array.from(metadata),
148
+ tfheThreads
147
149
  };
148
150
  this.worker.postMessage(message);
149
151
  });
@@ -191,14 +193,39 @@ function areWorkersAvailable() {
191
193
  // web/index.ts
192
194
  var tfheModule = null;
193
195
  var tfheInitialized = false;
194
- async function initTfhe() {
196
+ var tfheInitPromise = null;
197
+ var threadPoolPromise = null;
198
+ var threadPoolResult = null;
199
+ function getTfheThreadPoolStatus() {
200
+ return threadPoolResult;
201
+ }
202
+ var initTfhe = async () => {
195
203
  if (tfheInitialized)
196
204
  return false;
197
- tfheModule = await import('tfhe');
198
- await tfheModule.default();
199
- await tfheModule.init_panic_hook();
200
- tfheInitialized = true;
205
+ if (tfheInitPromise) {
206
+ await tfheInitPromise;
207
+ return false;
208
+ }
209
+ tfheInitPromise = (async () => {
210
+ const mod = await import('tfhe');
211
+ await mod.default();
212
+ await mod.init_panic_hook();
213
+ tfheModule = mod;
214
+ tfheInitialized = true;
215
+ })();
216
+ try {
217
+ await tfheInitPromise;
218
+ } catch (error) {
219
+ tfheInitPromise = null;
220
+ throw error;
221
+ }
201
222
  return true;
223
+ };
224
+ function createStartMainThreadPool(tfheThreads) {
225
+ return async () => {
226
+ threadPoolPromise ??= initTfheThreadPool(requireTfhe(), tfheThreads);
227
+ threadPoolResult = await threadPoolPromise;
228
+ };
202
229
  }
203
230
  function requireTfhe() {
204
231
  if (!tfheModule) {
@@ -234,13 +261,15 @@ var zkBuilderAndCrsGenerator = (fhe, crs) => {
234
261
  const zkCrs = _deserializeCompactPkeCrs(crs);
235
262
  return { zkBuilder, zkCrs };
236
263
  };
237
- async function zkProveWithWorker(fheKeyHex, crsHex, items, metadata) {
238
- const serializedItems = items.map((item) => ({
239
- utype: fheTypeToString(item.utype),
240
- data: typeof item.data === "bigint" ? item.data.toString() : item.data
241
- }));
242
- const workerManager2 = getWorkerManager();
243
- return await workerManager2.submitProof(fheKeyHex, crsHex, serializedItems, metadata);
264
+ function createZkProveWithWorker(tfheThreads) {
265
+ return async function zkProveWithWorker(fheKeyHex, crsHex, items, metadata) {
266
+ const serializedItems = items.map((item) => ({
267
+ utype: fheTypeToString(item.utype),
268
+ data: typeof item.data === "bigint" ? item.data.toString() : item.data
269
+ }));
270
+ const workerManager2 = getWorkerManager();
271
+ return await workerManager2.submitProof(fheKeyHex, crsHex, serializedItems, metadata, tfheThreads);
272
+ };
244
273
  }
245
274
  function createCofheConfig(config) {
246
275
  return createCofheConfigBase({
@@ -256,9 +285,10 @@ function createCofheClient(config) {
256
285
  tfhePublicKeyDeserializer,
257
286
  compactPkeCrsDeserializer,
258
287
  initTfhe,
288
+ beforeMainThreadProve: createStartMainThreadPool(config.tfheThreads),
259
289
  // Always provide the worker function if available - config.useWorkers controls usage
260
290
  // areWorkersAvailable will return true if the Worker API is available and false in Node.js
261
- zkProveWorkerFn: areWorkersAvailable() ? zkProveWithWorker : void 0
291
+ zkProveWorkerFn: areWorkersAvailable() ? createZkProveWithWorker(config.tfheThreads) : void 0
262
292
  });
263
293
  }
264
294
  function createCofheClientWithCustomWorker(config, customZkProveWorkerFn) {
@@ -268,8 +298,9 @@ function createCofheClientWithCustomWorker(config, customZkProveWorkerFn) {
268
298
  tfhePublicKeyDeserializer,
269
299
  compactPkeCrsDeserializer,
270
300
  initTfhe,
301
+ beforeMainThreadProve: createStartMainThreadPool(config.tfheThreads),
271
302
  zkProveWorkerFn: customZkProveWorkerFn
272
303
  });
273
304
  }
274
305
 
275
- export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, hasDOM, terminateWorker };
306
+ export { areWorkersAvailable, createCofheClient, createCofheClientWithCustomWorker, createCofheConfig, createSsrStorage, getTfheThreadPoolStatus, hasDOM, terminateWorker };
@@ -3,23 +3,77 @@
3
3
  // core/consts.ts
4
4
  var TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT = BigInt(1 << 30);
5
5
 
6
- // web/zkProve.worker.ts
7
- var tfheModule = null;
8
- var initialized = false;
9
- async function initTfhe() {
10
- if (initialized)
11
- return;
6
+ // web/tfheThreadPool.ts
7
+ var MAX_AUTO_THREADS = 8;
8
+ function resolveThreadCount(setting = "auto") {
9
+ if (setting === false)
10
+ return 1;
11
+ if (typeof setting === "number") {
12
+ return Number.isFinite(setting) && setting >= 1 ? Math.floor(setting) : 1;
13
+ }
14
+ const cores = globalThis.navigator?.hardwareConcurrency ?? 1;
15
+ if (!Number.isFinite(cores) || cores < 1)
16
+ return 1;
17
+ return Math.min(Math.floor(cores), MAX_AUTO_THREADS);
18
+ }
19
+ function isCrossOriginIsolated() {
20
+ return globalThis.crossOriginIsolated === true;
21
+ }
22
+ async function initTfheThreadPool(mod, setting = "auto") {
23
+ if (setting === false) {
24
+ return { enabled: false, threads: 1, reason: "disabled via config.tfheThreads" };
25
+ }
26
+ if (typeof mod.initThreadPool !== "function") {
27
+ return { enabled: false, threads: 1, reason: "tfhe build has no initThreadPool (single-threaded wasm)" };
28
+ }
29
+ if (typeof Worker === "undefined") {
30
+ return { enabled: false, threads: 1, reason: "Worker is unavailable" };
31
+ }
32
+ if (!isCrossOriginIsolated()) {
33
+ return {
34
+ enabled: false,
35
+ threads: 1,
36
+ 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"
37
+ };
38
+ }
39
+ const threads = resolveThreadCount(setting);
40
+ if (threads <= 1) {
41
+ return { enabled: false, threads: 1, reason: "resolved to a single thread" };
42
+ }
12
43
  try {
13
- tfheModule = await import('tfhe');
14
- await tfheModule.default();
15
- await tfheModule.init_panic_hook();
16
- initialized = true;
17
- console.log("[Worker] TFHE initialized");
44
+ await mod.initThreadPool(threads);
45
+ return { enabled: true, threads };
18
46
  } catch (error) {
19
- console.error("[Worker] Failed to initialize TFHE:", error);
20
- throw error;
47
+ return {
48
+ enabled: false,
49
+ threads: 1,
50
+ reason: `initThreadPool failed: ${error instanceof Error ? error.message : String(error)}`
51
+ };
21
52
  }
22
53
  }
54
+
55
+ // web/zkProve.worker.ts
56
+ var tfheModule = null;
57
+ var initPromise = null;
58
+ var threadPool = null;
59
+ function initTfhe(tfheThreads = "auto") {
60
+ initPromise ??= (async () => {
61
+ try {
62
+ tfheModule = await import('tfhe');
63
+ await tfheModule.default();
64
+ await tfheModule.init_panic_hook();
65
+ threadPool = await initTfheThreadPool(tfheModule, tfheThreads);
66
+ console.log(
67
+ threadPool.enabled ? `[Worker] TFHE initialized (rayon thread pool: ${threadPool.threads} threads)` : `[Worker] TFHE initialized (single-threaded: ${threadPool.reason})`
68
+ );
69
+ } catch (error) {
70
+ console.error("[Worker] Failed to initialize TFHE:", error);
71
+ initPromise = null;
72
+ throw error;
73
+ }
74
+ })();
75
+ return initPromise;
76
+ }
23
77
  function fromHexString(hexString) {
24
78
  const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
25
79
  const arr = cleanString.replace(/^0x/, "").match(/.{1,2}/g);
@@ -29,7 +83,7 @@ function fromHexString(hexString) {
29
83
  }
30
84
  if (typeof self !== "undefined") {
31
85
  self.onmessage = async (event) => {
32
- const { id, type, fheKeyHex, crsHex, items, metadata } = event.data;
86
+ const { id, type, fheKeyHex, crsHex, items, metadata, tfheThreads } = event.data;
33
87
  if (type !== "zkProve") {
34
88
  self.postMessage({
35
89
  id,
@@ -39,7 +93,7 @@ if (typeof self !== "undefined") {
39
93
  return;
40
94
  }
41
95
  try {
42
- await initTfhe();
96
+ await initTfhe(tfheThreads);
43
97
  if (!tfheModule) {
44
98
  throw new Error("TFHE module not initialized");
45
99
  }
@@ -1,21 +1,27 @@
1
- import { TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT } from './chunk-ESMZCFJY.js';
1
+ import { initTfheThreadPool } from './chunk-KSOV5B4B.js';
2
+ import { TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT } from './chunk-NMKMCAI3.js';
2
3
 
3
4
  // web/zkProve.worker.ts
4
5
  var tfheModule = null;
5
- var initialized = false;
6
- async function initTfhe() {
7
- if (initialized)
8
- return;
9
- try {
10
- tfheModule = await import('tfhe');
11
- await tfheModule.default();
12
- await tfheModule.init_panic_hook();
13
- initialized = true;
14
- console.log("[Worker] TFHE initialized");
15
- } catch (error) {
16
- console.error("[Worker] Failed to initialize TFHE:", error);
17
- throw error;
18
- }
6
+ var initPromise = null;
7
+ var threadPool = null;
8
+ function initTfhe(tfheThreads = "auto") {
9
+ initPromise ??= (async () => {
10
+ try {
11
+ tfheModule = await import('tfhe');
12
+ await tfheModule.default();
13
+ await tfheModule.init_panic_hook();
14
+ threadPool = await initTfheThreadPool(tfheModule, tfheThreads);
15
+ console.log(
16
+ threadPool.enabled ? `[Worker] TFHE initialized (rayon thread pool: ${threadPool.threads} threads)` : `[Worker] TFHE initialized (single-threaded: ${threadPool.reason})`
17
+ );
18
+ } catch (error) {
19
+ console.error("[Worker] Failed to initialize TFHE:", error);
20
+ initPromise = null;
21
+ throw error;
22
+ }
23
+ })();
24
+ return initPromise;
19
25
  }
20
26
  function fromHexString(hexString) {
21
27
  const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
@@ -26,7 +32,7 @@ function fromHexString(hexString) {
26
32
  }
27
33
  if (typeof self !== "undefined") {
28
34
  self.onmessage = async (event) => {
29
- const { id, type, fheKeyHex, crsHex, items, metadata } = event.data;
35
+ const { id, type, fheKeyHex, crsHex, items, metadata, tfheThreads } = event.data;
30
36
  if (type !== "zkProve") {
31
37
  self.postMessage({
32
38
  id,
@@ -36,7 +42,7 @@ if (typeof self !== "undefined") {
36
42
  return;
37
43
  }
38
44
  try {
39
- await initTfhe();
45
+ await initTfhe(tfheThreads);
40
46
  if (!tfheModule) {
41
47
  throw new Error("TFHE module not initialized");
42
48
  }
package/node/storage.ts CHANGED
@@ -6,7 +6,7 @@ import { promises as fs } from 'fs';
6
6
  import { join } from 'path';
7
7
 
8
8
  // Memory storage fallback
9
- const memoryStorage: Record<string, string> = {};
9
+ const memoryStorage: Record<string, unknown> = {};
10
10
 
11
11
  /**
12
12
  * Creates a node storage implementation using the filesystem
@@ -34,7 +34,7 @@ export const createNodeStorage = (): IStorage => {
34
34
  await fs.writeFile(filePath, JSON.stringify(value));
35
35
  } catch (e) {
36
36
  console.warn('Node.js filesystem modules not available, falling back to memory storage' + e);
37
- memoryStorage[name] = JSON.stringify(value);
37
+ memoryStorage[name] = value;
38
38
  }
39
39
  },
40
40
  removeItem: async (name: string) => {
@@ -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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cofhe/sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "SDK for Fhenix COFHE coprocessor interaction",
6
6
  "main": "./dist/core.cjs",
@@ -101,9 +101,9 @@
101
101
  "tsup": "8.0.2",
102
102
  "typescript": "5.5.4",
103
103
  "vitest": "3.2.4",
104
- "@cofhe/eslint-config": "0.2.1",
105
104
  "@cofhe/test-setup": "0.0.0",
106
- "@cofhe/tsconfig": "0.1.2"
105
+ "@cofhe/tsconfig": "0.1.2",
106
+ "@cofhe/eslint-config": "0.2.1"
107
107
  },
108
108
  "publishConfig": {
109
109
  "access": "public",
@@ -116,6 +116,8 @@
116
116
  "check:types": "tsc --noEmit",
117
117
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
118
118
  "test": "vitest run",
119
+ "test:live": "vitest run core/test/decrypt.test.ts node/test/inherited.test.ts web/test/inherited.web.test.ts",
120
+ "test:threadpool": "vitest run web/test/threadPoolOff.web.test.ts web/test/threadPoolOn.web.test.ts web/test/mainThreadPool.web.test.ts web/test/workerInitRace.web.test.ts --no-file-parallelism",
119
121
  "test:watch": "vitest",
120
122
  "test:coverage": "vitest run --coverage"
121
123
  }