@cofhe/sdk 0.1.0 → 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 (121) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/adapters/ethers5.test.ts +174 -0
  3. package/adapters/ethers5.ts +36 -0
  4. package/adapters/ethers6.test.ts +169 -0
  5. package/adapters/ethers6.ts +36 -0
  6. package/adapters/hardhat-node.ts +167 -0
  7. package/adapters/hardhat.hh2.test.ts +159 -0
  8. package/adapters/hardhat.ts +36 -0
  9. package/adapters/index.test.ts +20 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +99 -0
  12. package/adapters/test-utils.ts +53 -0
  13. package/adapters/types.ts +6 -0
  14. package/adapters/wagmi.test.ts +156 -0
  15. package/adapters/wagmi.ts +17 -0
  16. package/chains/chains/arbSepolia.ts +14 -0
  17. package/chains/chains/baseSepolia.ts +14 -0
  18. package/chains/chains/hardhat.ts +15 -0
  19. package/chains/chains/localcofhe.ts +14 -0
  20. package/chains/chains/sepolia.ts +14 -0
  21. package/chains/chains.test.ts +50 -0
  22. package/chains/defineChain.ts +18 -0
  23. package/chains/index.ts +35 -0
  24. package/chains/types.ts +32 -0
  25. package/core/baseBuilder.ts +119 -0
  26. package/core/client.test.ts +315 -0
  27. package/core/client.ts +292 -0
  28. package/core/clientTypes.ts +108 -0
  29. package/core/config.test.ts +235 -0
  30. package/core/config.ts +220 -0
  31. package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
  32. package/core/decrypt/cofheMocksSealOutput.ts +57 -0
  33. package/core/decrypt/decryptHandleBuilder.ts +287 -0
  34. package/core/decrypt/decryptUtils.ts +28 -0
  35. package/core/decrypt/tnSealOutputV1.ts +59 -0
  36. package/core/decrypt/tnSealOutputV2.ts +298 -0
  37. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  38. package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
  39. package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
  40. package/core/encrypt/encryptInputsBuilder.ts +560 -0
  41. package/core/encrypt/encryptUtils.ts +67 -0
  42. package/core/encrypt/zkPackProveVerify.ts +335 -0
  43. package/core/error.ts +168 -0
  44. package/core/fetchKeys.test.ts +195 -0
  45. package/core/fetchKeys.ts +144 -0
  46. package/core/index.ts +89 -0
  47. package/core/keyStore.test.ts +226 -0
  48. package/core/keyStore.ts +154 -0
  49. package/core/permits.test.ts +494 -0
  50. package/core/permits.ts +200 -0
  51. package/core/types.ts +398 -0
  52. package/core/utils.ts +130 -0
  53. package/dist/adapters.cjs +88 -0
  54. package/dist/adapters.d.cts +14576 -0
  55. package/dist/adapters.d.ts +14576 -0
  56. package/dist/adapters.js +83 -0
  57. package/dist/chains.cjs +114 -0
  58. package/dist/chains.d.cts +121 -0
  59. package/dist/chains.d.ts +121 -0
  60. package/dist/chains.js +1 -0
  61. package/dist/chunk-UGBVZNRT.js +818 -0
  62. package/dist/chunk-WEAZ25JO.js +105 -0
  63. package/dist/chunk-WGCRJCBR.js +2523 -0
  64. package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
  65. package/dist/clientTypes-Es7fyi65.d.ts +914 -0
  66. package/dist/core.cjs +3414 -0
  67. package/dist/core.d.cts +111 -0
  68. package/dist/core.d.ts +111 -0
  69. package/dist/core.js +3 -0
  70. package/dist/node.cjs +3286 -0
  71. package/dist/node.d.cts +22 -0
  72. package/dist/node.d.ts +22 -0
  73. package/dist/node.js +91 -0
  74. package/dist/permit-fUSe6KKq.d.cts +349 -0
  75. package/dist/permit-fUSe6KKq.d.ts +349 -0
  76. package/dist/permits.cjs +871 -0
  77. package/dist/permits.d.cts +1045 -0
  78. package/dist/permits.d.ts +1045 -0
  79. package/dist/permits.js +1 -0
  80. package/dist/types-KImPrEIe.d.cts +48 -0
  81. package/dist/types-KImPrEIe.d.ts +48 -0
  82. package/dist/web.cjs +3478 -0
  83. package/dist/web.d.cts +38 -0
  84. package/dist/web.d.ts +38 -0
  85. package/dist/web.js +240 -0
  86. package/dist/zkProve.worker.cjs +93 -0
  87. package/dist/zkProve.worker.d.cts +2 -0
  88. package/dist/zkProve.worker.d.ts +2 -0
  89. package/dist/zkProve.worker.js +91 -0
  90. package/node/client.test.ts +147 -0
  91. package/node/config.test.ts +68 -0
  92. package/node/encryptInputs.test.ts +155 -0
  93. package/node/index.ts +97 -0
  94. package/node/storage.ts +51 -0
  95. package/package.json +27 -15
  96. package/permits/index.ts +68 -0
  97. package/permits/localstorage.test.ts +117 -0
  98. package/permits/permit.test.ts +477 -0
  99. package/permits/permit.ts +405 -0
  100. package/permits/sealing.test.ts +84 -0
  101. package/permits/sealing.ts +131 -0
  102. package/permits/signature.ts +79 -0
  103. package/permits/store.test.ts +128 -0
  104. package/permits/store.ts +166 -0
  105. package/permits/test-utils.ts +20 -0
  106. package/permits/types.ts +191 -0
  107. package/permits/utils.ts +62 -0
  108. package/permits/validation.test.ts +288 -0
  109. package/permits/validation.ts +369 -0
  110. package/web/client.web.test.ts +147 -0
  111. package/web/config.web.test.ts +69 -0
  112. package/web/encryptInputs.web.test.ts +172 -0
  113. package/web/index.ts +161 -0
  114. package/web/storage.ts +34 -0
  115. package/web/worker.builder.web.test.ts +148 -0
  116. package/web/worker.config.web.test.ts +329 -0
  117. package/web/worker.output.web.test.ts +84 -0
  118. package/web/workerManager.test.ts +80 -0
  119. package/web/workerManager.ts +214 -0
  120. package/web/workerManager.web.test.ts +114 -0
  121. package/web/zkProve.worker.ts +133 -0
