@cofhe/sdk 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) 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 +11 -1
  9. package/core/clientTypes.ts +3 -1
  10. package/core/consts.ts +9 -0
  11. package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
  12. package/core/decrypt/decryptForTxBuilder.ts +16 -2
  13. package/core/decrypt/decryptForViewBuilder.ts +14 -7
  14. package/core/decrypt/polling.ts +14 -0
  15. package/core/decrypt/tnDecryptV2.ts +250 -110
  16. package/core/decrypt/tnSealOutputV2.ts +245 -104
  17. package/core/decrypt/verifyDecryptResult.ts +65 -0
  18. package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
  19. package/core/encrypt/zkPackProveVerify.ts +10 -19
  20. package/core/fetchKeys.ts +0 -2
  21. package/core/index.ts +9 -1
  22. package/core/keyStore.ts +5 -2
  23. package/core/permits.ts +5 -0
  24. package/core/{client.test.ts → test/client.test.ts} +7 -7
  25. package/core/{config.test.ts → test/config.test.ts} +1 -1
  26. package/core/test/decrypt.test.ts +252 -0
  27. package/core/test/decryptBuilders.test.ts +390 -0
  28. package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
  29. package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
  30. package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
  31. package/core/{permits.test.ts → test/permits.test.ts} +42 -1
  32. package/core/test/pollCallbacks.test.ts +563 -0
  33. package/core/types.ts +13 -0
  34. package/dist/chains.d.cts +2 -2
  35. package/dist/chains.d.ts +2 -2
  36. package/dist/chunk-4FP4V35O.js +13 -0
  37. package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
  38. package/dist/{chunk-MXND5SVN.js → chunk-S7OKGLFD.js} +485 -207
  39. package/dist/{clientTypes-kkrRdawm.d.ts → clientTypes-BSbwairE.d.cts} +23 -6
  40. package/dist/{clientTypes-ACVWbrXL.d.cts → clientTypes-DDmcgZ0a.d.ts} +23 -6
  41. package/dist/core.cjs +561 -244
  42. package/dist/core.d.cts +24 -6
  43. package/dist/core.d.ts +24 -6
  44. package/dist/core.js +3 -2
  45. package/dist/node.cjs +566 -246
  46. package/dist/node.d.cts +3 -3
  47. package/dist/node.d.ts +3 -3
  48. package/dist/node.js +14 -7
  49. package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.cts} +34 -4
  50. package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.ts} +34 -4
  51. package/dist/permits.cjs +66 -29
  52. package/dist/permits.d.cts +18 -13
  53. package/dist/permits.d.ts +18 -13
  54. package/dist/permits.js +2 -1
  55. package/dist/web.cjs +604 -256
  56. package/dist/web.d.cts +8 -4
  57. package/dist/web.d.ts +8 -4
  58. package/dist/web.js +49 -14
  59. package/dist/zkProve.worker.cjs +72 -64
  60. package/dist/zkProve.worker.js +71 -64
  61. package/node/index.ts +13 -4
  62. package/node/test/client.test.ts +25 -0
  63. package/node/test/config.test.ts +16 -0
  64. package/node/test/inherited.test.ts +244 -0
  65. package/node/test/tfheinit.test.ts +56 -0
  66. package/package.json +24 -22
  67. package/permits/permit.ts +31 -5
  68. package/permits/sealing.ts +1 -1
  69. package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
  70. package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
  71. package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
  72. package/permits/{store.test.ts → test/store.test.ts} +2 -2
  73. package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
  74. package/permits/types.ts +1 -1
  75. package/permits/validation.ts +42 -2
  76. package/web/const.ts +2 -0
  77. package/web/index.ts +40 -11
  78. package/web/storage.ts +18 -3
  79. package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
  80. package/web/test/config.web.test.ts +16 -0
  81. package/web/test/inherited.web.test.ts +245 -0
  82. package/web/test/tfheinit.web.test.ts +62 -0
  83. package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
  84. package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
  85. package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
  86. package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
  87. package/web/zkProve.worker.ts +94 -84
  88. package/node/client.test.ts +0 -147
  89. package/node/config.test.ts +0 -68
  90. package/node/encryptInputs.test.ts +0 -155
  91. package/web/config.web.test.ts +0 -69
  92. package/web/encryptInputs.web.test.ts +0 -172
  93. package/web/worker.builder.web.test.ts +0 -148
  94. /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
  95. /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
