@cofhe/sdk 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +14 -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/index.ts +3 -1
  11. package/core/baseBuilder.ts +30 -49
  12. package/core/client.test.ts +94 -77
  13. package/core/client.ts +133 -149
  14. package/core/clientTypes.ts +108 -0
  15. package/core/config.test.ts +22 -11
  16. package/core/config.ts +16 -9
  17. package/core/decrypt/decryptHandleBuilder.ts +51 -45
  18. package/core/decrypt/{tnSealOutput.ts → tnSealOutputV1.ts} +1 -1
  19. package/core/decrypt/tnSealOutputV2.ts +298 -0
  20. package/core/encrypt/cofheMocksZkVerifySign.ts +16 -10
  21. package/core/encrypt/encryptInputsBuilder.test.ts +132 -116
  22. package/core/encrypt/encryptInputsBuilder.ts +159 -111
  23. package/core/encrypt/encryptUtils.ts +6 -3
  24. package/core/encrypt/zkPackProveVerify.ts +70 -8
  25. package/core/error.ts +0 -2
  26. package/core/fetchKeys.test.ts +1 -18
  27. package/core/fetchKeys.ts +0 -26
  28. package/core/index.ts +29 -17
  29. package/core/keyStore.ts +65 -38
  30. package/core/permits.test.ts +253 -1
  31. package/core/permits.ts +80 -16
  32. package/core/types.ts +198 -152
  33. package/core/utils.ts +43 -1
  34. package/dist/adapters.d.cts +38 -20
  35. package/dist/adapters.d.ts +38 -20
  36. package/dist/chains.cjs +14 -1
  37. package/dist/chains.d.cts +23 -1
  38. package/dist/chains.d.ts +23 -1
  39. package/dist/chains.js +1 -1
  40. package/dist/{chunk-LU7BMUUT.js → chunk-UGBVZNRT.js} +39 -25
  41. package/dist/{chunk-GZCQQYVI.js → chunk-WEAZ25JO.js} +14 -2
  42. package/dist/{chunk-KFGPTJ6X.js → chunk-WGCRJCBR.js} +1920 -1692
  43. package/dist/{types-bB7wLj0q.d.cts → clientTypes-5_1nwtUe.d.cts} +308 -347
  44. package/dist/{types-PhwGgQvs.d.ts → clientTypes-Es7fyi65.d.ts} +308 -347
  45. package/dist/core.cjs +2872 -2632
  46. package/dist/core.d.cts +101 -6
  47. package/dist/core.d.ts +101 -6
  48. package/dist/core.js +3 -3
  49. package/dist/node.cjs +2716 -2520
  50. package/dist/node.d.cts +3 -3
  51. package/dist/node.d.ts +3 -3
  52. package/dist/node.js +4 -3
  53. package/dist/{permit-S9CnI6MF.d.cts → permit-fUSe6KKq.d.cts} +31 -15
  54. package/dist/{permit-S9CnI6MF.d.ts → permit-fUSe6KKq.d.ts} +31 -15
  55. package/dist/permits.cjs +39 -24
  56. package/dist/permits.d.cts +137 -148
  57. package/dist/permits.d.ts +137 -148
  58. package/dist/permits.js +1 -1
  59. package/dist/web.cjs +2929 -2518
  60. package/dist/web.d.cts +21 -5
  61. package/dist/web.d.ts +21 -5
  62. package/dist/web.js +185 -9
  63. package/dist/zkProve.worker.cjs +93 -0
  64. package/dist/zkProve.worker.d.cts +2 -0
  65. package/dist/zkProve.worker.d.ts +2 -0
  66. package/dist/zkProve.worker.js +91 -0
  67. package/node/client.test.ts +20 -25
  68. package/node/encryptInputs.test.ts +18 -38
  69. package/node/index.ts +1 -0
  70. package/package.json +14 -14
  71. package/permits/index.ts +1 -0
  72. package/permits/localstorage.test.ts +0 -1
  73. package/permits/permit.test.ts +25 -22
  74. package/permits/permit.ts +30 -21
  75. package/permits/sealing.test.ts +3 -3
  76. package/permits/sealing.ts +2 -2
  77. package/permits/store.ts +5 -7
  78. package/permits/test-utils.ts +1 -1
  79. package/permits/types.ts +17 -0
  80. package/permits/utils.ts +0 -1
  81. package/permits/validation.ts +24 -4
  82. package/web/client.web.test.ts +20 -25
  83. package/web/config.web.test.ts +0 -2
  84. package/web/encryptInputs.web.test.ts +31 -54
  85. package/web/index.ts +65 -1
  86. package/web/storage.ts +19 -5
  87. package/web/worker.builder.web.test.ts +148 -0
  88. package/web/worker.config.web.test.ts +329 -0
  89. package/web/worker.output.web.test.ts +84 -0
  90. package/web/workerManager.test.ts +80 -0
  91. package/web/workerManager.ts +214 -0
  92. package/web/workerManager.web.test.ts +114 -0
  93. package/web/zkProve.worker.ts +133 -0
  94. package/core/result.test.ts +0 -180
  95. package/core/result.ts +0 -67
  96. package/core/test-utils.ts +0 -45
