@cofhe/sdk 0.3.2 → 0.5.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 (97) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/adapters/{ethers5.test.ts → test/ethers5.test.ts} +2 -2
  3. package/adapters/{ethers6.test.ts → test/ethers6.test.ts} +2 -2
  4. package/adapters/{hardhat.hh2.test.ts → test/hardhat.hh2.test.ts} +2 -2
  5. package/adapters/{index.test.ts → test/index.test.ts} +1 -1
  6. package/adapters/{wagmi.test.ts → test/wagmi.test.ts} +1 -1
  7. package/chains/{chains.test.ts → test/chains.test.ts} +1 -1
  8. package/core/client.ts +15 -5
  9. package/core/clientTypes.ts +7 -5
  10. package/core/consts.ts +9 -0
  11. package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
  12. package/core/decrypt/decryptForTxBuilder.ts +24 -10
  13. package/core/decrypt/decryptForViewBuilder.ts +14 -7
  14. package/core/decrypt/polling.ts +14 -0
  15. package/core/decrypt/tnDecryptUtils.ts +65 -0
  16. package/core/decrypt/{tnDecrypt.ts → tnDecryptV1.ts} +7 -70
  17. package/core/decrypt/tnDecryptV2.ts +483 -0
  18. package/core/decrypt/tnSealOutputV2.ts +245 -104
  19. package/core/decrypt/verifyDecryptResult.ts +65 -0
  20. package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
  21. package/core/encrypt/zkPackProveVerify.ts +10 -19
  22. package/core/fetchKeys.ts +0 -2
  23. package/core/index.ts +9 -1
  24. package/core/keyStore.ts +5 -2
  25. package/core/permits.ts +8 -3
  26. package/core/{client.test.ts → test/client.test.ts} +7 -7
  27. package/core/{config.test.ts → test/config.test.ts} +1 -1
  28. package/core/test/decrypt.test.ts +252 -0
  29. package/core/test/decryptBuilders.test.ts +390 -0
  30. package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
  31. package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
  32. package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
  33. package/core/{permits.test.ts → test/permits.test.ts} +42 -1
  34. package/core/test/pollCallbacks.test.ts +563 -0
  35. package/core/types.ts +21 -0
  36. package/dist/chains.d.cts +2 -2
  37. package/dist/chains.d.ts +2 -2
  38. package/dist/chunk-4FP4V35O.js +13 -0
  39. package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
  40. package/dist/{chunk-LWMRB6SD.js → chunk-S7OKGLFD.js} +615 -198
  41. package/dist/{clientTypes-Y43CKbOz.d.cts → clientTypes-BSbwairE.d.cts} +38 -13
  42. package/dist/{clientTypes-PQha8zes.d.ts → clientTypes-DDmcgZ0a.d.ts} +38 -13
  43. package/dist/core.cjs +691 -235
  44. package/dist/core.d.cts +24 -6
  45. package/dist/core.d.ts +24 -6
  46. package/dist/core.js +3 -2
  47. package/dist/node.cjs +696 -237
  48. package/dist/node.d.cts +3 -3
  49. package/dist/node.d.ts +3 -3
  50. package/dist/node.js +14 -7
  51. package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.cts} +34 -4
  52. package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.ts} +34 -4
  53. package/dist/permits.cjs +66 -29
  54. package/dist/permits.d.cts +18 -13
  55. package/dist/permits.d.ts +18 -13
  56. package/dist/permits.js +2 -1
  57. package/dist/web.cjs +718 -242
  58. package/dist/web.d.cts +8 -4
  59. package/dist/web.d.ts +8 -4
  60. package/dist/web.js +34 -11
  61. package/dist/zkProve.worker.cjs +6 -3
  62. package/dist/zkProve.worker.js +5 -3
  63. package/node/index.ts +13 -4
  64. package/node/test/client.test.ts +25 -0
  65. package/node/test/config.test.ts +16 -0
  66. package/node/test/inherited.test.ts +244 -0
  67. package/node/test/tfheinit.test.ts +56 -0
  68. package/package.json +24 -22
  69. package/permits/permit.ts +31 -5
  70. package/permits/sealing.ts +1 -1
  71. package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
  72. package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
  73. package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
  74. package/permits/{store.test.ts → test/store.test.ts} +2 -2
  75. package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
  76. package/permits/types.ts +1 -1
  77. package/permits/validation.ts +42 -2
  78. package/web/const.ts +2 -0
  79. package/web/index.ts +20 -6
  80. package/web/storage.ts +18 -3
  81. package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
  82. package/web/test/config.web.test.ts +16 -0
  83. package/web/test/inherited.web.test.ts +245 -0
  84. package/web/test/tfheinit.web.test.ts +62 -0
  85. package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
  86. package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
  87. package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
  88. package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
  89. package/web/zkProve.worker.ts +4 -3
  90. package/node/client.test.ts +0 -147
  91. package/node/config.test.ts +0 -68
  92. package/node/encryptInputs.test.ts +0 -155
  93. package/web/config.web.test.ts +0 -69
  94. package/web/encryptInputs.web.test.ts +0 -172
  95. package/web/worker.builder.web.test.ts +0 -148
  96. /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
  97. /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
