@zama-fhe/relayer-sdk 0.1.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +55 -0
  3. package/bin/relayer.js +61 -0
  4. package/bin/utils.js +14 -0
  5. package/bundle/fhevm.js +40713 -0
  6. package/bundle/fhevm.umd.cjs +24 -0
  7. package/bundle/kms_lib_bg.wasm +0 -0
  8. package/bundle/tfhe_bg.wasm +0 -0
  9. package/bundle/workerHelpers.js +2 -0
  10. package/bundle.d.ts +1 -0
  11. package/bundle.js +12 -0
  12. package/lib/config.d.ts +30 -0
  13. package/lib/index.d.ts +29 -0
  14. package/lib/init.d.ts +7 -0
  15. package/lib/kms_lib_bg.wasm +0 -0
  16. package/lib/node.cjs +1152 -0
  17. package/lib/node.d.ts +2 -0
  18. package/lib/relayer/decryptUtils.d.ts +2 -0
  19. package/lib/relayer/handles.d.ts +4 -0
  20. package/lib/relayer/network.d.ts +31 -0
  21. package/lib/relayer/network.test.d.ts +1 -0
  22. package/lib/relayer/publicDecrypt.d.ts +3 -0
  23. package/lib/relayer/publicDecrypt.test.d.ts +1 -0
  24. package/lib/relayer/sendEncryption.d.ts +41 -0
  25. package/lib/relayer/sendEncryption.test.d.ts +1 -0
  26. package/lib/relayer/userDecrypt.d.ts +11 -0
  27. package/lib/relayer/userDecrypt.test.d.ts +1 -0
  28. package/lib/sdk/encrypt.d.ts +34 -0
  29. package/lib/sdk/encrypt.test.d.ts +1 -0
  30. package/lib/sdk/encryptionTypes.d.ts +13 -0
  31. package/lib/sdk/keypair.d.ts +34 -0
  32. package/lib/sdk/keypair.test.d.ts +1 -0
  33. package/lib/test/index.d.ts +10 -0
  34. package/lib/tfhe.d.ts +7 -0
  35. package/lib/tfhe_bg.wasm +0 -0
  36. package/lib/utils.d.ts +9 -0
  37. package/lib/web.d.ts +2 -0
  38. package/lib/web.js +27305 -0
  39. package/lib/workerHelpers.js +24774 -0
  40. package/node.d.ts +1 -0
  41. package/node.js +1 -0
  42. package/package.json +99 -0
  43. package/web.d.ts +1 -0
  44. package/web.js +1 -0
