@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,751 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { EncryptInputsBuilder } from './encryptInputsBuilder.js';
3
+ import {
4
+ type EncryptableItem,
5
+ FheTypes,
6
+ Encryptable,
7
+ type EncryptableUint128,
8
+ EncryptStep,
9
+ type TfheInitializer,
10
+ } from '../types.js';
11
+ import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
12
+ import { fromHexString, toHexString } from '../utils.js';
13
+ import { type PublicClient, createPublicClient, http, type WalletClient, createWalletClient } from 'viem';
14
+ import { privateKeyToAccount } from 'viem/accounts';
15
+ import { arbitrumSepolia } from 'viem/chains';
16
+ import { type CofhesdkConfig, createCofhesdkConfigBase } from '../config.js';
17
+ import { type ZkBuilderAndCrsGenerator } from './zkPackProveVerify.js';
18
+ import { type KeysStorage, createKeysStore } from '../keyStore.js';
19
+ import { type FheKeyDeserializer } from '../fetchKeys.js';
20
+
21
+ const MockZkVerifierUrl = 'http://localhost:3001';
22
+
23
+ // Test private keys (well-known test keys from Anvil/Hardhat)
24
+ const BOB_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Bob - always issuer
25
+
26
+ // Create real viem clients for Arbitrum Sepolia
27
+ const publicClient: PublicClient = createPublicClient({
28
+ chain: arbitrumSepolia,
29
+ transport: http(),
30
+ });
31
+
32
+ const bobWalletClient: WalletClient = createWalletClient({
33
+ chain: arbitrumSepolia,
34
+ transport: http(),
35
+ account: privateKeyToAccount(BOB_PRIVATE_KEY),
36
+ });
37
+
38
+ const stringifyWithBigInt = (obj: any): string => JSON.stringify(obj, (_, v) => (typeof v === 'bigint' ? `${v}n` : v));
39
+
40
+ const parseWithBigInt = (str: string): any =>
41
+ JSON.parse(str, (_, v) => {
42
+ if (typeof v === 'string' && /^\d+n$/.test(v)) {
43
+ return BigInt(v.slice(0, -1));
44
+ }
45
+ return v;
46
+ });
47
+
48
+ // packMetadata function removed as it's no longer needed
49
+ const unpackMetadata = (metadata: string) => {
50
+ const [signer, securityZone, chainId] = metadata.split('-');
51
+ return { signer, securityZone: parseInt(securityZone), chainId: parseInt(chainId) };
52
+ };
53
+
54
+ export const deconstructZkPoKMetadata = (
55
+ metadata: Uint8Array
56
+ ): { accountAddr: string; securityZone: number; chainId: number } => {
57
+ if (metadata.length < 53) {
58
+ // 1 + 20 + 32 = 53 bytes minimum
59
+ throw new CofhesdkError({
60
+ code: CofhesdkErrorCode.InternalError,
61
+ message: 'Invalid metadata: insufficient length',
62
+ });
63
+ }
64
+
65
+ // Extract security zone (first byte)
66
+ const securityZone = metadata[0];
67
+
68
+ // Extract account address (next 20 bytes)
69
+ const accountBytes = metadata.slice(1, 21);
70
+ const accountAddr = '0x' + toHexString(accountBytes);
71
+
72
+ // Extract chain ID (next 32 bytes, big-endian u256)
73
+ const chainIdBytes = metadata.slice(21, 53);
74
+
75
+ // Convert from big-endian u256 to number
76
+ let chainId = 0;
77
+ for (let i = 0; i < 32; i++) {
78
+ chainId = (chainId << 8) | chainIdBytes[i];
79
+ }
80
+
81
+ return {
82
+ accountAddr,
83
+ securityZone,
84
+ chainId,
85
+ };
86
+ };
87
+
88
+ class MockZkListBuilder {
89
+ private items: EncryptableItem[];
90
+ constructor(items: EncryptableItem[] = []) {
91
+ this.items = items;
92
+ }
93
+ push_boolean(data: boolean): void {
94
+ this.items.push({ utype: FheTypes.Bool, data, securityZone: 0 });
95
+ }
96
+ push_u8(data: number): void {
97
+ this.items.push({ utype: FheTypes.Uint8, data: BigInt(data), securityZone: 0 });
98
+ }
99
+ push_u16(data: number): void {
100
+ this.items.push({ utype: FheTypes.Uint16, data: BigInt(data), securityZone: 0 });
101
+ }
102
+ push_u32(data: number): void {
103
+ this.items.push({ utype: FheTypes.Uint32, data: BigInt(data), securityZone: 0 });
104
+ }
105
+ push_u64(data: bigint): void {
106
+ this.items.push({ utype: FheTypes.Uint64, data, securityZone: 0 });
107
+ }
108
+ push_u128(data: bigint): void {
109
+ this.items.push({ utype: FheTypes.Uint128, data, securityZone: 0 });
110
+ }
111
+ push_u160(data: bigint): void {
112
+ this.items.push({ utype: FheTypes.Uint160, data, securityZone: 0 });
113
+ }
114
+ build_with_proof_packed(_crs: any, metadata: Uint8Array, _computeLoad: 1): MockZkProvenList {
115
+ // Clear items to prevent persisting items between tests
116
+ const returnItems = this.items;
117
+ this.items = [];
118
+
119
+ return new MockZkProvenList(returnItems, metadata);
120
+ }
121
+ }
122
+
123
+ const MockCrs = {
124
+ free: () => {},
125
+ serialize: () => new Uint8Array(),
126
+ safe_serialize: () => new Uint8Array(),
127
+ };
128
+
129
+ // Setup fetch mock for http://localhost:3001/verify
130
+ // Simulates verification of zk proof
131
+ // Returns {ctHash: stringified value, signature: `${account_addr}-${security_zone}-${chain_id}-`, recid: 0}
132
+ // Expects the proof to be created by the MockZkListBuilder `build_with_proof_packed` above
133
+ const mockFetch = vi.fn();
134
+ global.fetch = mockFetch;
135
+ const setupZkVerifyMock = () => {
136
+ mockFetch.mockImplementation((url: string, options: any) => {
137
+ if (url === `${MockZkVerifierUrl}/verify`) {
138
+ const body = JSON.parse(options.body as string);
139
+ const { packed_list, account_addr, security_zone, chain_id } = body;
140
+
141
+ // Decode the proof data
142
+ const arr = fromHexString(packed_list);
143
+ const decoded = new TextDecoder().decode(arr);
144
+ const decodedData = parseWithBigInt(decoded);
145
+ const { items } = decodedData;
146
+
147
+ // Create mock verify results
148
+ const mockResults = items.map((item: EncryptableItem) => ({
149
+ ct_hash: BigInt(item.data).toString(),
150
+ signature: `${account_addr}-${security_zone}-${chain_id}-`,
151
+ recid: 0,
152
+ }));
153
+
154
+ return Promise.resolve({
155
+ ok: true,
156
+ json: () =>
157
+ Promise.resolve({
158
+ status: 'success',
159
+ data: mockResults,
160
+ error: null,
161
+ }),
162
+ });
163
+ }
164
+
165
+ // For other URLs, return a 404
166
+ return Promise.resolve({
167
+ ok: false,
168
+ status: 404,
169
+ text: () => Promise.resolve('Not Found'),
170
+ });
171
+ });
172
+ };
173
+
174
+ // Create a test keysStorage instance (non-persisted for tests)
175
+ let keysStorage: KeysStorage;
176
+
177
+ const insertMockKeys = (chainId: number, securityZone: number) => {
178
+ keysStorage.setFheKey(chainId, securityZone, '0x1234567890');
179
+ keysStorage.setCrs(chainId, '0x1234567890');
180
+ };
181
+
182
+ const mockTfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string) => {
183
+ return buff;
184
+ };
185
+
186
+ const mockCompactPkeCrsDeserializer: FheKeyDeserializer = (buff: string) => {
187
+ return buff;
188
+ };
189
+
190
+ const mockInitTfhe: TfheInitializer = () => {
191
+ return Promise.resolve(true);
192
+ };
193
+
194
+ const mockZkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: string) => {
195
+ return {
196
+ zkBuilder: new MockZkListBuilder(),
197
+ zkCrs: MockCrs,
198
+ };
199
+ };
200
+
201
+ const createMockCofhesdkConfig = (chainId: number, zkVerifierUrl: string) => {
202
+ return createCofhesdkConfigBase({
203
+ supportedChains: [
204
+ {
205
+ id: chainId,
206
+ name: 'Mock Chain',
207
+ network: 'Mock Network',
208
+ coFheUrl: MockZkVerifierUrl,
209
+ thresholdNetworkUrl: MockZkVerifierUrl,
210
+ environment: 'TESTNET',
211
+ verifierUrl: zkVerifierUrl,
212
+ },
213
+ ],
214
+ });
215
+ };
216
+
217
+ class MockZkProvenList {
218
+ private items: EncryptableItem[];
219
+ private metadata: Uint8Array;
220
+
221
+ constructor(items: EncryptableItem[], metadata: Uint8Array) {
222
+ this.items = items;
223
+ this.metadata = metadata;
224
+ }
225
+
226
+ serialize(): Uint8Array {
227
+ // Serialize this.items into JSON, then encode as Uint8Array (utf-8)
228
+ const json = stringifyWithBigInt({ items: this.items, metadata: this.metadata });
229
+ return new TextEncoder().encode(json);
230
+ }
231
+ }
232
+
233
+ describe('EncryptInputsBuilder', () => {
234
+ const defaultSender = '0x1234567890123456789012345678901234567890';
235
+ const defaultChainId = 1;
236
+ const createDefaultParams = () => {
237
+ return {
238
+ inputs: [Encryptable.uint128(100n)] as [EncryptableUint128],
239
+ account: defaultSender,
240
+ chainId: defaultChainId,
241
+
242
+ config: createMockCofhesdkConfig(defaultChainId, MockZkVerifierUrl),
243
+ publicClient: publicClient,
244
+ walletClient: bobWalletClient,
245
+
246
+ tfhePublicKeyDeserializer: mockTfhePublicKeyDeserializer,
247
+ compactPkeCrsDeserializer: mockCompactPkeCrsDeserializer,
248
+ zkBuilderAndCrsGenerator: mockZkBuilderAndCrsGenerator,
249
+ initTfhe: mockInitTfhe,
250
+ zkProveWorkerFn: undefined,
251
+ keysStorage: keysStorage,
252
+ requireConnected: vi.fn(),
253
+ };
254
+ };
255
+
256
+ let builder: EncryptInputsBuilder<[EncryptableUint128]>;
257
+
258
+ beforeEach(() => {
259
+ // Create a fresh keysStorage instance for each test (non-persisted)
260
+ keysStorage = createKeysStore(null);
261
+ setupZkVerifyMock();
262
+ insertMockKeys(defaultChainId, 0);
263
+ builder = new EncryptInputsBuilder(createDefaultParams());
264
+ });
265
+
266
+ describe('constructor and initialization', () => {
267
+ it('should initialize with default values', () => {
268
+ expect(builder).toBeInstanceOf(EncryptInputsBuilder);
269
+ });
270
+
271
+ it('should set default security zone to 0', () => {
272
+ const builderWithDefaultZone = new EncryptInputsBuilder({
273
+ ...createDefaultParams(),
274
+ securityZone: undefined,
275
+ });
276
+ // We can't directly test private properties, but we can test behavior
277
+ expect(builderWithDefaultZone).toBeInstanceOf(EncryptInputsBuilder);
278
+ });
279
+
280
+ it('should throw an error if config is not set', async () => {
281
+ // Should throw before .encrypt() is called
282
+ try {
283
+ new EncryptInputsBuilder({
284
+ ...createDefaultParams(),
285
+ config: undefined as unknown as CofhesdkConfig,
286
+ });
287
+ } catch (error) {
288
+ expect(error).toBeInstanceOf(CofhesdkError);
289
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.MissingConfig);
290
+ }
291
+ });
292
+
293
+ it('should throw an error if tfhePublicKeyDeserializer is not set', async () => {
294
+ // Should throw before .encrypt() is called
295
+ try {
296
+ new EncryptInputsBuilder({
297
+ ...createDefaultParams(),
298
+ tfhePublicKeyDeserializer: undefined as unknown as FheKeyDeserializer,
299
+ });
300
+ } catch (error) {
301
+ expect(error).toBeInstanceOf(CofhesdkError);
302
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.MissingTfhePublicKeyDeserializer);
303
+ }
304
+ });
305
+
306
+ it('should throw an error if compactPkeCrsDeserializer is not set', async () => {
307
+ // Should throw before .encrypt() is called
308
+ try {
309
+ new EncryptInputsBuilder({
310
+ ...createDefaultParams(),
311
+ compactPkeCrsDeserializer: undefined as unknown as FheKeyDeserializer,
312
+ });
313
+ } catch (error) {
314
+ expect(error).toBeInstanceOf(CofhesdkError);
315
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.MissingCompactPkeCrsDeserializer);
316
+ }
317
+ });
318
+
319
+ it('should throw an error if initTfhe throws an error', async () => {
320
+ try {
321
+ await new EncryptInputsBuilder({
322
+ ...createDefaultParams(),
323
+ initTfhe: vi.fn().mockRejectedValue(new Error('Failed to initialize TFHE')),
324
+ }).encrypt();
325
+ } catch (error) {
326
+ expect(error).toBeInstanceOf(CofhesdkError);
327
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.InitTfheFailed);
328
+ }
329
+ });
330
+
331
+ it('should not throw an error if initTfhe is set', async () => {
332
+ const result = await new EncryptInputsBuilder({
333
+ ...createDefaultParams(),
334
+ initTfhe: mockInitTfhe,
335
+ }).encrypt();
336
+ expect(result).toBeDefined();
337
+ });
338
+ });
339
+
340
+ describe('sender', () => {
341
+ it('should set sender and return builder for chaining', () => {
342
+ const sender = '0x9876543210987654321098765432109876543210';
343
+
344
+ const result = builder.setAccount(sender);
345
+
346
+ expect(result).toBe(builder);
347
+ expect(result.getAccount()).toBe(sender);
348
+ });
349
+
350
+ it('should allow chaining with other methods', () => {
351
+ const sender = '0x1111111111111111111111111111111111111111';
352
+ const securityZone = 5;
353
+
354
+ const result = builder
355
+ .setAccount(sender)
356
+ .setSecurityZone(securityZone)
357
+ .setStepCallback(() => {});
358
+
359
+ expect(result).toBe(builder);
360
+ expect(result.getAccount()).toBe(sender);
361
+ expect(result.getSecurityZone()).toBe(securityZone);
362
+ });
363
+
364
+ it('should throw an error if account is not set', async () => {
365
+ try {
366
+ await new EncryptInputsBuilder({
367
+ ...createDefaultParams(),
368
+ account: undefined,
369
+ }).encrypt();
370
+ } catch (error) {
371
+ expect(error).toBeInstanceOf(CofhesdkError);
372
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.AccountUninitialized);
373
+ }
374
+ });
375
+ });
376
+
377
+ describe('setSecurityZone', () => {
378
+ it('should set security zone and return builder for chaining', () => {
379
+ const securityZone = 42;
380
+ const result = builder.setSecurityZone(securityZone);
381
+ expect(result).toBe(builder);
382
+ expect(result.getSecurityZone()).toBe(securityZone);
383
+ });
384
+
385
+ it('should allow chaining with other methods', () => {
386
+ const sender = '0x2222222222222222222222222222222222222222';
387
+ const securityZone = 10;
388
+
389
+ const result = builder
390
+ .setSecurityZone(securityZone)
391
+ .setAccount(sender)
392
+ .setStepCallback(() => {});
393
+
394
+ expect(result).toBe(builder);
395
+ expect(result.getAccount()).toBe(sender);
396
+ expect(result.getSecurityZone()).toBe(securityZone);
397
+ });
398
+ });
399
+
400
+ describe('chainId', () => {
401
+ it('should set chain id and return builder for chaining', () => {
402
+ const chainId = 2;
403
+ const result = builder.setChainId(chainId);
404
+ expect(result).toBe(builder);
405
+ expect(result.getChainId()).toBe(chainId);
406
+ });
407
+
408
+ it('should throw an error if chainId is not set', async () => {
409
+ try {
410
+ await new EncryptInputsBuilder({
411
+ ...createDefaultParams(),
412
+ chainId: undefined,
413
+ }).encrypt();
414
+ } catch (error) {
415
+ expect(error).toBeInstanceOf(CofhesdkError);
416
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.ChainIdUninitialized);
417
+ }
418
+ });
419
+ });
420
+
421
+ describe('zkVerifierUrl', () => {
422
+ it('should throw if zkVerifierUrl is not set', async () => {
423
+ try {
424
+ await new EncryptInputsBuilder({
425
+ ...createDefaultParams(),
426
+ inputs: [Encryptable.uint128(100n)] as [EncryptableUint128],
427
+ account: '0x1234567890123456789012345678901234567890',
428
+ chainId: 1,
429
+ config: createMockCofhesdkConfig(defaultChainId, undefined as unknown as string),
430
+ }).encrypt();
431
+ } catch (error) {
432
+ expect(error).toBeInstanceOf(CofhesdkError);
433
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.ZkVerifierUrlUninitialized);
434
+ }
435
+ });
436
+ });
437
+
438
+ describe('setStepCallback', () => {
439
+ it('should set step callback and return builder for chaining', () => {
440
+ const callback = vi.fn();
441
+ const result = builder.setStepCallback(callback);
442
+ expect(result).toBe(builder);
443
+ });
444
+
445
+ it('should allow chaining with other methods', () => {
446
+ const callback = vi.fn();
447
+ const result = builder.setStepCallback(callback).setSecurityZone(15);
448
+
449
+ expect(result).toBe(builder);
450
+ });
451
+ });
452
+
453
+ describe('encrypt', () => {
454
+ it('should execute the full encryption flow with step callbacks', async () => {
455
+ const stepCallback = vi.fn();
456
+ builder.setStepCallback(stepCallback);
457
+
458
+ const result = await builder.encrypt();
459
+
460
+ // Verify step callbacks were called in order
461
+ expect(stepCallback).toHaveBeenCalledTimes(10);
462
+
463
+ expect(stepCallback).toHaveBeenNthCalledWith(
464
+ 1,
465
+ EncryptStep.InitTfhe,
466
+ expect.objectContaining({
467
+ isStart: true,
468
+ isEnd: false,
469
+ duration: 0,
470
+ })
471
+ );
472
+ expect(stepCallback).toHaveBeenNthCalledWith(
473
+ 2,
474
+ EncryptStep.InitTfhe,
475
+ expect.objectContaining({
476
+ isStart: false,
477
+ isEnd: true,
478
+ duration: expect.any(Number),
479
+ })
480
+ );
481
+ expect(stepCallback).toHaveBeenNthCalledWith(
482
+ 3,
483
+ EncryptStep.FetchKeys,
484
+ expect.objectContaining({
485
+ isStart: true,
486
+ isEnd: false,
487
+ duration: 0,
488
+ })
489
+ );
490
+ expect(stepCallback).toHaveBeenNthCalledWith(
491
+ 4,
492
+ EncryptStep.FetchKeys,
493
+ expect.objectContaining({
494
+ isStart: false,
495
+ isEnd: true,
496
+ duration: expect.any(Number),
497
+ })
498
+ );
499
+ expect(stepCallback).toHaveBeenNthCalledWith(
500
+ 5,
501
+ EncryptStep.Pack,
502
+ expect.objectContaining({
503
+ isStart: true,
504
+ isEnd: false,
505
+ duration: 0,
506
+ })
507
+ );
508
+ expect(stepCallback).toHaveBeenNthCalledWith(
509
+ 6,
510
+ EncryptStep.Pack,
511
+ expect.objectContaining({
512
+ isStart: false,
513
+ isEnd: true,
514
+ duration: expect.any(Number),
515
+ })
516
+ );
517
+ expect(stepCallback).toHaveBeenNthCalledWith(
518
+ 7,
519
+ EncryptStep.Prove,
520
+ expect.objectContaining({
521
+ isStart: true,
522
+ isEnd: false,
523
+ duration: 0,
524
+ })
525
+ );
526
+ expect(stepCallback).toHaveBeenNthCalledWith(
527
+ 8,
528
+ EncryptStep.Prove,
529
+ expect.objectContaining({
530
+ isStart: false,
531
+ isEnd: true,
532
+ duration: expect.any(Number),
533
+ })
534
+ );
535
+ expect(stepCallback).toHaveBeenNthCalledWith(
536
+ 9,
537
+ EncryptStep.Verify,
538
+ expect.objectContaining({
539
+ isStart: true,
540
+ isEnd: false,
541
+ duration: 0,
542
+ })
543
+ );
544
+ expect(stepCallback).toHaveBeenNthCalledWith(
545
+ 10,
546
+ EncryptStep.Verify,
547
+ expect.objectContaining({
548
+ isStart: false,
549
+ isEnd: true,
550
+ duration: expect.any(Number),
551
+ })
552
+ );
553
+
554
+ // Verify result structure
555
+ expect(result).toBeDefined();
556
+ expect(Array.isArray(result)).toBe(true);
557
+
558
+ // Verify result embedded metadata
559
+ const [encrypted] = result;
560
+ const encryptedMetadata = unpackMetadata(encrypted.signature);
561
+ expect(encryptedMetadata).toBeDefined();
562
+ expect(encryptedMetadata.signer).toBe(defaultSender);
563
+ expect(encryptedMetadata.securityZone).toBe(0);
564
+ expect(encryptedMetadata.chainId).toBe(defaultChainId);
565
+ });
566
+
567
+ it('should use overridden account when set', async () => {
568
+ const overriddenSender = '0x5555555555555555555555555555555555555555';
569
+ builder.setAccount(overriddenSender);
570
+
571
+ const result = await builder.encrypt();
572
+
573
+ // Verify result embedded metadata
574
+ const [encrypted] = result;
575
+ const encryptedMetadata = unpackMetadata(encrypted.signature);
576
+ expect(encryptedMetadata).toBeDefined();
577
+ expect(encryptedMetadata.signer).toBe(overriddenSender);
578
+ expect(encryptedMetadata.securityZone).toBe(0);
579
+ expect(encryptedMetadata.chainId).toBe(defaultChainId);
580
+ });
581
+
582
+ it('should use overridden security zone when set', async () => {
583
+ const overriddenZone = 7;
584
+ builder.setSecurityZone(overriddenZone);
585
+
586
+ insertMockKeys(defaultChainId, overriddenZone);
587
+
588
+ const result = await builder.encrypt();
589
+
590
+ // Verify result embedded metadata
591
+ const [encrypted] = result;
592
+ const encryptedMetadata = unpackMetadata(encrypted.signature);
593
+ expect(encryptedMetadata).toBeDefined();
594
+ expect(encryptedMetadata.signer).toBe(defaultSender);
595
+ expect(encryptedMetadata.securityZone).toBe(overriddenZone);
596
+ expect(encryptedMetadata.chainId).toBe(defaultChainId);
597
+ });
598
+
599
+ it('should work without step callback', async () => {
600
+ // No step callback set
601
+ const result = await builder.encrypt();
602
+
603
+ expect(result).toBeDefined();
604
+ expect(Array.isArray(result)).toBe(true);
605
+ // Should not throw when no callback is set
606
+ });
607
+
608
+ it('should handle multiple input types', async () => {
609
+ const multiInputBuilder = new EncryptInputsBuilder({
610
+ ...createDefaultParams(),
611
+ inputs: [Encryptable.uint128(100n), Encryptable.bool(true)] as [
612
+ ReturnType<typeof Encryptable.uint128>,
613
+ ReturnType<typeof Encryptable.bool>,
614
+ ],
615
+ });
616
+
617
+ const result = await multiInputBuilder.encrypt();
618
+
619
+ expect(result).toBeDefined();
620
+ expect(Array.isArray(result)).toBe(true);
621
+ });
622
+
623
+ it('should throw an error if total bits exceeds 2048', async () => {
624
+ try {
625
+ await new EncryptInputsBuilder({
626
+ ...createDefaultParams(),
627
+ inputs: [
628
+ Encryptable.uint128(100n),
629
+ Encryptable.uint128(100n),
630
+ Encryptable.uint128(100n),
631
+ Encryptable.uint128(100n),
632
+ Encryptable.uint128(100n),
633
+ Encryptable.uint128(100n),
634
+ Encryptable.uint128(100n),
635
+ Encryptable.uint128(100n),
636
+ Encryptable.uint128(100n),
637
+ Encryptable.uint128(100n),
638
+ Encryptable.uint128(100n),
639
+ Encryptable.uint128(100n),
640
+ Encryptable.uint128(100n),
641
+ Encryptable.uint128(100n),
642
+ Encryptable.uint128(100n),
643
+ Encryptable.uint128(100n),
644
+ Encryptable.uint128(100n),
645
+ Encryptable.uint128(100n),
646
+ Encryptable.uint128(100n),
647
+ Encryptable.uint128(100n),
648
+ ],
649
+ }).encrypt();
650
+ } catch (error) {
651
+ expect(error).toBeInstanceOf(CofhesdkError);
652
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.ZkPackFailed);
653
+ }
654
+ });
655
+
656
+ it('should throw an error if utype is invalid', async () => {
657
+ try {
658
+ await new EncryptInputsBuilder({
659
+ ...createDefaultParams(),
660
+ inputs: [
661
+ {
662
+ data: 10n,
663
+ utype: FheTypes.Uint10, // Invalid utype
664
+ },
665
+ ] as unknown as [EncryptableItem],
666
+ });
667
+ } catch (error) {
668
+ expect(error).toBeInstanceOf(CofhesdkError);
669
+ expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.ZkPackFailed);
670
+ }
671
+ });
672
+ });
673
+
674
+ // TODO: Implement error handling tests
675
+ // describe('error handling', () => {
676
+ // it('should handle ZK pack errors gracefully', async () => {
677
+ // const result = await builder.encrypt();
678
+ // expectResultError(result, CofhesdkErrorCode.InternalError, 'ZK pack failed');
679
+ // });
680
+
681
+ // it('should handle ZK prove errors gracefully', async () => {
682
+ // const result = await builder.encrypt();
683
+ // expectResultError(result, CofhesdkErrorCode.InternalError, 'ZK prove failed');
684
+ // });
685
+
686
+ // it('should handle ZK verify errors gracefully', async () => {
687
+ // const result = await builder.encrypt();
688
+ // expectResultError(result, CofhesdkErrorCode.InternalError, 'ZK verify failed');
689
+ // });
690
+ // });
691
+
692
+ describe('integration scenarios', () => {
693
+ it('should work with the complete builder chain', async () => {
694
+ const sender = '0x9999999999999999999999999999999999999999';
695
+ const securityZone = 3;
696
+
697
+ insertMockKeys(defaultChainId, securityZone);
698
+
699
+ const stepCallback = vi.fn();
700
+ const result = await builder
701
+ .setAccount(sender)
702
+ .setSecurityZone(securityZone)
703
+ .setStepCallback(stepCallback)
704
+ .encrypt();
705
+
706
+ expect(result).toBeDefined();
707
+ expect(stepCallback).toHaveBeenCalledTimes(10);
708
+
709
+ // Verify result embedded metadata
710
+ const [encrypted] = result;
711
+ const encryptedMetadata = unpackMetadata(encrypted.signature);
712
+ expect(encryptedMetadata).toBeDefined();
713
+ expect(encryptedMetadata.signer).toBe(sender);
714
+ expect(encryptedMetadata.securityZone).toBe(securityZone);
715
+ expect(encryptedMetadata.chainId).toBe(defaultChainId);
716
+ });
717
+
718
+ it('should maintain state across method calls', async () => {
719
+ const sender = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
720
+ const securityZone = 99;
721
+
722
+ insertMockKeys(defaultChainId, securityZone);
723
+
724
+ builder.setAccount(sender);
725
+ builder.setSecurityZone(securityZone);
726
+
727
+ // Call encrypt multiple times to ensure state is maintained
728
+ const result1 = await builder.encrypt();
729
+ const result2 = await builder.encrypt();
730
+
731
+ expect(result1).toBeDefined();
732
+ expect(result2).toBeDefined();
733
+
734
+ // Verify result embedded metadata
735
+ const [encrypted1] = result1;
736
+ const encryptedMetadata1 = unpackMetadata(encrypted1.signature);
737
+ expect(encryptedMetadata1).toBeDefined();
738
+ expect(encryptedMetadata1.signer).toBe(sender);
739
+ expect(encryptedMetadata1.securityZone).toBe(securityZone);
740
+ expect(encryptedMetadata1.chainId).toBe(defaultChainId);
741
+
742
+ // Verify result embedded metadata
743
+ const [encrypted2] = result2;
744
+ const encryptedMetadata2 = unpackMetadata(encrypted2.signature);
745
+ expect(encryptedMetadata2).toBeDefined();
746
+ expect(encryptedMetadata2.signer).toBe(sender);
747
+ expect(encryptedMetadata2.securityZone).toBe(securityZone);
748
+ expect(encryptedMetadata2.chainId).toBe(defaultChainId);
749
+ });
750
+ });
751
+ });