@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/adapters/ethers6.ts +28 -28
  3. package/adapters/hardhat.ts +0 -1
  4. package/adapters/index.test.ts +14 -19
  5. package/adapters/smartWallet.ts +81 -73
  6. package/adapters/test-utils.ts +45 -45
  7. package/adapters/types.ts +3 -3
  8. package/chains/chains/localcofhe.ts +14 -0
  9. package/chains/chains.test.ts +2 -1
  10. package/chains/defineChain.ts +2 -2
  11. package/chains/index.ts +3 -1
  12. package/chains/types.ts +3 -3
  13. package/core/baseBuilder.ts +30 -49
  14. package/core/client.test.ts +200 -72
  15. package/core/client.ts +152 -148
  16. package/core/clientTypes.ts +114 -0
  17. package/core/config.test.ts +30 -11
  18. package/core/config.ts +26 -13
  19. package/core/consts.ts +18 -0
  20. package/core/decrypt/cofheMocksSealOutput.ts +2 -4
  21. package/core/decrypt/decryptHandleBuilder.ts +51 -45
  22. package/core/decrypt/{tnSealOutput.ts → tnSealOutputV1.ts} +1 -1
  23. package/core/decrypt/tnSealOutputV2.ts +298 -0
  24. package/core/encrypt/cofheMocksZkVerifySign.ts +15 -16
  25. package/core/encrypt/encryptInputsBuilder.test.ts +132 -116
  26. package/core/encrypt/encryptInputsBuilder.ts +159 -111
  27. package/core/encrypt/encryptUtils.ts +6 -3
  28. package/core/encrypt/zkPackProveVerify.ts +70 -8
  29. package/core/error.ts +0 -2
  30. package/core/fetchKeys.test.ts +1 -18
  31. package/core/fetchKeys.ts +0 -26
  32. package/core/index.ts +37 -17
  33. package/core/keyStore.ts +65 -38
  34. package/core/permits.test.ts +255 -4
  35. package/core/permits.ts +83 -18
  36. package/core/types.ts +198 -152
  37. package/core/utils.ts +43 -1
  38. package/dist/adapters.d.cts +38 -20
  39. package/dist/adapters.d.ts +38 -20
  40. package/dist/chains.cjs +18 -8
  41. package/dist/chains.d.cts +31 -9
  42. package/dist/chains.d.ts +31 -9
  43. package/dist/chains.js +1 -1
  44. package/dist/{chunk-KFGPTJ6X.js → chunk-I5WFEYXX.js} +1768 -1526
  45. package/dist/{chunk-LU7BMUUT.js → chunk-R3B5TMVX.js} +330 -197
  46. package/dist/{chunk-GZCQQYVI.js → chunk-TBLR7NNE.js} +18 -9
  47. package/dist/{types-PhwGgQvs.d.ts → clientTypes-RqkgkV2i.d.ts} +331 -429
  48. package/dist/{types-bB7wLj0q.d.cts → clientTypes-e4filDzK.d.cts} +331 -429
  49. package/dist/core.cjs +3000 -2625
  50. package/dist/core.d.cts +113 -7
  51. package/dist/core.d.ts +113 -7
  52. package/dist/core.js +3 -3
  53. package/dist/node.cjs +2851 -2526
  54. package/dist/node.d.cts +4 -4
  55. package/dist/node.d.ts +4 -4
  56. package/dist/node.js +4 -3
  57. package/dist/{permit-S9CnI6MF.d.cts → permit-MZ502UBl.d.cts} +54 -41
  58. package/dist/{permit-S9CnI6MF.d.ts → permit-MZ502UBl.d.ts} +54 -41
  59. package/dist/permits.cjs +328 -195
  60. package/dist/permits.d.cts +113 -825
  61. package/dist/permits.d.ts +113 -825
  62. package/dist/permits.js +1 -1
  63. package/dist/types-YiAC4gig.d.cts +33 -0
  64. package/dist/types-YiAC4gig.d.ts +33 -0
  65. package/dist/web.cjs +3067 -2527
  66. package/dist/web.d.cts +22 -6
  67. package/dist/web.d.ts +22 -6
  68. package/dist/web.js +185 -9
  69. package/dist/zkProve.worker.cjs +93 -0
  70. package/dist/zkProve.worker.d.cts +2 -0
  71. package/dist/zkProve.worker.d.ts +2 -0
  72. package/dist/zkProve.worker.js +91 -0
  73. package/node/client.test.ts +20 -25
  74. package/node/encryptInputs.test.ts +18 -38
  75. package/node/index.ts +1 -0
  76. package/package.json +15 -15
  77. package/permits/index.ts +1 -0
  78. package/permits/localstorage.test.ts +9 -14
  79. package/permits/onchain-utils.ts +221 -0
  80. package/permits/permit.test.ts +76 -27
  81. package/permits/permit.ts +58 -95
  82. package/permits/sealing.test.ts +3 -3
  83. package/permits/sealing.ts +2 -2
  84. package/permits/store.test.ts +10 -50
  85. package/permits/store.ts +9 -21
  86. package/permits/test-utils.ts +11 -3
  87. package/permits/types.ts +39 -9
  88. package/permits/utils.ts +0 -5
  89. package/permits/validation.test.ts +29 -32
  90. package/permits/validation.ts +114 -176
  91. package/web/client.web.test.ts +20 -25
  92. package/web/config.web.test.ts +0 -2
  93. package/web/encryptInputs.web.test.ts +31 -54
  94. package/web/index.ts +65 -1
  95. package/web/storage.ts +19 -5
  96. package/web/worker.builder.web.test.ts +148 -0
  97. package/web/worker.config.web.test.ts +329 -0
  98. package/web/worker.output.web.test.ts +84 -0
  99. package/web/workerManager.test.ts +80 -0
  100. package/web/workerManager.ts +214 -0
  101. package/web/workerManager.web.test.ts +114 -0
  102. package/web/zkProve.worker.ts +133 -0
  103. package/core/result.test.ts +0 -180
  104. package/core/result.ts +0 -67
  105. package/core/test-utils.ts +0 -45
  106. package/dist/types-KImPrEIe.d.cts +0 -48
  107. package/dist/types-KImPrEIe.d.ts +0 -48