@@ -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,31 +10,45 @@ 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
- // Import tfhe for web
22
- import init, { init_panic_hook, TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
22
+ // Type-only import for tfhe — the runtime is loaded lazily via `await import('tfhe')`
23
+ // inside `initTfhe()` so that simply importing `@cofhe/sdk/web` (e.g. transitively
24
+ // through `@cofhe/react`) does not pull tfhe — and its worker helpers that
25
+ // reference `self` at module top — into the import graph during Next.js SSR.
26
+ import type { TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
27
+ import { hasDOM } from './const';
23
28
 
24
29
  /**
25
30
  * Internal function to initialize TFHE for web
26
31
  * Called automatically on first encryption - users don't need to call this manually
27
32
  * @returns true if TFHE was initialized, false if already initialized
28
33
  */
34
+ let tfheModule: typeof import('tfhe') | null = null;
29
35
  let tfheInitialized = false;
30
36
  async function initTfhe(): Promise<boolean> {
31
37
  if (tfheInitialized) return false;
32
- await init();
33
- await init_panic_hook();
38
+ tfheModule = await import('tfhe');
39
+ await tfheModule.default();
40
+ await tfheModule.init_panic_hook();
34
41
  tfheInitialized = true;
35
42
  return true;
36
43
  }
37
44
 
45
+ function requireTfhe(): typeof import('tfhe') {
46
+ if (!tfheModule) {
47
+ throw new Error('TFHE not initialized — call initTfhe() (or any client method that triggers it) first');
48
+ }
49
+ return tfheModule;
50
+ }
51
+
38
52
  /**
39
53
  * Utility to convert the hex string key to a Uint8Array for use with tfhe
40
54
  */
@@ -45,12 +59,23 @@ const fromHexString = (hexString: string): Uint8Array => {
45
59
  return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
46
60
  };
47
61
 
62
+ const _deserializeTfhePublicKey = (buff: string): TfheCompactPublicKey => {
63
+ return requireTfhe().TfheCompactPublicKey.safe_deserialize(
64
+ fromHexString(buff),
65
+ TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT
66
+ );
67
+ };
68
+
69
+ const _deserializeCompactPkeCrs = (buff: string): CompactPkeCrs => {
70
+ return requireTfhe().CompactPkeCrs.safe_deserialize(fromHexString(buff), TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT);
71
+ };
72
+
48
73
  /**
49
74
  * Serializer for TFHE public keys
50
75
  * Validates that the buffer can be deserialized into a TfheCompactPublicKey
51
76
  */
52
77
  const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
53
- TfheCompactPublicKey.deserialize(fromHexString(buff));
78
+ _deserializeTfhePublicKey(buff);
54
79
  };
55
80
 
56
81
  /**
@@ -58,7 +83,7 @@ const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
58
83
  * Validates that the buffer can be deserialized into ZkCompactPkePublicParams
59
84
  */
60
85
  const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
61
- CompactPkeCrs.deserialize(fromHexString(buff));
86
+ _deserializeCompactPkeCrs(buff);
62
87
  };
63
88
 
64
89
  /**
@@ -66,9 +91,9 @@ const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
66
91
  * This is used internally by the SDK to create encrypted inputs
67
92
  */
68
93
  const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: string) => {
69
- const fhePublicKey = TfheCompactPublicKey.deserialize(fromHexString(fhe));
70
- const zkBuilder = ProvenCompactCiphertextList.builder(fhePublicKey);
71
- const zkCrs = CompactPkeCrs.deserialize(fromHexString(crs));
94
+ const fhePublicKey = _deserializeTfhePublicKey(fhe);
95
+ const zkBuilder = requireTfhe().ProvenCompactCiphertextList.builder(fhePublicKey);
96
+ const zkCrs = _deserializeCompactPkeCrs(crs);
72
97
 
73
98
  return { zkBuilder, zkCrs };
74
99
  };