@@ -8,6 +8,8 @@ const SerializedSealingPair = z.object({
8
8
  publicKey: z.string(),
9
9
  });
10
10
 
11
+ const DEFAULT_EXPIRATION_FN = () => Math.round(Date.now() / 1000) + 7 * 24 * 60 * 60; // 7 days from now
12
+
11
13
  const zPermitWithDefaults = z.object({
12
14
  name: z.string().optional().default('Unnamed Permit'),
13
15
  type: z.enum(['self', 'sharing', 'recipient']),
@@ -19,7 +21,7 @@ const zPermitWithDefaults = z.object({
19
21
  .refine((val) => val !== zeroAddress, {
20
22
  message: 'Permit issuer :: must not be zeroAddress',
21
23
  }),
22
- expiration: z.number().optional().default(1000000000000),
24
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
23
25
  recipient: z
24
26
  .string()
25
27
  .optional()
@@ -75,11 +77,14 @@ export const SelfPermitOptionsValidator = z
75
77
  .refine((val) => isAddress(val), {
76
78
  message: 'Self permit issuer :: invalid address',
77
79
  })
80
+ .refine((val) => is0xPrefixed(val), {
81
+ message: 'Self permit issuer :: must be 0x prefixed',
82
+ })
78
83
  .refine((val) => val !== zeroAddress, {
79
84
  message: 'Self permit issuer :: must not be zeroAddress',
80
85
  }),
81
86
  name: z.string().optional().default('Unnamed Permit'),
82
- expiration: z.number().optional().default(1000000000000),
87
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
83
88
  recipient: z
84
89
  .string()
85
90
  .optional()
@@ -87,6 +92,9 @@ export const SelfPermitOptionsValidator = z
87
92
  .refine((val) => isAddress(val), {
88
93
  message: 'Self permit recipient :: invalid address',
89
94
  })
95
+ .refine((val) => is0xPrefixed(val), {
96
+ message: 'Self permit recipient :: must be 0x prefixed',
97
+ })
90
98
  .refine((val) => val === zeroAddress, {
91
99
  message: 'Self permit recipient :: must be zeroAddress',
92
100
  }),
@@ -148,6 +156,9 @@ export const SharingPermitOptionsValidator = z
148
156
  .refine((val) => isAddress(val), {
149
157
  message: 'Sharing permit issuer :: invalid address',
150
158
  })
159
+ .refine((val) => is0xPrefixed(val), {
160
+ message: 'Sharing permit issuer :: must be 0x prefixed',
161
+ })
151
162
  .refine((val) => val !== zeroAddress, {
152
163
  message: 'Sharing permit issuer :: must not be zeroAddress',
153
164
  }),
@@ -156,11 +167,14 @@ export const SharingPermitOptionsValidator = z
156
167
  .refine((val) => isAddress(val), {
157
168
  message: 'Sharing permit recipient :: invalid address',
158
169
  })
170
+ .refine((val) => is0xPrefixed(val), {
171
+ message: 'Sharing permit recipient :: must be 0x prefixed',
172
+ })
159
173
  .refine((val) => val !== zeroAddress, {
160
174
  message: 'Sharing permit recipient :: must not be zeroAddress',
161
175
  }),
162
176
  name: z.string().optional().default('Unnamed Permit'),
163
- expiration: z.number().optional().default(1000000000000),
177
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
164
178
  validatorId: z.number().optional().default(0),
165
179
  validatorContract: z
166
180
  .string()
@@ -219,6 +233,9 @@ export const ImportPermitOptionsValidator = z
219
233
  .refine((val) => isAddress(val), {
220
234
  message: 'Import permit issuer :: invalid address',
221
235
  })
236
+ .refine((val) => is0xPrefixed(val), {
237
+ message: 'Import permit issuer :: must be 0x prefixed',
238
+ })
222
239
  .refine((val) => val !== zeroAddress, {
223
240
  message: 'Import permit issuer :: must not be zeroAddress',
224
241
  }),
@@ -227,6 +244,9 @@ export const ImportPermitOptionsValidator = z
227
244
  .refine((val) => isAddress(val), {
228
245
  message: 'Import permit recipient :: invalid address',
229
246
  })
247
+ .refine((val) => is0xPrefixed(val), {
248
+ message: 'Import permit recipient :: must be 0x prefixed',
249
+ })
230
250
  .refine((val) => val !== zeroAddress, {
231
251
  message: 'Import permit recipient :: must not be zeroAddress',
232
252
  }),
@@ -239,7 +259,7 @@ export const ImportPermitOptionsValidator = z
239
259
  message: 'Import permit :: issuerSignature must be provided',
240
260
  }),
241
261
  name: z.string().optional().default('Unnamed Permit'),
242
- expiration: z.number().optional().default(1000000000000),
262
+ expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
243
263
  validatorId: z.number().optional().default(0),
244
264
  validatorContract: z
245
265
  .string()
@@ -1,4 +1,4 @@
1
- import { type CofhesdkClient } from '@/core';
1
+ import { CofhesdkErrorCode, CofhesdkError, type CofhesdkClient } from '@/core';
2
2
  import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
3
3
 
4
4
  import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
@@ -60,11 +60,16 @@ describe('@cofhe/web - Client', () => {
60
60
  });
61
61
  });