package/permits/permit.ts CHANGED
@@ -22,7 +22,6 @@ import {
22
22
  validateImportPermit,
23
23
  ValidationUtils,
24
24
  } from './validation.js';
25
- import * as z from 'zod';
26
25
  import { SignatureUtils } from './signature.js';
27
26
  import { GenerateSealingKey, SealingKey } from './sealing.js';
28
27
  import { checkPermitValidityOnChain, getAclEIP712Domain } from './onchain-utils.js';
@@ -217,9 +216,9 @@ export const PermitUtils = {
217
216
  },
218
217
 
219
218
  /**
220
- * Validate a permit
219
+ * Validate a permit (schema-level validation)
221
220
  */
222
- validate: (permit: Permit) => {
221
+ validateSchema: (permit: Permit) => {
223
222
  if (permit.type === 'self') {
224
223
  return validateSelfPermit(permit);
225
224
  } else if (permit.type === 'sharing') {
@@ -231,12 +230,28 @@ export const PermitUtils = {
231
230
  }
232
231
  },
233
232
 
233
+ /**
234
+ * Validate a permit (holistic validation).
235
+ *
236
+ * This validates:
237
+ * - Permit schema (shape + invariants)
238
+ * - Permit is signed
239
+ * - Permit is not expired
240
+ *
241
+ * For schema-only validation, use `validateSchema(permit)`.
242
+ */
243
+ validate: (permit: Permit) => {
244
+ const validated = PermitUtils.validateSchema(permit);
245
+ ValidationUtils.assertSignedAndNotExpired(validated as Permit);
246
+ return validated;
247
+ },
248
+
234
249
  /**
235
250
  * Get the permission object from a permit (for use in contracts)
236
251
  */
237
252
  getPermission: (permit: Permit, skipValidation = false): Permission => {
238
253
  if (!skipValidation) {
239
- PermitUtils.validate(permit);
254
+ PermitUtils.validateSchema(permit);
240
255
  }
241
256
 
242
257
  return {
@@ -308,8 +323,19 @@ export const PermitUtils = {
308
323
  },
309
324
 
310
325
  /**
311
- * Check if permit is valid
326
+ * Check if permit is signed and not expired
312
327
  */
328
+ isSignedAndNotExpired: (permit: Permit) => {
329
+ return ValidationUtils.isSignedAndNotExpired(permit);
330
+ },
331
+
332
+ /**
333
+ * Assert that permit is signed and not expired
334
+ */
335
+ assertSignedAndNotExpired: (permit: Permit): void => {
336
+ return ValidationUtils.assertSignedAndNotExpired(permit);
337
+ },
338
+
313
339
  isValid: (permit: Permit) => {
314
340
  return ValidationUtils.isValid(permit);
315
341
  },
@@ -1,4 +1,4 @@
1
- import * as nacl from 'tweetnacl';
1
+ import nacl from 'tweetnacl';
2
2
  import { fromHexString, toBeArray, toBigInt, toHexString, isBigIntOrNumber, isString } from './utils.js';
3
3
 
4
4
  const PRIVATE_KEY_LENGTH = 64;
@@ -11,8 +11,8 @@ import {
11
11
  setActivePermitHash,
12
12
  PermitUtils,
13
13
  permitStore,
14
- } from './index.js';
15
- import { createMockPermit } from './test-utils.js';
14
+ } from '../index.js';
15
+ import { createMockPermit } from '../test-utils.js';
16
16
 
17
17
  // Type declarations for happy-dom environment
18
18
  declare const localStorage: {
@@ -4,7 +4,7 @@ import {
4
4
  type CreateSelfPermitOptions,
5
5
  type CreateSharingPermitOptions,
6
6
  type ImportSharedPermitOptions,
7
- } from './index.js';
7
+ } from '../index.js';
8
8
  import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
9
9
  import { arbitrumSepolia } from 'viem/chains';
10
10
  import { privateKeyToAccount } from 'viem/accounts';
@@ -399,6 +399,29 @@ describe('PermitUtils Tests', () => {
399
399
  expect(parsed).not.toHaveProperty('sealingPair');
400
400
  expect(parsed).not.toHaveProperty('issuerSignature');
401
401
  });
402
+
403
+ it('should export sharing permit data with recipient and issuerSignature', async () => {
404
+ const permit = await PermitUtils.createSharingAndSign(
405
+ {
406
+ issuer: bobAddress,
407
+ recipient: aliceAddress,
408
+ name: 'Test Sharing Permit',
409
+ },
410
+ publicClient,
411
+ bobWalletClient
412
+ );
413
+
414
+ const exported = PermitUtils.export(permit);
415
+ const parsed = JSON.parse(exported);
416
+
417
+ expect(parsed.name).toBe('Test Sharing Permit');
418
+ expect(parsed.type).toBe('sharing');
419
+ expect(parsed.issuer).toBe(bobAddress);
420
+ expect(parsed.recipient).toBe(aliceAddress);
421
+ expect(parsed.issuerSignature).toBeDefined();
422
+ expect(parsed.issuerSignature).not.toBe('0x');
423
+ expect(parsed).not.toHaveProperty('sealingPair');
424
+ });
402
425
  });
403
426
 
404
427
  describe('updateName', () => {
@@ -459,6 +482,17 @@ describe('PermitUtils Tests', () => {
459
482
  expect(validation.valid).toBe(true);
460
483
  expect(validation.error).toBeNull();
461
484
  });
485
+
486
+ it('should throw on validate() for expired signed permit', async () => {
487
+ const expiredPermit = PermitUtils.createSelf({
488
+ issuer: bobAddress,
489
+ name: 'Expired Permit',
490
+ expiration: Math.floor(Date.now() / 1000) - 3600,
491
+ });
492
+
493
+ const signedExpiredPermit = await PermitUtils.sign(expiredPermit, publicClient, bobWalletClient);
494
+ expect(() => PermitUtils.validate(signedExpiredPermit)).toThrow('Permit is expired');
495
+ });
462
496
  });
463
497
 
464
498
  describe('real contract interactions', () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { SealingKey, GenerateSealingKey } from './index.js';
2
+ import { SealingKey, GenerateSealingKey } from '../index.js';
3
3
 
4
4
  describe('SealingKey', () => {
5
5
  it('should create a SealingKey with valid keys', () => {
@@ -13,9 +13,9 @@ import {
13
13
  getActivePermitHash,
14
14
  setActivePermitHash,
15
15
  PermitUtils,
16
- } from './index.js';
16
+ } from '../index.js';
17
17
 
18
- import { createMockPermit } from './test-utils.js';
18
+ import { createMockPermit } from '../test-utils.js';
19
19
 
20
20
  describe('Storage Tests', () => {
21
21
  const chainId = 1;
@@ -11,8 +11,8 @@ import {
11
11
  type CreateSelfPermitOptions,
12
12
  type CreateSharingPermitOptions,
13
13
  type ImportSharedPermitOptions,
14
- } from './index.js';
15
- import { createMockPermit } from './test-utils.js';
14
+ } from '../index.js';
15
+ import { createMockPermit } from '../test-utils.js';
16
16
 
17
17
  describe('Validation Tests', () => {
18
18
  describe('validateSelfPermitOptions', () => {
@@ -247,14 +247,14 @@ describe('Validation Tests', () => {
247
247
  });
248
248
  });
249
249
 
250
- describe('isValid', () => {
250
+ describe('isSignedAndNotExpired', () => {
251
251
  it('should return valid for valid permit', async () => {
252
252
  const permit = {
253
253
  ...(await createMockPermit()),
254
254
  expiration: Math.floor(Date.now() / 1000) + 3600,
255
255
  issuerSignature: '0x1234567890abcdef' as `0x${string}`,
256
256
  };
257
- const result = ValidationUtils.isValid(permit);
257
+ const result = ValidationUtils.isSignedAndNotExpired(permit);
258
258
  expect(result.valid).toBe(true);
259
259
  expect(result.error).toBeNull();
260
260
  });
@@ -265,7 +265,7 @@ describe('Validation Tests', () => {
265
265
  expiration: Math.floor(Date.now() / 1000) - 3600,
266
266
  issuerSignature: '0x1234567890abcdef' as `0x${string}`,
267
267
  };
268
- const result = ValidationUtils.isValid(permit);
268
+ const result = ValidationUtils.isSignedAndNotExpired(permit);
269
269
  expect(result.valid).toBe(false);
270
270
  expect(result.error).toBe('expired');
271
271
  });
@@ -276,10 +276,86 @@ describe('Validation Tests', () => {
276
276
  expiration: Math.floor(Date.now() / 1000) + 3600,
277
277
  issuerSignature: '0x' as `0x${string}`,
278
278
  };
279
- const result = ValidationUtils.isValid(permit);
279
+ const result = ValidationUtils.isSignedAndNotExpired(permit);
280
280
  expect(result.valid).toBe(false);
281
281
  expect(result.error).toBe('not-signed');
282
282
  });
283
283
  });
284
+
285
+ describe('assertSignedAndNotExpired', () => {
286
+ it('should not throw for valid permit', async () => {
287
+ const permit = {
288
+ ...(await createMockPermit()),
289
+ expiration: Math.floor(Date.now() / 1000) + 3600,
290
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
291
+ };
292
+ expect(() => ValidationUtils.assertSignedAndNotExpired(permit)).not.toThrow();
293
+ });
294
+
295
+ it('should throw for expired permit', async () => {
296
+ const permit = {
297
+ ...(await createMockPermit()),
298
+ expiration: Math.floor(Date.now() / 1000) - 3600,
299
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
300
+ };
301
+ expect(() => ValidationUtils.assertSignedAndNotExpired(permit)).toThrow('Permit is expired');
302
+ });
303
+
304
+ it('should throw for unsigned permit', async () => {
305
+ const permit = {
306
+ ...(await createMockPermit()),
307
+ expiration: Math.floor(Date.now() / 1000) + 3600,
308
+ issuerSignature: '0x' as `0x${string}`,
309
+ };
310
+ expect(() => ValidationUtils.assertSignedAndNotExpired(permit)).toThrow('Permit is not signed');
311
+ });
312
+ });
313
+
314
+ describe('isValid', () => {
315
+ it('should return invalid-schema for schema-invalid permit', async () => {
316
+ const permit = {
317
+ ...(await createMockPermit()),
318
+ type: 'self' as const,
319
+ expiration: Math.floor(Date.now() / 1000) + 3600,
320
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
321
+ // Self permits must have recipient == zeroAddress per schema.
322
+ recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' as `0x${string}`,
323
+ };
324
+
325
+ const result = ValidationUtils.isValid(permit as unknown as Permit);
326
+ expect(result.valid).toBe(false);
327
+ expect(result.error).toBe('invalid-schema');
328
+ });
329
+
330
+ it('should return expired for expired but otherwise schema-valid permit', async () => {
331
+ const permit = {
332
+ ...(await createMockPermit()),
333
+ type: 'self' as const,
334
+ expiration: Math.floor(Date.now() / 1000) - 3600,
335
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
336
+ recipient: '0x0000000000000000000000000000000000000000' as `0x${string}`,
337
+ recipientSignature: '0x' as `0x${string}`,
338
+ };
339
+
340
+ const result = ValidationUtils.isValid(permit as unknown as Permit);
341
+ expect(result.valid).toBe(false);
342
+ expect(result.error).toBe('expired');
343
+ });
344
+
345
+ it('should return valid for schema-valid, signed, non-expired permit', async () => {
346
+ const permit = {
347
+ ...(await createMockPermit()),
348
+ type: 'self' as const,
349
+ expiration: Math.floor(Date.now() / 1000) + 3600,
350
+ issuerSignature: '0x1234567890abcdef' as `0x${string}`,
351
+ recipient: '0x0000000000000000000000000000000000000000' as `0x${string}`,
352
+ recipientSignature: '0x' as `0x${string}`,
353
+ };
354
+
355
+ const result = ValidationUtils.isValid(permit as unknown as Permit);
356
+ expect(result.valid).toBe(true);
357
+ expect(result.error).toBeNull();
358
+ });
359
+ });
284
360
  });
285
361
  });
package/permits/types.ts CHANGED
@@ -189,7 +189,7 @@ export type PermitHashFields = Pick<
189
189
  */
190
190
  export interface ValidationResult {
191
191
  valid: boolean;
192
- error: string | null;
192
+ error: 'invalid-schema' | 'expired' | 'not-signed' | null;
193
193
  }
194
194
 
195
195
  /**
@@ -273,9 +273,9 @@ export const ValidationUtils = {
273
273
  },
274
274
 
275
275
  /**
276
- * Overall validity checker of a permit
276
+ * Checks that a permit is signed and not expired.
277
277
  */
278
- isValid: (permit: Permit): ValidationResult => {
278
+ isSignedAndNotExpired: (permit: Permit): ValidationResult => {
279
279
  if (ValidationUtils.isExpired(permit)) {
280
280
  return { valid: false, error: 'expired' };
281
281
  }
@@ -284,4 +284,44 @@ export const ValidationUtils = {
284
284
  }
285
285
  return { valid: true, error: null };
286
286
  },
287
+
288
+ /**
289
+ * Asserts that a permit is signed and not expired.
290
+ *
291
+ * Throws `Error` with message:
292
+ * - `Permit is expired`
293
+ * - `Permit is not signed`
294
+ */
295
+ assertSignedAndNotExpired: (permit: Permit): void => {
296
+ const result = ValidationUtils.isSignedAndNotExpired(permit);
297
+ if (result.valid) return;
298
+
299
+ if (result.error === 'expired') {
300
+ throw new Error('Permit is expired');
301
+ }
302
+ if (result.error === 'not-signed') {
303
+ throw new Error('Permit is not signed');
304
+ }
305
+
306
+ // Should be unreachable, but keeps this future-proof.
307
+ throw new Error('Permit is invalid');
308
+ },
309
+
310
+ isValid: (permit: Permit): ValidationResult => {
311
+ const schema =
312
+ permit.type === 'self'
313
+ ? SelfPermitValidator
314
+ : permit.type === 'sharing'
315
+ ? SharingPermitValidator
316
+ : permit.type === 'recipient'
317
+ ? ImportPermitValidator
318
+ : null;
319
+
320
+ if (schema == null) return { valid: false, error: 'invalid-schema' };
321
+
322
+ const schemaResult = schema.safeParse(permit);
323
+ if (!schemaResult.success) return { valid: false, error: 'invalid-schema' };
324
+
325
+ return ValidationUtils.isSignedAndNotExpired(permit);
326
+ },
287
327
  };
package/web/const.ts ADDED
@@ -0,0 +1,2 @@
1
+ export const hasDOM =
2
+ typeof (globalThis as any)?.document !== 'undefined' && typeof (globalThis as any)?.window !== 'undefined';
package/web/index.ts CHANGED
@@ -10,16 +10,18 @@ import {
10
10
  type FheKeyDeserializer,
11
11
  type EncryptableItem,
12
12
  fheTypeToString,
13
+ TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT,
13
14
  } from '@/core';
14
15
 
15
16
  // Import web-specific storage (internal use only)
16
- import { createWebStorage } from './storage.js';
17
+ import { createSsrStorage, createWebStorage } from './storage.js';
17
18
 
18
19
  // Import worker manager
19
20
  import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
20
21
 
21
22
  // Import tfhe for web
22
23
  import init, { init_panic_hook, TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
24
+ import { hasDOM } from './const';
23
25
 
24
26
  /**
25
27
  * Internal function to initialize TFHE for web
@@ -45,12 +47,20 @@ const fromHexString = (hexString: string): Uint8Array => {
45
47
  return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
46
48
  };
47
49
 
50
+ const _deserializeTfhePublicKey = (buff: string): TfheCompactPublicKey => {
51
+ return TfheCompactPublicKey.safe_deserialize(fromHexString(buff), TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT);
52
+ };
53
+
54
+ const _deserializeCompactPkeCrs = (buff: string): CompactPkeCrs => {
55
+ return CompactPkeCrs.safe_deserialize(fromHexString(buff), TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT);
56
+ };
57
+
48
58
  /**
49
59
  * Serializer for TFHE public keys
50
60
  * Validates that the buffer can be deserialized into a TfheCompactPublicKey
51
61
  */
52
62
  const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
53
- TfheCompactPublicKey.deserialize(fromHexString(buff));
63
+ _deserializeTfhePublicKey(buff);
54
64
  };
55
65
 
56
66
  /**
@@ -58,7 +68,7 @@ const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
58
68
  * Validates that the buffer can be deserialized into ZkCompactPkePublicParams
59
69
  */
60
70
  const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
61
- CompactPkeCrs.deserialize(fromHexString(buff));
71
+ _deserializeCompactPkeCrs(buff);
62
72
  };
63
73
 
64
74
  /**
@@ -66,9 +76,9 @@ const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
66
76
  * This is used internally by the SDK to create encrypted inputs
67
77
  */
68
78
  const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: string) => {
69
- const fhePublicKey = TfheCompactPublicKey.deserialize(fromHexString(fhe));
79
+ const fhePublicKey = _deserializeTfhePublicKey(fhe);
70
80
  const zkBuilder = ProvenCompactCiphertextList.builder(fhePublicKey);
71
- const zkCrs = CompactPkeCrs.deserialize(fromHexString(crs));
81
+ const zkCrs = _deserializeCompactPkeCrs(crs);
72
82
 
73
83
  return { zkBuilder, zkCrs };
74
84
  };
@@ -103,7 +113,8 @@ export function createCofheConfig(config: CofheInputConfig): CofheConfig {
103
113
  return createCofheConfigBase({
104
114
  environment: 'web',
105
115
  ...config,
106
- fheKeyStorage: config.fheKeyStorage === null ? null : config.fheKeyStorage ?? createWebStorage(),
116
+ fheKeyStorage:
117
+ config.fheKeyStorage === null ? null : config.fheKeyStorage ?? (hasDOM ? createWebStorage() : createSsrStorage()),
107
118
  });
108
119
  }
109
120
 
@@ -159,3 +170,6 @@ export function createCofheClientWithCustomWorker(
159
170
  zkProveWorkerFn: customZkProveWorkerFn,
160
171
  });
161
172
  }
