@cofhe/sdk 0.0.0-beta-20251027110729

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 (110) hide show
  1. package/CHANGELOG.md +47 -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 +37 -0
  9. package/adapters/index.test.ts +25 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +91 -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/sepolia.ts +14 -0
  20. package/chains/chains.test.ts +49 -0
  21. package/chains/defineChain.ts +18 -0
  22. package/chains/index.ts +33 -0
  23. package/chains/types.ts +32 -0
  24. package/core/baseBuilder.ts +138 -0
  25. package/core/client.test.ts +298 -0
  26. package/core/client.ts +308 -0
  27. package/core/config.test.ts +224 -0
  28. package/core/config.ts +213 -0
  29. package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
  30. package/core/decrypt/cofheMocksSealOutput.ts +57 -0
  31. package/core/decrypt/decryptHandleBuilder.ts +281 -0
  32. package/core/decrypt/decryptUtils.ts +28 -0
  33. package/core/decrypt/tnSealOutput.ts +59 -0
  34. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  35. package/core/encrypt/cofheMocksZkVerifySign.ts +278 -0
  36. package/core/encrypt/encryptInputsBuilder.test.ts +735 -0
  37. package/core/encrypt/encryptInputsBuilder.ts +512 -0
  38. package/core/encrypt/encryptUtils.ts +64 -0
  39. package/core/encrypt/zkPackProveVerify.ts +273 -0
  40. package/core/error.ts +170 -0
  41. package/core/fetchKeys.test.ts +212 -0
  42. package/core/fetchKeys.ts +170 -0
  43. package/core/index.ts +77 -0
  44. package/core/keyStore.test.ts +226 -0
  45. package/core/keyStore.ts +127 -0
  46. package/core/permits.test.ts +242 -0
  47. package/core/permits.ts +136 -0
  48. package/core/result.test.ts +180 -0
  49. package/core/result.ts +67 -0
  50. package/core/test-utils.ts +45 -0
  51. package/core/types.ts +352 -0
  52. package/core/utils.ts +88 -0
  53. package/dist/adapters.cjs +88 -0
  54. package/dist/adapters.d.cts +14558 -0
  55. package/dist/adapters.d.ts +14558 -0
  56. package/dist/adapters.js +83 -0
  57. package/dist/chains.cjs +101 -0
  58. package/dist/chains.d.cts +99 -0
  59. package/dist/chains.d.ts +99 -0
  60. package/dist/chains.js +1 -0
  61. package/dist/chunk-GZCQQYVI.js +93 -0
  62. package/dist/chunk-KFGPTJ6X.js +2295 -0
  63. package/dist/chunk-LU7BMUUT.js +804 -0
  64. package/dist/core.cjs +3174 -0
  65. package/dist/core.d.cts +16 -0
  66. package/dist/core.d.ts +16 -0
  67. package/dist/core.js +3 -0
  68. package/dist/node.cjs +3090 -0
  69. package/dist/node.d.cts +22 -0
  70. package/dist/node.d.ts +22 -0
  71. package/dist/node.js +90 -0
  72. package/dist/permit-S9CnI6MF.d.cts +333 -0
  73. package/dist/permit-S9CnI6MF.d.ts +333 -0
  74. package/dist/permits.cjs +856 -0
  75. package/dist/permits.d.cts +1056 -0
  76. package/dist/permits.d.ts +1056 -0
  77. package/dist/permits.js +1 -0
  78. package/dist/types-KImPrEIe.d.cts +48 -0
  79. package/dist/types-KImPrEIe.d.ts +48 -0
  80. package/dist/types-PhwGgQvs.d.ts +953 -0
  81. package/dist/types-bB7wLj0q.d.cts +953 -0
  82. package/dist/web.cjs +3067 -0
  83. package/dist/web.d.cts +22 -0
  84. package/dist/web.d.ts +22 -0
  85. package/dist/web.js +64 -0
  86. package/node/client.test.ts +152 -0
  87. package/node/config.test.ts +68 -0
  88. package/node/encryptInputs.test.ts +175 -0
  89. package/node/index.ts +96 -0
  90. package/node/storage.ts +51 -0
  91. package/package.json +120 -0
  92. package/permits/index.ts +67 -0
  93. package/permits/localstorage.test.ts +118 -0
  94. package/permits/permit.test.ts +474 -0
  95. package/permits/permit.ts +396 -0
  96. package/permits/sealing.test.ts +84 -0
  97. package/permits/sealing.ts +131 -0
  98. package/permits/signature.ts +79 -0
  99. package/permits/store.test.ts +128 -0
  100. package/permits/store.ts +168 -0
  101. package/permits/test-utils.ts +20 -0
  102. package/permits/types.ts +174 -0
  103. package/permits/utils.ts +63 -0
  104. package/permits/validation.test.ts +288 -0
  105. package/permits/validation.ts +349 -0
  106. package/web/client.web.test.ts +152 -0
  107. package/web/config.web.test.ts +71 -0
  108. package/web/encryptInputs.web.test.ts +195 -0
  109. package/web/index.ts +97 -0
  110. package/web/storage.ts +20 -0