@@ -103,7 +128,8 @@ export function createCofheConfig(config: CofheInputConfig): CofheConfig {
103
128
  return createCofheConfigBase({
104
129
  environment: 'web',
105
130
  ...config,
106
- fheKeyStorage: config.fheKeyStorage === null ? null : config.fheKeyStorage ?? createWebStorage(),
131
+ fheKeyStorage:
132
+ config.fheKeyStorage === null ? null : config.fheKeyStorage ?? (hasDOM ? createWebStorage() : createSsrStorage()),
107
133
  });
108
134
  }
109
135
 
@@ -159,3 +185,6 @@ export function createCofheClientWithCustomWorker(
159
185
  zkProveWorkerFn: customZkProveWorkerFn,
160
186
  });
161
187
  }
188
+
189
+ export { createSsrStorage };
190
+ 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
+ });
@@ -0,0 +1,245 @@
1
+ import { Encryptable, FheTypes, type CofheClient } from '@/core';
2
+ import { arbSepolia as cofheArbSepolia, getChainById } from '@/chains';
3
+ import {
4
+ TEST_PRIVATE_KEY,
5
+ PRIMARY_TEST_CHAIN,
6
+ primaryTestChainRegistry,
7
+ isPrimaryTestChainReady,
8
+ } from '@cofhe/test-setup';
9
+
10
+ import { createCofheClient, createCofheConfig } from '../index.js';
11
+
12
+ import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
13
+ import type { Chain, PublicClient, WalletClient } from 'viem';
14
+ import { createPublicClient, createWalletClient, http } from 'viem';
15
+ import { privateKeyToAccount } from 'viem/accounts';
16
+ import { arbitrumSepolia, baseSepolia, sepolia } from 'viem/chains';
17
+
18
+ const DEFAULT_TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
19
+ const BOB_PRIVATE_KEY = (TEST_PRIVATE_KEY || DEFAULT_TEST_PRIVATE_KEY) as `0x${string}`;
20
+ const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d';
21
+
22
+ const bobAccount = privateKeyToAccount(BOB_PRIVATE_KEY);
23
+ const aliceAccount = privateKeyToAccount(ALICE_PRIVATE_KEY);
24
+
25
+ const VIEM_CHAINS: Record<number, Chain> = {
26
+ 421614: arbitrumSepolia,
27
+ 84532: baseSepolia,
28
+ 11155111: sepolia,
29
+ };
30
+
31
+ describe('@cofhe/web - Inherited Client Tests', () => {
32
+ let cofheClient: CofheClient;
33
+ let publicClient: PublicClient;
34
+ let bobWalletClient: WalletClient;
35
+ let aliceWalletClient: WalletClient;
36
+
37
+ beforeAll(() => {
38
+ publicClient = createPublicClient({
39
+ chain: arbitrumSepolia,
40
+ transport: http(),
41
+ });
42
+
43
+ bobWalletClient = createWalletClient({
44
+ chain: arbitrumSepolia,
45
+ transport: http(),
46
+ account: bobAccount,
47
+ });
48
+
49
+ aliceWalletClient = createWalletClient({
50
+ chain: arbitrumSepolia,
51
+ transport: http(),
52
+ account: aliceAccount,
53
+ });
54
+ });
55
+
56
+ beforeEach(() => {
57
+ const config = createCofheConfig({
58
+ supportedChains: [cofheArbSepolia],
59
+ });
60
+ cofheClient = createCofheClient(config);
61
+ });
62
+
63
+ describe('Client Creation', () => {
64
+ it('should create a client with expected surface', () => {
65
+ expect(cofheClient).toBeDefined();
66
+ expect(cofheClient.config).toBeDefined();
67
+ expect(cofheClient.connected).toBe(false);
68
+ expect(typeof cofheClient.connect).toBe('function');
69
+ expect(typeof cofheClient.disconnect).toBe('function');
70
+ expect(typeof cofheClient.encryptInputs).toBe('function');
71
+ expect(typeof cofheClient.decryptForView).toBe('function');
72
+ expect(typeof cofheClient.decryptForTx).toBe('function');
73
+ expect(typeof cofheClient.getSnapshot).toBe('function');
74
+ expect(typeof cofheClient.subscribe).toBe('function');
75
+ expect(cofheClient.permits).toBeDefined();
76
+ });
77
+ });
78
+
79
+ describe('Connection', () => {
80
+ it('should connect to a real chain', async () => {
81
+ await cofheClient.connect(publicClient, bobWalletClient);
82
+
83
+ expect(cofheClient.connected).toBe(true);
84
+
85
+ const snapshot = cofheClient.getSnapshot();
86
+ expect(snapshot.connected).toBe(true);
87
+ expect(snapshot.chainId).toBe(cofheArbSepolia.id);
88
+ expect(snapshot.account).toBe(bobAccount.address);
89
+ }, 30000);
90
+ });
91
+
92
+ describe('Encrypt Input', () => {
93
+ it('should encrypt a uint128 value', async () => {
94
+ await cofheClient.connect(publicClient, bobWalletClient);
95
+
96
+ const encrypted = await cofheClient.encryptInputs([Encryptable.uint128(100n)]).execute();
97
+
98
+ expect(encrypted).toBeDefined();
99
+ expect(encrypted.length).toBe(1);
100
+ expect(encrypted[0].utype).toBe(FheTypes.Uint128);
101
+ expect(encrypted[0].ctHash).toBeDefined();
102
+ expect(typeof encrypted[0].ctHash).toBe('bigint');
103
+ expect(encrypted[0].signature).toBeDefined();
104
+ expect(typeof encrypted[0].signature).toBe('string');
105
+ expect(encrypted[0].securityZone).toBe(0);
106
+ }, 60000);
107
+ });
108
+
109
+ describe('Self Permit', () => {
110
+ it('should create a self permit', async () => {
111
+ await cofheClient.connect(publicClient, bobWalletClient);
112
+
113
+ const permit = await cofheClient.permits.createSelf({
114
+ issuer: bobAccount.address,
115
+ name: 'Test Self Permit',
116
+ });
117
+
118
+ expect(permit).toBeDefined();
119
+ expect(permit.type).toBe('self');
120
+ expect(permit.name).toBe('Test Self Permit');
121
+ expect(permit.issuer).toBe(bobAccount.address);
122
+ expect(permit.issuerSignature).not.toBe('0x');
123
+ expect(permit.sealingPair).toBeDefined();
124
+ expect(permit.sealingPair.publicKey).toBeDefined();
125
+
126
+ const activePermit = cofheClient.permits.getActivePermit();
127
+ expect(activePermit).toBeDefined();
128
+ expect(activePermit!.hash).toBe(permit.hash);
129
+ }, 30000);
130
+ });
131
+
132
+ describe('Sharing Permit', () => {
133
+ it('should create a sharing permit, export it, and import it as another user', async () => {
134
+ await cofheClient.connect(publicClient, bobWalletClient);
135
+
136
+ const sharingPermit = await cofheClient.permits.createSharing({
137
+ issuer: bobAccount.address,
138
+ recipient: aliceAccount.address,
139
+ name: 'Test Sharing Permit',
140
+ });
141
+
142
+ expect(sharingPermit).toBeDefined();
143
+ expect(sharingPermit.type).toBe('sharing');
144
+ expect(sharingPermit.issuer).toBe(bobAccount.address);
145
+ expect(sharingPermit.recipient).toBe(aliceAccount.address);
146
+ expect(sharingPermit.issuerSignature).not.toBe('0x');
147
+
148
+ const exported = cofheClient.permits.export(sharingPermit);
149
+ expect(exported).toBeDefined();
150
+ const parsed = JSON.parse(exported);
151
+ expect(parsed.type).toBe('sharing');
152
+ expect(parsed.issuer).toBe(bobAccount.address);
153
+ expect(parsed.recipient).toBe(aliceAccount.address);
154
+ expect(parsed.issuerSignature).toBeDefined();
155
+ expect(parsed).not.toHaveProperty('sealingPair');
156
+
157
+ const aliceConfig = createCofheConfig({
158
+ supportedChains: [cofheArbSepolia],
159
+ });
160
+ const aliceClient = createCofheClient(aliceConfig);
161
+ await aliceClient.connect(publicClient, aliceWalletClient);
162
+
163
+ const importedPermit = await aliceClient.permits.importShared(exported);
164
+
165
+ expect(importedPermit).toBeDefined();
166
+ expect(importedPermit.type).toBe('recipient');
167
+ expect(importedPermit.issuer).toBe(bobAccount.address);
168
+ expect(importedPermit.recipient).toBe(aliceAccount.address);
169
+ expect(importedPermit.recipientSignature).not.toBe('0x');
170
+ expect(importedPermit.sealingPair).toBeDefined();
171
+ }, 30000);
172
+ });
173
+
174
+ describe('Decrypt (read-only, pre-stored values)', () => {
175
+ let decryptClient: CofheClient;
176
+ let decryptPublicClient: PublicClient;
177
+ let decryptWalletClient: WalletClient;
178
+
179
+ let privateCtHash: `0x${string}`;
180
+ let privateValue: bigint;
181
+ let publicCtHash: `0x${string}`;
182
+ let publicValue: bigint;
183
+
184
+ beforeAll(() => {
185
+ if (!isPrimaryTestChainReady(primaryTestChainRegistry)) {
186
+ throw new Error('Primary test chain registry not initialized. Run `pnpm test:setup` first.');
187
+ }
188
+
189
+ const reg = primaryTestChainRegistry;
190
+ const viemChain = VIEM_CHAINS[reg.chainId];
191
+ if (!viemChain) throw new Error(`No viem chain mapping for chain ${reg.chainId}`);
192
+
193
+ const cofheChain = getChainById(reg.chainId);
194
+ if (!cofheChain) throw new Error(`No cofhe chain config for chain ${reg.chainId}`);
195
+
196
+ privateCtHash = reg.privateValue.ctHash as `0x${string}`;
197
+ privateValue = BigInt(reg.privateValue.value);
198
+ publicCtHash = reg.publicValue.ctHash as `0x${string}`;
199
+ publicValue = BigInt(reg.publicValue.value);
200
+
201
+ decryptPublicClient = createPublicClient({ chain: viemChain, transport: http() });
202
+ decryptWalletClient = createWalletClient({ chain: viemChain, transport: http(), account: bobAccount });
203
+
204
+ const config = createCofheConfig({ supportedChains: [cofheChain] });
205
+ decryptClient = createCofheClient(config);
206
+ });
207
+
208
+ it('decryptForView — private value with permit', async () => {
209
+ await decryptClient.connect(decryptPublicClient, decryptWalletClient);
210
+
211
+ await decryptClient.permits.createSelf({
212
+ issuer: bobAccount.address,
213
+ name: 'Decrypt View Permit',
214
+ });
215
+
216
+ const result = await decryptClient.decryptForView(privateCtHash, FheTypes.Uint32).execute();
217
+ expect(result).toBe(privateValue);
218
+ }, 180000);
219
+
220
+ it('decryptForTx — public value without permit', async () => {
221
+ await decryptClient.connect(decryptPublicClient, decryptWalletClient);
222
+
223
+ const result = await decryptClient.decryptForTx(publicCtHash).withoutPermit().execute();
224
+
225
+ expect(BigInt(result.ctHash)).toBe(BigInt(publicCtHash));
226
+ expect(result.decryptedValue).toBe(publicValue);
227
+ expect(result.signature).toMatch(/^0x[0-9a-fA-F]+$/);
228
+ }, 180000);
229
+
230
+ it('decryptForTx — private value with permit', async () => {
231
+ await decryptClient.connect(decryptPublicClient, decryptWalletClient);
232
+
233
+ const permit = await decryptClient.permits.createSelf({
234
+ issuer: bobAccount.address,
235
+ name: 'Decrypt Tx Permit',
236
+ });
237
+
238
+ const result = await decryptClient.decryptForTx(privateCtHash).withPermit(permit).execute();
239
+
240
+ expect(BigInt(result.ctHash)).toBe(BigInt(privateCtHash));
241
+ expect(result.decryptedValue).toBe(privateValue);
242
+ expect(result.signature).toBeDefined();
243
+ }, 180000);
244
+ });
245
+ });
@@ -0,0 +1,62 @@
1
+ import { arbSepolia as cofheArbSepolia } from '@/chains';
2
+ import { Encryptable, FheTypes, type CofheClient, CofheErrorCode, CofheError } from '@/core';
3
+
4
+ import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
5
+ import type { PublicClient, WalletClient } from 'viem';
6
+ import { createPublicClient, createWalletClient, http } from 'viem';
7
+ import { privateKeyToAccount } from 'viem/accounts';
8
+ import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
9
+ import { createCofheClient, createCofheConfig } from '../index.js';
10
+
11
+ // Real test setup - runs in browser with real tfhe
12
+ const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
13
+
14
+ describe('@cofhe/web - TFHE Initialization Browser Tests', () => {
15
+ let cofheClient: CofheClient;
16
+ let publicClient: PublicClient;
17
+ let walletClient: WalletClient;
18
+
19
+ beforeAll(() => {
20
+ // Create real viem clients
21
+ publicClient = createPublicClient({
22
+ chain: viemArbitrumSepolia,
23
+ transport: http(),
24
+ });
25
+
26
+ const account = privateKeyToAccount(TEST_PRIVATE_KEY);
27
+ walletClient = createWalletClient({
28
+ chain: viemArbitrumSepolia,
29
+ transport: http(),
30
+ account,
31
+ });
32
+ });
33
+
34
+ beforeEach(() => {
35
+ const config = createCofheConfig({
36
+ supportedChains: [cofheArbSepolia],
37
+ });
38
+ cofheClient = createCofheClient(config);
39
+ });
40
+
41
+ describe('Browser TFHE Initialization', () => {
42
+ it('should initialize tfhe on first encryption', async () => {
43
+ await cofheClient.connect(publicClient, walletClient);
44
+
45
+ // This will trigger real TFHE initialization in browser
46
+ const result = await cofheClient.encryptInputs([Encryptable.uint128(100n)]).execute();
47
+
48
+ // If we get here, TFHE was initialized successfully
49
+ expect(result).toBeDefined();
50
+ }, 60000); // Longer timeout for real operations
51
+
52
+ it('should handle multiple encryptions without re-initializing', async () => {
53
+ await cofheClient.connect(publicClient, walletClient);
54
+
55
+ // First encryption
56
+ expect(cofheClient.encryptInputs([Encryptable.uint128(100n)]).execute()).resolves.not.toThrow();
57
+
58
+ // Second encryption should reuse initialization
59
+ expect(cofheClient.encryptInputs([Encryptable.uint64(50n)]).execute()).resolves.not.toThrow();
60
+ }, 60000);
61
+ });
62
+ });
@@ -6,7 +6,7 @@ import type { PublicClient, WalletClient } from 'viem';
6
6
  import { createPublicClient, createWalletClient, http } from 'viem';