173
+
174
+ export { createSsrStorage };
175
+ export { hasDOM } from './const';
package/web/storage.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  import type { IStorage } from '@/core';
2
2
  import { constructClient } from 'iframe-shared-storage';
3
+ import { hasDOM } from './const';
3
4
  /**
4
- * Creates a web storage implementation using IndexedDB
5
+ * Creates a web storage implementation using IndexedDB.
6
+ * Must only be called in a browser environment (requires `document` for iframe injection).
5
7
  * @returns IStorage implementation for browser environments
6
8
  */
7
- export const createWebStorage = (): IStorage => {
9
+ export const createWebStorage = (opts = { enableLog: false }): IStorage => {
10
+ if (!hasDOM) throw new Error('createWebStorage can only be used in a browser environment');
11
+
8
12
  const client = constructClient({
9
13
  iframe: {
10
14
  src: 'https://iframe-shared-storage.vercel.app/hub.html',
11
15
  messagingOptions: {
12
- enableLog: 'both',
16
+ enableLog: opts.enableLog ? 'both' : undefined,
13
17
  },
14
18
 
15
19
  iframeReadyTimeoutMs: 30_000, // if the iframe is not initied during this interval AND a reuqest is made, such request will throw an error
@@ -32,3 +36,14 @@ export const createWebStorage = (): IStorage => {
32
36
  },
33
37
  };
34
38
  };
39
+
40
+ export function createSsrStorage(): IStorage {
41
+ // TODO: consider doing something like wagmi's cookies storage for SSR - this in-memory storage will not persist across requests, but it also won't throw errors if accessed in SSR (e.g. during getServerSideProps in Next.js)
42
+ // https://wagmi.sh/react/guides/ssr#_1-set-up-cookie-storage
43
+ console.warn('using no-op server-side SSR storage');
44
+ return {
45
+ getItem: async () => null,
46
+ setItem: async () => {},
47
+ removeItem: async () => {},
48
+ };
49
+ }
@@ -6,7 +6,7 @@ import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
6
6
  import type { PublicClient, WalletClient } from 'viem';
7
7
  import { createPublicClient, createWalletClient, http } from 'viem';
8
8
  import { privateKeyToAccount } from 'viem/accounts';
9
- import { createCofheClient, createCofheConfig } from './index.js';
9
+ import { createCofheClient, createCofheConfig } from '../index.js';
10
10
 
11
11
  // Real test setup - runs in browser
12
12
  const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
@@ -141,7 +141,19 @@ describe('@cofhe/web - Client', () => {
141
141
  expect(builder).toBeDefined();
142
142
  expect(typeof builder.setChainId).toBe('function');
143
143
  expect(typeof builder.setAccount).toBe('function');
144
+ expect(typeof builder.onPoll).toBe('function');
144
145
  expect(typeof builder.execute).toBe('function');
145
146
  }, 30000);
147
+
148
+ it('should create decryptForTx builder after connection', async () => {
149
+ await cofheClient.connect(publicClient, walletClient);
150
+
151
+ const builder = cofheClient.decryptForTx('0x123');
152
+
153
+ expect(builder).toBeDefined();
154
+ expect(typeof builder.setChainId).toBe('function');
155
+ expect(typeof builder.setAccount).toBe('function');
156
+ expect(typeof builder.onPoll).toBe('function');
157
+ }, 30000);
146
158
  });
147
159
  });
@@ -0,0 +1,16 @@
1
+ import { arbSepolia } from '@/chains';
2
+
3
+ import { describe, it, expect } from 'vitest';
4
+ import { createCofheConfig } from '../index.js';
5
+
6
+ describe('@cofhe/web - Config', () => {
7
+ it('should automatically inject IndexedDB storage as default', () => {
8
+ const config = createCofheConfig({
9
+ supportedChains: [arbSepolia],
10
+ });
11
+
12
+ expect(config.fheKeyStorage).toBeDefined();
13
+ expect(config.fheKeyStorage).not.toBeNull();
14
+ expect(config.supportedChains).toEqual([arbSepolia]);
15
+ });
16
+ });