@@ -0,0 +1,170 @@
1
+ import { hardhat } from '@/chains';
2
+
3
+ import { type CofhesdkConfig, getCoFheUrlOrThrow } from './config.js';
4
+ import { type KeysStorage } from './keyStore.js';
5
+
6
+ const PUBLIC_KEY_LENGTH_MIN = 15_000;
7
+ // eslint-disable-next-line no-unused-vars
8
+ export type FheKeyDeserializer = (buff: string) => void;
9
+
10
+ const checkKeyValidity = (key: string | undefined, serializer: FheKeyDeserializer) => {
11
+ if (key == null || key.length === 0) return [false, `Key is null or empty <${key}>`];
12
+ try {
13
+ serializer(key);
14
+ return [true, `Key is valid`];
15
+ } catch (err) {
16
+ return [false, `Serialization failed <${err}> key length <${key.length}>`];
17
+ }
18
+ };
19
+
20
+ const fetchFhePublicKey = async (
21
+ coFheUrl: string,
22
+ chainId: number,
23
+ securityZone: number,
24
+ tfhePublicKeyDeserializer: FheKeyDeserializer,
25
+ keysStorage?: KeysStorage | null
26
+ ): Promise<[string, boolean]> => {
27
+ // Escape if key already exists
28
+ const storedKey = keysStorage?.getFheKey(chainId, securityZone);
29
+ const [storedKeyValid] = checkKeyValidity(storedKey, tfhePublicKeyDeserializer);
30
+ if (storedKeyValid) return [storedKey!, false];
31
+
32
+ let pk_data: string | undefined = undefined;
33
+
34
+ try {
35
+ const pk_res = await fetch(`${coFheUrl}/GetNetworkPublicKey`, {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ },
40
+ body: JSON.stringify({ securityZone }),
41
+ });
42
+ const json = (await pk_res.json()) as { publicKey: string };
43
+ pk_data = json.publicKey;
44
+ } catch (err) {
45
+ throw new Error(`Error fetching FHE publicKey; fetching from CoFHE failed with error ${err}`);
46
+ }
47
+
48
+ if (pk_data == null || typeof pk_data !== 'string') {
49
+ throw new Error(`Error fetching FHE publicKey; fetched result invalid: missing or not a string`);
50
+ }
51
+
52
+ if (pk_data === '0x') {
53
+ throw new Error('Error fetching FHE publicKey; provided chain is not FHE enabled / not found');
54
+ }
55
+
56
+ if (pk_data.length < PUBLIC_KEY_LENGTH_MIN) {
57
+ throw new Error(
58
+ `Error fetching FHE publicKey; got shorter than expected key length: ${pk_data.length}. Expected length >= ${PUBLIC_KEY_LENGTH_MIN}`
59
+ );
60
+ }
61
+
62
+ // Check validity by serializing
63
+ try {
64
+ tfhePublicKeyDeserializer(pk_data);
65
+ } catch (err) {
66
+ throw new Error(`Error serializing FHE publicKey; ${err}`);
67
+ }
68
+
69
+ // Store result
70
+ keysStorage?.setFheKey(chainId, securityZone, pk_data);
71
+
72
+ return [pk_data, true];
73
+ };
74
+
75
+ const fetchCrs = async (
76
+ coFheUrl: string,
77
+ chainId: number,
78
+ securityZone: number,
79
+ compactPkeCrsDeserializer: FheKeyDeserializer,
80
+ keysStorage?: KeysStorage | null
81
+ ): Promise<[string, boolean]> => {
82
+ // Escape if key already exists
83
+ const storedKey = keysStorage?.getCrs(chainId);
84
+ const [storedKeyValid] = checkKeyValidity(storedKey, compactPkeCrsDeserializer);
85
+ if (storedKeyValid) return [storedKey!, false];
86
+
87
+ let crs_data: string | undefined = undefined;
88
+
89
+ try {
90
+ const crs_res = await fetch(`${coFheUrl}/GetCrs`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ },
95
+ body: JSON.stringify({ securityZone }),
96
+ });
97
+ const json = (await crs_res.json()) as { crs: string };
98
+ crs_data = json.crs;
99
+ } catch (err) {
100
+ throw new Error(`Error fetching CRS; fetching failed with error ${err}`);
101
+ }
102
+
103
+ if (crs_data == null || typeof crs_data !== 'string') {
104
+ throw new Error(`Error fetching CRS; invalid: missing or not a string`);
105
+ }
106
+
107
+ try {
108
+ compactPkeCrsDeserializer(crs_data);
109
+ } catch (err) {
110
+ console.error(`Error serializing CRS ${err}`);
111
+ throw new Error(`Error serializing CRS; ${err}`);
112
+ }
113
+
114
+ keysStorage?.setCrs(chainId, crs_data);
115
+
116
+ return [crs_data, true];
117
+ };
118
+
119
+ /**
120
+ * Retrieves the FHE public key and the CRS from the provider.
121
+ * If the key/crs already exists in the store it is returned, else it is fetched, stored, and returned
122
+ * @param {CofhesdkConfig} config - The configuration object for the CoFHE SDK
123
+ * @param {number} chainId - The chain to fetch the FHE key for, if no chainId provided, undefined is returned
124
+ * @param securityZone - The security zone for which to retrieve the key (default 0).
125
+ * @param tfhePublicKeyDeserializer - The serializer for the FHE public key (used for validation).
126
+ * @param compactPkeCrsDeserializer - The serializer for the CRS (used for validation).
127
+ * @param keysStorage - The keys storage instance to use (optional)
128
+ * @returns {Promise<[[string, boolean], [string, boolean]]>} - A promise that resolves to [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]]
129
+ */
130
+ export const fetchKeys = async (
131
+ config: CofhesdkConfig,
132
+ chainId: number,
133
+ securityZone: number = 0,
134
+ tfhePublicKeyDeserializer: FheKeyDeserializer,
135
+ compactPkeCrsDeserializer: FheKeyDeserializer,
136
+ keysStorage?: KeysStorage | null
137
+ ): Promise<[[string, boolean], [string, boolean]]> => {
138
+ // Get cofhe url from config
139
+ const coFheUrl = getCoFheUrlOrThrow(config, chainId);
140
+
141
+ return await Promise.all([
142
+ fetchFhePublicKey(coFheUrl, chainId, securityZone, tfhePublicKeyDeserializer, keysStorage),
143
+ fetchCrs(coFheUrl, chainId, securityZone, compactPkeCrsDeserializer, keysStorage),
144
+ ]);
145
+ };
146
+
147
+ /**
148
+ * Fetches the FHE public key and the CRS for all chains in the config
149
+ * @param {CofhesdkConfig} config - The configuration object for the CoFHE SDK
150
+ * @param {number} securityZone - The security zone for which to retrieve the key (default 0).
151
+ * @param tfhePublicKeyDeserializer - The serializer for the FHE public key (used for validation).
152
+ * @param compactPkeCrsDeserializer - The serializer for the CRS (used for validation).
153
+ * @param keysStorage - The keys storage instance to use (optional)
154
+ * @returns {Promise<void>} - A promise that resolves when the keys are fetched and stored.
155
+ */
156
+ export const fetchMultichainKeys = async (
157
+ config: CofhesdkConfig,
158
+ securityZone: number = 0,
159
+ tfhePublicKeyDeserializer: FheKeyDeserializer,
160
+ compactPkeCrsDeserializer: FheKeyDeserializer,
161
+ keysStorage?: KeysStorage | null
162
+ ): Promise<void> => {
163
+ await Promise.all(
164
+ config.supportedChains
165
+ .filter((chain) => chain.id !== hardhat.id)
166
+ .map((chain) =>
167
+ fetchKeys(config, chain.id, securityZone, tfhePublicKeyDeserializer, compactPkeCrsDeserializer, keysStorage)
168
+ )
169
+ );
170
+ };
package/core/index.ts ADDED
@@ -0,0 +1,77 @@
1
+ // Client (base implementations)
2
+ export { createCofhesdkClientBase } from './client.js';
3
+
4
+ // Configuration (base implementations)
5
+ export { createCofhesdkConfigBase, getCofhesdkConfigItem } from './config.js';
6
+ export type { CofhesdkConfig, CofhesdkInputConfig, CofhesdkInternalConfig } from './config.js';
7
+
8
+ // Types
9
+ export type {
10
+ // Client types
11
+ CofhesdkClient,
12
+ CofhesdkClientParams,
13
+ CofhesdkClientConnectionState,
14
+ CofhesdkClientPermits,
15
+ IStorage,
16
+ // Primitive types
17
+ Primitive,
18
+ LiteralToPrimitive,
19
+ // Encryptable types
20
+ EncryptableItem,
21
+ EncryptableBool,
22
+ EncryptableUint8,
23
+ EncryptableUint16,
24
+ EncryptableUint32,
25
+ EncryptableUint64,
26
+ EncryptableUint128,
27
+ EncryptableAddress,
28
+ // Encrypted types
29
+ EncryptedNumber,
30
+ EncryptedItemInput,
31
+ EncryptedBoolInput,
32
+ EncryptedUint8Input,
33
+ EncryptedUint16Input,
34
+ EncryptedUint32Input,
35
+ EncryptedUint64Input,
36
+ EncryptedUint128Input,
37
+ EncryptedUint256Input,
38
+ EncryptedAddressInput,
39
+ EncryptedItemInputs,
40
+ EncryptableToEncryptedItemInputMap,
41
+ // Decryption types
42
+ UnsealedItem,
43
+ // Util types
44
+ EncryptStepCallbackFunction as EncryptSetStateFn,
45
+ } from './types.js';
46
+ export { FheTypes, FheUintUTypes, FheAllUTypes, Encryptable, isEncryptableItem, EncryptStep } from './types.js';
47
+
48
+ // Error handling
49
+ export { CofhesdkError, CofhesdkErrorCode, isCofhesdkError } from './error.js';
50
+ export type { CofhesdkErrorParams } from './error.js';
51
+
52
+ // Result types
53
+ export {
54
+ ResultErr,
55
+ ResultOk,
56
+ ResultErrOrInternal,
57
+ ResultHttpError,
58
+ ResultValidationError,
59
+ resultWrapper,
60
+ resultWrapperSync,
61
+ } from './result.js';
62
+ export type { Result } from './result.js';
63
+
64
+ // Key fetching
65
+ export { fetchKeys, fetchMultichainKeys } from './fetchKeys.js';
66
+ export type { FheKeyDeserializer } from './fetchKeys.js';
67
+
68
+ // Key storage
69
+ export { createKeysStore } from './keyStore.js';
70
+ export type { KeysStorage, KeysStore } from './keyStore.js';
71
+
72
+ // Builders (exported via client, but can be imported directly for typing)
73
+ export { EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
74
+ export { DecryptHandlesBuilder } from './decrypt/decryptHandleBuilder.js';
75
+
76
+ // ZK utilities
77
+ export type { ZkBuilderAndCrsGenerator } from './encrypt/zkPackProveVerify.js';
@@ -0,0 +1,226 @@
1
+ /* eslint-disable turbo/no-undeclared-env-vars */
2
+ /* eslint-disable no-undef */
3
+
4
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
5
+ import { createKeysStore, type KeysStore, type KeysStorage } from './keyStore.js';
6
+
7
+ // Mock the storage module
8
+ const mockStorage = {
9
+ getItem: vi.fn(),
10
+ setItem: vi.fn(),
11
+ removeItem: vi.fn(),
12
+ };
13
+
14
+ vi.mock('../src/storage', () => ({
15
+ getStorage: () => mockStorage,
16
+ }));
17
+
18
+ describe('KeyStore', () => {
19
+ let keysStorage: KeysStorage;
20
+
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ // Create a fresh keysStorage instance for each test (with mock storage)
24
+ keysStorage = createKeysStore(mockStorage as any);
25
+ });
26
+
27
+ afterEach(() => {
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ describe('Store Structure', () => {
32
+ it('should have initial empty state', () => {
33
+ const state = keysStorage.store.getState();
34
+ expect(state).toEqual({
35
+ fhe: {},
36
+ crs: {},
37
+ });
38
+ });
39
+
40
+ it('should be a Zustand store with persist', () => {
41
+ expect(keysStorage.store).toBeDefined();
42
+ expect(keysStorage.store.getState).toBeDefined();
43
+ expect(keysStorage.store.setState).toBeDefined();
44
+ // In test environment, persist might not be fully initialized
45
+ if ('persist' in keysStorage.store) {
46
+ expect((keysStorage.store as any).persist).toBeDefined();
47
+ }
48
+ });
49
+ });
50
+
51
+ describe('FHE Key Management', () => {
52
+ const testChainId = 1337;
53
+ const testSecurityZone = 0;
54
+ const testKey = '0x1234567890';
55
+
56
+ it('should set and get FHE key', () => {
57
+ keysStorage.setFheKey(testChainId, testSecurityZone, testKey);
58
+
59
+ const retrievedKey = keysStorage.getFheKey(testChainId, testSecurityZone);
60
+
61
+ expect(retrievedKey).toEqual(testKey);
62
+ });
63
+
64
+ it('should handle multiple security zones', () => {
65
+ const key0 = '0x1234567890';
66
+ const key1 = '0x2345678901';
67
+
68
+ keysStorage.setFheKey(testChainId, 0, key0);
69
+ keysStorage.setFheKey(testChainId, 1, key1);
70
+
71
+ expect(keysStorage.getFheKey(testChainId, 0)).toEqual(key0);
72
+ expect(keysStorage.getFheKey(testChainId, 1)).toEqual(key1);
73
+ });
74
+
75
+ it('should handle multiple chains', () => {
76
+ const chain1Key = '0x1234567890';
77
+ const chain2Key = '0x2345678901';
78
+
79
+ keysStorage.setFheKey(1, testSecurityZone, chain1Key);
80
+ keysStorage.setFheKey(2, testSecurityZone, chain2Key);
81
+
82
+ expect(keysStorage.getFheKey(1, testSecurityZone)).toEqual(chain1Key);
83
+ expect(keysStorage.getFheKey(2, testSecurityZone)).toEqual(chain2Key);
84
+ });
85
+
86
+ it('should return undefined for non-existent keys', () => {
87
+ expect(keysStorage.getFheKey(999, 0)).toBeUndefined();
88
+ expect(keysStorage.getFheKey(testChainId, 999)).toBeUndefined();
89
+ expect(keysStorage.getFheKey(undefined, 0)).toBeUndefined();
90
+ expect(keysStorage.getFheKey(testChainId, undefined as any)).toBeUndefined();
91
+ });
92
+ });
93
+
94
+ describe('CRS Management', () => {
95
+ const testChainId = 1337;
96
+ const testCrs = '0x1234567890';
97
+
98
+ it('should set and get CRS', () => {
99
+ keysStorage.setCrs(testChainId, testCrs);
100
+
101
+ const retrievedCrs = keysStorage.getCrs(testChainId);
102
+
103
+ expect(retrievedCrs).toEqual(testCrs);
104
+ });
105
+
106
+ it('should handle multiple chains for CRS', () => {
107
+ const crs1 = '0x1234567890';
108
+ const crs2 = '0x2345678901';
109
+
110
+ keysStorage.setCrs(1, crs1);
111
+ keysStorage.setCrs(2, crs2);
112
+
113
+ expect(keysStorage.getCrs(1)).toEqual(crs1);
114
+ expect(keysStorage.getCrs(2)).toEqual(crs2);
115
+ });
116
+
117
+ it('should return undefined for non-existent CRS', () => {
118
+ expect(keysStorage.getCrs(999)).toBeUndefined();
119
+ expect(keysStorage.getCrs(undefined)).toBeUndefined();
120
+ });
121
+ });
122
+
123
+ describe('Storage Utilities', () => {
124
+ it('should clear keys storage', async () => {
125
+ await keysStorage.clearKeysStorage();
126
+
127
+ expect(mockStorage.removeItem).toHaveBeenCalledWith('cofhesdk-keys');
128
+ });
129
+
130
+ it('should rehydrate keys store', async () => {
131
+ const mockRehydrate = vi.fn();
132
+
133
+ // Mock the persist object if it doesn't exist
134
+ if (!('persist' in keysStorage.store)) {
135
+ (keysStorage.store as any).persist = {};
136
+ }
137
+ (keysStorage.store as any).persist.rehydrate = mockRehydrate;
138
+
139
+ await keysStorage.rehydrateKeysStore();
140
+
141
+ expect(mockRehydrate).toHaveBeenCalled();
142
+ });
143
+ });
144
+
145
+ describe('keysStorage Object', () => {
146
+ it('should have all required methods', () => {
147
+ expect(keysStorage).toBeDefined();
148
+ expect(keysStorage.store).toBeDefined();
149
+ expect(keysStorage.getFheKey).toBeDefined();
150
+ expect(keysStorage.getCrs).toBeDefined();
151
+ expect(keysStorage.setFheKey).toBeDefined();
152
+ expect(keysStorage.setCrs).toBeDefined();
153
+ expect(keysStorage.clearKeysStorage).toBeDefined();
154
+ expect(keysStorage.rehydrateKeysStore).toBeDefined();
155
+ });
156
+
157
+ it('should work through keysStorage object', () => {
158
+ const testChainId = 1337;
159
+ const testKey = '0x1234567890';
160
+ const testCrs = '0x2345678901';
161
+
162
+ keysStorage.setFheKey(testChainId, 0, testKey);
163
+ keysStorage.setCrs(testChainId, testCrs);
164
+
165
+ expect(keysStorage.getFheKey(testChainId, 0)).toEqual(testKey);
166
+ expect(keysStorage.getCrs(testChainId)).toEqual(testCrs);
167
+ });
168
+ });
169
+
170
+ describe('State Management', () => {
171
+ it('should update state immutably', () => {
172
+ const initialState = keysStorage.store.getState();
173
+ const testChainId = 1337;
174
+ const testKey = '0x1234567890';
175
+
176
+ keysStorage.setFheKey(testChainId, 0, testKey);
177
+
178
+ const newState = keysStorage.store.getState();
179
+
180
+ // State should be different objects
181
+ expect(newState).not.toBe(initialState);
182
+ expect(newState.fhe).not.toBe(initialState.fhe);
183
+
184
+ // But should contain the new key
185
+ expect(newState.fhe[testChainId][0]).toEqual(testKey);
186
+ });
187
+
188
+ it('should preserve existing data when adding new keys', () => {
189
+ const key1 = '0x1234567890';
190
+ const key2 = '0x2345678901';
191
+ const crs1 = '0x3456789012';
192
+
193
+ keysStorage.setFheKey(1, 0, key1);
194
+ keysStorage.setFheKey(2, 0, key2);
195
+ keysStorage.setCrs(1, crs1);
196
+
197
+ const state = keysStorage.store.getState();
198
+
199
+ expect(state.fhe[1][0]).toEqual(key1);
200
+ expect(state.fhe[2][0]).toEqual(key2);
201
+ expect(state.crs[1]).toEqual(crs1);
202
+ });
203
+ });
204
+
205
+ describe('Type Safety', () => {
206
+ it('should have correct TypeScript types', () => {
207
+ const state: KeysStore = keysStorage.store.getState();
208
+
209
+ // These should compile without TypeScript errors
210
+ expect(typeof state.fhe).toBe('object');
211
+ expect(typeof state.crs).toBe('object');
212
+
213
+ // Test that the types allow proper access patterns
214
+ const chainId = 1337;
215
+ const securityZone = 0;
216
+
217
+ if (state.fhe[chainId] && state.fhe[chainId][securityZone]) {
218
+ expect(state.fhe[chainId][securityZone]).toBeInstanceOf(String);
219
+ }
220
+
221
+ if (state.crs[chainId]) {
222
+ expect(state.crs[chainId]).toBeInstanceOf(String);
223
+ }
224
+ });
225
+ });
226
+ });
@@ -0,0 +1,127 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { createStore, type StoreApi } from 'zustand/vanilla';
3
+ import { persist, createJSONStorage } from 'zustand/middleware';
4
+ import { produce } from 'immer';
5
+ import { type IStorage } from './types.js';
6
+
7
+ // Type definitions
8
+ type ChainRecord<T> = Record<string, T>;
9
+ type SecurityZoneRecord<T> = Record<number, T>;
10
+
11
+ // Keys store for FHE keys and CRS
12
+ export type KeysStore = {
13
+ fhe: ChainRecord<SecurityZoneRecord<string | undefined>>;
14
+ crs: ChainRecord<string | undefined>;
15
+ };
16
+
17
+ export type KeysStorage = {
18
+ store: StoreApi<KeysStore>;
19
+ getFheKey: (chainId: number | undefined, securityZone?: number) => string | undefined;
20
+ getCrs: (chainId: number | undefined) => string | undefined;
21
+ setFheKey: (chainId: number, securityZone: number, key: string) => void;
22
+ setCrs: (chainId: number, crs: string) => void;
23
+ clearKeysStorage: () => Promise<void>;
24
+ rehydrateKeysStore: () => Promise<void>;
25
+ };
26
+
27
+ /**
28
+ * Creates a keys storage instance using the provided storage implementation
29
+ * @param storage - The storage implementation to use (IStorage interface), or null for non-persisted store
30
+ * @returns A KeysStorage instance with all utility methods
31
+ */
32
+ export function createKeysStore(storage: IStorage | null): KeysStorage {
33
+ // Conditionally create store with or without persist wrapper
34
+ const keysStore = storage
35
+ ? createStore<KeysStore>()(
36
+ persist(
37
+ () => ({
38
+ fhe: {},
39
+ crs: {},
40
+ }),
41
+ {
42
+ name: 'cofhesdk-keys',
43
+ storage: createJSONStorage(() => storage),
44
+ merge: (persistedState, currentState) => {
45
+ const persisted = persistedState as KeysStore;
46
+ const current = currentState as KeysStore;
47
+
48
+ // Deep merge for fhe
49
+ const mergedFhe: KeysStore['fhe'] = { ...persisted.fhe };
50
+ const allChainIds = new Set([...Object.keys(current.fhe), ...Object.keys(persisted.fhe)]);
51
+ for (const chainId of allChainIds) {
52
+ const persistedZones = persisted.fhe[chainId] || {};
53
+ const currentZones = current.fhe[chainId] || {};
54
+ mergedFhe[chainId] = { ...persistedZones, ...currentZones };
55
+ }
56
+
57
+ // Deep merge for crs
58
+ const mergedCrs: KeysStore['crs'] = { ...persisted.crs, ...current.crs };
59
+
60
+ return {
61
+ fhe: mergedFhe,
62
+ crs: mergedCrs,
63
+ };
64
+ },
65
+ }
66
+ )
67
+ )
68
+ : createStore<KeysStore>()(() => ({
69
+ fhe: {},
70
+ crs: {},
71
+ }));
72
+
73
+ // Utility functions
74
+
75
+ const getFheKey = (chainId: number | undefined, securityZone = 0) => {
76
+ if (chainId == null || securityZone == null) return undefined;
77
+ const stored = keysStore.getState().fhe[chainId]?.[securityZone];
78
+ return stored;
79
+ };
80
+
81
+ const getCrs = (chainId: number | undefined) => {
82
+ if (chainId == null) return undefined;
83
+ const stored = keysStore.getState().crs[chainId];
84
+ return stored;
85
+ };
86
+
87
+ const setFheKey = (chainId: number, securityZone: number, key: string) => {
88
+ keysStore.setState(
89
+ produce<KeysStore>((state: KeysStore) => {
90
+ if (state.fhe[chainId] == null) state.fhe[chainId] = {};
91
+ state.fhe[chainId][securityZone] = key;
92
+ })
93
+ );
94
+ };
95
+
96
+ const setCrs = (chainId: number, crs: string) => {
97
+ keysStore.setState(
98
+ produce<KeysStore>((state: KeysStore) => {
99
+ state.crs[chainId] = crs;
100
+ })
101
+ );
102
+ };
103
+
104
+ const clearKeysStorage = async () => {
105
+ if (storage) {
106
+ await storage.removeItem('cofhesdk-keys');
107
+ }
108
+ // If no storage, this is a no-op
109
+ };
110
+
111
+ const rehydrateKeysStore = async () => {
112
+ if ('persist' in keysStore) {
113
+ if ((keysStore.persist as any).hasHydrated()) return;
114
+ await (keysStore.persist as any).rehydrate();
115
+ }
116
+ };
117
+
118
+ return {
119
+ store: keysStore,
120
+ getFheKey,
121
+ getCrs,
122
+ setFheKey,
123
+ setCrs,
124
+ clearKeysStorage,
125
+ rehydrateKeysStore,
126
+ };
127
+ }