@@ -0,0 +1,560 @@
1
+ import {
2
+ type ZkBuilderAndCrsGenerator,
3
+ type ZkProveWorkerFunction,
4
+ zkPack,
5
+ zkProve,
6
+ zkProveWithWorker,
7
+ zkVerify,
8
+ constructZkPoKMetadata,
9
+ } from './zkPackProveVerify.js';
10
+ import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
11
+ import {
12
+ type EncryptStepCallbackFunction,
13
+ EncryptStep,
14
+ type EncryptableItem,
15
+ type EncryptedItemInput,
16
+ type EncryptedItemInputs,
17
+ type TfheInitializer,
18
+ type EncryptStepCallbackContext,
19
+ } from '../types.js';
20
+ import { cofheMocksCheckEncryptableBits, cofheMocksZkVerifySign } from './cofheMocksZkVerifySign.js';
21
+ import { hardhat } from 'viem/chains';
22
+ import { fetchKeys, type FheKeyDeserializer } from '../fetchKeys.js';
23
+ import { getZkVerifierUrlOrThrow } from '../config.js';
24
+ import { type WalletClient } from 'viem';
25
+ import { sleep } from '../utils.js';
26
+ import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
27
+ import { type KeysStorage } from '../keyStore.js';
28
+
29
+ type EncryptInputsBuilderParams<T extends EncryptableItem[]> = BaseBuilderParams & {
30
+ inputs: [...T];
31
+ securityZone?: number;
32
+
33
+ zkvWalletClient?: WalletClient | undefined;
34
+
35
+ tfhePublicKeyDeserializer: FheKeyDeserializer | undefined;
36
+ compactPkeCrsDeserializer: FheKeyDeserializer | undefined;
37
+ zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator | undefined;
38
+ initTfhe: TfheInitializer | undefined;
39
+ zkProveWorkerFn: ZkProveWorkerFunction | undefined;
40
+
41
+ keysStorage: KeysStorage | undefined;
42
+ };
43
+
44
+ /**
45
+ * EncryptInputsBuilder exposes a builder pattern for encrypting inputs.
46
+ * account, securityZone, and chainId can be overridden in the builder.
47
+ * config, tfhePublicKeyDeserializer, compactPkeCrsDeserializer, and zkBuilderAndCrsGenerator are required to be set in the builder.
48
+ */
49
+
50
+ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuilder {
51
+ private securityZone: number;
52
+ private stepCallback?: EncryptStepCallbackFunction;
53
+ private inputItems: [...T];
54
+
55
+ private zkvWalletClient: WalletClient | undefined;
56
+
57
+ private tfhePublicKeyDeserializer: FheKeyDeserializer;
58
+ private compactPkeCrsDeserializer: FheKeyDeserializer;
59
+ private zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator;
60
+ private initTfhe: TfheInitializer | undefined;
61
+ private zkProveWorkerFn: ZkProveWorkerFunction | undefined;
62
+
63
+ private keysStorage: KeysStorage | undefined;
64
+
65
+ // Worker configuration (from config, overrideable)
66
+ private useWorker: boolean;
67
+
68
+ private stepTimestamps: Record<EncryptStep, number> = {
69
+ [EncryptStep.InitTfhe]: 0,
70
+ [EncryptStep.FetchKeys]: 0,
71
+ [EncryptStep.Pack]: 0,
72
+ [EncryptStep.Prove]: 0,
73
+ [EncryptStep.Verify]: 0,
74
+ };
75
+
76
+ constructor(params: EncryptInputsBuilderParams<T>) {
77
+ super({
78
+ config: params.config,
79
+ publicClient: params.publicClient,
80
+ walletClient: params.walletClient,
81
+ chainId: params.chainId,
82
+ account: params.account,
83
+ requireConnected: params.requireConnected,
84
+ });
85
+
86
+ this.inputItems = params.inputs;
87
+ this.securityZone = params.securityZone ?? 0;
88
+
89
+ this.zkvWalletClient = params.zkvWalletClient;
90
+
91
+ // Check that tfhePublicKeyDeserializer is provided
92
+ if (!params.tfhePublicKeyDeserializer) {
93
+ throw new CofhesdkError({
94
+ code: CofhesdkErrorCode.MissingTfhePublicKeyDeserializer,
95
+ message: 'EncryptInputsBuilder tfhePublicKeyDeserializer is undefined',
96
+ hint: 'Ensure client has been created with a tfhePublicKeyDeserializer.',
97
+ context: {
98
+ tfhePublicKeyDeserializer: params.tfhePublicKeyDeserializer,
99
+ },
100
+ });
101
+ }
102
+ this.tfhePublicKeyDeserializer = params.tfhePublicKeyDeserializer;
103
+
104
+ // Check that compactPkeCrsDeserializer is provided
105
+ if (!params.compactPkeCrsDeserializer) {
106
+ throw new CofhesdkError({
107
+ code: CofhesdkErrorCode.MissingCompactPkeCrsDeserializer,
108
+ message: 'EncryptInputsBuilder compactPkeCrsDeserializer is undefined',
109
+ hint: 'Ensure client has been created with a compactPkeCrsDeserializer.',
110
+ context: {
111
+ compactPkeCrsDeserializer: params.compactPkeCrsDeserializer,
112
+ },
113
+ });
114
+ }
115
+ this.compactPkeCrsDeserializer = params.compactPkeCrsDeserializer;
116
+
117
+ // Check that zkBuilderAndCrsGenerator is provided
118
+ if (!params.zkBuilderAndCrsGenerator) {
119
+ throw new CofhesdkError({
120
+ code: CofhesdkErrorCode.MissingZkBuilderAndCrsGenerator,
121
+ message: 'EncryptInputsBuilder zkBuilderAndCrsGenerator is undefined',
122
+ hint: 'Ensure client has been created with a zkBuilderAndCrsGenerator.',
123
+ context: {
124
+ zkBuilderAndCrsGenerator: params.zkBuilderAndCrsGenerator,
125
+ },
126
+ });
127
+ }
128
+ this.zkBuilderAndCrsGenerator = params.zkBuilderAndCrsGenerator;
129
+
130
+ // Optional tfhe initialization function, will be run if provided
131
+ this.initTfhe = params.initTfhe;
132
+
133
+ // Optional zkProve worker function, will be used on web if useWorkers is true and worker function is provided
134
+ this.zkProveWorkerFn = params.zkProveWorkerFn;
135
+
136
+ // Keys storage is used to store the FHE key and CRS
137
+ this.keysStorage = params.keysStorage;
138
+
139
+ // Initialize useWorker from config (can be overridden via setUseWorker) - default to true
140
+ this.useWorker = params.config?.useWorkers ?? true;
141
+ }
142
+
143
+ /**
144
+ * @param account - Account that will create the tx using the encrypted inputs.
145
+ *
146
+ * If not provided, the account will be fetched from the connected walletClient.
147
+ *
148
+ * Example:
149
+ * ```typescript
150
+ * const encrypted = await encryptInputs([Encryptable.uint128(10n)])
151
+ * .setAccount("0x123")
152
+ * .encrypt();
153
+ * ```
154
+ *
155
+ * @returns The chainable EncryptInputsBuilder instance.
156
+ */
157
+ setAccount(account: string): EncryptInputsBuilder<T> {
158
+ this.account = account;
159
+ return this;
160
+ }
161
+
162
+ getAccount(): string | undefined {
163
+ return this.account;
164
+ }
165
+
166
+ /**
167
+ * @param chainId - Chain that will consume the encrypted inputs.
168
+ *
169
+ * If not provided, the chainId will be fetched from the connected publicClient.
170
+ *
171
+ * Example:
172
+ * ```typescript
173
+ * const encrypted = await encryptInputs([Encryptable.uint128(10n)])
174
+ * .setChainId(11155111)
175
+ * .encrypt();
176
+ * ```
177
+ *
178
+ * @returns The chainable EncryptInputsBuilder instance.
179
+ */
180
+ setChainId(chainId: number): EncryptInputsBuilder<T> {
181
+ this.chainId = chainId;
182
+ return this;
183
+ }
184
+
185
+ getChainId(): number | undefined {
186
+ return this.chainId;
187
+ }
188
+
189
+ /**
190
+ * @param securityZone - Security zone to encrypt the inputs for.
191
+ *
192
+ * If not provided, the default securityZone 0 will be used.
193
+ *
194
+ * Example:
195
+ * ```typescript
196
+ * const encrypted = await encryptInputs([Encryptable.uint128(10n)])
197
+ * .setSecurityZone(1)
198
+ * .encrypt();
199
+ * ```
200
+ *
201
+ * @returns The chainable EncryptInputsBuilder instance.
202
+ */
203
+ setSecurityZone(securityZone: number): EncryptInputsBuilder<T> {
204
+ this.securityZone = securityZone;
205
+ return this;
206
+ }
207
+
208
+ getSecurityZone(): number {
209
+ return this.securityZone;
210
+ }
211
+
212
+ /**
213
+ * @param useWorker - Whether to use Web Workers for ZK proof generation.
214
+ *
215
+ * Overrides the config-level useWorkers setting for this specific encryption.
216
+ *
217
+ * Example:
218
+ * ```typescript
219
+ * const encrypted = await encryptInputs([Encryptable.uint128(10n)])
220
+ * .setUseWorker(false)
221
+ * .encrypt();
222
+ * ```
223
+ *
224
+ * @returns The chainable EncryptInputsBuilder instance.
225
+ */
226
+ setUseWorker(useWorker: boolean): EncryptInputsBuilder<T> {
227
+ this.useWorker = useWorker;
228
+ return this;
229
+ }
230
+
231
+ /**
232
+ * Gets the current worker configuration.
233
+ *
234
+ * @returns Whether Web Workers are enabled for this encryption.
235
+ *
236
+ * Example:
237
+ * ```typescript
238
+ * const builder = encryptInputs([Encryptable.uint128(10n)]);
239
+ * console.log(builder.getUseWorker()); // true (from config)
240
+ * builder.setUseWorker(false);
241
+ * console.log(builder.getUseWorker()); // false (overridden)
242
+ * ```
243
+ */
244
+ getUseWorker(): boolean {
245
+ return this.useWorker;
246
+ }
247
+
248
+ /**
249
+ * @param callback - Function to be called with the encryption step.
250
+ *
251
+ * Useful for debugging and tracking the progress of the encryption process.
252
+ * Useful for a UI element that shows the progress of the encryption process.
253
+ *
254
+ * Example:
255
+ * ```typescript
256
+ * const encrypted = await encryptInputs([Encryptable.uint128(10n)])
257
+ * .setStepCallback((step: EncryptStep) => console.log(step))
258
+ * .encrypt();
259
+ * ```
260
+ *
261
+ * @returns The EncryptInputsBuilder instance.
262
+ */
263
+ setStepCallback(callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T> {
264
+ this.stepCallback = callback;
265
+ return this;
266
+ }
267
+
268
+ getStepCallback(): EncryptStepCallbackFunction | undefined {
269
+ return this.stepCallback;
270
+ }
271
+
272
+ /**
273
+ * Fires the step callback if set
274
+ */
275
+ private fireStepStart(
276
+ step: EncryptStep,
277
+ context: Omit<EncryptStepCallbackContext, 'isStart' | 'isEnd' | 'duration'> = {}
278
+ ) {
279
+ if (!this.stepCallback) return;
280
+ this.stepTimestamps[step] = Date.now();
281
+ this.stepCallback(step, { ...context, isStart: true, isEnd: false, duration: 0 });
282
+ }
283
+ private fireStepEnd(
284
+ step: EncryptStep,
285
+ context: Omit<EncryptStepCallbackContext, 'isStart' | 'isEnd' | 'duration'> = {}
286
+ ) {
287
+ if (!this.stepCallback) return;
288
+ const duration = Date.now() - this.stepTimestamps[step];
289
+ this.stepCallback(step, { ...context, isStart: false, isEnd: true, duration });
290
+ }
291
+
292
+ /**
293
+ * zkVerifierUrl is included in the chains exported from cofhesdk/chains for use in CofhesdkConfig.supportedChains
294
+ * Users should generally not set this manually.
295
+ */
296
+ private async getZkVerifierUrl(): Promise<string> {
297
+ this.assertChainId();
298
+ return getZkVerifierUrlOrThrow(this.config, this.chainId);
299
+ }
300
+
301
+ /**
302
+ * initTfhe is a platform-specific dependency injected into core/createCofhesdkClientBase by web/createCofhesdkClient and node/createCofhesdkClient
303
+ * web/ uses zama "tfhe"
304
+ * node/ uses zama "node-tfhe"
305
+ * Users should not set this manually.
306
+ */
307
+ private async initTfheOrThrow(): Promise<boolean> {
308
+ if (!this.initTfhe) return false;
309
+
310
+ try {
311
+ return await this.initTfhe();
312
+ } catch (error) {
313
+ throw CofhesdkError.fromError(error, {
314
+ code: CofhesdkErrorCode.InitTfheFailed,
315
+ message: `Failed to initialize TFHE`,
316
+ context: {
317
+ initTfhe: this.initTfhe,
318
+ },
319
+ });
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Fetches the FHE key and CRS from the CoFHE API
325
+ * If the key/crs already exists in the store it is returned, else it is fetched, stored, and returned
326
+ */
327
+ private async fetchFheKeyAndCrs(): Promise<{
328
+ fheKey: string;
329
+ fheKeyFetchedFromCoFHE: boolean;
330
+ crs: string;
331
+ crsFetchedFromCoFHE: boolean;
332
+ }> {
333
+ this.assertChainId();
334
+ const securityZone = this.getSecurityZone();
335
+
336
+ try {
337
+ await this.keysStorage?.rehydrateKeysStore();
338
+ } catch (error) {
339
+ throw CofhesdkError.fromError(error, {
340
+ code: CofhesdkErrorCode.RehydrateKeysStoreFailed,
341
+ message: `Failed to rehydrate keys store`,
342
+ context: {
343
+ keysStorage: this.keysStorage,
344
+ },
345
+ });
346
+ }
347
+
348
+ let fheKey: string | undefined;
349
+ let fheKeyFetchedFromCoFHE: boolean = false;
350
+ let crs: string | undefined;
351
+ let crsFetchedFromCoFHE: boolean = false;
352
+
353
+ try {
354
+ [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
355
+ this.config,
356
+ this.chainId,
357
+ securityZone,
358
+ this.tfhePublicKeyDeserializer,
359
+ this.compactPkeCrsDeserializer,
360
+ this.keysStorage
361
+ );
362
+ } catch (error) {
363
+ throw CofhesdkError.fromError(error, {
364
+ code: CofhesdkErrorCode.FetchKeysFailed,
365
+ message: `Failed to fetch FHE key and CRS`,
366
+ context: {
367
+ config: this.config,
368
+ chainId: this.chainId,
369
+ securityZone,
370
+ compactPkeCrsDeserializer: this.compactPkeCrsDeserializer,
371
+ tfhePublicKeyDeserializer: this.tfhePublicKeyDeserializer,
372
+ },
373
+ });
374
+ }
375
+
376
+ if (!fheKey) {
377
+ throw new CofhesdkError({
378
+ code: CofhesdkErrorCode.MissingFheKey,
379
+ message: `FHE key not found`,
380
+ context: {
381
+ chainId: this.chainId,
382
+ securityZone,
383
+ },
384
+ });
385
+ }
386
+
387
+ if (!crs) {
388
+ throw new CofhesdkError({
389
+ code: CofhesdkErrorCode.MissingCrs,
390
+ message: `CRS not found for chainId <${this.chainId}>`,
391
+ context: {
392
+ chainId: this.chainId,
393
+ },
394
+ });
395
+ }
396
+
397
+ return { fheKey, fheKeyFetchedFromCoFHE, crs, crsFetchedFromCoFHE };
398
+ }
399
+
400
+ /**
401
+ * @dev Encrypt against the cofheMocks instead of CoFHE
402
+ *
403
+ * In the cofheMocks, the MockZkVerifier contract is deployed on hardhat to a fixed address, this contract handles mocking the zk verifying.
404
+ * cofheMocksInsertPackedHashes - stores the ctHashes and their plaintext values for on-chain mocking of FHE operations.
405
+ * cofheMocksZkCreateProofSignatures - creates signatures to be included in the encrypted inputs. The signers address is known and verified in the mock contracts.
406
+ */
407
+ private async mocksEncrypt(): Promise<[...EncryptedItemInputs<T>]> {
408
+ this.assertAccount();
409
+ this.assertPublicClient();
410
+ this.assertWalletClient();
411
+
412
+ this.fireStepStart(EncryptStep.InitTfhe);
413
+ await sleep(100);
414
+ this.fireStepEnd(EncryptStep.InitTfhe, { tfheInitializationExecuted: false });
415
+
416
+ this.fireStepStart(EncryptStep.FetchKeys);
417
+ await sleep(100);
418
+ this.fireStepEnd(EncryptStep.FetchKeys, { fheKeyFetchedFromCoFHE: false, crsFetchedFromCoFHE: false });
419
+
420
+ this.fireStepStart(EncryptStep.Pack);
421
+ await cofheMocksCheckEncryptableBits(this.inputItems);
422
+ await sleep(100);
423
+ this.fireStepEnd(EncryptStep.Pack);
424
+
425
+ this.fireStepStart(EncryptStep.Prove);
426
+ await sleep(500);
427
+ this.fireStepEnd(EncryptStep.Prove);
428
+
429
+ this.fireStepStart(EncryptStep.Verify);
430
+ await sleep(500);
431
+ const signedResults = await cofheMocksZkVerifySign(
432
+ this.inputItems,
433
+ this.account,
434
+ this.securityZone,
435
+ this.publicClient,
436
+ this.walletClient,
437
+ this.zkvWalletClient
438
+ );
439
+ const encryptedInputs: EncryptedItemInput[] = signedResults.map(({ ct_hash, signature }, index) => ({
440
+ ctHash: BigInt(ct_hash),
441
+ securityZone: this.securityZone,
442
+ utype: this.inputItems[index].utype,
443
+ signature,
444
+ }));
445
+ this.fireStepEnd(EncryptStep.Verify);
446
+
447
+ return encryptedInputs as [...EncryptedItemInputs<T>];
448
+ }
449
+
450
+ /**
451
+ * In the production context, perform a true encryption with the CoFHE coprocessor.
452
+ */
453
+ private async productionEncrypt(): Promise<[...EncryptedItemInputs<T>]> {
454
+ this.assertAccount();
455
+ this.assertChainId();
456
+
457
+ this.fireStepStart(EncryptStep.InitTfhe);
458
+
459
+ // Deferred initialization of tfhe wasm until encrypt is called
460
+ // Returns true if tfhe was initialized, false if already initialized
461
+ const tfheInitializationExecuted = await this.initTfheOrThrow();
462
+
463
+ this.fireStepEnd(EncryptStep.InitTfhe, { tfheInitializationExecuted });
464
+
465
+ this.fireStepStart(EncryptStep.FetchKeys);
466
+
467
+ // Deferred fetching of fheKey and crs until encrypt is called
468
+ // if the key/crs is already in the store, it is not fetched from the CoFHE API
469
+ const { fheKey, fheKeyFetchedFromCoFHE, crs, crsFetchedFromCoFHE } = await this.fetchFheKeyAndCrs();
470
+
471
+ let { zkBuilder, zkCrs } = this.zkBuilderAndCrsGenerator(fheKey, crs);
472
+
473
+ this.fireStepEnd(EncryptStep.FetchKeys, { fheKeyFetchedFromCoFHE, crsFetchedFromCoFHE });
474
+
475
+ this.fireStepStart(EncryptStep.Pack);
476
+
477
+ zkBuilder = zkPack(this.inputItems, zkBuilder);
478
+
479
+ this.fireStepEnd(EncryptStep.Pack);
480
+
481
+ this.fireStepStart(EncryptStep.Prove);
482
+
483
+ // Construct metadata once (used by both worker and main thread paths)
484
+ const metadata = constructZkPoKMetadata(this.account, this.securityZone, this.chainId);
485
+
486
+ let proof: Uint8Array | null = null;
487
+ let usedWorker = false;
488
+ let workerFailedError: string | undefined;
489
+
490
+ // Decision logic: try worker if enabled and available, fallback to main thread
491
+ if (this.useWorker && this.zkProveWorkerFn) {
492
+ try {
493
+ // Call worker function directly (no packing needed, worker does it)
494
+ proof = await zkProveWithWorker(this.zkProveWorkerFn, fheKey, crs, this.inputItems, metadata);
495
+ usedWorker = true;
496
+ } catch (error) {
497
+ // Worker failed - capture error for debugging
498
+ workerFailedError = error instanceof Error ? error.message : String(error);
499
+ }
500
+ }
501
+
502
+ if (proof == null) {
503
+ // Use main thread directly (workers disabled or unavailable)
504
+ proof = await zkProve(zkBuilder, zkCrs, metadata);
505
+ usedWorker = false;
506
+ }
507
+
508
+ this.fireStepEnd(EncryptStep.Prove, {
509
+ useWorker: this.useWorker,
510
+ usedWorker,
511
+ workerFailedError,
512
+ });
513
+
514
+ this.fireStepStart(EncryptStep.Verify);
515
+
516
+ const zkVerifierUrl = await this.getZkVerifierUrl();
517
+
518
+ const verifyResults = await zkVerify(zkVerifierUrl, proof, this.account, this.securityZone, this.chainId);
519
+ // Add securityZone and utype to the verify results
520
+ const encryptedInputs: EncryptedItemInput[] = verifyResults.map(
521
+ ({ ct_hash, signature }: { ct_hash: string; signature: string }, index: number) => ({
522
+ ctHash: BigInt(ct_hash),
523
+ securityZone: this.securityZone,
524
+ utype: this.inputItems[index].utype,
525
+ signature,
526
+ })
527
+ );
528
+
529
+ this.fireStepEnd(EncryptStep.Verify);
530
+
531
+ return encryptedInputs as [...EncryptedItemInputs<T>];
532
+ }
533
+
534
+ /**
535
+ * Final step of the encryption process. MUST BE CALLED LAST IN THE CHAIN.
536
+ *
537
+ * This will:
538
+ * - Pack the encryptable items into a zk proof
539
+ * - Prove the zk proof
540
+ * - Verify the zk proof with CoFHE
541
+ * - Package and return the encrypted inputs
542
+ *
543
+ * Example:
544
+ * ```typescript
545
+ * const encrypted = await encryptInputs([Encryptable.uint128(10n)])
546
+ * .setAccount('0x123...890') // optional
547
+ * .setChainId(11155111) // optional
548
+ * .encrypt(); // execute
549
+ * ```
550
+ *
551
+ * @returns The encrypted inputs.
552
+ */
553
+ async encrypt(): Promise<[...EncryptedItemInputs<T>]> {
554
+ // On hardhat chain, interact with MockZkVerifier contract instead of CoFHE
555
+ if (this.chainId === hardhat.id) return this.mocksEncrypt();
556
+
557
+ // On other chains, interact with CoFHE coprocessor
558
+ return this.productionEncrypt();
559
+ }
560
+ }
@@ -0,0 +1,67 @@
1
+ /* eslint-disable no-redeclare */
2
+ import {
3
+ type EncryptableItem,
4
+ isEncryptableItem,
5
+ type EncryptedItemInput,
6
+ type EncryptedItemInputs,
7
+ } from '../types.js';
8
+
9
+ export function encryptExtract<T>(item: T): EncryptableItem[];
10
+ export function encryptExtract<T extends any[]>(item: [...T]): EncryptableItem[];
11
+ export function encryptExtract<T>(item: T) {
12
+ if (isEncryptableItem(item)) {
13
+ return item;
14
+ }
15
+
16
+ // Object | Array
17
+ if (typeof item === 'object' && item !== null) {
18
+ if (Array.isArray(item)) {
19
+ // Array - recurse
20
+ return item.flatMap((nestedItem) => encryptExtract(nestedItem));
21
+ } else {
22
+ // Object - recurse
23
+ return Object.values(item).flatMap((value) => encryptExtract(value));
24
+ }
25
+ }
26
+
27
+ return [];
28
+ }
29
+
30
+ export function encryptReplace<T>(
31
+ item: T,
32
+ encryptedItems: EncryptedItemInput[]
33
+ ): [EncryptedItemInputs<T>, EncryptedItemInput[]];
34
+ export function encryptReplace<T extends any[]>(
35
+ item: [...T],
36
+ encryptedItems: EncryptedItemInput[]
37
+ ): [...EncryptedItemInputs<T>, EncryptedItemInput[]];
38
+ export function encryptReplace<T>(item: T, encryptedItems: EncryptedItemInput[]) {
39
+ if (isEncryptableItem(item)) {
40
+ return [encryptedItems[0], encryptedItems.slice(1)];
41
+ }
42
+
43
+ // Object | Array
44
+ if (typeof item === 'object' && item !== null) {
45
+ if (Array.isArray(item)) {
46
+ // Array - recurse
47
+ return item.reduce<[any[], EncryptedItemInput[]]>(
48
+ ([acc, remaining], item) => {
49
+ const [newItem, newRemaining] = encryptReplace(item, remaining);
50
+ return [[...acc, newItem], newRemaining];
51
+ },
52
+ [[], encryptedItems]
53
+ );
54
+ } else {
55
+ // Object - recurse
56
+ return Object.entries(item).reduce<[Record<string, any>, EncryptedItemInput[]]>(
57
+ ([acc, remaining], [key, value]) => {
58
+ const [newValue, newRemaining] = encryptReplace(value, remaining);
59
+ return [{ ...acc, [key]: newValue }, newRemaining];
60
+ },
61
+ [{}, encryptedItems]
62
+ );
63
+ }
64
+ }
65
+
66
+ return [item, encryptedItems];
67
+ }