62
62
 
63
+ describe('Environment', () => {
64
+ it('should have the correct environment', () => {
65
+ expect(cofhesdkClient.config.environment).toBe('web');
66
+ });
67
+ });
68
+
63
69
  describe('Connection', () => {
64
70
  it('should connect to real chain', async () => {
65
- const result = await cofhesdkClient.connect(publicClient, walletClient);
71
+ await cofhesdkClient.connect(publicClient, walletClient);
66
72
 
67
- expect(result.success).toBe(true);
68
73
  expect(cofhesdkClient.connected).toBe(true);
69
74
 
70
75
  const snapshot = cofhesdkClient.getSnapshot();
@@ -74,15 +79,17 @@ describe('@cofhe/web - Client', () => {
74
79
  }, 30000);
75
80
 
76
81
  it('should handle network errors', async () => {
77
- const result = await cofhesdkClient.connect(
78
- {
79
- getChainId: vi.fn().mockRejectedValue(new Error('Network error')),
80
- } as unknown as PublicClient,
81
- walletClient
82
- );
83
-
84
- expect(result.success).toBe(false);
85
- expect(cofhesdkClient.connected).toBe(false);
82
+ try {
83
+ await cofhesdkClient.connect(
84
+ {
85
+ getChainId: vi.fn().mockRejectedValue(new Error('Network error')),
86
+ } as unknown as PublicClient,
87
+ walletClient
88
+ );
89
+ } catch (error) {
90
+ expect(error).toBeInstanceOf(CofhesdkError);
91
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.PublicWalletGetChainIdFailed);
92
+ }
86
93
  }, 30000);
87
94
  });
88
95
 
@@ -113,23 +120,11 @@ describe('@cofhe/web - Client', () => {
113
120
  }, 30000);
114
121
  });
115
122
 