package/web/index.ts CHANGED
@@ -8,11 +8,16 @@ import {
8
8
  type CofhesdkInputConfig,
9
9
  type ZkBuilderAndCrsGenerator,
10
10
  type FheKeyDeserializer,
11
+ type EncryptableItem,
12
+ fheTypeToString,
11
13
  } from '@/core';
12
14
 
13
15
  // Import web-specific storage (internal use only)
14
16
  import { createWebStorage } from './storage.js';
15
17
 
18
+ // Import worker manager
19
+ import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
20
+
16
21
  // Import tfhe for web
17
22
  import init, { init_panic_hook, TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
18
23
 
@@ -68,6 +73,27 @@ const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: st
68
73
  return { zkBuilder, zkCrs };
69
74
  };
70
75
 
76
+ /**
77
+ * Worker-enabled zkProve function
78
+ * This submits proof generation to a Web Worker
79
+ */
80
+ async function zkProveWithWorker(
81
+ fheKeyHex: string,
82
+ crsHex: string,
83
+ items: EncryptableItem[],
84
+ metadata: Uint8Array
85
+ ): Promise<Uint8Array> {
86
+ // Serialize items for worker (convert enum to string name)
87
+ const serializedItems = items.map((item) => ({
88
+ utype: fheTypeToString(item.utype),
89
+ data: typeof item.data === 'bigint' ? item.data.toString() : item.data,
90
+ }));
91
+
92
+ // Submit to worker
93
+ const workerManager = getWorkerManager();
94
+ return await workerManager.submitProof(fheKeyHex, crsHex, serializedItems, metadata);
95
+ }
96
+
71
97
  /**
72
98
  * Creates a CoFHE SDK configuration for web with IndexedDB storage as default
73
99
  * @param config - The CoFHE SDK input configuration (fheKeyStorage will default to IndexedDB if not provided)
@@ -75,6 +101,7 @@ const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: st
75
101
  */
