@cofhe/sdk 0.0.0-alpha-20260409113701

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 (132) hide show
  1. package/CHANGELOG.md +146 -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 +429 -0
  27. package/core/client.ts +341 -0
  28. package/core/clientTypes.ts +119 -0
  29. package/core/config.test.ts +242 -0
  30. package/core/config.ts +225 -0
  31. package/core/consts.ts +22 -0
  32. package/core/decrypt/MockThresholdNetworkAbi.ts +179 -0
  33. package/core/decrypt/cofheMocksDecryptForTx.ts +84 -0
  34. package/core/decrypt/cofheMocksDecryptForView.ts +48 -0
  35. package/core/decrypt/decryptForTxBuilder.ts +359 -0
  36. package/core/decrypt/decryptForViewBuilder.ts +332 -0
  37. package/core/decrypt/decryptUtils.ts +28 -0
  38. package/core/decrypt/pollCallbacks.test.ts +194 -0
  39. package/core/decrypt/polling.ts +14 -0
  40. package/core/decrypt/tnDecryptUtils.ts +65 -0
  41. package/core/decrypt/tnDecryptV1.ts +171 -0
  42. package/core/decrypt/tnDecryptV2.ts +365 -0
  43. package/core/decrypt/tnSealOutputV1.ts +59 -0
  44. package/core/decrypt/tnSealOutputV2.ts +324 -0
  45. package/core/decrypt/verifyDecryptResult.ts +52 -0
  46. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  47. package/core/encrypt/cofheMocksZkVerifySign.ts +281 -0
  48. package/core/encrypt/encryptInputsBuilder.test.ts +747 -0
  49. package/core/encrypt/encryptInputsBuilder.ts +583 -0
  50. package/core/encrypt/encryptUtils.ts +67 -0
  51. package/core/encrypt/zkPackProveVerify.ts +335 -0
  52. package/core/error.ts +168 -0
  53. package/core/fetchKeys.test.ts +195 -0
  54. package/core/fetchKeys.ts +144 -0
  55. package/core/index.ts +106 -0
  56. package/core/keyStore.test.ts +226 -0
  57. package/core/keyStore.ts +154 -0
  58. package/core/permits.test.ts +493 -0
  59. package/core/permits.ts +201 -0
  60. package/core/types.ts +419 -0
  61. package/core/utils.ts +130 -0
  62. package/dist/adapters.cjs +88 -0
  63. package/dist/adapters.d.cts +14576 -0
  64. package/dist/adapters.d.ts +14576 -0
  65. package/dist/adapters.js +83 -0
  66. package/dist/chains.cjs +111 -0
  67. package/dist/chains.d.cts +121 -0
  68. package/dist/chains.d.ts +121 -0
  69. package/dist/chains.js +1 -0
  70. package/dist/chunk-36FBWLUS.js +3310 -0
  71. package/dist/chunk-7HLGHV67.js +990 -0
  72. package/dist/chunk-TBLR7NNE.js +102 -0
  73. package/dist/clientTypes-AVSCBet7.d.cts +998 -0
  74. package/dist/clientTypes-flH1ju82.d.ts +998 -0
  75. package/dist/core.cjs +4362 -0
  76. package/dist/core.d.cts +138 -0
  77. package/dist/core.d.ts +138 -0
  78. package/dist/core.js +3 -0
  79. package/dist/node.cjs +4225 -0
  80. package/dist/node.d.cts +22 -0
  81. package/dist/node.d.ts +22 -0
  82. package/dist/node.js +91 -0
  83. package/dist/permit-jRirYqFt.d.cts +376 -0
  84. package/dist/permit-jRirYqFt.d.ts +376 -0
  85. package/dist/permits.cjs +1025 -0
  86. package/dist/permits.d.cts +353 -0
  87. package/dist/permits.d.ts +353 -0
  88. package/dist/permits.js +1 -0
  89. package/dist/types-YiAC4gig.d.cts +33 -0
  90. package/dist/types-YiAC4gig.d.ts +33 -0
  91. package/dist/web.cjs +4434 -0
  92. package/dist/web.d.cts +42 -0
  93. package/dist/web.d.ts +42 -0
  94. package/dist/web.js +256 -0
  95. package/dist/zkProve.worker.cjs +93 -0
  96. package/dist/zkProve.worker.d.cts +2 -0
  97. package/dist/zkProve.worker.d.ts +2 -0
  98. package/dist/zkProve.worker.js +91 -0
  99. package/node/client.test.ts +159 -0
  100. package/node/config.test.ts +68 -0
  101. package/node/encryptInputs.test.ts +155 -0
  102. package/node/index.ts +97 -0
  103. package/node/storage.ts +51 -0
  104. package/package.json +121 -0
  105. package/permits/index.ts +68 -0
  106. package/permits/localstorage.test.ts +113 -0
  107. package/permits/onchain-utils.ts +221 -0
  108. package/permits/permit.test.ts +534 -0
  109. package/permits/permit.ts +386 -0
  110. package/permits/sealing.test.ts +84 -0
  111. package/permits/sealing.ts +131 -0
  112. package/permits/signature.ts +79 -0
  113. package/permits/store.test.ts +88 -0
  114. package/permits/store.ts +156 -0
  115. package/permits/test-utils.ts +28 -0
  116. package/permits/types.ts +204 -0
  117. package/permits/utils.ts +58 -0
  118. package/permits/validation.test.ts +361 -0
  119. package/permits/validation.ts +327 -0
  120. package/web/client.web.test.ts +159 -0
  121. package/web/config.web.test.ts +69 -0
  122. package/web/const.ts +2 -0
  123. package/web/encryptInputs.web.test.ts +172 -0
  124. package/web/index.ts +166 -0
  125. package/web/storage.ts +49 -0
  126. package/web/worker.builder.web.test.ts +148 -0
  127. package/web/worker.config.web.test.ts +329 -0
  128. package/web/worker.output.web.test.ts +84 -0
  129. package/web/workerManager.test.ts +80 -0
  130. package/web/workerManager.ts +214 -0
  131. package/web/workerManager.web.test.ts +114 -0
  132. package/web/zkProve.worker.ts +133 -0