116
- describe('Initialization Results', () => {
117
- it('should have keyFetchResult promise', () => {
118
- expect(cofhesdkClient.initializationResults).toBeDefined();
119
- expect(cofhesdkClient.initializationResults.keyFetchResult).toBeInstanceOf(Promise);
120
- });
121
-
122
- it('should resolve keyFetchResult', async () => {
123
- const result = await cofhesdkClient.initializationResults.keyFetchResult;
124
- expect(result.success).toBe(true);
125
- });
126
- });
127
-
128
123
  describe('Builder Creation', () => {
129
124
  it('should create encrypt builder after connection', async () => {
130
125
  await cofhesdkClient.connect(publicClient, walletClient);
131
126
 
132
- const builder = cofhesdkClient.encryptInputs([{ data: 100n, utype: 2 }]);
127
+ const builder = cofhesdkClient.encryptInputs([{ data: 100n, utype: 2, securityZone: 0 }]);
133
128
 
134
129
  expect(builder).toBeDefined();
135
130
  expect(typeof builder.setChainId).toBe('function');
@@ -42,14 +42,12 @@ describe('@cofhe/web - Config', () => {
42
42
  it('should preserve all other config options', () => {
43
43
  const config = createCofhesdkConfig({
44
44
  supportedChains: [arbSepolia],
45
- fheKeysPrefetching: 'OFF',
46
45
  mocks: {
47
46
  sealOutputDelay: 500,
48
47
  },
49
48
  });
50
49
 
51
50
  expect(config.supportedChains).toEqual([arbSepolia]);
52
- expect(config.fheKeysPrefetching).toBe('OFF');
53
51
  expect(config.mocks.sealOutputDelay).toBe(500);
54
52
  expect(config.fheKeyStorage).toBeDefined();
55
53
  });
@@ -1,5 +1,5 @@
1
1
  import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
2
- import { Encryptable, FheTypes, type CofhesdkClient, type Result, CofhesdkErrorCode, CofhesdkError } from '@/core';
2
+ import { Encryptable, FheTypes, type CofhesdkClient, CofhesdkErrorCode, CofhesdkError } from '@/core';
3
3
 
4
4
  import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
5
5
  import type { PublicClient, WalletClient } from 'viem';
@@ -11,22 +11,6 @@ import { createCofhesdkClient, createCofhesdkConfig } from './index.js';
11
11
  // Real test setup - runs in browser with real tfhe
12
12
  const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
13
13
 
14
- const expectResultSuccess = <T>(result: Result<T>): T => {
15
- expect(result.success, `Result error: ${result.error?.toString()}`).toBe(true);
16
- return result.data!;
17
- };
18
-
19
- const expectResultError = <T>(result: Result<T>, errorCode?: CofhesdkErrorCode): void => {
20
- expect(result.success).toBe(false);
21
- expect(result.data).toBe(null);
22
- expect(result.error).not.toBe(null);
23
- const error = result.error as CofhesdkError;
24
- expect(error).toBeInstanceOf(CofhesdkError);
25
- if (errorCode) {
26
- expect(error.code, `Result error: ${result.error?.toString()}`).toBe(errorCode);
27
- }
28
- };
29
-
30
14
  describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
31
15
  let cofhesdkClient: CofhesdkClient;
32
16
  let publicClient: PublicClient;
@@ -60,22 +44,19 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
60
44
 
61
45
  // This will trigger real TFHE initialization in browser
62
46
  const result = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
63
- const encrypted = expectResultSuccess(result);
64
47
 
65
48
  // If we get here, TFHE was initialized successfully
66
- expect(encrypted).toBeDefined();
49
+ expect(result).toBeDefined();
67
50
  }, 60000); // Longer timeout for real operations
68
51
 
69
52
  it('should handle multiple encryptions without re-initializing', async () => {
70
53
  await cofhesdkClient.connect(publicClient, walletClient);
71
54
 
72
55
  // First encryption
73
- const result1 = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
74
- expectResultSuccess(result1);
56
+ expect(cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt()).resolves.not.toThrow();
75
57
 
76
58
  // Second encryption should reuse initialization
77
- const result2 = await cofhesdkClient.encryptInputs([Encryptable.uint64(50n)]).encrypt();
78
- expectResultSuccess(result2);
59
+ expect(cofhesdkClient.encryptInputs([Encryptable.uint64(50n)]).encrypt()).resolves.not.toThrow();
79
60
  }, 60000);
80
61
  });
81
62
 
@@ -84,15 +65,15 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
84
65
  await cofhesdkClient.connect(publicClient, walletClient);
85
66
 
86
67
  const result = await cofhesdkClient.encryptInputs([Encryptable.bool(true)]).encrypt();
87
- const encrypted = expectResultSuccess(result);
88
68
 
89
- expect(encrypted.length).toBe(1);
90
- expect(encrypted[0].utype).toBe(FheTypes.Bool);
91
- expect(encrypted[0].ctHash).toBeDefined();
92
- expect(typeof encrypted[0].ctHash).toBe('bigint');
93
- expect(encrypted[0].signature).toBeDefined();
94
- expect(typeof encrypted[0].signature).toBe('string');
95
- expect(encrypted[0].securityZone).toBe(0);
69
+ expect(result).toBeDefined();
70
+ expect(result.length).toBe(1);
71
+ expect(result[0].utype).toBe(FheTypes.Bool);
72
+ expect(result[0].ctHash).toBeDefined();
73
+ expect(typeof result[0].ctHash).toBe('bigint');
74
+ expect(result[0].signature).toBeDefined();
75
+ expect(typeof result[0].signature).toBe('string');
76
+ expect(result[0].securityZone).toBe(0);
96
77
  }, 60000);