7
7
  import { privateKeyToAccount } from 'viem/accounts';
8
8
  import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
9
- import { createCofheClient, createCofheConfig, createCofheClientWithCustomWorker } from './index.js';
9
+ import { createCofheClient, createCofheConfig, createCofheClientWithCustomWorker } from '../index.js';
10
10
 
11
11
  const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
12
12
 
@@ -6,7 +6,7 @@ import type { PublicClient, WalletClient } from 'viem';
6
6
  import { createPublicClient, createWalletClient, http } from 'viem';
7
7
  import { privateKeyToAccount } from 'viem/accounts';
8
8
  import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
9
- import { createCofheClient, createCofheConfig } from './index.js';
9
+ import { createCofheClient, createCofheConfig } from '../index.js';
10
10
 
11
11
  const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
12
12
 
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, afterEach } from 'vitest';
2
- import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
2
+ import { getWorkerManager, terminateWorker, areWorkersAvailable } from '../workerManager.js';
3
3
 
4
4
  describe('WorkerManager', () => {
5
5
  afterEach(() => {
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, afterEach } from 'vitest';
2
- import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
2
+ import { getWorkerManager, terminateWorker, areWorkersAvailable } from '../workerManager.js';
3
3
 
4
4
  describe('WorkerManager (Browser)', () => {
5
5
  afterEach(() => {