@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.
- package/CHANGELOG.md +146 -0
- package/adapters/ethers5.test.ts +174 -0
- package/adapters/ethers5.ts +36 -0
- package/adapters/ethers6.test.ts +169 -0
- package/adapters/ethers6.ts +36 -0
- package/adapters/hardhat-node.ts +167 -0
- package/adapters/hardhat.hh2.test.ts +159 -0
- package/adapters/hardhat.ts +36 -0
- package/adapters/index.test.ts +20 -0
- package/adapters/index.ts +5 -0
- package/adapters/smartWallet.ts +99 -0
- package/adapters/test-utils.ts +53 -0
- package/adapters/types.ts +6 -0
- package/adapters/wagmi.test.ts +156 -0
- package/adapters/wagmi.ts +17 -0
- package/chains/chains/arbSepolia.ts +14 -0
- package/chains/chains/baseSepolia.ts +14 -0
- package/chains/chains/hardhat.ts +15 -0
- package/chains/chains/localcofhe.ts +14 -0
- package/chains/chains/sepolia.ts +14 -0
- package/chains/chains.test.ts +50 -0
- package/chains/defineChain.ts +18 -0
- package/chains/index.ts +35 -0
- package/chains/types.ts +32 -0
- package/core/baseBuilder.ts +119 -0
- package/core/client.test.ts +429 -0
- package/core/client.ts +341 -0
- package/core/clientTypes.ts +119 -0
- package/core/config.test.ts +242 -0
- package/core/config.ts +225 -0
- package/core/consts.ts +22 -0
- package/core/decrypt/MockThresholdNetworkAbi.ts +179 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +84 -0
- package/core/decrypt/cofheMocksDecryptForView.ts +48 -0
- package/core/decrypt/decryptForTxBuilder.ts +359 -0
- package/core/decrypt/decryptForViewBuilder.ts +332 -0
- package/core/decrypt/decryptUtils.ts +28 -0
- package/core/decrypt/pollCallbacks.test.ts +194 -0
- package/core/decrypt/polling.ts +14 -0
- package/core/decrypt/tnDecryptUtils.ts +65 -0
- package/core/decrypt/tnDecryptV1.ts +171 -0
- package/core/decrypt/tnDecryptV2.ts +365 -0
- package/core/decrypt/tnSealOutputV1.ts +59 -0
- package/core/decrypt/tnSealOutputV2.ts +324 -0
- package/core/decrypt/verifyDecryptResult.ts +52 -0
- package/core/encrypt/MockZkVerifierAbi.ts +106 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +281 -0
- package/core/encrypt/encryptInputsBuilder.test.ts +747 -0
- package/core/encrypt/encryptInputsBuilder.ts +583 -0
- package/core/encrypt/encryptUtils.ts +67 -0
- package/core/encrypt/zkPackProveVerify.ts +335 -0
- package/core/error.ts +168 -0
- package/core/fetchKeys.test.ts +195 -0
- package/core/fetchKeys.ts +144 -0
- package/core/index.ts +106 -0
- package/core/keyStore.test.ts +226 -0
- package/core/keyStore.ts +154 -0
- package/core/permits.test.ts +493 -0
- package/core/permits.ts +201 -0
- package/core/types.ts +419 -0
- package/core/utils.ts +130 -0
- package/dist/adapters.cjs +88 -0
- package/dist/adapters.d.cts +14576 -0
- package/dist/adapters.d.ts +14576 -0
- package/dist/adapters.js +83 -0
- package/dist/chains.cjs +111 -0
- package/dist/chains.d.cts +121 -0
- package/dist/chains.d.ts +121 -0
- package/dist/chains.js +1 -0
- package/dist/chunk-36FBWLUS.js +3310 -0
- package/dist/chunk-7HLGHV67.js +990 -0
- package/dist/chunk-TBLR7NNE.js +102 -0
- package/dist/clientTypes-AVSCBet7.d.cts +998 -0
- package/dist/clientTypes-flH1ju82.d.ts +998 -0
- package/dist/core.cjs +4362 -0
- package/dist/core.d.cts +138 -0
- package/dist/core.d.ts +138 -0
- package/dist/core.js +3 -0
- package/dist/node.cjs +4225 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +91 -0
- package/dist/permit-jRirYqFt.d.cts +376 -0
- package/dist/permit-jRirYqFt.d.ts +376 -0
- package/dist/permits.cjs +1025 -0
- package/dist/permits.d.cts +353 -0
- package/dist/permits.d.ts +353 -0
- package/dist/permits.js +1 -0
- package/dist/types-YiAC4gig.d.cts +33 -0
- package/dist/types-YiAC4gig.d.ts +33 -0
- package/dist/web.cjs +4434 -0
- package/dist/web.d.cts +42 -0
- package/dist/web.d.ts +42 -0
- package/dist/web.js +256 -0
- package/dist/zkProve.worker.cjs +93 -0
- package/dist/zkProve.worker.d.cts +2 -0
- package/dist/zkProve.worker.d.ts +2 -0
- package/dist/zkProve.worker.js +91 -0
- package/node/client.test.ts +159 -0
- package/node/config.test.ts +68 -0
- package/node/encryptInputs.test.ts +155 -0
- package/node/index.ts +97 -0
- package/node/storage.ts +51 -0
- package/package.json +121 -0
- package/permits/index.ts +68 -0
- package/permits/localstorage.test.ts +113 -0
- package/permits/onchain-utils.ts +221 -0
- package/permits/permit.test.ts +534 -0
- package/permits/permit.ts +386 -0
- package/permits/sealing.test.ts +84 -0
- package/permits/sealing.ts +131 -0
- package/permits/signature.ts +79 -0
- package/permits/store.test.ts +88 -0
- package/permits/store.ts +156 -0
- package/permits/test-utils.ts +28 -0
- package/permits/types.ts +204 -0
- package/permits/utils.ts +58 -0
- package/permits/validation.test.ts +361 -0
- package/permits/validation.ts +327 -0
- package/web/client.web.test.ts +159 -0
- package/web/config.web.test.ts +69 -0
- package/web/const.ts +2 -0
- package/web/encryptInputs.web.test.ts +172 -0
- package/web/index.ts +166 -0
- package/web/storage.ts +49 -0
- package/web/worker.builder.web.test.ts +148 -0
- package/web/worker.config.web.test.ts +329 -0
- package/web/worker.output.web.test.ts +84 -0
- package/web/workerManager.test.ts +80 -0
- package/web/workerManager.ts +214 -0
- package/web/workerManager.web.test.ts +114 -0
- package/web/zkProve.worker.ts +133 -0
package/core/client.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import type { CreateSelfPermitOptions, CreateSharingPermitOptions, ImportSharedPermitOptions } from '@/permits';
|
|
2
|
+
|
|
3
|
+
import { createStore } from 'zustand/vanilla';
|
|
4
|
+
import { type Hex, type PublicClient, type WalletClient } from 'viem';
|
|
5
|
+
import { CofheError, CofheErrorCode } from './error.js';
|
|
6
|
+
import { EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
|
|
7
|
+
import { createKeysStore } from './keyStore.js';
|
|
8
|
+
import { permits } from './permits.js';
|
|
9
|
+
import { DecryptForViewBuilder } from './decrypt/decryptForViewBuilder.js';
|
|
10
|
+
import { DecryptForTxBuilder, type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js';
|
|
11
|
+
import { verifyDecryptResult as verifyDecryptResultStandalone } from './decrypt/verifyDecryptResult.js';
|
|
12
|
+
import { getPublicClientChainID, getWalletClientAccount } from './utils.js';
|
|
13
|
+
import type { CofheClientConnectionState, CofheClientParams, CofheClient, CofheClientPermits } from './clientTypes.js';
|
|
14
|
+
import type { EncryptableItem, FheTypes } from './types.js';
|
|
15
|
+
import type { CofheConfig } from './config.js';
|
|
16
|
+
|
|
17
|
+
export const InitialConnectStore: CofheClientConnectionState = {
|
|
18
|
+
connected: false,
|
|
19
|
+
connecting: false,
|
|
20
|
+
connectError: undefined,
|
|
21
|
+
chainId: undefined,
|
|
22
|
+
account: undefined,
|
|
23
|
+
publicClient: undefined,
|
|
24
|
+
walletClient: undefined,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a CoFHE client instance (base implementation)
|
|
29
|
+
* @param {CofheClientParams} opts - Initialization options including config and platform-specific serializers
|
|
30
|
+
* @returns {CofheClient} - The CoFHE client instance
|
|
31
|
+
*/
|
|
32
|
+
export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
33
|
+
opts: CofheClientParams<TConfig>
|
|
34
|
+
): CofheClient<TConfig> {
|
|
35
|
+
// Create keysStorage instance using configured storage
|
|
36
|
+
const keysStorage = createKeysStore(opts.config.fheKeyStorage);
|
|
37
|
+
|
|
38
|
+
// Zustand store for reactive state management
|
|
39
|
+
|
|
40
|
+
const connectStore = createStore<CofheClientConnectionState>(() => InitialConnectStore);
|
|
41
|
+
|
|
42
|
+
// Minimal cancellation mechanism: incremented on each connect/disconnect.
|
|
43
|
+
// If a connect finishes after a disconnect, it must not overwrite the disconnected state.
|
|
44
|
+
let connectAttemptId = 0;
|
|
45
|
+
|
|
46
|
+
// Helper to update state
|
|
47
|
+
const updateConnectState = (partial: Partial<CofheClientConnectionState>) => {
|
|
48
|
+
connectStore.setState((state) => ({ ...state, ...partial }));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Called before any operation, throws of connection not yet established
|
|
52
|
+
const _requireConnected = () => {
|
|
53
|
+
const state = connectStore.getState();
|
|
54
|
+
const notConnected =
|
|
55
|
+
!state.connected || !state.account || !state.chainId || !state.publicClient || !state.walletClient;
|
|
56
|
+
if (notConnected) {
|
|
57
|
+
throw new CofheError({
|
|
58
|
+
code: CofheErrorCode.NotConnected,
|
|
59
|
+
message: 'Client must be connected, account and chainId must be initialized',
|
|
60
|
+
hint: 'Ensure client.connect() has been called and awaited.',
|
|
61
|
+
context: {
|
|
62
|
+
connected: state.connected,
|
|
63
|
+
account: state.account,
|
|
64
|
+
chainId: state.chainId,
|
|
65
|
+
publicClient: state.publicClient,
|
|
66
|
+
walletClient: state.walletClient,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// LIFECYCLE
|
|
73
|
+
|
|
74
|
+
async function connect(publicClient: PublicClient, walletClient: WalletClient) {
|
|
75
|
+
const state = connectStore.getState();
|
|
76
|
+
|
|
77
|
+
// Exit if already connected and clients are the same
|
|
78
|
+
if (state.connected && state.publicClient === publicClient && state.walletClient === walletClient) return;
|
|
79
|
+
|
|
80
|
+
connectAttemptId += 1;
|
|
81
|
+
const localAttemptId = connectAttemptId;
|
|
82
|
+
|
|
83
|
+
// Set connecting state
|
|
84
|
+
updateConnectState({
|
|
85
|
+
...InitialConnectStore,
|
|
86
|
+
connecting: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Fetch chainId and account
|
|
90
|
+
try {
|
|
91
|
+
const chainId = await getPublicClientChainID(publicClient);
|
|
92
|
+
const account = await getWalletClientAccount(walletClient);
|
|
93
|
+
|
|
94
|
+
// If a disconnect (or a newer connect) happened while awaiting, ignore this completion.
|
|
95
|
+
if (localAttemptId !== connectAttemptId) return;
|
|
96
|
+
|
|
97
|
+
updateConnectState({
|
|
98
|
+
connected: true,
|
|
99
|
+
connecting: false,
|
|
100
|
+
connectError: undefined,
|
|
101
|
+
chainId,
|
|
102
|
+
account,
|
|
103
|
+
publicClient,
|
|
104
|
+
walletClient,
|
|
105
|
+
});
|
|
106
|
+
} catch (e) {
|
|
107
|
+
// Ignore stale errors too.
|
|
108
|
+
if (localAttemptId !== connectAttemptId) return;
|
|
109
|
+
|
|
110
|
+
updateConnectState({
|
|
111
|
+
...InitialConnectStore,
|
|
112
|
+
connectError: e,
|
|
113
|
+
});
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function disconnect() {
|
|
119
|
+
connectAttemptId += 1;
|
|
120
|
+
updateConnectState({ ...InitialConnectStore });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// CLIENT OPERATIONS
|
|
124
|
+
|
|
125
|
+
function encryptInputs<T extends EncryptableItem[]>(inputs: [...T]): EncryptInputsBuilder<[...T]> {
|
|
126
|
+
const state = connectStore.getState();
|
|
127
|
+
|
|
128
|
+
return new EncryptInputsBuilder({
|
|
129
|
+
inputs,
|
|
130
|
+
account: state.account ?? undefined,
|
|
131
|
+
chainId: state.chainId ?? undefined,
|
|
132
|
+
|
|
133
|
+
config: opts.config,
|
|
134
|
+
publicClient: state.publicClient ?? undefined,
|
|
135
|
+
walletClient: state.walletClient ?? undefined,
|
|
136
|
+
zkvWalletClient: opts.config._internal?.zkvWalletClient,
|
|
137
|
+
|
|
138
|
+
tfhePublicKeyDeserializer: opts.tfhePublicKeyDeserializer,
|
|
139
|
+
compactPkeCrsDeserializer: opts.compactPkeCrsDeserializer,
|
|
140
|
+
zkBuilderAndCrsGenerator: opts.zkBuilderAndCrsGenerator,
|
|
141
|
+
initTfhe: opts.initTfhe,
|
|
142
|
+
zkProveWorkerFn: opts.zkProveWorkerFn,
|
|
143
|
+
|
|
144
|
+
keysStorage,
|
|
145
|
+
|
|
146
|
+
requireConnected: _requireConnected,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function decryptForView<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U> {
|
|
151
|
+
const state = connectStore.getState();
|
|
152
|
+
|
|
153
|
+
return new DecryptForViewBuilder({
|
|
154
|
+
ctHash,
|
|
155
|
+
utype,
|
|
156
|
+
chainId: state.chainId,
|
|
157
|
+
account: state.account,
|
|
158
|
+
|
|
159
|
+
config: opts.config,
|
|
160
|
+
publicClient: state.publicClient,
|
|
161
|
+
walletClient: state.walletClient,
|
|
162
|
+
|
|
163
|
+
requireConnected: _requireConnected,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function decryptForTx(ctHash: bigint | string): DecryptForTxBuilderUnset {
|
|
168
|
+
const state = connectStore.getState();
|
|
169
|
+
|
|
170
|
+
return new DecryptForTxBuilder({
|
|
171
|
+
ctHash,
|
|
172
|
+
chainId: state.chainId,
|
|
173
|
+
account: state.account,
|
|
174
|
+
|
|
175
|
+
config: opts.config,
|
|
176
|
+
publicClient: state.publicClient,
|
|
177
|
+
walletClient: state.walletClient,
|
|
178
|
+
|
|
179
|
+
requireConnected: _requireConnected,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// VERIFY DECRYPT RESULT
|
|
184
|
+
function verifyDecryptResult(handle: bigint | string, cleartext: bigint, signature: Hex): Promise<boolean> {
|
|
185
|
+
_requireConnected();
|
|
186
|
+
const { publicClient } = connectStore.getState();
|
|
187
|
+
return verifyDecryptResultStandalone(handle, cleartext, signature, publicClient!);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// PERMITS - Context-aware wrapper
|
|
191
|
+
|
|
192
|
+
const _getChainIdAndAccount = (chainId?: number, account?: string) => {
|
|
193
|
+
const state = connectStore.getState();
|
|
194
|
+
const _chainId = chainId ?? state.chainId;
|
|
195
|
+
const _account = account ?? state.account;
|
|
196
|
+
|
|
197
|
+
if (_chainId == null || _account == null) {
|
|
198
|
+
throw new CofheError({
|
|
199
|
+
code: CofheErrorCode.NotConnected,
|
|
200
|
+
message: 'ChainId or account not available.',
|
|
201
|
+
hint: 'Ensure client.connect() has been called, or provide chainId and account explicitly.',
|
|
202
|
+
context: {
|
|
203
|
+
chainId: _chainId,
|
|
204
|
+
account: _account,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { chainId: _chainId, account: _account };
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const clientPermits: CofheClientPermits = {
|
|
213
|
+
// Pass through store access
|
|
214
|
+
getSnapshot: permits.getSnapshot,
|
|
215
|
+
subscribe: permits.subscribe,
|
|
216
|
+
|
|
217
|
+
// Creation methods (require connection)
|
|
218
|
+
createSelf: async (
|
|
219
|
+
options: CreateSelfPermitOptions,
|
|
220
|
+
clients?: { publicClient: PublicClient; walletClient: WalletClient }
|
|
221
|
+
) => {
|
|
222
|
+
_requireConnected();
|
|
223
|
+
const { publicClient, walletClient } = clients ?? connectStore.getState();
|
|
224
|
+
return permits.createSelf(options, publicClient!, walletClient!);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
createSharing: async (
|
|
228
|
+
options: CreateSharingPermitOptions,
|
|
229
|
+
clients?: { publicClient: PublicClient; walletClient: WalletClient }
|
|
230
|
+
) => {
|
|
231
|
+
_requireConnected();
|
|
232
|
+
const { publicClient, walletClient } = clients ?? connectStore.getState();
|
|
233
|
+
return permits.createSharing(options, publicClient!, walletClient!);
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
importShared: async (
|
|
237
|
+
options: ImportSharedPermitOptions | string,
|
|
238
|
+
clients?: { publicClient: PublicClient; walletClient: WalletClient }
|
|
239
|
+
) => {
|
|
240
|
+
_requireConnected();
|
|
241
|
+
const { publicClient, walletClient } = clients ?? connectStore.getState();
|
|
242
|
+
return permits.importShared(options, publicClient!, walletClient!);
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
// Get or create methods (require connection)
|
|
246
|
+
getOrCreateSelfPermit: async (chainId?: number, account?: string, options?: CreateSelfPermitOptions) => {
|
|
247
|
+
_requireConnected();
|
|
248
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
249
|
+
const { publicClient, walletClient } = connectStore.getState();
|
|
250
|
+
return permits.getOrCreateSelfPermit(publicClient!, walletClient!, _chainId, _account, options);
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
getOrCreateSharingPermit: async (options: CreateSharingPermitOptions, chainId?: number, account?: string) => {
|
|
254
|
+
_requireConnected();
|
|
255
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
256
|
+
const { publicClient, walletClient } = connectStore.getState();
|
|
257
|
+
return permits.getOrCreateSharingPermit(publicClient!, walletClient!, options, _chainId, _account);
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// Retrieval methods (auto-fill chainId/account)
|
|
261
|
+
getPermit: (hash: string, chainId?: number, account?: string) => {
|
|
262
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
263
|
+
return permits.getPermit(_chainId, _account, hash);
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
getPermits: (chainId?: number, account?: string) => {
|
|
267
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
268
|
+
return permits.getPermits(_chainId, _account);
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
getActivePermit: (chainId?: number, account?: string) => {
|
|
272
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
273
|
+
return permits.getActivePermit(_chainId, _account);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
getActivePermitHash: (chainId?: number, account?: string) => {
|
|
277
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
278
|
+
return permits.getActivePermitHash(_chainId, _account);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
// Mutation methods (auto-fill chainId/account)
|
|
282
|
+
selectActivePermit: (hash: string, chainId?: number, account?: string) => {
|
|
283
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
284
|
+
return permits.selectActivePermit(_chainId, _account, hash);
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
removePermit: async (hash: string, chainId?: number, account?: string) => {
|
|
288
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
289
|
+
return permits.removePermit(_chainId, _account, hash);
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
removeActivePermit: async (chainId?: number, account?: string) => {
|
|
293
|
+
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
294
|
+
return permits.removeActivePermit(_chainId, _account);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
// Utils (no context needed)
|
|
298
|
+
getHash: permits.getHash,
|
|
299
|
+
serialize: permits.serialize,
|
|
300
|
+
deserialize: permits.deserialize,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
// Zustand reactive accessors (don't export store directly to prevent mutation)
|
|
305
|
+
getSnapshot: connectStore.getState,
|
|
306
|
+
subscribe: connectStore.subscribe,
|
|
307
|
+
|
|
308
|
+
// flags (read-only: reflect snapshot)
|
|
309
|
+
get connection() {
|
|
310
|
+
return connectStore.getState();
|
|
311
|
+
},
|
|
312
|
+
get connected() {
|
|
313
|
+
return connectStore.getState().connected;
|
|
314
|
+
},
|
|
315
|
+
get connecting() {
|
|
316
|
+
return connectStore.getState().connecting;
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// config & platform-specific (read-only)
|
|
320
|
+
config: opts.config,
|
|
321
|
+
|
|
322
|
+
connect,
|
|
323
|
+
disconnect,
|
|
324
|
+
encryptInputs,
|
|
325
|
+
decryptForView,
|
|
326
|
+
/**
|
|
327
|
+
* @deprecated Use `decryptForView` instead. Kept for backward compatibility.
|
|
328
|
+
*/
|
|
329
|
+
decryptHandle: decryptForView,
|
|
330
|
+
decryptForTx,
|
|
331
|
+
verifyDecryptResult,
|
|
332
|
+
permits: clientPermits,
|
|
333
|
+
|
|
334
|
+
// Add SDK-specific methods below that require connection
|
|
335
|
+
// Example:
|
|
336
|
+
// async encryptData(data: unknown) {
|
|
337
|
+
// requireConnected();
|
|
338
|
+
// // Use state.publicClient and state.walletClient for implementation
|
|
339
|
+
// },
|
|
340
|
+
};
|
|
341
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// TODO: Extract client types to its own file, keep this one as primitives
|
|
2
|
+
import { type Hex, type PublicClient, type WalletClient } from 'viem';
|
|
3
|
+
import { type CofheConfig } from './config.js';
|
|
4
|
+
import { type DecryptForViewBuilder } from './decrypt/decryptForViewBuilder.js';
|
|
5
|
+
import { type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js';
|
|
6
|
+
import { type EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
|
|
7
|
+
import { type ZkBuilderAndCrsGenerator, type ZkProveWorkerFunction } from './encrypt/zkPackProveVerify.js';
|
|
8
|
+
import { type FheKeyDeserializer } from './fetchKeys.js';
|
|
9
|
+
import { permits } from './permits.js';
|
|
10
|
+
import type { EncryptableItem, FheTypes, TfheInitializer } from './types.js';
|
|
11
|
+
import type { PermitUtils } from 'permits/permit.js';
|
|
12
|
+
import type {
|
|
13
|
+
CreateSelfPermitOptions,
|
|
14
|
+
Permit,
|
|
15
|
+
CreateSharingPermitOptions,
|
|
16
|
+
ImportSharedPermitOptions,
|
|
17
|
+
SharingPermit,
|
|
18
|
+
RecipientPermit,
|
|
19
|
+
SelfPermit,
|
|
20
|
+
} from 'permits/types.js';
|
|
21
|
+
|
|
22
|
+
// CLIENT
|
|
23
|
+
|
|
24
|
+
export type CofheClient<TConfig extends CofheConfig = CofheConfig> = {
|
|
25
|
+
// --- state access ---
|
|
26
|
+
getSnapshot(): CofheClientConnectionState;
|
|
27
|
+
subscribe(listener: Listener): () => void;
|
|
28
|
+
|
|
29
|
+
// --- convenience flags (read-only) ---
|
|
30
|
+
readonly connection: CofheClientConnectionState;
|
|
31
|
+
readonly connected: boolean;
|
|
32
|
+
readonly connecting: boolean;
|
|
33
|
+
|
|
34
|
+
// --- config & platform-specific ---
|
|
35
|
+
readonly config: TConfig;
|
|
36
|
+
|
|
37
|
+
connect(publicClient: PublicClient, walletClient: WalletClient): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Clears the current connection state (account/chainId/clients) and marks the client as disconnected.
|
|
40
|
+
*
|
|
41
|
+
* This does not delete persisted permits or stored FHE keys; it only resets the in-memory connection.
|
|
42
|
+
*/
|
|
43
|
+
disconnect(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Types docstring
|
|
46
|
+
*/
|
|
47
|
+
encryptInputs<T extends EncryptableItem[]>(inputs: [...T]): EncryptInputsBuilder<[...T]>;
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated Use `decryptForView` instead. Kept for backward compatibility.
|
|
50
|
+
*/
|
|
51
|
+
decryptHandle<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
|
|
52
|
+
decryptForView<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
|
|
53
|
+
decryptForTx(ctHash: bigint | string): DecryptForTxBuilderUnset;
|
|
54
|
+
verifyDecryptResult(handle: bigint | string, cleartext: bigint, signature: Hex): Promise<boolean>;
|
|
55
|
+
permits: CofheClientPermits;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type CofheClientConnectionState = {
|
|
59
|
+
connected: boolean;
|
|
60
|
+
connecting: boolean;
|
|
61
|
+
connectError: unknown | undefined;
|
|
62
|
+
chainId: number | undefined;
|
|
63
|
+
account: `0x${string}` | undefined;
|
|
64
|
+
publicClient: PublicClient | undefined;
|
|
65
|
+
walletClient: WalletClient | undefined;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type Listener = (snapshot: CofheClientConnectionState) => void;
|
|
69
|
+
|
|
70
|
+
export type CofheClientPermitsClients = {
|
|
71
|
+
publicClient: PublicClient;
|
|
72
|
+
walletClient: WalletClient;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type CofheClientPermits = {
|
|
76
|
+
getSnapshot: typeof permits.getSnapshot;
|
|
77
|
+
subscribe: typeof permits.subscribe;
|
|
78
|
+
|
|
79
|
+
// Creation methods (require connection, no params)
|
|
80
|
+
createSelf: (options: CreateSelfPermitOptions, clients?: CofheClientPermitsClients) => Promise<SelfPermit>;
|
|
81
|
+
createSharing: (options: CreateSharingPermitOptions, clients?: CofheClientPermitsClients) => Promise<SharingPermit>;
|
|
82
|
+
importShared: (
|
|
83
|
+
options: ImportSharedPermitOptions | string,
|
|
84
|
+
clients?: CofheClientPermitsClients
|
|
85
|
+
) => Promise<RecipientPermit>;
|
|
86
|
+
|
|
87
|
+
// Retrieval methods (chainId/account optional)
|
|
88
|
+
getPermit: (hash: string, chainId?: number, account?: string) => Permit | undefined;
|
|
89
|
+
getPermits: (chainId?: number, account?: string) => Record<string, Permit>;
|
|
90
|
+
getActivePermit: (chainId?: number, account?: string) => Permit | undefined;
|
|
91
|
+
getActivePermitHash: (chainId?: number, account?: string) => string | undefined;
|
|
92
|
+
|
|
93
|
+
// Get or create methods (get active or create new, chainId/account optional)
|
|
94
|
+
getOrCreateSelfPermit: (chainId?: number, account?: string, options?: CreateSelfPermitOptions) => Promise<Permit>;
|
|
95
|
+
getOrCreateSharingPermit: (
|
|
96
|
+
options: CreateSharingPermitOptions,
|
|
97
|
+
chainId?: number,
|
|
98
|
+
account?: string
|
|
99
|
+
) => Promise<Permit>;
|
|
100
|
+
|
|
101
|
+
// Mutation methods (chainId/account optional)
|
|
102
|
+
selectActivePermit: (hash: string, chainId?: number, account?: string) => void;
|
|
103
|
+
removePermit: (hash: string, chainId?: number, account?: string) => void;
|
|
104
|
+
removeActivePermit: (chainId?: number, account?: string) => void;
|
|
105
|
+
|
|
106
|
+
// Utils
|
|
107
|
+
getHash: typeof PermitUtils.getHash;
|
|
108
|
+
serialize: typeof PermitUtils.serialize;
|
|
109
|
+
deserialize: typeof PermitUtils.deserialize;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type CofheClientParams<TConfig extends CofheConfig> = {
|
|
113
|
+
config: TConfig;
|
|
114
|
+
zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator;
|
|
115
|
+
tfhePublicKeyDeserializer: FheKeyDeserializer;
|
|
116
|
+
compactPkeCrsDeserializer: FheKeyDeserializer;
|
|
117
|
+
initTfhe: TfheInitializer;
|
|
118
|
+
zkProveWorkerFn?: ZkProveWorkerFunction;
|
|
119
|
+
};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { sepolia, hardhat } from '@/chains';
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
createCofheConfigBase,
|
|
6
|
+
getCofheConfigItem,
|
|
7
|
+
type CofheInputConfig,
|
|
8
|
+
getSupportedChainOrThrow,
|
|
9
|
+
getCoFheUrlOrThrow,
|
|
10
|
+
getZkVerifierUrlOrThrow,
|
|
11
|
+
getThresholdNetworkUrlOrThrow,
|
|
12
|
+
} from './config.js';
|
|
13
|
+
|
|
14
|
+
describe('createCofheConfigBase', () => {
|
|
15
|
+
const validBaseConfig: CofheInputConfig = {
|
|
16
|
+
supportedChains: [],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const setNestedValue = (obj: any, path: string, value: any): void => {
|
|
20
|
+
const keys = path.split('.');
|
|
21
|
+
const lastKey = keys.pop()!;
|
|
22
|
+
const target = keys.reduce((acc, key) => {
|
|
23
|
+
if (!acc[key]) acc[key] = {};
|
|
24
|
+
return acc[key];
|
|
25
|
+
}, obj);
|
|
26
|
+
target[lastKey] = value;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getNestedValue = (obj: any, path: string): any => {
|
|
30
|
+
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const expectInvalidConfigItem = (path: string, value: any, log = false): void => {
|
|
34
|
+
const config = { ...validBaseConfig };
|
|
35
|
+
setNestedValue(config, path, value);
|
|
36
|
+
if (log) {
|
|
37
|
+
console.log('expect config invalid', path, value, config);
|
|
38
|
+
try {
|
|
39
|
+
createCofheConfigBase(config as CofheInputConfig);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.log('expect config invalid', path, value, config, e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
expect(() => createCofheConfigBase(config as CofheInputConfig)).toThrow('Invalid cofhe configuration:');
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const expectValidConfigItem = (path: string, value: any, expectedValue: any): void => {
|
|
48
|
+
const config = { ...validBaseConfig };
|
|
49
|
+
setNestedValue(config, path, value);
|
|
50
|
+
const result = createCofheConfigBase(config);
|
|
51
|
+
expect(getNestedValue(result, path)).toEqual(expectedValue);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
it('environment', () => {
|
|
55
|
+
expectInvalidConfigItem('environment', 'not-a-valid-environment');
|
|
56
|
+
expectInvalidConfigItem('environment', 123);
|
|
57
|
+
expectInvalidConfigItem('environment', {});
|
|
58
|
+
|
|
59
|
+
expectValidConfigItem('environment', 'node', 'node');
|
|
60
|
+
expectValidConfigItem('environment', 'hardhat', 'hardhat');
|
|
61
|
+
expectValidConfigItem('environment', 'web', 'web');
|
|
62
|
+
expectValidConfigItem('environment', 'react', 'react');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('supportedChains', () => {
|
|
66
|
+
expectInvalidConfigItem('supportedChains', {});
|
|
67
|
+
expectInvalidConfigItem('supportedChains', 'not-an-array');
|
|
68
|
+
expectInvalidConfigItem('supportedChains', null);
|
|
69
|
+
expectInvalidConfigItem('supportedChains', undefined);
|
|
70
|
+
|
|
71
|
+
expectValidConfigItem('supportedChains', [sepolia], [sepolia]);
|
|
72
|
+
expectValidConfigItem('supportedChains', [sepolia, hardhat], [sepolia, hardhat]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('defaultPermitExpiration', () => {
|
|
76
|
+
expectInvalidConfigItem('defaultPermitExpiration', 'not-a-number');
|
|
77
|
+
expectInvalidConfigItem('defaultPermitExpiration', null);
|
|
78
|
+
|
|
79
|
+
expectValidConfigItem('defaultPermitExpiration', 5, 5);
|
|
80
|
+
expectValidConfigItem('defaultPermitExpiration', undefined, 60 * 60 * 24 * 30);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('fheKeyStorage', async () => {
|
|
84
|
+
expectInvalidConfigItem('fheKeyStorage', 'not-an-object');
|
|
85
|
+
|
|
86
|
+
expectValidConfigItem('fheKeyStorage', undefined, null);
|
|
87
|
+
expectValidConfigItem('fheKeyStorage', null, null);
|
|
88
|
+
|
|
89
|
+
let getItemCalled = false;
|
|
90
|
+
let setItemCalled = false;
|
|
91
|
+
let removeItemCalled = false;
|
|
92
|
+
|
|
93
|
+
const fakeStorage = {
|
|
94
|
+
getItem: (name: string) => {
|
|
95
|
+
getItemCalled = true;
|
|
96
|
+
return Promise.resolve(null);
|
|
97
|
+
},
|
|
98
|
+
setItem: (name: string, value: any) => {
|
|
99
|
+
setItemCalled = true;
|
|
100
|
+
return Promise.resolve();
|
|
101
|
+
},
|
|
102
|
+
removeItem: (name: string) => {
|
|
103
|
+
removeItemCalled = true;
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const config = { ...validBaseConfig, fheKeyStorage: fakeStorage };
|
|
109
|
+
const result = createCofheConfigBase(config);
|
|
110
|
+
|
|
111
|
+
expect(result.fheKeyStorage).not.toBeNull();
|
|
112
|
+
await result.fheKeyStorage!.getItem('test');
|
|
113
|
+
await result.fheKeyStorage!.setItem('test', 'test');
|
|
114
|
+
await result.fheKeyStorage!.removeItem('test');
|
|
115
|
+
|
|
116
|
+
expect(getItemCalled).toBe(true);
|
|
117
|
+
expect(setItemCalled).toBe(true);
|
|
118
|
+
expect(removeItemCalled).toBe(true);
|
|
119
|
+
|
|
120
|
+
const invalidStorageNotAFunction = {
|
|
121
|
+
getItem: 'not-a-function',
|
|
122
|
+
setItem: 'not-a-function',
|
|
123
|
+
removeItem: 'not-a-function',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
expectInvalidConfigItem('fheKeyStorage', invalidStorageNotAFunction);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('mocks', () => {
|
|
130
|
+
expectInvalidConfigItem('mocks', 'not-an-object');
|
|
131
|
+
expectInvalidConfigItem('mocks', null);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('mocks.decryptDelay', () => {
|
|
135
|
+
expectInvalidConfigItem('mocks.decryptDelay', 'not-a-number');
|
|
136
|
+
expectInvalidConfigItem('mocks.decryptDelay', null);
|
|
137
|
+
|
|
138
|
+
expectValidConfigItem('mocks.decryptDelay', undefined, 0);
|
|
139
|
+
expectValidConfigItem('mocks.decryptDelay', 1000, 1000);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('mocks.encryptDelay', () => {
|
|
143
|
+
expectInvalidConfigItem('mocks.encryptDelay', 'not-a-number');
|
|
144
|
+
expectInvalidConfigItem('mocks.encryptDelay', null);
|
|
145
|
+
expectInvalidConfigItem('mocks.encryptDelay', [100, 100, 100]); // wrong tuple length
|
|
146
|
+
expectInvalidConfigItem('mocks.encryptDelay', ['a', 'b', 'c', 'd', 'e']); // non-number elements
|
|
147
|
+
|
|
148
|
+
expectValidConfigItem('mocks.encryptDelay', undefined, [100, 100, 100, 500, 500]);
|
|
149
|
+
expectValidConfigItem('mocks.encryptDelay', 200, 200);
|
|
150
|
+
expectValidConfigItem('mocks.encryptDelay', 0, 0);
|
|
151
|
+
expectValidConfigItem('mocks.encryptDelay', [10, 20, 30, 40, 50], [10, 20, 30, 40, 50]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('useWorkers', () => {
|
|
155
|
+
expectInvalidConfigItem('useWorkers', 'not-a-boolean');
|
|
156
|
+
expectInvalidConfigItem('useWorkers', null);
|
|
157
|
+
expectInvalidConfigItem('useWorkers', 123);
|
|
158
|
+
expectInvalidConfigItem('useWorkers', {});
|
|
159
|
+
|
|
160
|
+
expectValidConfigItem('useWorkers', true, true);
|
|
161
|
+
expectValidConfigItem('useWorkers', false, false);
|
|
162
|
+
expectValidConfigItem('useWorkers', undefined, true); // defaults to true
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should get config item', () => {
|
|
166
|
+
const config: CofheInputConfig = {
|
|
167
|
+
supportedChains: [sepolia],
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const result = createCofheConfigBase(config);
|
|
171
|
+
|
|
172
|
+
const supportedChains = getCofheConfigItem(result, 'supportedChains');
|
|
173
|
+
expect(supportedChains).toEqual(config.supportedChains);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('Config helper functions', () => {
|
|
178
|
+
const config = createCofheConfigBase({
|
|
179
|
+
supportedChains: [sepolia, hardhat],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('getSupportedChainOrThrow', () => {
|
|
183
|
+
it('should return chain when found', () => {
|
|
184
|
+
expect(getSupportedChainOrThrow(config, sepolia.id)).toEqual(sepolia);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should throw UnsupportedChain error when not found', () => {
|
|
188
|
+
expect(() => getSupportedChainOrThrow(config, 999999)).toThrow();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('getCoFheUrlOrThrow', () => {
|
|
193
|
+
it('should return coFheUrl', () => {
|
|
194
|
+
expect(getCoFheUrlOrThrow(config, sepolia.id)).toBe(sepolia.coFheUrl);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should throw when chain not found', () => {
|
|
198
|
+
expect(() => getCoFheUrlOrThrow(config, 999999)).toThrow();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should throw MissingConfig when url not set', () => {
|
|
202
|
+
const configWithoutUrl = createCofheConfigBase({
|
|
203
|
+
supportedChains: [{ ...sepolia, coFheUrl: undefined } as any],
|
|
204
|
+
});
|
|
205
|
+
expect(() => getCoFheUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('getZkVerifierUrlOrThrow', () => {
|
|
210
|
+
it('should return verifierUrl', () => {
|
|
211
|
+
expect(getZkVerifierUrlOrThrow(config, sepolia.id)).toBe(sepolia.verifierUrl);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should throw when chain not found', () => {
|
|
215
|
+
expect(() => getZkVerifierUrlOrThrow(config, 999999)).toThrow();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should throw ZkVerifierUrlUninitialized when url not set', () => {
|
|
219
|
+
const configWithoutUrl = createCofheConfigBase({
|
|
220
|
+
supportedChains: [{ ...sepolia, verifierUrl: undefined } as any],
|
|
221
|
+
});
|
|
222
|
+
expect(() => getZkVerifierUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('getThresholdNetworkUrlOrThrow', () => {
|
|
227
|
+
it('should return thresholdNetworkUrl', () => {
|
|
228
|
+
expect(getThresholdNetworkUrlOrThrow(config, sepolia.id)).toBe(sepolia.thresholdNetworkUrl);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should throw when chain not found', () => {
|
|
232
|
+
expect(() => getThresholdNetworkUrlOrThrow(config, 999999)).toThrow();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should throw ThresholdNetworkUrlUninitialized when url not set', () => {
|
|
236
|
+
const configWithoutUrl = createCofheConfigBase({
|
|
237
|
+
supportedChains: [{ ...sepolia, thresholdNetworkUrl: undefined } as any],
|
|
238
|
+
});
|
|
239
|
+
expect(() => getThresholdNetworkUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|