76
102
  export function createCofhesdkConfig(config: CofhesdkInputConfig): CofhesdkConfig {
77
103
  return createCofhesdkConfigBase({
104
+ environment: 'web',
78
105
  ...config,
79
106
  fheKeyStorage: config.fheKeyStorage === null ? null : config.fheKeyStorage ?? createWebStorage(),
80
107
  });
@@ -83,15 +110,52 @@ export function createCofhesdkConfig(config: CofhesdkInputConfig): CofhesdkConfi
83
110
  /**
84
111
  * Creates a CoFHE SDK client instance for web with TFHE automatically configured
85
112
  * TFHE will be initialized automatically on first encryption - no manual setup required
113
+ * Workers are automatically enabled if available (can be disabled via config.useWorkers)
86
114
  * @param config - The CoFHE SDK configuration (use createCofhesdkConfig to create with web defaults)
87
115
  * @returns The CoFHE SDK client instance
88
116
  */
89
- export function createCofhesdkClient(config: CofhesdkConfig): CofhesdkClient {
117
+ export function createCofhesdkClient<TConfig extends CofhesdkConfig>(config: TConfig): CofhesdkClient<TConfig> {
118
+ return createCofhesdkClientBase({
119
+ config,
120
+ zkBuilderAndCrsGenerator,
121
+ tfhePublicKeyDeserializer,
122
+ compactPkeCrsDeserializer,
123
+ initTfhe,
124
+ // Always provide the worker function if available - config.useWorkers controls usage
125
+ // areWorkersAvailable will return true if the Worker API is available and false in Node.js
126
+ zkProveWorkerFn: areWorkersAvailable() ? zkProveWithWorker : undefined,
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Terminate the worker (call on app cleanup)
132
+ */
133
+ export { terminateWorker };
134
+
135
+ /**
136
+ * Check if workers are available
137
+ */
138
+ export { areWorkersAvailable };
139
+
140
+ /**
141
+ * Test helper: Create a client with custom worker function (for testing fallback behavior)
142
+ * @internal - Only for testing purposes
143
+ */
144
+ export function createCofhesdkClientWithCustomWorker(
145
+ config: CofhesdkConfig,
146
+ customZkProveWorkerFn: (
147
+ fheKeyHex: string,
148
+ crsHex: string,
149
+ items: EncryptableItem[],
150
+ metadata: Uint8Array
151
+ ) => Promise<Uint8Array>
152
+ ): CofhesdkClient {
90
153
  return createCofhesdkClientBase({
91
154
  config,
92
155
  zkBuilderAndCrsGenerator,
93
156
  tfhePublicKeyDeserializer,
94
157
  compactPkeCrsDeserializer,
95
158
  initTfhe,
159
+ zkProveWorkerFn: customZkProveWorkerFn,
96
160
  });
97
161
  }
package/web/storage.ts CHANGED
@@ -1,20 +1,34 @@
1
1
  import type { IStorage } from '@/core';
2
- import { get, set, del } from 'idb-keyval';
3
-
2
+ import { constructClient } from 'iframe-shared-storage';
4
3
  /**
5
4
  * Creates a web storage implementation using IndexedDB
6
5
  * @returns IStorage implementation for browser environments
7
6
  */
8
7
  export const createWebStorage = (): IStorage => {
8
+ const client = constructClient({
9
+ iframe: {
10
+ src: 'https://iframe-shared-storage.vercel.app/hub.html',
11
+ messagingOptions: {
12
+ enableLog: 'both',
13
+ },
14
+
15
+ iframeReadyTimeoutMs: 30_000, // if the iframe is not initied during this interval AND a reuqest is made, such request will throw an error
16
+ methodCallTimeoutMs: 10_000, // if a method call is not answered during this interval, the call will throw an error
17
+ methodCallRetries: 3, // number of retries for a method call if it times out
18
+ },
19
+ });
20
+
21
+ const indexedDBKeyval = client.indexedDBKeyval;
9
22
  return {
10
23
  getItem: async (name: string) => {
11
- return (await get(name)) || null;
24
+ // IndexedDBKeyval returns undefined if not found, but we want null (a json-deserialized value is expected)
25
+ return (await indexedDBKeyval.get(name)) ?? null;
12
26
  },
13
27
  setItem: async (name: string, value: any) => {
14
- await set(name, value);
28
+ await indexedDBKeyval.set(name, value);
15
29
  },
16
30
  removeItem: async (name: string) => {
17
- await del(name);
31
+ await indexedDBKeyval.del(name);
18
32
  },
19
33
  };
20
34
  };
@@ -0,0 +1,148 @@
1
+ import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
2
+ import { createCofhesdkClient, createCofhesdkConfig } from './index.js';
3
+ import { Encryptable, type CofhesdkClient } from '@/core';
4
+ import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
5
+ import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
6
+ import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
7
+ import { privateKeyToAccount } from 'viem/accounts';
8
+
9
+ const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
10
+
11
+ describe('@cofhe/sdk/web - EncryptInputsBuilder Worker Methods', () => {
12
+ let cofhesdkClient: CofhesdkClient;
13
+ let publicClient: PublicClient;
14
+ let walletClient: WalletClient;
15
+
16
+ beforeAll(() => {
17
+ publicClient = createPublicClient({
18
+ chain: viemArbitrumSepolia,
19
+ transport: http(),
20
+ });
21
+
22
+ const account = privateKeyToAccount(TEST_PRIVATE_KEY);
23
+ walletClient = createWalletClient({
24
+ chain: viemArbitrumSepolia,
25
+ transport: http(),
26
+ account,
27
+ });
28
+ });
29
+
30
+ beforeEach(async () => {
31
+ const config = createCofhesdkConfig({
32
+ supportedChains: [cofhesdkArbSepolia],
33
+ });
34
+ cofhesdkClient = createCofhesdkClient(config);
35
+ await cofhesdkClient.connect(publicClient, walletClient);
36
+ });
37
+
38
+ describe('setUseWorker method', () => {
39
+ it('should have setUseWorker method on EncryptInputsBuilder', () => {
40
+ const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]);
41
+
42
+ expect(builder).toHaveProperty('setUseWorker');
43
+ expect(typeof builder.setUseWorker).toBe('function');
44
+ });
45
+
46
+ it('should return builder for method chaining', () => {
47
+ const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]);
48
+ const returnedBuilder = builder.setUseWorker(false);
49
+
50
+ // Should return the same builder instance (or at least same type)
51
+ expect(returnedBuilder).toBe(builder);
52
+ });
53
+
54
+ it('should allow chaining with other builder methods', () => {
55
+ // Should be able to chain setUseWorker with setStepCallback
56
+ const builder = cofhesdkClient
57
+ .encryptInputs([Encryptable.uint128(100n)])
58
+ .setUseWorker(false)
59
+ .setStepCallback(() => {});
60
+
61
+ expect(builder).toBeDefined();
62
+ expect(builder).toHaveProperty('encrypt');
63
+ });
64
+
65
+ it('should accept true parameter', () => {
66
+ expect(() => {
67
+ cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(true);
68
+ }).not.toThrow();
69
+ });
70
+
71
+ it('should accept false parameter', () => {
72
+ expect(() => {
73
+ cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(false);
74
+ }).not.toThrow();
75
+ });
76
+
77
+ it('should have getUseWorker method', () => {
78
+ const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]);
79
+
80
+ expect(builder).toHaveProperty('getUseWorker');
81
+ expect(typeof builder.getUseWorker).toBe('function');
82
+ });
83
+
84
+ it('should return current useWorker value', () => {
85
+ const builderWithWorkers = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(true);
86
+ const builderWithoutWorkers = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(false);
87
+
88
+ // Should reflect config values
89
+ expect(builderWithWorkers.getUseWorker()).toBe(true);
90
+ expect(builderWithoutWorkers.getUseWorker()).toBe(false);
91
+ });
92
+
93
+ it('should reflect changes from setUseWorker', () => {
94
+ const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(true);
95
+ expect(builder.getUseWorker()).toBe(true);
96
+
97
+ builder.setUseWorker(false);
98
+ expect(builder.getUseWorker()).toBe(false);
99
+
100
+ builder.setUseWorker(true);
101
+ expect(builder.getUseWorker()).toBe(true);
102
+ });
103
+ });
104
+
105
+ describe('Worker function availability', () => {
106
+ it('should initialize client without errors', () => {
107
+ const config = createCofhesdkConfig({
108
+ supportedChains: [cofhesdkArbSepolia],
109
+ useWorkers: true,
110
+ });
111
+
112
+ expect(() => {
113
+ createCofhesdkClient(config);
114
+ }).not.toThrow();
115
+ });
116
+
117
+ it('should handle worker function when workers enabled', async () => {
118
+ const config = createCofhesdkConfig({
119
+ supportedChains: [cofhesdkArbSepolia],
120
+ useWorkers: true,
121
+ });
122
+
123
+ const client = createCofhesdkClient(config);
124
+ await client.connect(publicClient, walletClient);
125
+ const builder = client.encryptInputs([Encryptable.uint128(100n)]);
126
+
127
+ // Should not throw even though workers aren't available in Node
128
+ expect(() => {
129
+ builder.setUseWorker(true);
130
+ }).not.toThrow();
131
+ });
132
+
133
+ it('should handle when workers disabled', async () => {
134
+ const config = createCofhesdkConfig({
135
+ supportedChains: [cofhesdkArbSepolia],
136
+ useWorkers: false,
137
+ });
138
+
139
+ const client = createCofhesdkClient(config);
140
+ await client.connect(publicClient, walletClient);
141
+ const builder = client.encryptInputs([Encryptable.uint128(100n)]);
142
+
143
+ expect(() => {
144
+ builder.setUseWorker(false);
145
+ }).not.toThrow();
146
+ });
147
+ });
148
+ });
@@ -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
+ });