@@ -0,0 +1,159 @@
1
+ import { CofheErrorCode, CofheError, type CofheClient } from '@/core';
2
+ import { arbSepolia as cofheArbSepolia } from '@/chains';
3
+
4
+ import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
5
+ import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
6
+ import type { PublicClient, WalletClient } from 'viem';
7
+ import { createPublicClient, createWalletClient, http } from 'viem';
8
+ import { privateKeyToAccount } from 'viem/accounts';
9
+ import { createCofheClient, createCofheConfig } from './index.js';
10
+
11
+ // Real test setup - runs in browser
12
+ const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
13
+ const TEST_ACCOUNT = privateKeyToAccount(TEST_PRIVATE_KEY).address;
14
+
15
+ describe('@cofhe/web - Client', () => {
16
+ let cofheClient: CofheClient;
17
+ let publicClient: PublicClient;
18
+ let walletClient: WalletClient;
19
+
20
+ beforeAll(() => {
21
+ // Create real viem clients
22
+ publicClient = createPublicClient({
23
+ chain: viemArbitrumSepolia,
24
+ transport: http(),
25
+ });
26
+
27
+ const account = privateKeyToAccount(TEST_PRIVATE_KEY);
28
+ walletClient = createWalletClient({
29
+ chain: viemArbitrumSepolia,
30
+ transport: http(),
31
+ account,
32
+ });
33
+ });
34
+
35
+ beforeEach(() => {
36
+ const config = createCofheConfig({
37
+ supportedChains: [cofheArbSepolia],
38
+ });
39
+ cofheClient = createCofheClient(config);
40
+ });
41
+
42
+ describe('Browser Client Initialization', () => {
43
+ it('should create a client with real tfhe for browser', () => {
44
+ expect(cofheClient).toBeDefined();
45
+ expect(cofheClient.config).toBeDefined();
46
+ expect(cofheClient.connected).toBe(false);
47
+ });
48
+
49
+ it('should automatically use IndexedDB storage as default', () => {
50
+ expect(cofheClient.config.fheKeyStorage).toBeDefined();
51
+ expect(cofheClient.config.fheKeyStorage).not.toBeNull();
52
+ });
53
+
54
+ it('should have all expected methods', () => {
55
+ expect(typeof cofheClient.connect).toBe('function');
56
+ expect(typeof cofheClient.encryptInputs).toBe('function');
57
+ expect(typeof cofheClient.decryptForView).toBe('function');
58
+ expect(typeof cofheClient.getSnapshot).toBe('function');
59
+ expect(typeof cofheClient.subscribe).toBe('function');
60
+ });
61
+ });
62
+
63
+ describe('Environment', () => {
64
+ it('should have the correct environment', () => {
65
+ expect(cofheClient.config.environment).toBe('web');
66
+ });
67
+ });
68
+
69
+ describe('Connection', () => {
70
+ it('should connect to real chain', async () => {
71
+ await cofheClient.connect(publicClient, walletClient);
72
+
73
+ expect(cofheClient.connected).toBe(true);
74
+
75
+ const snapshot = cofheClient.getSnapshot();
76
+ expect(snapshot.connected).toBe(true);
77
+ expect(snapshot.chainId).toBe(cofheArbSepolia.id);
78
+ expect(snapshot.account).toBe(TEST_ACCOUNT);
79
+ }, 30000);
80
+
81
+ it('should handle network errors', async () => {
82
+ try {
83
+ await cofheClient.connect(
84
+ {
85
+ getChainId: vi.fn().mockRejectedValue(new Error('Network error')),
86
+ } as unknown as PublicClient,
87
+ walletClient
88
+ );
89
+ } catch (error) {
90
+ expect(error).toBeInstanceOf(CofheError);
91
+ expect((error as CofheError).code).toBe(CofheErrorCode.PublicWalletGetChainIdFailed);
92
+ }
93
+ }, 30000);
94
+ });
95
+
96
+ describe('State Management', () => {
97
+ it('should track connection state changes', async () => {
98
+ const states: any[] = [];
99
+ const unsubscribe = cofheClient.subscribe((snapshot) => {
100
+ states.push({ ...snapshot });
101
+ });
102
+
103
+ await cofheClient.connect(publicClient, walletClient);
104
+
105
+ unsubscribe();
106
+
107
+ expect(states.length).toBeGreaterThan(0);
108
+
109
+ // First state should be connecting
110
+ const firstState = states.find((s) => s.connecting);
111
+ expect(firstState).toBeDefined();
112
+ expect(firstState?.connecting).toBe(true);
113
+ expect(firstState?.connected).toBe(false);
114
+
115
+ // Last state should be connected
116
+ const lastState = states[states.length - 1];
117
+ expect(lastState.connected).toBe(true);
118
+ expect(lastState.connecting).toBe(false);
119
+ expect(lastState.chainId).toBe(cofheArbSepolia.id);
120
+ }, 30000);
121
+ });
122
+
123
+ describe('Builder Creation', () => {
124
+ it('should create encrypt builder after connection', async () => {
125
+ await cofheClient.connect(publicClient, walletClient);
126
+
127
+ const builder = cofheClient.encryptInputs([{ data: 100n, utype: 2, securityZone: 0 }]);
128
+
129
+ expect(builder).toBeDefined();
130
+ expect(typeof builder.setChainId).toBe('function');
131
+ expect(typeof builder.setAccount).toBe('function');
132
+ expect(typeof builder.setSecurityZone).toBe('function');
133
+ expect(typeof builder.execute).toBe('function');
134
+ }, 30000);
135
+
136
+ it('should create decrypt builder after connection', async () => {
137
+ await cofheClient.connect(publicClient, walletClient);
138
+
139
+ const builder = cofheClient.decryptForView('0x123', 2);
140
+
141
+ expect(builder).toBeDefined();
142
+ expect(typeof builder.setChainId).toBe('function');
143
+ expect(typeof builder.setAccount).toBe('function');
144
+ expect(typeof builder.onPoll).toBe('function');
145
+ expect(typeof builder.execute).toBe('function');
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);
158
+ });
159
+ });
@@ -0,0 +1,69 @@
1
+ import { arbSepolia } from '@/chains';
2
+
3
+ import { describe, it, expect } from 'vitest';
4
+ import { createCofheConfig, createCofheClient } from './index.js';
5
+
6
+ describe('@cofhe/web - Config', () => {
7
+ describe('createCofheConfig', () => {
8
+ it('should automatically inject IndexedDB storage as default', () => {
9
+ const config = createCofheConfig({
10
+ supportedChains: [arbSepolia],
11
+ });
12
+
13
+ expect(config.fheKeyStorage).toBeDefined();
14
+ expect(config.fheKeyStorage).not.toBeNull();
15
+ expect(config.supportedChains).toEqual([arbSepolia]);
16
+ });
17
+
18
+ it('should allow overriding storage', async () => {
19
+ const customStorage = {
20
+ getItem: () => Promise.resolve(10),
21
+ setItem: () => Promise.resolve(),
22
+ removeItem: () => Promise.resolve(),
23
+ };
24
+
25
+ const config = createCofheConfig({
26
+ supportedChains: [arbSepolia],
27
+ fheKeyStorage: customStorage,
28
+ });
29
+
30
+ expect(await config.fheKeyStorage!.getItem('test')).toBe(10);
31
+ });
32
+
33
+ it('should allow null storage', () => {
34
+ const config = createCofheConfig({
35
+ supportedChains: [arbSepolia],
36
+ fheKeyStorage: null,
37
+ });
38
+
39
+ expect(config.fheKeyStorage).toBeNull();
40
+ });
41
+
42
+ it('should preserve all other config options', () => {
43
+ const config = createCofheConfig({
44
+ supportedChains: [arbSepolia],
45
+ mocks: {
46
+ decryptDelay: 500,
47
+ },
48
+ });
49
+
50
+ expect(config.supportedChains).toEqual([arbSepolia]);
51
+ expect(config.mocks.decryptDelay).toBe(500);
52
+ expect(config.fheKeyStorage).toBeDefined();
53
+ });
54
+ });
55
+
56
+ describe('createCofheClient with config', () => {
57
+ it('should create client with validated config', () => {
58
+ const config = createCofheConfig({
59
+ supportedChains: [arbSepolia],
60
+ });
61
+
62
+ const client = createCofheClient(config);
63
+
64
+ expect(client).toBeDefined();
65
+ expect(client.config).toBe(config);
66
+ expect(client.config.fheKeyStorage).toBeDefined();
67
+ });
68
+ });
69
+ });
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';
@@ -0,0 +1,172 @@
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 - Encrypt Inputs 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
+
63
+ describe('Browser Encryption', () => {
64
+ it('should encrypt a bool with real TFHE in browser', async () => {
65
+ await cofheClient.connect(publicClient, walletClient);
66
+
67
+ const result = await cofheClient.encryptInputs([Encryptable.bool(true)]).execute();
68
+
69
+ expect(result).toBeDefined();
70
+ expect(result.length).toBe(1);
71
+ expect(result[0].utype).toBe(FheTypes.Bool);
72
+ expect(result[0].ctHash).toBeDefined();
73
+ expect(typeof result[0].ctHash).toBe('bigint');
74
+ expect(result[0].signature).toBeDefined();
75
+ expect(typeof result[0].signature).toBe('string');
76
+ expect(result[0].securityZone).toBe(0);
77
+ }, 60000);
78
+
79
+ it('should encrypt all supported types together', async () => {
80
+ await cofheClient.connect(publicClient, walletClient);
81
+
82
+ const inputs = [
83
+ Encryptable.bool(false),
84
+ Encryptable.uint8(1n),
85
+ Encryptable.uint16(2n),
86
+ Encryptable.uint32(3n),
87
+ Encryptable.uint64(4n),
88
+ Encryptable.uint128(5n),
89
+ Encryptable.address('0x742d35Cc6634C0532925a3b844D16faC4c175E99'),
90
+ ];
91
+
92
+ const result = await cofheClient.encryptInputs(inputs).execute();
93
+ expect(result).toBeDefined();
94
+
95
+ expect(result.length).toBe(7);
96
+ // Verify each type
97
+ expect(result[0].utype).toBe(FheTypes.Bool);
98
+ expect(result[1].utype).toBe(FheTypes.Uint8);
99
+ expect(result[2].utype).toBe(FheTypes.Uint16);
100
+ expect(result[3].utype).toBe(FheTypes.Uint32);
101
+ expect(result[4].utype).toBe(FheTypes.Uint64);
102
+ expect(result[5].utype).toBe(FheTypes.Uint128);
103
+ expect(result[6].utype).toBe(FheTypes.Uint160);
104
+ }, 90000); // Longer timeout for multiple encryptions
105
+ });
106
+
107
+ describe('Browser Builder Pattern', () => {
108
+ it('should support chaining builder methods with real encryption', async () => {
109
+ await cofheClient.connect(publicClient, walletClient);
110
+
111
+ const snapshot = cofheClient.getSnapshot();
112
+ const encrypted = await cofheClient
113
+ .encryptInputs([Encryptable.uint128(100n)])
114
+ .setChainId(snapshot.chainId!)
115
+ .setAccount(snapshot.account!)
116
+ .setSecurityZone(0)
117
+ .execute();
118
+
119
+ expect(encrypted.length).toBe(1);
120
+ expect(encrypted[0].utype).toBe(FheTypes.Uint128);
121
+ }, 60000);
122
+ });
123
+
124
+ describe('Browser Error Handling', () => {
125
+ it('should fail gracefully when not connected', async () => {
126
+ // Don't connect the client
127
+ try {
128
+ const promise = cofheClient.encryptInputs([Encryptable.uint128(100n)]).execute();
129
+ } catch (error) {
130
+ expect(error).toBeInstanceOf(CofheError);
131
+ expect((error as CofheError).code).toBe(CofheErrorCode.NotConnected);
132
+ }
133
+ }, 30000);
134
+
135
+ it('should handle invalid CoFHE URL', async () => {
136
+ const badConfig = createCofheConfig({
137
+ supportedChains: [
138
+ {
139
+ ...cofheArbSepolia,
140
+ coFheUrl: 'http://invalid-cofhe-url.local',
141
+ verifierUrl: 'http://invalid-verifier-url.local',
142
+ },
143
+ ],
144
+ });
145
+
146
+ const badClient = createCofheClient(badConfig);
147
+ await badClient.connect(publicClient, walletClient);
148
+
149
+ const promise = badClient.encryptInputs([Encryptable.uint128(100n)]).execute();
150
+ expect(promise).rejects.toThrow();
151
+ }, 60000);
152
+ });
153
+
154
+ describe('Browser Performance', () => {
155
+ it('should handle consecutive encryptions efficiently', async () => {
156
+ await cofheClient.connect(publicClient, walletClient);
157
+
158
+ const start = Date.now();
159
+
160
+ // Perform 5 encryptions
161
+ for (let i = 0; i < 5; i++) {
162
+ await cofheClient.encryptInputs([Encryptable.uint128(BigInt(i))]).execute();
163
+ }
164
+
165
+ const duration = Date.now() - start;
166
+
167
+ // Should complete all 5 encryptions in reasonable time
168
+ // This is a sanity check, not a strict benchmark
169
+ expect(duration).toBeLessThan(120000); // 2 minutes max
170
+ }, 180000); // 3 minute timeout
171
+ });
172
+ });
package/web/index.ts ADDED
@@ -0,0 +1,166 @@
1
+ // Web specific functionality only
2
+
3
+ import {
4
+ createCofheClientBase,
5
+ createCofheConfigBase,
6
+ type CofheClient,
7
+ type CofheConfig,
8
+ type CofheInputConfig,
9
+ type ZkBuilderAndCrsGenerator,
10
+ type FheKeyDeserializer,
11
+ type EncryptableItem,
12
+ fheTypeToString,
13
+ } from '@/core';
14
+
15
+ // Import web-specific storage (internal use only)
16
+ import { createSsrStorage, createWebStorage } from './storage.js';
17
+
18
+ // Import worker manager
19
+ import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
20
+
21
+ // Import tfhe for web
22
+ import init, { init_panic_hook, TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
23
+ import { hasDOM } from './const';
24
+
25
+ /**
26
+ * Internal function to initialize TFHE for web
27
+ * Called automatically on first encryption - users don't need to call this manually
28
+ * @returns true if TFHE was initialized, false if already initialized
29
+ */
30
+ let tfheInitialized = false;
31
+ async function initTfhe(): Promise<boolean> {
32
+ if (tfheInitialized) return false;
33
+ await init();
34
+ await init_panic_hook();
35
+ tfheInitialized = true;
36
+ return true;
37
+ }
38
+
39
+ /**
40
+ * Utility to convert the hex string key to a Uint8Array for use with tfhe
41
+ */
42
+ const fromHexString = (hexString: string): Uint8Array => {
43
+ const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
44
+ const arr = cleanString.replace(/^0x/, '').match(/.{1,2}/g);
45
+ if (!arr) return new Uint8Array();
46
+ return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
47
+ };
48
+
49
+ /**
50
+ * Serializer for TFHE public keys
51
+ * Validates that the buffer can be deserialized into a TfheCompactPublicKey
52
+ */
53
+ const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
54
+ TfheCompactPublicKey.deserialize(fromHexString(buff));
55
+ };
56
+
57
+ /**
58
+ * Serializer for Compact PKE CRS
59
+ * Validates that the buffer can be deserialized into ZkCompactPkePublicParams
60
+ */
61
+ const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
62
+ CompactPkeCrs.deserialize(fromHexString(buff));
63
+ };
64
+
65
+ /**
66
+ * Creates a ZK builder and CRS from FHE public key and CRS buffers
67
+ * This is used internally by the SDK to create encrypted inputs
68
+ */
69
+ const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: string) => {
70
+ const fhePublicKey = TfheCompactPublicKey.deserialize(fromHexString(fhe));
71
+ const zkBuilder = ProvenCompactCiphertextList.builder(fhePublicKey);
72
+ const zkCrs = CompactPkeCrs.deserialize(fromHexString(crs));
73
+
74
+ return { zkBuilder, zkCrs };
75
+ };
76
+
77
+ /**
78
+ * Worker-enabled zkProve function
79
+ * This submits proof generation to a Web Worker
80
+ */
81
+ async function zkProveWithWorker(
82
+ fheKeyHex: string,
83
+ crsHex: string,
84
+ items: EncryptableItem[],
85
+ metadata: Uint8Array
86
+ ): Promise<Uint8Array> {
87
+ // Serialize items for worker (convert enum to string name)
88
+ const serializedItems = items.map((item) => ({
89
+ utype: fheTypeToString(item.utype),
90
+ data: typeof item.data === 'bigint' ? item.data.toString() : item.data,
91
+ }));
92
+
93
+ // Submit to worker
94
+ const workerManager = getWorkerManager();
95
+ return await workerManager.submitProof(fheKeyHex, crsHex, serializedItems, metadata);
96
+ }
97
+
98
+ /**
99
+ * Creates a CoFHE configuration for web with IndexedDB storage as default
100
+ * @param config - The CoFHE input configuration (fheKeyStorage will default to IndexedDB if not provided)
101
+ * @returns The CoFHE configuration with web defaults applied
102
+ */
103
+ export function createCofheConfig(config: CofheInputConfig): CofheConfig {
104
+ return createCofheConfigBase({
105
+ environment: 'web',
106
+ ...config,
107
+ fheKeyStorage:
108
+ config.fheKeyStorage === null ? null : config.fheKeyStorage ?? (hasDOM ? createWebStorage() : createSsrStorage()),
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Creates a CoFHE client instance for web with TFHE automatically configured
114
+ * TFHE will be initialized automatically on first encryption - no manual setup required
115
+ * Workers are automatically enabled if available (can be disabled via config.useWorkers)
116
+ * @param config - The CoFHE configuration (use createCofheConfig to create with web defaults)
117
+ * @returns The CoFHE client instance
118
+ */
119
+ export function createCofheClient<TConfig extends CofheConfig>(config: TConfig): CofheClient<TConfig> {
120
+ return createCofheClientBase({
121
+ config,
122
+ zkBuilderAndCrsGenerator,
123
+ tfhePublicKeyDeserializer,
124
+ compactPkeCrsDeserializer,
125
+ initTfhe,
126
+ // Always provide the worker function if available - config.useWorkers controls usage
127
+ // areWorkersAvailable will return true if the Worker API is available and false in Node.js
128
+ zkProveWorkerFn: areWorkersAvailable() ? zkProveWithWorker : undefined,
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Terminate the worker (call on app cleanup)
134
+ */
135
+ export { terminateWorker };
136
+
137
+ /**
138
+ * Check if workers are available
139
+ */
140
+ export { areWorkersAvailable };
141
+
142
+ /**
143
+ * Test helper: Create a client with custom worker function (for testing fallback behavior)
144
+ * @internal - Only for testing purposes
145
+ */
146
+ export function createCofheClientWithCustomWorker(
147
+ config: CofheConfig,
148
+ customZkProveWorkerFn: (
149
+ fheKeyHex: string,
150
+ crsHex: string,
151
+ items: EncryptableItem[],
152
+ metadata: Uint8Array
153
+ ) => Promise<Uint8Array>
154
+ ): CofheClient {
155
+ return createCofheClientBase({
156
+ config,
157
+ zkBuilderAndCrsGenerator,
158
+ tfhePublicKeyDeserializer,
159
+ compactPkeCrsDeserializer,
160
+ initTfhe,
161
+ zkProveWorkerFn: customZkProveWorkerFn,
162
+ });
163
+ }
164
+
165
+ export { createSsrStorage };
166
+ export { hasDOM } from './const';
package/web/storage.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { IStorage } from '@/core';
2
+ import { constructClient } from 'iframe-shared-storage';
3
+ import { hasDOM } from './const';
4
+ /**
5
+ * Creates a web storage implementation using IndexedDB.
6
+ * Must only be called in a browser environment (requires `document` for iframe injection).
7
+ * @returns IStorage implementation for browser environments
8
+ */
9
+ export const createWebStorage = (opts = { enableLog: false }): IStorage => {
10
+ if (!hasDOM) throw new Error('createWebStorage can only be used in a browser environment');
11
+
12
+ const client = constructClient({
13
+ iframe: {
14
+ src: 'https://iframe-shared-storage.vercel.app/hub.html',
15
+ messagingOptions: {
16
+ enableLog: opts.enableLog ? 'both' : undefined,
17
+ },
18
+
19
+ iframeReadyTimeoutMs: 30_000, // if the iframe is not initied during this interval AND a reuqest is made, such request will throw an error
20
+ methodCallTimeoutMs: 10_000, // if a method call is not answered during this interval, the call will throw an error
21
+ methodCallRetries: 3, // number of retries for a method call if it times out
22
+ },
23
+ });
24
+
25
+ const indexedDBKeyval = client.indexedDBKeyval;
26
+ return {
27
+ getItem: async (name: string) => {
28
+ // IndexedDBKeyval returns undefined if not found, but we want null (a json-deserialized value is expected)
29
+ return (await indexedDBKeyval.get(name)) ?? null;
30
+ },
31
+ setItem: async (name: string, value: any) => {
32
+ await indexedDBKeyval.set(name, value);
33
+ },
34
+ removeItem: async (name: string) => {
35
+ await indexedDBKeyval.del(name);
36
+ },
37
+ };
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
+ }