package/lib/node.cjs ADDED
@@ -0,0 +1,1152 @@
1
+ 'use strict';
2
+
3
+ var ethers = require('ethers');
4
+ var nodeTfhe = require('node-tfhe');
5
+ var nodeTkms = require('node-tkms');
6
+ var createHash = require('keccak');
7
+ var fetchRetry = require('fetch-retry');
8
+
9
+ const SERIALIZED_SIZE_LIMIT_CIPHERTEXT = BigInt(1024 * 1024 * 512);
10
+ const SERIALIZED_SIZE_LIMIT_PK = BigInt(1024 * 1024 * 512);
11
+ const SERIALIZED_SIZE_LIMIT_CRS = BigInt(1024 * 1024 * 512);
12
+ const cleanURL = (url) => {
13
+ if (!url)
14
+ return '';
15
+ return url.endsWith('/') ? url.slice(0, -1) : url;
16
+ };
17
+ const numberToHex = (num) => {
18
+ let hex = num.toString(16);
19
+ return hex.length % 2 ? '0' + hex : hex;
20
+ };
21
+ const fromHexString = (hexString) => {
22
+ const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g);
23
+ if (!arr)
24
+ return new Uint8Array();
25
+ return Uint8Array.from(arr.map((byte) => parseInt(byte, 16)));
26
+ };
27
+ const toHexString = (bytes, with0x = false) => `${with0x ? '0x' : ''}${bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')}`;
28
+ const bytesToBigInt = function (byteArray) {
29
+ if (!byteArray || byteArray?.length === 0) {
30
+ return BigInt(0);
31
+ }
32
+ const hex = Array.from(byteArray)
33
+ .map((b) => b.toString(16).padStart(2, '0')) // byte to hex
34
+ .join('');
35
+ return BigInt(`0x${hex}`);
36
+ };
37
+
38
+ const keyurlCache = {};
39
+ const getKeysFromRelayer = async (url, publicKeyId) => {
40
+ if (keyurlCache[url]) {
41
+ return keyurlCache[url];
42
+ }
43
+ try {
44
+ const response = await fetch(`${url}/v1/keyurl`);
45
+ if (!response.ok) {
46
+ throw new Error(`HTTP error! status: ${response.status}`);
47
+ }
48
+ const data = await response.json();
49
+ if (data) {
50
+ let pubKeyUrl;
51
+ // If no publicKeyId is provided, use the first one
52
+ // Warning: if there are multiple keys available, the first one will most likely never be the
53
+ // same between several calls (fetching the infos is non-deterministic)
54
+ if (!publicKeyId) {
55
+ pubKeyUrl = data.response.fhe_key_info[0].fhe_public_key.urls[0];
56
+ publicKeyId = data.response.fhe_key_info[0].fhe_public_key.data_id;
57
+ }
58
+ else {
59
+ // If a publicKeyId is provided, get the corresponding info
60
+ const keyInfo = data.response.fhe_key_info.find((info) => info.fhe_public_key.data_id === publicKeyId);
61
+ if (!keyInfo) {
62
+ throw new Error(`Could not find FHE key info with data_id ${publicKeyId}`);
63
+ }
64
+ // TODO: Get a given party's public key url instead of the first one
65
+ pubKeyUrl = keyInfo.fhe_public_key.urls[0];
66
+ }
67
+ const publicKeyResponse = await fetch(pubKeyUrl);
68
+ if (!publicKeyResponse.ok) {
69
+ throw new Error(`HTTP error! status: ${publicKeyResponse.status} on ${publicKeyResponse.url}`);
70
+ }
71
+ let publicKey;
72
+ if (typeof publicKeyResponse.bytes === 'function') {
73
+ // bytes is not widely supported yet
74
+ publicKey = await publicKeyResponse.bytes();
75
+ }
76
+ else {
77
+ publicKey = new Uint8Array(await publicKeyResponse.arrayBuffer());
78
+ }
79
+ const publicParamsUrl = data.response.crs['2048'].urls[0];
80
+ const publicParamsId = data.response.crs['2048'].data_id;
81
+ const publicParams2048Response = await fetch(publicParamsUrl);
82
+ if (!publicParams2048Response.ok) {
83
+ throw new Error(`HTTP error! status: ${publicParams2048Response.status} on ${publicParams2048Response.url}`);
84
+ }
85
+ const publicParams2048 = await publicParams2048Response.bytes();
86
+ let pub_key;
87
+ try {
88
+ pub_key = nodeTfhe.TfheCompactPublicKey.safe_deserialize(publicKey, SERIALIZED_SIZE_LIMIT_PK);
89
+ }
90
+ catch (e) {
91
+ throw new Error('Invalid public key (deserialization failed)', {
92
+ cause: e,
93
+ });
94
+ }
95
+ let crs;
96
+ try {
97
+ crs = nodeTfhe.CompactPkeCrs.safe_deserialize(new Uint8Array(publicParams2048), SERIALIZED_SIZE_LIMIT_CRS);
98
+ }
99
+ catch (e) {
100
+ throw new Error('Invalid crs (deserialization failed)', {
101
+ cause: e,
102
+ });
103
+ }
104
+ const result = {
105
+ publicKey: pub_key,
106
+ publicKeyId,
107
+ publicParams: {
108
+ 2048: {
109
+ publicParams: crs,
110
+ publicParamsId,
111
+ },
112
+ },
113
+ };
114
+ keyurlCache[url] = result;
115
+ return result;
116
+ }
117
+ else {
118
+ throw new Error('No public key available');
119
+ }
120
+ }
121
+ catch (e) {
122
+ throw new Error('Impossible to fetch public key: wrong relayer url.', {
123
+ cause: e,
124
+ });
125
+ }
126
+ };
127
+
128
+ const abiKmsVerifier = [
129
+ 'function getKmsSigners() view returns (address[])',
130
+ 'function getThreshold() view returns (uint256)',
131
+ ];
132
+ const abiInputVerifier = [
133
+ 'function getCoprocessorSigners() view returns (address[])',
134
+ 'function getThreshold() view returns (uint256)',
135
+ ];
136
+ const getProvider = (config) => {
137
+ if (typeof config.network === 'string') {
138
+ return new ethers.JsonRpcProvider(config.network);
139
+ }
140
+ else if (config.network) {
141
+ return new ethers.BrowserProvider(config.network);
142
+ }
143
+ throw new Error('You must provide a network URL or a EIP1193 object (eg: window.ethereum)');
144
+ };
145
+ const getChainId = async (provider, config) => {
146
+ if (config.chainId && typeof config.chainId === 'number') {
147
+ return config.chainId;
148
+ }
149
+ else if (config.chainId && typeof config.chainId !== 'number') {
150
+ throw new Error('chainId must be a number.');
151
+ }
152
+ else {
153
+ const chainId = (await provider.getNetwork()).chainId;
154
+ return Number(chainId);
155
+ }
156
+ };
157
+ const getTfheCompactPublicKey = async (config) => {
158
+ if (config.relayerUrl && !config.publicKey) {
159
+ const inputs = await getKeysFromRelayer(cleanURL(config.relayerUrl));
160
+ return { publicKey: inputs.publicKey, publicKeyId: inputs.publicKeyId };
161
+ }
162
+ else if (config.publicKey && config.publicKey.data && config.publicKey.id) {
163
+ const buff = config.publicKey.data;
164
+ try {
165
+ return {
166
+ publicKey: nodeTfhe.TfheCompactPublicKey.safe_deserialize(buff, SERIALIZED_SIZE_LIMIT_PK),
167
+ publicKeyId: config.publicKey.id,
168
+ };
169
+ }
170
+ catch (e) {
171
+ throw new Error('Invalid public key (deserialization failed)', {
172
+ cause: e,
173
+ });
174
+ }
175
+ }
176
+ else {
177
+ throw new Error('You must provide a public key with its public key ID.');
178
+ }
179
+ };
180
+ const getPublicParams = async (config) => {
181
+ if (config.relayerUrl && !config.publicParams) {
182
+ const inputs = await getKeysFromRelayer(cleanURL(config.relayerUrl));
183
+ return inputs.publicParams;
184
+ }
185
+ else if (config.publicParams && config.publicParams['2048']) {
186
+ const buff = config.publicParams['2048'].publicParams;
187
+ try {
188
+ return {
189
+ 2048: {
190
+ publicParams: nodeTfhe.CompactPkeCrs.safe_deserialize(buff, SERIALIZED_SIZE_LIMIT_CRS),
191
+ publicParamsId: config.publicParams['2048'].publicParamsId,
192
+ },
193
+ };
194
+ }
195
+ catch (e) {
196
+ throw new Error('Invalid public key (deserialization failed)', {
197
+ cause: e,
198
+ });
199
+ }
200
+ }
201
+ else {
202
+ throw new Error('You must provide a valid CRS with its CRS ID.');
203
+ }
204
+ };
205
+ const getKMSSigners = async (provider, config) => {
206
+ const kmsContract = new ethers.Contract(config.kmsContractAddress, abiKmsVerifier, provider);
207
+ const signers = await kmsContract.getKmsSigners();
208
+ return signers;
209
+ };
210
+ const getKMSSignersThreshold = async (provider, config) => {
211
+ const kmsContract = new ethers.Contract(config.kmsContractAddress, abiKmsVerifier, provider);
212
+ const threshold = await kmsContract.getThreshold();
213
+ return Number(threshold); // threshold is always supposed to fit in a number
214
+ };
215
+ const getCoprocessorSigners = async (provider, config) => {
216
+ const inputContract = new ethers.Contract(config.inputVerifierContractAddress, abiInputVerifier, provider);
217
+ const signers = await inputContract.getCoprocessorSigners();
218
+ return signers;
219
+ };
220
+ const getCoprocessorSignersThreshold = async (provider, config) => {
221
+ const inputContract = new ethers.Contract(config.inputVerifierContractAddress, abiInputVerifier, provider);
222
+ const threshold = await inputContract.getThreshold();
223
+ return Number(threshold); // threshold is always supposed to fit in a number
224
+ };
225
+
226
+ const NumEncryptedBits = {
227
+ 0: 2, // ebool
228
+ 2: 8, // euint8
229
+ 3: 16, // euint16
230
+ 4: 32, // euint32
231
+ 5: 64, // euint64
232
+ 6: 128, // euint128
233
+ 7: 160, // eaddress
234
+ 8: 256, // euint256
235
+ 9: 512, // ebytes64
236
+ 10: 1024, // ebytes128
237
+ 11: 2048, // ebytes256
238
+ };
239
+ function checkEncryptedBits(handles) {
240
+ let total = 0;
241
+ for (const handle of handles) {
242
+ if (handle.length !== 66) {
243
+ throw new Error(`Handle ${handle} is not of valid length`);
244
+ }
245
+ const hexPair = handle.slice(-4, -2).toLowerCase();
246
+ const typeDiscriminant = parseInt(hexPair, 16);
247
+ if (!(typeDiscriminant in NumEncryptedBits)) {
248
+ throw new Error(`Handle ${handle} is not of valid type`);
249
+ }
250
+ total +=
251
+ NumEncryptedBits[typeDiscriminant];
252
+ // enforce 2048‑bit limit
253
+ if (total > 2048) {
254
+ throw new Error('Cannot decrypt more than 2048 encrypted bits in a single request');
255
+ }
256
+ }
257
+ return total;
258
+ }
259
+
260
+ const aclABI$1 = [
261
+ 'function persistAllowed(bytes32 handle, address account) view returns (bool)',
262
+ ];
263
+ const MAX_USER_DECRYPT_CONTRACT_ADDRESSES = 10;
264
+ const MAX_USER_DECRYPT_DURATION_DAYS = BigInt(365);
265
+ function formatAccordingToType(decryptedBigInt, type) {
266
+ if (type === 0) {
267
+ // ebool
268
+ return decryptedBigInt === BigInt(1);
269
+ }
270
+ else if (type === 7) {
271
+ // eaddress
272
+ return ethers.getAddress('0x' + decryptedBigInt.toString(16).padStart(40, '0'));
273
+ }
274
+ else if (type === 9) {
275
+ // ebytes64
276
+ return '0x' + decryptedBigInt.toString(16).padStart(128, '0');
277
+ }
278
+ else if (type === 10) {
279
+ // ebytes128
280
+ return '0x' + decryptedBigInt.toString(16).padStart(256, '0');
281
+ }
282
+ else if (type === 11) {
283
+ // ebytes256
284
+ return '0x' + decryptedBigInt.toString(16).padStart(512, '0');
285
+ } // euintXXX
286
+ return decryptedBigInt;
287
+ }
288
+ function buildUserDecryptedResult(handles, listBigIntDecryptions) {
289
+ let typesList = [];
290
+ for (const handle of handles) {
291
+ const hexPair = handle.slice(-4, -2).toLowerCase();
292
+ const typeDiscriminant = parseInt(hexPair, 16);
293
+ typesList.push(typeDiscriminant);
294
+ }
295
+ let results = {};
296
+ handles.forEach((handle, idx) => (results[handle] = formatAccordingToType(listBigIntDecryptions[idx], typesList[idx])));
297
+ return results;
298
+ }
299
+ function checkDeadlineValidity(startTimestamp, durationDays) {
300
+ if (durationDays === BigInt(0)) {
301
+ throw Error('durationDays is null');
302
+ }
303
+ if (durationDays > MAX_USER_DECRYPT_DURATION_DAYS) {
304
+ throw Error(`durationDays is above max duration of ${MAX_USER_DECRYPT_DURATION_DAYS}`);
305
+ }
306
+ const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
307
+ if (startTimestamp > currentTimestamp) {
308
+ throw Error('startTimestamp is set in the future');
309
+ }
310
+ const durationInSeconds = durationDays * BigInt(86400);
311
+ if (startTimestamp + durationInSeconds < currentTimestamp) {
312
+ throw Error('User decrypt request has expired');
313
+ }
314
+ }
315
+ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider) => async (_handles, privateKey, publicKey, signature, contractAddresses, userAddress, startTimestamp, durationDays) => {
316
+ // Casting handles if string
317
+ const handles = _handles.map((h) => ({
318
+ handle: typeof h.handle === 'string'
319
+ ? toHexString(fromHexString(h.handle), true)
320
+ : toHexString(h.handle, true),
321
+ contractAddress: h.contractAddress,
322
+ }));
323
+ checkEncryptedBits(handles.map((h) => h.handle));
324
+ checkDeadlineValidity(BigInt(startTimestamp), BigInt(durationDays));
325
+ const acl = new ethers.ethers.Contract(aclContractAddress, aclABI$1, provider);
326
+ const verifications = handles.map(async ({ handle, contractAddress }) => {
327
+ const userAllowed = await acl.persistAllowed(handle, userAddress);
328
+ const contractAllowed = await acl.persistAllowed(handle, contractAddress);
329
+ if (!userAllowed) {
330
+ throw new Error(`User ${userAddress} is not authorized to user decrypt handle ${handle}!`);
331
+ }
332
+ if (!contractAllowed) {
333
+ throw new Error(`dapp contract ${contractAddress} is not authorized to user decrypt handle ${handle}!`);
334
+ }
335
+ if (userAddress === contractAddress) {
336
+ throw new Error(`userAddress ${userAddress} should not be equal to contractAddress when requesting user decryption!`);
337
+ }
338
+ });
339
+ const contractAddressesLength = contractAddresses.length;
340
+ if (contractAddressesLength === 0) {
341
+ throw Error('contractAddresses is empty');
342
+ }
343
+ if (contractAddressesLength > MAX_USER_DECRYPT_CONTRACT_ADDRESSES) {
344
+ throw Error(`contractAddresses max length of ${MAX_USER_DECRYPT_CONTRACT_ADDRESSES} exceeded`);
345
+ }
346
+ await Promise.all(verifications).catch((e) => {
347
+ throw e;
348
+ });
349
+ const payloadForRequest = {
350
+ handleContractPairs: handles,
351
+ requestValidity: {
352
+ startTimestamp: startTimestamp.toString(), // Convert to string
353
+ durationDays: durationDays.toString(), // Convert to string
354
+ },
355
+ contractsChainId: chainId.toString(), // Convert to string
356
+ contractAddresses: contractAddresses.map((c) => ethers.getAddress(c)),
357
+ userAddress: ethers.getAddress(userAddress),
358
+ signature: signature.replace(/^(0x)/, ''),
359
+ publicKey: publicKey.replace(/^(0x)/, ''),
360
+ };
361
+ const options = {
362
+ method: 'POST',
363
+ headers: {
364
+ 'Content-Type': 'application/json',
365
+ },
366
+ body: JSON.stringify(payloadForRequest),
367
+ };
368
+ let pubKey;
369
+ let privKey;
370
+ try {
371
+ pubKey = nodeTkms.u8vec_to_cryptobox_pk(fromHexString(publicKey));
372
+ privKey = nodeTkms.u8vec_to_cryptobox_sk(fromHexString(privateKey));
373
+ }
374
+ catch (e) {
375
+ throw new Error('Invalid public or private key', { cause: e });
376
+ }
377
+ let response;
378
+ let json;
379
+ try {
380
+ response = await fetch(`${relayerUrl}/v1/user-decrypt`, options);
381
+ if (!response.ok) {
382
+ throw new Error(`User decrypt failed: relayer respond with HTTP code ${response.status}`);
383
+ }
384
+ }
385
+ catch (e) {
386
+ throw new Error("User decrypt failed: Relayer didn't respond", {
387
+ cause: e,
388
+ });
389
+ }
390
+ try {
391
+ json = await response.json();
392
+ }
393
+ catch (e) {
394
+ throw new Error("User decrypt failed: Relayer didn't return a JSON", {
395
+ cause: e,
396
+ });
397
+ }
398
+ if (json.status === 'failure') {
399
+ throw new Error("User decrypt failed: the user decryption didn't succeed for an unknown reason", { cause: json });
400
+ }
401
+ const client = nodeTkms.new_client(kmsSigners, userAddress, 'default');
402
+ try {
403
+ const buffer = new ArrayBuffer(32);
404
+ const view = new DataView(buffer);
405
+ view.setUint32(28, gatewayChainId, false);
406
+ const chainIdArrayBE = new Uint8Array(buffer);
407
+ const eip712Domain = {
408
+ name: 'Decryption',
409
+ version: '1',
410
+ chain_id: chainIdArrayBE,
411
+ verifying_contract: verifyingContractAddress,
412
+ salt: null,
413
+ };
414
+ const payloadForVerification = {
415
+ signature,
416
+ client_address: userAddress,
417
+ enc_key: publicKey.replace(/^0x/, ''),
418
+ ciphertext_handles: handles.map((h) => h.handle.replace(/^0x/, '')),
419
+ eip712_verifying_contract: verifyingContractAddress,
420
+ };
421
+ const decryption = nodeTkms.process_user_decryption_resp_from_js(client, payloadForVerification, eip712Domain, json.response, pubKey, privKey, true);
422
+ const listBigIntDecryptions = decryption.map((d) => bytesToBigInt(d.bytes));
423
+ const results = buildUserDecryptedResult(handles.map((h) => h.handle), listBigIntDecryptions);
424
+ return results;
425
+ }
426
+ catch (e) {
427
+ throw new Error('An error occured during decryption', { cause: e });
428
+ }
429
+ };
430
+
431
+ const checkEncryptedValue = (value, bits) => {
432
+ if (value == null)
433
+ throw new Error('Missing value');
434
+ let limit;
435
+ if (bits >= 8) {
436
+ limit = BigInt(`0x${new Array(bits / 8).fill(null).reduce((v) => `${v}ff`, '')}`);
437
+ }
438
+ else {
439
+ limit = BigInt(2 ** bits - 1);
440
+ }
441
+ if (typeof value !== 'number' && typeof value !== 'bigint')
442
+ throw new Error('Value must be a number or a bigint.');
443
+ if (value > limit) {
444
+ throw new Error(`The value exceeds the limit for ${bits}bits integer (${limit.toString()}).`);
445
+ }
446
+ };
447
+ const createEncryptedInput = ({ aclContractAddress, chainId, tfheCompactPublicKey, publicParams, contractAddress, userAddress, }) => {
448
+ if (!ethers.isAddress(contractAddress)) {
449
+ throw new Error('Contract address is not a valid address.');
450
+ }
451
+ if (!ethers.isAddress(userAddress)) {
452
+ throw new Error('User address is not a valid address.');
453
+ }
454
+ const publicKey = tfheCompactPublicKey;
455
+ const bits = [];
456
+ const builder = nodeTfhe.CompactCiphertextList.builder(publicKey);
457
+ let ciphertextWithZKProof = new Uint8Array(); // updated in `_prove`
458
+ const checkLimit = (added) => {
459
+ if (bits.reduce((acc, val) => acc + Math.max(2, val), 0) + added > 2048) {
460
+ throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
461
+ }
462
+ if (bits.length + 1 > 256)
463
+ throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
464
+ };
465
+ return {
466
+ addBool(value) {
467
+ if (value == null)
468
+ throw new Error('Missing value');
469
+ if (typeof value !== 'boolean' &&
470
+ typeof value !== 'number' &&
471
+ typeof value !== 'bigint')
472
+ throw new Error('The value must be a boolean, a number or a bigint.');
473
+ if (Number(value) > 1)
474
+ throw new Error('The value must be 1 or 0.');
475
+ checkEncryptedValue(Number(value), 1);
476
+ checkLimit(2);
477
+ builder.push_boolean(!!value);
478
+ bits.push(1); // ebool takes 2 encrypted bits
479
+ return this;
480
+ },
481
+ add8(value) {
482
+ checkEncryptedValue(value, 8);
483
+ checkLimit(8);
484
+ builder.push_u8(Number(value));
485
+ bits.push(8);
486
+ return this;
487
+ },
488
+ add16(value) {
489
+ checkEncryptedValue(value, 16);
490
+ checkLimit(16);
491
+ builder.push_u16(Number(value));
492
+ bits.push(16);
493
+ return this;
494
+ },
495
+ add32(value) {
496
+ checkEncryptedValue(value, 32);
497
+ checkLimit(32);
498
+ builder.push_u32(Number(value));
499
+ bits.push(32);
500
+ return this;
501
+ },
502
+ add64(value) {
503
+ checkEncryptedValue(value, 64);
504
+ checkLimit(64);
505
+ builder.push_u64(BigInt(value));
506
+ bits.push(64);
507
+ return this;
508
+ },
509
+ add128(value) {
510
+ checkEncryptedValue(value, 128);
511
+ checkLimit(128);
512
+ builder.push_u128(BigInt(value));
513
+ bits.push(128);
514
+ return this;
515
+ },
516
+ addAddress(value) {
517
+ if (!ethers.isAddress(value)) {
518
+ throw new Error('The value must be a valid address.');
519
+ }
520
+ checkLimit(160);
521
+ builder.push_u160(BigInt(value));
522
+ bits.push(160);
523
+ return this;
524
+ },
525
+ add256(value) {
526
+ checkEncryptedValue(value, 256);
527
+ checkLimit(256);
528
+ builder.push_u256(BigInt(value));
529
+ bits.push(256);
530
+ return this;
531
+ },
532
+ addBytes64(value) {
533
+ if (value.length !== 64)
534
+ throw Error('Uncorrect length of input Uint8Array, should be 64 for an ebytes64');
535
+ const bigIntValue = bytesToBigInt(value);
536
+ checkEncryptedValue(bigIntValue, 512);
537
+ checkLimit(512);
538
+ builder.push_u512(bigIntValue);
539
+ bits.push(512);
540
+ return this;
541
+ },
542
+ addBytes128(value) {
543
+ if (value.length !== 128)
544
+ throw Error('Uncorrect length of input Uint8Array, should be 128 for an ebytes128');
545
+ const bigIntValue = bytesToBigInt(value);
546
+ checkEncryptedValue(bigIntValue, 1024);
547
+ checkLimit(1024);
548
+ builder.push_u1024(bigIntValue);
549
+ bits.push(1024);
550
+ return this;
551
+ },
552
+ addBytes256(value) {
553
+ if (value.length !== 256)
554
+ throw Error('Uncorrect length of input Uint8Array, should be 256 for an ebytes256');
555
+ const bigIntValue = bytesToBigInt(value);
556
+ checkEncryptedValue(bigIntValue, 2048);
557
+ checkLimit(2048);
558
+ builder.push_u2048(bigIntValue);
559
+ bits.push(2048);
560
+ return this;
561
+ },
562
+ getBits() {
563
+ return bits;
564
+ },
565
+ encrypt() {
566
+ const getClosestPP = () => {
567
+ const getKeys = (obj) => Object.keys(obj);
568
+ const totalBits = bits.reduce((total, v) => total + v, 0);
569
+ const ppTypes = getKeys(publicParams);
570
+ const closestPP = ppTypes.find((k) => Number(k) >= totalBits);
571
+ if (!closestPP) {
572
+ throw new Error(`Too many bits in provided values. Maximum is ${ppTypes[ppTypes.length - 1]}.`);
573
+ }
574
+ return closestPP;
575
+ };
576
+ const closestPP = getClosestPP();
577
+ const pp = publicParams[closestPP].publicParams;
578
+ const buffContract = fromHexString(contractAddress);
579
+ const buffUser = fromHexString(userAddress);
580
+ const buffAcl = fromHexString(aclContractAddress);
581
+ const buffChainId = fromHexString(chainId.toString(16).padStart(64, '0'));
582
+ const auxData = new Uint8Array(buffContract.length + buffUser.length + buffAcl.length + 32);
583
+ auxData.set(buffContract, 0);
584
+ auxData.set(buffUser, 20);
585
+ auxData.set(buffAcl, 40);
586
+ auxData.set(buffChainId, auxData.length - buffChainId.length);
587
+ const encrypted = builder.build_with_proof_packed(pp, auxData, nodeTfhe.ZkComputeLoad.Verify);
588
+ ciphertextWithZKProof = encrypted.safe_serialize(SERIALIZED_SIZE_LIMIT_CIPHERTEXT);
589
+ return ciphertextWithZKProof;
590
+ },
591
+ };
592
+ };
593
+
594
+ const ENCRYPTION_TYPES = {
595
+ 1: 0, // ebool takes 2 encrypted bits
596
+ 8: 2,
597
+ 16: 3,
598
+ 32: 4,
599
+ 64: 5,
600
+ 128: 6,
601
+ 160: 7,
602
+ 256: 8,
603
+ 512: 9,
604
+ 1024: 10,
605
+ 2048: 11,
606
+ };
607
+
608
+ const MAX_UINT64 = BigInt('18446744073709551615'); // 2^64 - 1
609
+ const computeHandles = (ciphertextWithZKProof, bitwidths, aclContractAddress, chainId, ciphertextVersion) => {
610
+ // Should be identical to:
611
+ // https://github.com/zama-ai/fhevm-backend/blob/bae00d1b0feafb63286e94acdc58dc88d9c481bf/fhevm-engine/zkproof-worker/src/verifier.rs#L301
612
+ const blob_hash = createHash('keccak256')
613
+ .update(Buffer.from(ciphertextWithZKProof))
614
+ .digest();
615
+ const aclContractAddress20Bytes = Buffer.from(fromHexString(aclContractAddress));
616
+ const hex = chainId.toString(16).padStart(64, '0'); // 64 hex chars = 32 bytes
617
+ const chainId32Bytes = Buffer.from(hex, 'hex');
618
+ const handles = bitwidths.map((bitwidth, encryptionIndex) => {
619
+ const encryptionType = ENCRYPTION_TYPES[bitwidth];
620
+ const encryptionIndex1Byte = Buffer.from([encryptionIndex]);
621
+ const handleHash = createHash('keccak256')
622
+ .update(blob_hash)
623
+ .update(encryptionIndex1Byte)
624
+ .update(aclContractAddress20Bytes)
625
+ .update(chainId32Bytes)
626
+ .digest();
627
+ const dataInput = new Uint8Array(32);
628
+ dataInput.set(handleHash, 0);
629
+ // Check if chainId exceeds 8 bytes
630
+ if (BigInt(chainId) > MAX_UINT64) {
631
+ throw new Error('ChainId exceeds maximum allowed value (8 bytes)'); // fhevm assumes chainID is only taking up to 8 bytes
632
+ }
633
+ const chainId8Bytes = chainId32Bytes.slice(24, 32);
634
+ dataInput[21] = encryptionIndex;
635
+ chainId8Bytes.copy(dataInput, 22);
636
+ dataInput[30] = encryptionType;
637
+ dataInput[31] = ciphertextVersion;
638
+ return dataInput;
639
+ });
640
+ return handles;
641
+ };
642
+
643
+ const currentCiphertextVersion = () => {
644
+ return 0;
645
+ };
646
+ function isThresholdReached$1(coprocessorSigners, recoveredAddresses, threshold) {
647
+ const addressMap = new Map();
648
+ recoveredAddresses.forEach((address, index) => {
649
+ if (addressMap.has(address)) {
650
+ const duplicateValue = address;
651
+ throw new Error(`Duplicate coprocessor signer address found: ${duplicateValue} appears multiple times in recovered addresses`);
652
+ }
653
+ addressMap.set(address, index);
654
+ });
655
+ for (const address of recoveredAddresses) {
656
+ if (!coprocessorSigners.includes(address)) {
657
+ throw new Error(`Invalid address found: ${address} is not in the list of coprocessor signers`);
658
+ }
659
+ }
660
+ return recoveredAddresses.length >= threshold;
661
+ }
662
+ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners) => (contractAddress, userAddress) => {
663
+ if (!ethers.isAddress(contractAddress)) {
664
+ throw new Error('Contract address is not a valid address.');
665
+ }
666
+ if (!ethers.isAddress(userAddress)) {
667
+ throw new Error('User address is not a valid address.');
668
+ }
669
+ const input = createEncryptedInput({
670
+ aclContractAddress,
671
+ chainId,
672
+ tfheCompactPublicKey,
673
+ publicParams,
674
+ contractAddress,
675
+ userAddress,
676
+ });
677
+ return {
678
+ _input: input,
679
+ addBool(value) {
680
+ input.addBool(value);
681
+ return this;
682
+ },
683
+ add8(value) {
684
+ input.add8(value);
685
+ return this;
686
+ },
687
+ add16(value) {
688
+ input.add16(value);
689
+ return this;
690
+ },
691
+ add32(value) {
692
+ input.add32(value);
693
+ return this;
694
+ },
695
+ add64(value) {
696
+ input.add64(value);
697
+ return this;
698
+ },
699
+ add128(value) {
700
+ input.add128(value);
701
+ return this;
702
+ },
703
+ add256(value) {
704
+ input.add256(value);
705
+ return this;
706
+ },
707
+ addBytes64(value) {
708
+ input.addBytes64(value);
709
+ return this;
710
+ },
711
+ addBytes128(value) {
712
+ input.addBytes128(value);
713
+ return this;
714
+ },
715
+ addBytes256(value) {
716
+ input.addBytes256(value);
717
+ return this;
718
+ },
719
+ addAddress(value) {
720
+ input.addAddress(value);
721
+ return this;
722
+ },
723
+ getBits() {
724
+ return input.getBits();
725
+ },
726
+ encrypt: async () => {
727
+ const bits = input.getBits();
728
+ const ciphertext = input.encrypt();
729
+ // https://github.com/zama-ai/fhevm-relayer/blob/978b08f62de060a9b50d2c6cc19fd71b5fb8d873/src/input_http_listener.rs#L13C1-L22C1
730
+ const payload = {
731
+ contractAddress: ethers.getAddress(contractAddress),
732
+ userAddress: ethers.getAddress(userAddress),
733
+ ciphertextWithInputVerification: toHexString(ciphertext),
734
+ contractChainId: '0x' + chainId.toString(16),
735
+ };
736
+ const options = {
737
+ method: 'POST',
738
+ headers: {
739
+ 'Content-Type': 'application/json',
740
+ },
741
+ body: JSON.stringify(payload),
742
+ };
743
+ const url = `${relayerUrl}/v1/input-proof`;
744
+ let json;
745
+ try {
746
+ const response = await fetch(url, options);
747
+ if (!response.ok) {
748
+ throw new Error(`Relayer didn't response correctly. Bad status ${response.statusText}. Content: ${await response.text()}`);
749
+ }
750
+ try {
751
+ json = await response.json();
752
+ }
753
+ catch (e) {
754
+ throw new Error("Relayer didn't response correctly. Bad JSON.", {
755
+ cause: e,
756
+ });
757
+ }
758
+ }
759
+ catch (e) {
760
+ throw new Error("Relayer didn't response correctly.", {
761
+ cause: e,
762
+ });
763
+ }
764
+ const handles = computeHandles(ciphertext, bits, aclContractAddress, chainId, currentCiphertextVersion());
765
+ // Note that the hex strings returned by the relayer do have have the 0x prefix
766
+ if (json.response.handles && json.response.handles.length > 0) {
767
+ const responseHandles = json.response.handles.map(fromHexString);
768
+ if (handles.length != responseHandles.length) {
769
+ throw new Error(`Incorrect Handles list sizes: (expected) ${handles.length} != ${responseHandles.length} (received)`);
770
+ }
771
+ for (let index = 0; index < handles.length; index += 1) {
772
+ let handle = handles[index];
773
+ let responseHandle = responseHandles[index];
774
+ let expected = toHexString(handle);
775
+ let current = toHexString(responseHandle);
776
+ if (expected !== current) {
777
+ throw new Error(`Incorrect Handle ${index}: (expected) ${expected} != ${current} (received)`);
778
+ }
779
+ }
780
+ }
781
+ const signatures = json.response.signatures;
782
+ // verify signatures for inputs:
783
+ const domain = {
784
+ name: 'InputVerification',
785
+ version: '1',
786
+ chainId: gatewayChainId,
787
+ verifyingContract: verifyingContractAddressInputVerification,
788
+ };
789
+ const types = {
790
+ CiphertextVerification: [
791
+ { name: 'ctHandles', type: 'bytes32[]' },
792
+ { name: 'userAddress', type: 'address' },
793
+ { name: 'contractAddress', type: 'address' },
794
+ { name: 'contractChainId', type: 'uint256' },
795
+ ],
796
+ };
797
+ const recoveredAddresses = signatures.map((signature) => {
798
+ const sig = signature.startsWith('0x') ? signature : `0x${signature}`;
799
+ const recoveredAddress = ethers.ethers.verifyTypedData(domain, types, {
800
+ ctHandles: handles,
801
+ userAddress,
802
+ contractAddress,
803
+ contractChainId: chainId,
804
+ }, sig);
805
+ return recoveredAddress;
806
+ });
807
+ const thresholdReached = isThresholdReached$1(coprocessorSigners, recoveredAddresses, thresholdCoprocessorSigners);
808
+ if (!thresholdReached) {
809
+ throw Error('Coprocessor signers threshold is not reached');
810
+ }
811
+ // inputProof is len(list_handles) + numCoprocessorSigners + list_handles + signatureCoprocessorSigners (1+1+NUM_HANDLES*32+65*numSigners)
812
+ let inputProof = numberToHex(handles.length);
813
+ const numSigners = signatures.length;
814
+ inputProof += numberToHex(numSigners);
815
+ const listHandlesStr = handles.map((i) => toHexString(i));
816
+ listHandlesStr.map((handle) => (inputProof += handle));
817
+ signatures.map((signature) => (inputProof += signature.slice(2))); // removes the '0x' prefix from the `signature` string
818
+ return {
819
+ handles,
820
+ inputProof: fromHexString(inputProof),
821
+ };
822
+ },
823
+ };
824
+ };
825
+
826
+ const aclABI = [
827
+ 'function isAllowedForDecryption(bytes32 handle) view returns (bool)',
828
+ ];
829
+ function isThresholdReached(kmsSigners, recoveredAddresses, threshold) {
830
+ const addressMap = new Map();
831
+ recoveredAddresses.forEach((address, index) => {
832
+ if (addressMap.has(address)) {
833
+ const duplicateValue = address;
834
+ throw new Error(`Duplicate KMS signer address found: ${duplicateValue} appears multiple times in recovered addresses`);
835
+ }
836
+ addressMap.set(address, index);
837
+ });
838
+ for (const address of recoveredAddresses) {
839
+ if (!kmsSigners.includes(address)) {
840
+ throw new Error(`Invalid address found: ${address} is not in the list of KMS signers`);
841
+ }
842
+ }
843
+ return recoveredAddresses.length >= threshold;
844
+ }
845
+ const CiphertextType = {
846
+ 0: 'bool',
847
+ 2: 'uint256',
848
+ 3: 'uint256',
849
+ 4: 'uint256',
850
+ 5: 'uint256',
851
+ 6: 'uint256',
852
+ 7: 'address',
853
+ 8: 'uint256',
854
+ 9: 'bytes',
855
+ 10: 'bytes',
856
+ 11: 'bytes',
857
+ };
858
+ function deserializeDecryptedResult(handles, decryptedResult) {
859
+ let typesList = [];
860
+ for (const handle of handles) {
861
+ const hexPair = handle.slice(-4, -2).toLowerCase();
862
+ const typeDiscriminant = parseInt(hexPair, 16);
863
+ typesList.push(typeDiscriminant);
864
+ }
865
+ const restoredEncoded = '0x' +
866
+ '00'.repeat(32) + // dummy requestID (ignored)
867
+ decryptedResult.slice(2) +
868
+ '00'.repeat(32); // dummy empty bytes[] length (ignored)
869
+ const abiTypes = typesList.map((t) => {
870
+ const abiType = CiphertextType[t]; // all types are valid because this was supposedly checked already inside the `checkEncryptedBits` function
871
+ return abiType;
872
+ });
873
+ const coder = new ethers.AbiCoder();
874
+ const decoded = coder.decode(['uint256', ...abiTypes, 'bytes[]'], restoredEncoded);
875
+ // strip dummy first/last element
876
+ const rawValues = decoded.slice(1, 1 + typesList.length);
877
+ let results = {};
878
+ handles.forEach((handle, idx) => (results[handle] = rawValues[idx]));
879
+ return results;
880
+ }
881
+ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider) => async (_handles) => {
882
+ const acl = new ethers.ethers.Contract(aclContractAddress, aclABI, provider);
883
+ let handles;
884
+ try {
885
+ handles = await Promise.all(_handles.map(async (_handle) => {
886
+ const handle = typeof _handle === 'string'
887
+ ? toHexString(fromHexString(_handle), true)
888
+ : toHexString(_handle, true);
889
+ const isAllowedForDecryption = await acl.isAllowedForDecryption(handle);
890
+ if (!isAllowedForDecryption) {
891
+ throw new Error(`Handle ${handle} is not allowed for public decryption!`);
892
+ }
893
+ return handle;
894
+ }));
895
+ }
896
+ catch (e) {
897
+ throw e;
898
+ }
899
+ const verifications = handles.map(async (ctHandle) => {
900
+ const isAllowedForDecryption = await acl.isAllowedForDecryption(ctHandle);
901
+ if (!isAllowedForDecryption) {
902
+ throw new Error(`Handle ${ctHandle} is not allowed for public decryption!`);
903
+ }
904
+ });
905
+ await Promise.all(verifications).catch((e) => {
906
+ throw e;
907
+ });
908
+ // check 2048 bits limit
909
+ checkEncryptedBits(handles);
910
+ const payloadForRequest = {
911
+ ciphertextHandles: handles,
912
+ };
913
+ const options = {
914
+ method: 'POST',
915
+ headers: {
916
+ 'Content-Type': 'application/json',
917
+ },
918
+ body: JSON.stringify(payloadForRequest),
919
+ };
920
+ let response;
921
+ let json;
922
+ try {
923
+ response = await fetch(`${relayerUrl}/v1/public-decrypt`, options);
924
+ if (!response.ok) {
925
+ throw new Error(`Public decrypt failed: relayer respond with HTTP code ${response.status}`);
926
+ }
927
+ }
928
+ catch (e) {
929
+ throw new Error("Public decrypt failed: Relayer didn't respond", {
930
+ cause: e,
931
+ });
932
+ }
933
+ try {
934
+ json = await response.json();
935
+ }
936
+ catch (e) {
937
+ throw new Error("Public decrypt failed: Relayer didn't return a JSON", {
938
+ cause: e,
939
+ });
940
+ }
941
+ if (json.status === 'failure') {
942
+ throw new Error("Public decrypt failed: the public decrypt didn't succeed for an unknown reason", { cause: json });
943
+ }
944
+ // verify signatures on decryption:
945
+ const domain = {
946
+ name: 'Decryption',
947
+ version: '1',
948
+ chainId: gatewayChainId,
949
+ verifyingContract: verifyingContractAddress,
950
+ };
951
+ const types = {
952
+ PublicDecryptVerification: [
953
+ { name: 'ctHandles', type: 'bytes32[]' },
954
+ { name: 'decryptedResult', type: 'bytes' },
955
+ ],
956
+ };
957
+ const result = json.response[0];
958
+ const decryptedResult = result.decrypted_value.startsWith('0x')
959
+ ? result.decrypted_value
960
+ : `0x${result.decrypted_value}`;
961
+ const signatures = result.signatures;
962
+ const recoveredAddresses = signatures.map((signature) => {
963
+ const sig = signature.startsWith('0x') ? signature : `0x${signature}`;
964
+ const recoveredAddress = ethers.ethers.verifyTypedData(domain, types, { ctHandles: handles, decryptedResult }, sig);
965
+ return recoveredAddress;
966
+ });
967
+ const thresholdReached = isThresholdReached(kmsSigners, recoveredAddresses, thresholdSigners);
968
+ if (!thresholdReached) {
969
+ throw Error('KMS signers threshold is not reached');
970
+ }
971
+ const results = deserializeDecryptedResult(handles, decryptedResult);
972
+ return results;
973
+ };
974
+
975
+ /**
976
+ * Creates an EIP712 structure specifically for user decrypt requests
977
+ *
978
+ * @param gatewayChainId The chain ID of the gateway
979
+ * @param verifyingContract The address of the contract that will verify the signature
980
+ * @param publicKey The user's public key as a hex string or Uint8Array
981
+ * @param contractAddresses Array of contract addresses that can access the decryption
982
+ * @param contractsChainId The chain ID where the contracts are deployed
983
+ * @param startTimestamp The timestamp when the decryption permission becomes valid
984
+ * @param durationDays How many days the decryption permission remains valid
985
+ * @returns EIP712 typed data structure for user decryption
986
+ */
987
+ const createEIP712 = (gatewayChainId, verifyingContract, contractsChainId) => (publicKey, contractAddresses, startTimestamp, durationDays, delegatedAccount) => {
988
+ if (delegatedAccount && !ethers.isAddress(delegatedAccount))
989
+ throw new Error('Invalid delegated account.');
990
+ if (!ethers.isAddress(verifyingContract)) {
991
+ throw new Error('Invalid verifying contract address.');
992
+ }
993
+ if (!contractAddresses.every((c) => ethers.isAddress(c))) {
994
+ throw new Error('Invalid contract address.');
995
+ }
996
+ // Format the public key based on its type
997
+ const formattedPublicKey = typeof publicKey === 'string'
998
+ ? publicKey.startsWith('0x')
999
+ ? publicKey
1000
+ : `0x${publicKey}`
1001
+ : publicKey;
1002
+ // Convert timestamps to strings if they're bigints
1003
+ const formattedStartTimestamp = typeof startTimestamp === 'number'
1004
+ ? startTimestamp.toString()
1005
+ : startTimestamp;
1006
+ const formattedDurationDays = typeof durationDays === 'number' ? durationDays.toString() : durationDays;
1007
+ const EIP712Domain = [
1008
+ { name: 'name', type: 'string' },
1009
+ { name: 'version', type: 'string' },
1010
+ { name: 'chainId', type: 'uint256' },
1011
+ { name: 'verifyingContract', type: 'address' },
1012
+ ];
1013
+ const domain = {
1014
+ name: 'Decryption',
1015
+ version: '1',
1016
+ chainId: gatewayChainId,
1017
+ verifyingContract,
1018
+ };
1019
+ if (delegatedAccount) {
1020
+ return {
1021
+ types: {
1022
+ EIP712Domain,
1023
+ DelegatedUserDecryptRequestVerification: [
1024
+ { name: 'publicKey', type: 'bytes' },
1025
+ { name: 'contractAddresses', type: 'address[]' },
1026
+ { name: 'contractsChainId', type: 'uint256' },
1027
+ { name: 'startTimestamp', type: 'uint256' },
1028
+ { name: 'durationDays', type: 'uint256' },
1029
+ {
1030
+ name: 'delegatedAccount',
1031
+ type: 'address',
1032
+ },
1033
+ ],
1034
+ },
1035
+ primaryType: 'DelegatedUserDecryptRequestVerification',
1036
+ domain,
1037
+ message: {
1038
+ publicKey: formattedPublicKey,
1039
+ contractAddresses,
1040
+ contractsChainId,
1041
+ startTimestamp: formattedStartTimestamp,
1042
+ durationDays: formattedDurationDays,
1043
+ delegatedAccount: delegatedAccount,
1044
+ },
1045
+ };
1046
+ }
1047
+ return {
1048
+ types: {
1049
+ EIP712Domain,
1050
+ UserDecryptRequestVerification: [
1051
+ { name: 'publicKey', type: 'bytes' },
1052
+ { name: 'contractAddresses', type: 'address[]' },
1053
+ { name: 'contractsChainId', type: 'uint256' },
1054
+ { name: 'startTimestamp', type: 'uint256' },
1055
+ { name: 'durationDays', type: 'uint256' },
1056
+ ],
1057
+ },
1058
+ primaryType: 'UserDecryptRequestVerification',
1059
+ domain,
1060
+ message: {
1061
+ publicKey: formattedPublicKey,
1062
+ contractAddresses,
1063
+ contractsChainId,
1064
+ startTimestamp: formattedStartTimestamp,
1065
+ durationDays: formattedDurationDays,
1066
+ },
1067
+ };
1068
+ };
1069
+ const generateKeypair = () => {
1070
+ const keypair = nodeTkms.cryptobox_keygen();
1071
+ return {
1072
+ publicKey: toHexString(nodeTkms.cryptobox_pk_to_u8vec(nodeTkms.cryptobox_get_pk(keypair))),
1073
+ privateKey: toHexString(nodeTkms.cryptobox_sk_to_u8vec(keypair)),
1074
+ };
1075
+ };
1076
+
1077
+ global.fetch = fetchRetry(global.fetch, { retries: 5, retryDelay: 500 });
1078
+ const createInstance = async (config) => {
1079
+ const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, } = config;
1080
+ if (!kmsContractAddress || !ethers.isAddress(kmsContractAddress)) {
1081
+ throw new Error('KMS contract address is not valid or empty');
1082
+ }
1083
+ if (!verifyingContractAddressDecryption ||
1084
+ !ethers.isAddress(verifyingContractAddressDecryption)) {
1085
+ throw new Error('Verifying contract for Decryption address is not valid or empty');
1086
+ }
1087
+ if (!verifyingContractAddressInputVerification ||
1088
+ !ethers.isAddress(verifyingContractAddressInputVerification)) {
1089
+ throw new Error('Verifying contract for InputVerification address is not valid or empty');
1090
+ }
1091
+ if (!aclContractAddress || !ethers.isAddress(aclContractAddress)) {
1092
+ throw new Error('ACL contract address is not valid or empty');
1093
+ }
1094
+ if (publicKey && !(publicKey.data instanceof Uint8Array))
1095
+ throw new Error('publicKey must be a Uint8Array');
1096
+ const provider = getProvider(config);
1097
+ if (!provider) {
1098
+ throw new Error('No network has been provided!');
1099
+ }
1100
+ const chainId = await getChainId(provider, config);
1101
+ const publicKeyData = await getTfheCompactPublicKey(config);
1102
+ const publicParamsData = await getPublicParams(config);
1103
+ const kmsSigners = await getKMSSigners(provider, config);
1104
+ const thresholdKMSSigners = await getKMSSignersThreshold(provider, config);
1105
+ const coprocessorSigners = await getCoprocessorSigners(provider, config);
1106
+ const thresholdCoprocessorSigners = await getCoprocessorSignersThreshold(provider, config);
1107
+ return {
1108
+ createEncryptedInput: createRelayerEncryptedInput(aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, cleanURL(config.relayerUrl), publicKeyData.publicKey, publicParamsData, coprocessorSigners, thresholdCoprocessorSigners),
1109
+ generateKeypair,
1110
+ createEIP712: createEIP712(gatewayChainId, verifyingContractAddressDecryption, chainId),
1111
+ publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
1112
+ userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
1113
+ getPublicKey: () => publicKeyData.publicKey
1114
+ ? {
1115
+ publicKey: publicKeyData.publicKey.safe_serialize(SERIALIZED_SIZE_LIMIT_PK),
1116
+ publicKeyId: publicKeyData.publicKeyId,
1117
+ }
1118
+ : null,
1119
+ getPublicParams: (bits) => {
1120
+ if (publicParamsData[bits]) {
1121
+ return {
1122
+ publicParams: publicParamsData[bits].publicParams.safe_serialize(SERIALIZED_SIZE_LIMIT_CRS),
1123
+ publicParamsId: publicParamsData[bits].publicParamsId,
1124
+ };
1125
+ }
1126
+ return null;
1127
+ },
1128
+ };
1129
+ };
1130
+
1131
+ const createTfheKeypair = () => {
1132
+ const block_params = new nodeTfhe.ShortintParameters(nodeTfhe.ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128);
1133
+ const casting_params = new nodeTfhe.ShortintCompactPublicKeyEncryptionParameters(nodeTfhe.ShortintCompactPublicKeyEncryptionParametersName.V1_0_PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128);
1134
+ const config = nodeTfhe.TfheConfigBuilder.default()
1135
+ .use_custom_parameters(block_params)
1136
+ .use_dedicated_compact_public_key_parameters(casting_params)
1137
+ .build();
1138
+ let clientKey = nodeTfhe.TfheClientKey.generate(config);
1139
+ let publicKey = nodeTfhe.TfheCompactPublicKey.new(clientKey);
1140
+ const crs = nodeTfhe.CompactPkeCrs.from_config(config, 4 * 512);
1141
+ return { clientKey, publicKey, crs };
1142
+ };
1143
+ const createTfhePublicKey = () => {
1144
+ const { publicKey } = createTfheKeypair();
1145
+ return toHexString(publicKey.serialize());
1146
+ };
1147
+
1148
+ exports.createEIP712 = createEIP712;
1149
+ exports.createInstance = createInstance;
1150
+ exports.createTfheKeypair = createTfheKeypair;
1151
+ exports.createTfhePublicKey = createTfhePublicKey;
1152
+ exports.generateKeypair = generateKeypair;