@cofhe/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -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 +315 -0
- package/core/client.ts +292 -0
- package/core/clientTypes.ts +108 -0
- package/core/config.test.ts +235 -0
- package/core/config.ts +220 -0
- package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
- package/core/decrypt/cofheMocksSealOutput.ts +57 -0
- package/core/decrypt/decryptHandleBuilder.ts +287 -0
- package/core/decrypt/decryptUtils.ts +28 -0
- package/core/decrypt/tnSealOutputV1.ts +59 -0
- package/core/decrypt/tnSealOutputV2.ts +298 -0
- package/core/encrypt/MockZkVerifierAbi.ts +106 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
- package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
- package/core/encrypt/encryptInputsBuilder.ts +560 -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 +89 -0
- package/core/keyStore.test.ts +226 -0
- package/core/keyStore.ts +154 -0
- package/core/permits.test.ts +494 -0
- package/core/permits.ts +200 -0
- package/core/types.ts +398 -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 +114 -0
- package/dist/chains.d.cts +121 -0
- package/dist/chains.d.ts +121 -0
- package/dist/chains.js +1 -0
- package/dist/chunk-UGBVZNRT.js +818 -0
- package/dist/chunk-WEAZ25JO.js +105 -0
- package/dist/chunk-WGCRJCBR.js +2523 -0
- package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
- package/dist/clientTypes-Es7fyi65.d.ts +914 -0
- package/dist/core.cjs +3414 -0
- package/dist/core.d.cts +111 -0
- package/dist/core.d.ts +111 -0
- package/dist/core.js +3 -0
- package/dist/node.cjs +3286 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +91 -0
- package/dist/permit-fUSe6KKq.d.cts +349 -0
- package/dist/permit-fUSe6KKq.d.ts +349 -0
- package/dist/permits.cjs +871 -0
- package/dist/permits.d.cts +1045 -0
- package/dist/permits.d.ts +1045 -0
- package/dist/permits.js +1 -0
- package/dist/types-KImPrEIe.d.cts +48 -0
- package/dist/types-KImPrEIe.d.ts +48 -0
- package/dist/web.cjs +3478 -0
- package/dist/web.d.cts +38 -0
- package/dist/web.d.ts +38 -0
- package/dist/web.js +240 -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 +147 -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 +27 -15
- package/permits/index.ts +68 -0
- package/permits/localstorage.test.ts +117 -0
- package/permits/permit.test.ts +477 -0
- package/permits/permit.ts +405 -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 +128 -0
- package/permits/store.ts +166 -0
- package/permits/test-utils.ts +20 -0
- package/permits/types.ts +191 -0
- package/permits/utils.ts +62 -0
- package/permits/validation.test.ts +288 -0
- package/permits/validation.ts +369 -0
- package/web/client.web.test.ts +147 -0
- package/web/config.web.test.ts +69 -0
- package/web/encryptInputs.web.test.ts +172 -0
- package/web/index.ts +161 -0
- package/web/storage.ts +34 -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
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
|
|
2
|
+
import { type EncryptableItem, FheTypes } from '../types.js';
|
|
3
|
+
import { toBigIntOrThrow, validateBigIntInRange, toHexString, hexToBytes } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
// ===== TYPES =====
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Worker function type for ZK proof generation
|
|
9
|
+
* Platform-specific implementations (web) can provide this to enable worker-based proofs
|
|
10
|
+
*/
|
|
11
|
+
export type ZkProveWorkerFunction = (
|
|
12
|
+
fheKeyHex: string,
|
|
13
|
+
crsHex: string,
|
|
14
|
+
items: EncryptableItem[],
|
|
15
|
+
metadata: Uint8Array
|
|
16
|
+
) => Promise<Uint8Array>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Message sent from main thread to worker to request proof generation
|
|
20
|
+
*/
|
|
21
|
+
export interface ZkProveWorkerRequest {
|
|
22
|
+
id: string;
|
|
23
|
+
type: 'zkProve';
|
|
24
|
+
fheKeyHex: string;
|
|
25
|
+
crsHex: string;
|
|
26
|
+
items: Array<{
|
|
27
|
+
utype: string;
|
|
28
|
+
data: any;
|
|
29
|
+
}>;
|
|
30
|
+
metadata: number[]; // Uint8Array serialized as array
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Message sent from worker back to main thread with proof result
|
|
35
|
+
*/
|
|
36
|
+
export interface ZkProveWorkerResponse {
|
|
37
|
+
id: string;
|
|
38
|
+
type: 'success' | 'error' | 'ready';
|
|
39
|
+
result?: number[]; // Uint8Array serialized as array
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type VerifyResultRaw = {
|
|
44
|
+
ct_hash: string;
|
|
45
|
+
signature: string;
|
|
46
|
+
recid: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type VerifyResult = {
|
|
50
|
+
ct_hash: string;
|
|
51
|
+
signature: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ZkProvenCiphertextList = {
|
|
55
|
+
serialize(): Uint8Array;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ZkCompactPkeCrs = {
|
|
59
|
+
free(): void;
|
|
60
|
+
serialize(compress: boolean): Uint8Array;
|
|
61
|
+
safe_serialize(serialized_size_limit: bigint): Uint8Array;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type ZkCompactPkeCrsConstructor = {
|
|
65
|
+
deserialize(buffer: Uint8Array): ZkCompactPkeCrs;
|
|
66
|
+
safe_deserialize(buffer: Uint8Array, serialized_size_limit: bigint): ZkCompactPkeCrs;
|
|
67
|
+
from_config(config: unknown, max_num_bits: number): ZkCompactPkeCrs;
|
|
68
|
+
deserialize_from_public_params(buffer: Uint8Array): ZkCompactPkeCrs;
|
|
69
|
+
safe_deserialize_from_public_params(buffer: Uint8Array, serialized_size_limit: bigint): ZkCompactPkeCrs;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type ZkCiphertextListBuilder = {
|
|
73
|
+
push_boolean(data: boolean): void;
|
|
74
|
+
push_u8(data: number): void;
|
|
75
|
+
push_u16(data: number): void;
|
|
76
|
+
push_u32(data: number): void;
|
|
77
|
+
push_u64(data: bigint): void;
|
|
78
|
+
push_u128(data: bigint): void;
|
|
79
|
+
push_u160(data: bigint): void;
|
|
80
|
+
build_with_proof_packed(
|
|
81
|
+
crs: ZkCompactPkeCrs,
|
|
82
|
+
metadata: Uint8Array,
|
|
83
|
+
computeLoad: 1 // ZkComputeLoad.Verify
|
|
84
|
+
): ZkProvenCiphertextList;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type ZkBuilderAndCrsGenerator = (
|
|
88
|
+
fhe: string,
|
|
89
|
+
crs: string
|
|
90
|
+
) => { zkBuilder: ZkCiphertextListBuilder; zkCrs: ZkCompactPkeCrs };
|
|
91
|
+
|
|
92
|
+
// ===== CONSTANTS =====
|
|
93
|
+
|
|
94
|
+
export const MAX_UINT8: bigint = 255n;
|
|
95
|
+
export const MAX_UINT16: bigint = 65535n;
|
|
96
|
+
export const MAX_UINT32: bigint = 4294967295n;
|
|
97
|
+
export const MAX_UINT64: bigint = 18446744073709551615n; // 2^64 - 1
|
|
98
|
+
export const MAX_UINT128: bigint = 340282366920938463463374607431768211455n; // 2^128 - 1
|
|
99
|
+
export const MAX_UINT256: bigint = 115792089237316195423570985008687907853269984665640564039457584007913129640319n; // 2^256 - 1
|
|
100
|
+
export const MAX_UINT160: bigint = 1461501637330902918203684832716283019655932542975n; // 2^160 - 1
|
|
101
|
+
export const MAX_ENCRYPTABLE_BITS: number = 2048;
|
|
102
|
+
|
|
103
|
+
// ===== CORE FUNCTIONS =====
|
|
104
|
+
|
|
105
|
+
export const zkPack = (items: EncryptableItem[], builder: ZkCiphertextListBuilder): ZkCiphertextListBuilder => {
|
|
106
|
+
let totalBits = 0;
|
|
107
|
+
for (const item of items) {
|
|
108
|
+
switch (item.utype) {
|
|
109
|
+
case FheTypes.Bool: {
|
|
110
|
+
builder.push_boolean(item.data);
|
|
111
|
+
totalBits += 1;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case FheTypes.Uint8: {
|
|
115
|
+
const bint = toBigIntOrThrow(item.data);
|
|
116
|
+
validateBigIntInRange(bint, MAX_UINT8);
|
|
117
|
+
builder.push_u8(parseInt(bint.toString()));
|
|
118
|
+
totalBits += 8;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case FheTypes.Uint16: {
|
|
122
|
+
const bint = toBigIntOrThrow(item.data);
|
|
123
|
+
validateBigIntInRange(bint, MAX_UINT16);
|
|
124
|
+
builder.push_u16(parseInt(bint.toString()));
|
|
125
|
+
totalBits += 16;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case FheTypes.Uint32: {
|
|
129
|
+
const bint = toBigIntOrThrow(item.data);
|
|
130
|
+
validateBigIntInRange(bint, MAX_UINT32);
|
|
131
|
+
builder.push_u32(parseInt(bint.toString()));
|
|
132
|
+
totalBits += 32;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case FheTypes.Uint64: {
|
|
136
|
+
const bint = toBigIntOrThrow(item.data);
|
|
137
|
+
validateBigIntInRange(bint, MAX_UINT64);
|
|
138
|
+
builder.push_u64(bint);
|
|
139
|
+
totalBits += 64;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case FheTypes.Uint128: {
|
|
143
|
+
const bint = toBigIntOrThrow(item.data);
|
|
144
|
+
validateBigIntInRange(bint, MAX_UINT128);
|
|
145
|
+
builder.push_u128(bint);
|
|
146
|
+
totalBits += 128;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
// [U256-DISABLED]
|
|
150
|
+
// case FheTypes.Uint256: {
|
|
151
|
+
// const bint = toBigIntOrThrow(item.data);
|
|
152
|
+
// validateBigIntInRange(bint, MAX_UINT256);
|
|
153
|
+
// builder.push_u256(bint);
|
|
154
|
+
// totalBits += 256;
|
|
155
|
+
// break;
|
|
156
|
+
// }
|
|
157
|
+
case FheTypes.Uint160: {
|
|
158
|
+
const bint = toBigIntOrThrow(item.data);
|
|
159
|
+
validateBigIntInRange(bint, MAX_UINT160);
|
|
160
|
+
builder.push_u160(bint);
|
|
161
|
+
totalBits += 160;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
default: {
|
|
165
|
+
throw new CofhesdkError({
|
|
166
|
+
code: CofhesdkErrorCode.ZkPackFailed,
|
|
167
|
+
message: `Invalid utype: ${(item as any).utype}`,
|
|
168
|
+
hint: `Ensure that the utype is valid, using the Encryptable type, for example: Encryptable.uint128(100n)`,
|
|
169
|
+
context: {
|
|
170
|
+
item,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (totalBits > MAX_ENCRYPTABLE_BITS) {
|
|
178
|
+
throw new CofhesdkError({
|
|
179
|
+
code: CofhesdkErrorCode.ZkPackFailed,
|
|
180
|
+
message: `Total bits ${totalBits} exceeds ${MAX_ENCRYPTABLE_BITS}`,
|
|
181
|
+
hint: `Ensure that the total bits of the items to encrypt does not exceed ${MAX_ENCRYPTABLE_BITS}`,
|
|
182
|
+
context: {
|
|
183
|
+
totalBits,
|
|
184
|
+
maxBits: MAX_ENCRYPTABLE_BITS,
|
|
185
|
+
items,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return builder;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generates ZK proof using Web Worker (offloads heavy WASM computation)
|
|
195
|
+
* Serializes items and calls the platform-specific worker function
|
|
196
|
+
* @param workerFn - Platform-specific worker function (provided by web/index.ts)
|
|
197
|
+
* @param fheKeyHex - Hex-encoded FHE public key for worker deserialization
|
|
198
|
+
* @param crsHex - Hex-encoded CRS for worker deserialization
|
|
199
|
+
* @param items - Encryptable items to pack in the worker
|
|
200
|
+
* @param metadata - Pre-constructed ZK PoK metadata
|
|
201
|
+
* @returns The serialized proven ciphertext list
|
|
202
|
+
*/
|
|
203
|
+
export const zkProveWithWorker = async (
|
|
204
|
+
workerFn: ZkProveWorkerFunction,
|
|
205
|
+
fheKeyHex: string,
|
|
206
|
+
crsHex: string,
|
|
207
|
+
items: EncryptableItem[],
|
|
208
|
+
metadata: Uint8Array
|
|
209
|
+
): Promise<Uint8Array> => {
|
|
210
|
+
return await workerFn(fheKeyHex, crsHex, items, metadata);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generates ZK proof using main thread (synchronous WASM)
|
|
215
|
+
* This is the fallback when workers are disabled or unavailable
|
|
216
|
+
* @param builder - The ZK ciphertext list builder with packed inputs
|
|
217
|
+
* @param crs - The Compact PKE CRS for proof generation
|
|
218
|
+
* @param metadata - Pre-constructed ZK PoK metadata
|
|
219
|
+
* @returns The serialized proven ciphertext list
|
|
220
|
+
*/
|
|
221
|
+
export const zkProve = async (
|
|
222
|
+
builder: ZkCiphertextListBuilder,
|
|
223
|
+
crs: ZkCompactPkeCrs,
|
|
224
|
+
metadata: Uint8Array
|
|
225
|
+
): Promise<Uint8Array> => {
|
|
226
|
+
return new Promise((resolve) => {
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
const compactList = builder.build_with_proof_packed(
|
|
229
|
+
crs,
|
|
230
|
+
metadata,
|
|
231
|
+
1 // ZkComputeLoad.Verify
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
resolve(compactList.serialize());
|
|
235
|
+
}, 0);
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Constructs the ZK Proof of Knowledge metadata for the proof
|
|
241
|
+
* @internal - Used internally within the encrypt module
|
|
242
|
+
*/
|
|
243
|
+
export const constructZkPoKMetadata = (accountAddr: string, securityZone: number, chainId: number): Uint8Array => {
|
|
244
|
+
// Decode the account address from hex
|
|
245
|
+
const accountAddrNoPrefix = accountAddr.startsWith('0x') ? accountAddr.slice(2) : accountAddr;
|
|
246
|
+
const accountBytes = hexToBytes(accountAddrNoPrefix);
|
|
247
|
+
|
|
248
|
+
// Encode chainId as 32 bytes (u256) in big-endian format
|
|
249
|
+
const chainIdBytes = new Uint8Array(32);
|
|
250
|
+
|
|
251
|
+
// Since chain IDs are typically small numbers, we can just encode them
|
|
252
|
+
// directly without BigInt operations, filling only the necessary bytes
|
|
253
|
+
// from the right (least significant)
|
|
254
|
+
let value = chainId;
|
|
255
|
+
for (let i = 31; i >= 0 && value > 0; i--) {
|
|
256
|
+
chainIdBytes[i] = value & 0xff;
|
|
257
|
+
value = value >>> 8;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const metadata = new Uint8Array(1 + accountBytes.length + 32);
|
|
261
|
+
metadata[0] = securityZone;
|
|
262
|
+
metadata.set(accountBytes, 1);
|
|
263
|
+
metadata.set(chainIdBytes, 1 + accountBytes.length);
|
|
264
|
+
|
|
265
|
+
return metadata;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const zkVerify = async (
|
|
269
|
+
verifierUrl: string,
|
|
270
|
+
serializedBytes: Uint8Array,
|
|
271
|
+
address: string,
|
|
272
|
+
securityZone: number,
|
|
273
|
+
chainId: number
|
|
274
|
+
): Promise<VerifyResult[]> => {
|
|
275
|
+
// Convert bytearray to hex string
|
|
276
|
+
const packed_list = toHexString(serializedBytes);
|
|
277
|
+
|
|
278
|
+
const sz_byte = new Uint8Array([securityZone]);
|
|
279
|
+
|
|
280
|
+
// Construct request payload
|
|
281
|
+
const payload = {
|
|
282
|
+
packed_list,
|
|
283
|
+
account_addr: address,
|
|
284
|
+
security_zone: sz_byte[0],
|
|
285
|
+
chain_id: chainId,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const body = JSON.stringify(payload);
|
|
289
|
+
|
|
290
|
+
// Send request to verification server
|
|
291
|
+
try {
|
|
292
|
+
const response = await fetch(`${verifierUrl}/verify`, {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
headers: {
|
|
295
|
+
'Content-Type': 'application/json',
|
|
296
|
+
},
|
|
297
|
+
body,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (!response.ok) {
|
|
301
|
+
// Get the response body as text for better error details
|
|
302
|
+
const errorBody = await response.text();
|
|
303
|
+
throw new CofhesdkError({
|
|
304
|
+
code: CofhesdkErrorCode.ZkVerifyFailed,
|
|
305
|
+
message: `HTTP error! ZK proof verification failed - ${errorBody}`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const json = (await response.json()) as { status: string; data: VerifyResultRaw[]; error: string };
|
|
310
|
+
|
|
311
|
+
if (json.status !== 'success') {
|
|
312
|
+
throw new CofhesdkError({
|
|
313
|
+
code: CofhesdkErrorCode.ZkVerifyFailed,
|
|
314
|
+
message: `ZK proof verification response malformed - ${json.error}`,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return json.data.map(({ ct_hash, signature, recid }) => {
|
|
319
|
+
return {
|
|
320
|
+
ct_hash,
|
|
321
|
+
signature: concatSigRecid(signature, recid),
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
} catch (e) {
|
|
325
|
+
throw new CofhesdkError({
|
|
326
|
+
code: CofhesdkErrorCode.ZkVerifyFailed,
|
|
327
|
+
message: `ZK proof verification failed`,
|
|
328
|
+
cause: e instanceof Error ? e : undefined,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const concatSigRecid = (signature: string, recid: number): string => {
|
|
334
|
+
return signature + (recid + 27).toString(16).padStart(2, '0');
|
|
335
|
+
};
|
package/core/error.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export enum CofhesdkErrorCode {
|
|
2
|
+
InternalError = 'INTERNAL_ERROR',
|
|
3
|
+
UnknownEnvironment = 'UNKNOWN_ENVIRONMENT',
|
|
4
|
+
InitTfheFailed = 'INIT_TFHE_FAILED',
|
|
5
|
+
InitViemFailed = 'INIT_VIEM_FAILED',
|
|
6
|
+
InitEthersFailed = 'INIT_ETHERS_FAILED',
|
|
7
|
+
NotConnected = 'NOT_CONNECTED',
|
|
8
|
+
MissingPublicClient = 'MISSING_PUBLIC_CLIENT',
|
|
9
|
+
MissingWalletClient = 'MISSING_WALLET_CLIENT',
|
|
10
|
+
MissingProviderParam = 'MISSING_PROVIDER_PARAM',
|
|
11
|
+
EmptySecurityZonesParam = 'EMPTY_SECURITY_ZONES_PARAM',
|
|
12
|
+
InvalidPermitData = 'INVALID_PERMIT_DATA',
|
|
13
|
+
InvalidPermitDomain = 'INVALID_PERMIT_DOMAIN',
|
|
14
|
+
PermitNotFound = 'PERMIT_NOT_FOUND',
|
|
15
|
+
CannotRemoveLastPermit = 'CANNOT_REMOVE_LAST_PERMIT',
|
|
16
|
+
AccountUninitialized = 'ACCOUNT_UNINITIALIZED',
|
|
17
|
+
ChainIdUninitialized = 'CHAIN_ID_UNINITIALIZED',
|
|
18
|
+
SealOutputFailed = 'SEAL_OUTPUT_FAILED',
|
|
19
|
+
SealOutputReturnedNull = 'SEAL_OUTPUT_RETURNED_NULL',
|
|
20
|
+
InvalidUtype = 'INVALID_UTYPE',
|
|
21
|
+
DecryptFailed = 'DECRYPT_FAILED',
|
|
22
|
+
DecryptReturnedNull = 'DECRYPT_RETURNED_NULL',
|
|
23
|
+
ZkMocksInsertCtHashesFailed = 'ZK_MOCKS_INSERT_CT_HASHES_FAILED',
|
|
24
|
+
ZkMocksCalcCtHashesFailed = 'ZK_MOCKS_CALC_CT_HASHES_FAILED',
|
|
25
|
+
ZkMocksVerifySignFailed = 'ZK_MOCKS_VERIFY_SIGN_FAILED',
|
|
26
|
+
ZkMocksCreateProofSignatureFailed = 'ZK_MOCKS_CREATE_PROOF_SIGNATURE_FAILED',
|
|
27
|
+
ZkVerifyFailed = 'ZK_VERIFY_FAILED',
|
|
28
|
+
ZkPackFailed = 'ZK_PACK_FAILED',
|
|
29
|
+
ZkProveFailed = 'ZK_PROVE_FAILED',
|
|
30
|
+
EncryptRemainingInItems = 'ENCRYPT_REMAINING_IN_ITEMS',
|
|
31
|
+
ZkUninitialized = 'ZK_UNINITIALIZED',
|
|
32
|
+
ZkVerifierUrlUninitialized = 'ZK_VERIFIER_URL_UNINITIALIZED',
|
|
33
|
+
ThresholdNetworkUrlUninitialized = 'THRESHOLD_NETWORK_URL_UNINITIALIZED',
|
|
34
|
+
MissingConfig = 'MISSING_CONFIG',
|
|
35
|
+
UnsupportedChain = 'UNSUPPORTED_CHAIN',
|
|
36
|
+
MissingZkBuilderAndCrsGenerator = 'MISSING_ZK_BUILDER_AND_CRS_GENERATOR',
|
|
37
|
+
MissingTfhePublicKeyDeserializer = 'MISSING_TFHE_PUBLIC_KEY_DESERIALIZER',
|
|
38
|
+
MissingCompactPkeCrsDeserializer = 'MISSING_COMPACT_PKE_CRS_DESERIALIZER',
|
|
39
|
+
MissingFheKey = 'MISSING_FHE_KEY',
|
|
40
|
+
MissingCrs = 'MISSING_CRS',
|
|
41
|
+
FetchKeysFailed = 'FETCH_KEYS_FAILED',
|
|
42
|
+
PublicWalletGetChainIdFailed = 'PUBLIC_WALLET_GET_CHAIN_ID_FAILED',
|
|
43
|
+
PublicWalletGetAddressesFailed = 'PUBLIC_WALLET_GET_ADDRESSES_FAILED',
|
|
44
|
+
RehydrateKeysStoreFailed = 'REHYDRATE_KEYS_STORE_FAILED',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type CofhesdkErrorParams = {
|
|
48
|
+
code: CofhesdkErrorCode;
|
|
49
|
+
message: string;
|
|
50
|
+
cause?: Error;
|
|
51
|
+
hint?: string;
|
|
52
|
+
context?: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* CofhesdkError class
|
|
57
|
+
* This class is used to create errors that are specific to the CoFHE SDK
|
|
58
|
+
* It extends the Error class and adds a code, cause, hint, and context
|
|
59
|
+
* The code is used to identify the type of error
|
|
60
|
+
* The cause is used to indicate the inner error that caused the CofhesdkError
|
|
61
|
+
* The hint is used to provide a hint about how to fix the error
|
|
62
|
+
* The context is used to provide additional context about the state that caused the error
|
|
63
|
+
* The serialize method is used to serialize the error to a JSON string
|
|
64
|
+
* The toString method is used to provide a human-readable string representation of the error
|
|
65
|
+
*/
|
|
66
|
+
export class CofhesdkError extends Error {
|
|
67
|
+
public readonly code: CofhesdkErrorCode;
|
|
68
|
+
public readonly cause?: Error;
|
|
69
|
+
public readonly hint?: string;
|
|
70
|
+
public readonly context?: Record<string, unknown>;
|
|
71
|
+
|
|
72
|
+
constructor({ code, message, cause, hint, context }: CofhesdkErrorParams) {
|
|
73
|
+
// If there's a cause, append its message to provide full context
|
|
74
|
+
const fullMessage = cause ? `${message} | Caused by: ${cause.message}` : message;
|
|
75
|
+
|
|
76
|
+
super(fullMessage);
|
|
77
|
+
this.name = 'CofhesdkError';
|
|
78
|
+
this.code = code;
|
|
79
|
+
this.cause = cause;
|
|
80
|
+
this.hint = hint;
|
|
81
|
+
this.context = context;
|
|
82
|
+
|
|
83
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
84
|
+
if (Error.captureStackTrace) {
|
|
85
|
+
Error.captureStackTrace(this, CofhesdkError);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a CofhesdkError from an unknown error
|
|
91
|
+
* If the error is a CofhesdkError, it is returned unchanged, else a new CofhesdkError is created
|
|
92
|
+
* If a wrapperError is provided, it is used to create the new CofhesdkError, else a default is used
|
|
93
|
+
*/
|
|
94
|
+
static fromError(error: unknown, wrapperError?: CofhesdkErrorParams): CofhesdkError {
|
|
95
|
+
if (isCofhesdkError(error)) return error;
|
|
96
|
+
|
|
97
|
+
const cause = error instanceof Error ? error : new Error(`${error}`);
|
|
98
|
+
|
|
99
|
+
return new CofhesdkError({
|
|
100
|
+
code: wrapperError?.code ?? CofhesdkErrorCode.InternalError,
|
|
101
|
+
message: wrapperError?.message ?? 'An internal error occurred',
|
|
102
|
+
hint: wrapperError?.hint,
|
|
103
|
+
context: wrapperError?.context,
|
|
104
|
+
cause: cause,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Serializes the error to JSON string with proper handling of Error objects
|
|
110
|
+
*/
|
|
111
|
+
serialize(): string {
|
|
112
|
+
return bigintSafeJsonStringify({
|
|
113
|
+
name: this.name,
|
|
114
|
+
code: this.code,
|
|
115
|
+
message: this.message,
|
|
116
|
+
hint: this.hint,
|
|
117
|
+
context: this.context,
|
|
118
|
+
cause: this.cause
|
|
119
|
+
? {
|
|
120
|
+
name: this.cause.name,
|
|
121
|
+
message: this.cause.message,
|
|
122
|
+
stack: this.cause.stack,
|
|
123
|
+
}
|
|
124
|
+
: undefined,
|
|
125
|
+
stack: this.stack,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Returns a human-readable string representation of the error
|
|
131
|
+
*/
|
|
132
|
+
toString(): string {
|
|
133
|
+
const parts = [`${this.name} [${this.code}]: ${this.message}`];
|
|
134
|
+
|
|
135
|
+
if (this.hint) {
|
|
136
|
+
parts.push(`Hint: ${this.hint}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (this.context && Object.keys(this.context).length > 0) {
|
|
140
|
+
parts.push(`Context: ${bigintSafeJsonStringify(this.context)}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (this.stack) {
|
|
144
|
+
parts.push(`\nStack trace:`);
|
|
145
|
+
parts.push(this.stack);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (this.cause) {
|
|
149
|
+
parts.push(`\nCaused by: ${this.cause.name}: ${this.cause.message}`);
|
|
150
|
+
if (this.cause.stack) {
|
|
151
|
+
parts.push(this.cause.stack);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return parts.join('\n');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const bigintSafeJsonStringify = (value: unknown): string => {
|
|
160
|
+
return JSON.stringify(value, (key, value) => {
|
|
161
|
+
if (typeof value === 'bigint') {
|
|
162
|
+
return `${value}n`;
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const isCofhesdkError = (error: unknown): error is CofhesdkError => error instanceof CofhesdkError;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/* eslint-disable turbo/no-undeclared-env-vars */
|
|
2
|
+
/* eslint-disable no-undef */
|
|
3
|
+
|
|
4
|
+
import { sepolia, arbSepolia } from '@/chains';
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
import { fetchKeys } from './fetchKeys.js';
|
|
8
|
+
import { type CofhesdkConfig, createCofhesdkConfigBase } from './config.js';
|
|
9
|
+
import { createKeysStore, type KeysStorage } from './keyStore.js';
|
|
10
|
+
|
|
11
|
+
describe('fetchKeys', () => {
|
|
12
|
+
let config: CofhesdkConfig;
|
|
13
|
+
let mockTfhePublicKeyDeserializer: any;
|
|
14
|
+
let mockCompactPkeCrsDeserializer: any;
|
|
15
|
+
let keysStorage: KeysStorage;
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
// Reset all mocks
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
|
|
21
|
+
// Setup config with real chains
|
|
22
|
+
config = createCofhesdkConfigBase({
|
|
23
|
+
supportedChains: [sepolia, arbSepolia],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Setup mock serializers
|
|
27
|
+
mockTfhePublicKeyDeserializer = vi.fn();
|
|
28
|
+
mockCompactPkeCrsDeserializer = vi.fn();
|
|
29
|
+
|
|
30
|
+
// Create a fresh keysStorage instance for each test (non-persisted)
|
|
31
|
+
keysStorage = createKeysStore(null);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
vi.restoreAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should fetch and store FHE public key and CRS for Sepolia when not cached', async () => {
|
|
39
|
+
const [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
40
|
+
config,
|
|
41
|
+
sepolia.id,
|
|
42
|
+
0,
|
|
43
|
+
mockTfhePublicKeyDeserializer,
|
|
44
|
+
mockCompactPkeCrsDeserializer,
|
|
45
|
+
keysStorage
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(fheKeyFetchedFromCoFHE).toBe(true);
|
|
49
|
+
expect(crsFetchedFromCoFHE).toBe(true);
|
|
50
|
+
|
|
51
|
+
// Verify keys were stored
|
|
52
|
+
const storedFheKey = keysStorage.getFheKey(sepolia.id, 0);
|
|
53
|
+
const storedCrs = keysStorage.getCrs(sepolia.id);
|
|
54
|
+
|
|
55
|
+
expect(storedFheKey).toBeDefined();
|
|
56
|
+
expect(storedCrs).toBeDefined();
|
|
57
|
+
expect(mockTfhePublicKeyDeserializer).toHaveBeenCalled();
|
|
58
|
+
expect(mockCompactPkeCrsDeserializer).toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should fetch and store FHE public key and CRS for Arbitrum Sepolia when not cached', async () => {
|
|
62
|
+
const [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
63
|
+
config,
|
|
64
|
+
arbSepolia.id,
|
|
65
|
+
0,
|
|
66
|
+
mockTfhePublicKeyDeserializer,
|
|
67
|
+
mockCompactPkeCrsDeserializer,
|
|
68
|
+
keysStorage
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(fheKeyFetchedFromCoFHE).toBe(true);
|
|
72
|
+
expect(crsFetchedFromCoFHE).toBe(true);
|
|
73
|
+
|
|
74
|
+
// Verify keys were stored
|
|
75
|
+
const storedFheKey = keysStorage.getFheKey(arbSepolia.id, 0);
|
|
76
|
+
const storedCrs = keysStorage.getCrs(arbSepolia.id);
|
|
77
|
+
|
|
78
|
+
expect(storedFheKey).toBeDefined();
|
|
79
|
+
expect(storedCrs).toBeDefined();
|
|
80
|
+
expect(mockTfhePublicKeyDeserializer).toHaveBeenCalled();
|
|
81
|
+
expect(mockCompactPkeCrsDeserializer).toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should not fetch FHE key if already cached', async () => {
|
|
85
|
+
// Pre-populate with a cached key
|
|
86
|
+
const mockCachedKey = '0x1234567890';
|
|
87
|
+
keysStorage.setFheKey(sepolia.id, 0, mockCachedKey);
|
|
88
|
+
|
|
89
|
+
const [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
90
|
+
config,
|
|
91
|
+
sepolia.id,
|
|
92
|
+
0,
|
|
93
|
+
mockTfhePublicKeyDeserializer,
|
|
94
|
+
mockCompactPkeCrsDeserializer,
|
|
95
|
+
keysStorage
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(fheKeyFetchedFromCoFHE).toBe(false);
|
|
99
|
+
expect(crsFetchedFromCoFHE).toBe(true);
|
|
100
|
+
|
|
101
|
+
// Verify the cached key wasn't overwritten
|
|
102
|
+
const retrievedKey = keysStorage.getFheKey(sepolia.id, 0);
|
|
103
|
+
expect(retrievedKey).toEqual(mockCachedKey);
|
|
104
|
+
|
|
105
|
+
// Verify CRS was still fetched
|
|
106
|
+
const retrievedCrs = keysStorage.getCrs(sepolia.id);
|
|
107
|
+
expect(retrievedCrs).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should not fetch CRS if already cached', async () => {
|
|
111
|
+
// Pre-populate with a cached CRS
|
|
112
|
+
const mockCachedCrs = '0x2345678901';
|
|
113
|
+
keysStorage.setCrs(sepolia.id, mockCachedCrs);
|
|
114
|
+
|
|
115
|
+
const [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
116
|
+
config,
|
|
117
|
+
sepolia.id,
|
|
118
|
+
0,
|
|
119
|
+
mockTfhePublicKeyDeserializer,
|
|
120
|
+
mockCompactPkeCrsDeserializer,
|
|
121
|
+
keysStorage
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(fheKeyFetchedFromCoFHE).toBe(true);
|
|
125
|
+
expect(crsFetchedFromCoFHE).toBe(false);
|
|
126
|
+
|
|
127
|
+
// Verify the cached CRS wasn't overwritten
|
|
128
|
+
const retrievedCrs = keysStorage.getCrs(sepolia.id);
|
|
129
|
+
expect(retrievedCrs).toEqual(mockCachedCrs);
|
|
130
|
+
|
|
131
|
+
// Verify FHE key was still fetched
|
|
132
|
+
const retrievedKey = keysStorage.getFheKey(sepolia.id, 0);
|
|
133
|
+
expect(retrievedKey).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should not make any network calls if both keys are cached', async () => {
|
|
137
|
+
// Pre-populate both keys
|
|
138
|
+
const mockCachedKey = '0x1234567890';
|
|
139
|
+
const mockCachedCrs = '0x2345678901';
|
|
140
|
+
keysStorage.setFheKey(sepolia.id, 0, mockCachedKey);
|
|
141
|
+
keysStorage.setCrs(sepolia.id, mockCachedCrs);
|
|
142
|
+
|
|
143
|
+
const [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
144
|
+
config,
|
|
145
|
+
sepolia.id,
|
|
146
|
+
0,
|
|
147
|
+
mockTfhePublicKeyDeserializer,
|
|
148
|
+
mockCompactPkeCrsDeserializer,
|
|
149
|
+
keysStorage
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(fheKeyFetchedFromCoFHE).toBe(false);
|
|
153
|
+
expect(crsFetchedFromCoFHE).toBe(false);
|
|
154
|
+
|
|
155
|
+
// Verify both keys remain unchanged
|
|
156
|
+
const retrievedKey = keysStorage.getFheKey(sepolia.id, 0);
|
|
157
|
+
const retrievedCrs = keysStorage.getCrs(sepolia.id);
|
|
158
|
+
|
|
159
|
+
expect(retrievedKey).toEqual(mockCachedKey);
|
|
160
|
+
expect(retrievedCrs).toEqual(mockCachedCrs);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should throw error for unsupported chain ID', async () => {
|
|
164
|
+
await expect(
|
|
165
|
+
fetchKeys(
|
|
166
|
+
config,
|
|
167
|
+
999, // Non-existent chain
|
|
168
|
+
0,
|
|
169
|
+
mockTfhePublicKeyDeserializer,
|
|
170
|
+
mockCompactPkeCrsDeserializer,
|
|
171
|
+
keysStorage
|
|
172
|
+
)
|
|
173
|
+
).rejects.toThrow('Config does not support chain <999>');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should throw error when FHE public key serialization fails', async () => {
|
|
177
|
+
mockTfhePublicKeyDeserializer.mockImplementation(() => {
|
|
178
|
+
throw new Error('Serialization failed');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await expect(
|
|
182
|
+
fetchKeys(config, sepolia.id, 0, mockTfhePublicKeyDeserializer, mockCompactPkeCrsDeserializer, keysStorage)
|
|
183
|
+
).rejects.toThrow('Error serializing FHE publicKey; Error: Serialization failed');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should throw error when CRS serialization fails', async () => {
|
|
187
|
+
mockCompactPkeCrsDeserializer.mockImplementation(() => {
|
|
188
|
+
throw new Error('Serialization failed');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await expect(
|
|
192
|
+
fetchKeys(config, sepolia.id, 0, mockTfhePublicKeyDeserializer, mockCompactPkeCrsDeserializer, keysStorage)
|
|
193
|
+
).rejects.toThrow('Error serializing CRS; Error: Serialization failed');
|
|
194
|
+
});
|
|
195
|
+
});
|