97
78
 
98
79
  it('should encrypt all supported types together', async () => {
@@ -109,17 +90,17 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
109
90
  ];
110
91
 
111
92
  const result = await cofhesdkClient.encryptInputs(inputs).encrypt();
112
- const encrypted = expectResultSuccess(result);
93
+ expect(result).toBeDefined();
113
94
 
114
- expect(encrypted.length).toBe(7);
95
+ expect(result.length).toBe(7);
115
96
  // Verify each type
116
- expect(encrypted[0].utype).toBe(FheTypes.Bool);
117
- expect(encrypted[1].utype).toBe(FheTypes.Uint8);
118
- expect(encrypted[2].utype).toBe(FheTypes.Uint16);
119
- expect(encrypted[3].utype).toBe(FheTypes.Uint32);
120
- expect(encrypted[4].utype).toBe(FheTypes.Uint64);
121
- expect(encrypted[5].utype).toBe(FheTypes.Uint128);
122
- expect(encrypted[6].utype).toBe(FheTypes.Uint160);
97
+ expect(result[0].utype).toBe(FheTypes.Bool);
98
+ expect(result[1].utype).toBe(FheTypes.Uint8);
99
+ expect(result[2].utype).toBe(FheTypes.Uint16);
100
+ expect(result[3].utype).toBe(FheTypes.Uint32);
101
+ expect(result[4].utype).toBe(FheTypes.Uint64);
102
+ expect(result[5].utype).toBe(FheTypes.Uint128);
103
+ expect(result[6].utype).toBe(FheTypes.Uint160);
123
104
  }, 90000); // Longer timeout for multiple encryptions
124
105
  });
125
106
 
@@ -128,14 +109,13 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
128
109
  await cofhesdkClient.connect(publicClient, walletClient);
129
110
 
130
111
  const snapshot = cofhesdkClient.getSnapshot();
131
- const result = await cofhesdkClient
112
+ const encrypted = await cofhesdkClient
132
113
  .encryptInputs([Encryptable.uint128(100n)])
133
114
  .setChainId(snapshot.chainId!)
134
115
  .setAccount(snapshot.account!)
135
116
  .setSecurityZone(0)
136
117
  .encrypt();
137
118
 
138
- const encrypted = expectResultSuccess(result);
139
119
  expect(encrypted.length).toBe(1);
140
120
  expect(encrypted[0].utype).toBe(FheTypes.Uint128);
141
121
  }, 60000);
@@ -144,9 +124,12 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
144
124
  describe('Browser Error Handling', () => {
145
125
  it('should fail gracefully when not connected', async () => {
146
126
  // Don't connect the client
147
- const result = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
148
-
149
- expectResultError(result, CofhesdkErrorCode.NotConnected);
127
+ try {
128
+ const promise = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
129
+ } catch (error) {
130
+ expect(error).toBeInstanceOf(CofhesdkError);
131
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.NotConnected);
132
+ }
150
133
  }, 30000);
151
134
 
152
135
  it('should handle invalid CoFHE URL', async () => {
@@ -158,18 +141,13 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
158
141
  verifierUrl: 'http://invalid-verifier-url.local',
159
142
  },
160
143
  ],
161
- fheKeysPrefetching: 'OFF',
162
144
  });
163
145
 
164
146
  const badClient = createCofhesdkClient(badConfig);
165
147
  await badClient.connect(publicClient, walletClient);
166
148
 
167
- const result = await badClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
168
-
169
- expect(result.success).toBe(false);
170
- if (!result.success) {
171
- expect(result.error).toBeDefined();
172
- }
149
+ const promise = badClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
150
+ expect(promise).rejects.toThrow();
173
151
  }, 60000);
174
152
  });
175
153
 
@@ -181,8 +159,7 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
181
159
 
182
160
  // Perform 5 encryptions
183
161
  for (let i = 0; i < 5; i++) {
184
- const result = await cofhesdkClient.encryptInputs([Encryptable.uint128(BigInt(i))]).encrypt();
185
- expectResultSuccess(result);
162
+ await cofhesdkClient.encryptInputs([Encryptable.uint128(BigInt(i))]).encrypt();
186
163
  }
187
164
 
188
165
  const duration = Date.now() - start;
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
+ });