@zama-fhe/relayer-sdk 0.4.0-alpha.0 → 0.4.0-alpha.2

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/bin/relayer.js CHANGED
@@ -2,67 +2,140 @@
2
2
  'use strict';
3
3
 
4
4
  import { program } from 'commander';
5
- import { toHexString, prependHttps, throwError } from './utils.js';
6
- import { createInstance, SepoliaConfig } from '../lib/node.cjs';
7
-
8
- const allowedBits = [1, 4, 8, 16, 32, 64];
9
-
10
- let _instance;
11
-
12
- const getInstance = async (networkUrl) => {
13
- if (_instance) return _instance;
14
-
15
- try {
16
- // NOTE: hack to get the instance created
17
- const config = { ...SepoliaConfig, network: networkUrl };
18
- console.debug(`Using network ${config.network}`);
19
- _instance = await createInstance(config);
20
- } catch (e) {
21
- return throwError(
22
- `This network (${networkUrl}) doesn't seem to use Fhevm or use an incompatible version.`,
23
- e,
24
- );
25
- }
26
- return _instance;
27
- };
5
+ import { addCommonOptions } from './utils.js';
6
+ import { inputProofCommand } from './commands/input-proof.js';
7
+ import { configCommand } from './commands/config.js';
8
+ import { publicDecryptCommand } from './commands/public-decrypt.js';
28
9
 
10
+ ////////////////////////////////////////////////////////////////////////////////
11
+ // input-proof
12
+ ////////////////////////////////////////////////////////////////////////////////
13
+
14
+ // npx . input-proof --values true:ebool
15
+ // npx . input-proof --contract-address 0xb2a8A265dD5A27026693Aa6cE87Fb21Ac197b6b9 --user-address 0x37AC010c1c566696326813b840319B58Bb5840E4 --values true:ebool
29
16
  // TODO: be able to pass a full configuration, or simply the chain-id/name, or relayer-url
30
- program
31
- .command('encrypt')
32
- .argument('<contractAddress>', 'address of the contract')
33
- .argument('<userAddress>', 'address of the account')
34
- .argument('<values:bits...>', 'values with number of bits eg: 1:1 3324242:64')
35
- .requiredOption(
36
- '-n, --node <url>',
37
- 'url of the blockchain',
38
- 'https://eth-sepolia.public.blastapi.io',
39
- )
40
- .action(async (contractAddress, userAddress, valuesArr, options) => {
41
- const host = prependHttps(options.node);
42
- const instance = await getInstance(host);
43
- const encryptedInput = instance.createEncryptedInput(
44
- contractAddress,
45
- userAddress,
46
- );
47
- valuesArr.forEach((str, i) => {
48
- const [value, bits] = str.split(':');
49
- if (!allowedBits.includes(+bits)) throwError('Invalid number of bits');
50
- const suffix = +bits === 1 ? 'Bool' : bits === '160' ? 'Address' : bits;
51
- try {
52
- encryptedInput[`add${suffix}`](parseInt(value, 10));
53
- } catch (e) {
54
- return throwError(e.message);
55
- }
56
- });
57
- const result = await encryptedInput.encrypt();
58
-
59
- console.log('Input proof:');
60
- console.log(`0x${toHexString(result.inputProof)}`);
61
- console.log('Handles:');
62
- result.handles.forEach((handle, i) => {
63
- console.log(`Handle ${i}`);
64
- console.log(`0x${toHexString(handle)}`);
65
- });
17
+ addCommonOptions(program.command('input-proof'))
18
+ .requiredOption('--values <value:type-name...>', 'List of values')
19
+ .action(async (options) => {
20
+ await inputProofCommand(options);
21
+ });
22
+
23
+ ////////////////////////////////////////////////////////////////////////////////
24
+ // public-decrypt
25
+ ////////////////////////////////////////////////////////////////////////////////
26
+
27
+ addCommonOptions(program.command('public-decrypt'))
28
+ .requiredOption('--handles <handles...>', 'List of handles to decrypt')
29
+ .action(async (options) => {
30
+ await publicDecryptCommand(options);
31
+ });
32
+
33
+ ////////////////////////////////////////////////////////////////////////////////
34
+ // user-decrypt
35
+ ////////////////////////////////////////////////////////////////////////////////
36
+
37
+ addCommonOptions(program.command('user-decrypt'))
38
+ .requiredOption('--handles <handles...>', 'List of handles to decrypt')
39
+ .action(async (options) => {
40
+ const mod = await import('./commands/user-decrypt.js');
41
+ await mod.userDecryptCommand(options);
42
+ });
43
+
44
+ ////////////////////////////////////////////////////////////////////////////////
45
+ // handle
46
+ ////////////////////////////////////////////////////////////////////////////////
47
+
48
+ addCommonOptions(program.command('handle'))
49
+ .argument('<handles...>', 'List of handles to parse')
50
+ .action(async (handles, options) => {
51
+ const mod = await import('./commands/handle.js');
52
+ await mod.handleCommand(handles, options);
53
+ });
54
+
55
+ ////////////////////////////////////////////////////////////////////////////////
56
+ // config
57
+ ////////////////////////////////////////////////////////////////////////////////
58
+
59
+ // npx . config --contract-address 0xb2a8A265dD5A27026693Aa6cE87Fb21Ac197b6b9 --user-address 0x37AC010c1c566696326813b840319B58Bb5840E4
60
+ addCommonOptions(program.command('config')).action(async (options) => {
61
+ await configCommand(options);
62
+ });
63
+
64
+ ////////////////////////////////////////////////////////////////////////////////
65
+
66
+ // Create 'pubkey' command group
67
+ const pubkey = program.command('pubkey').description('Public key operations');
68
+
69
+ ////////////////////////////////////////////////////////////////////////////////
70
+ // pubkey info
71
+ ////////////////////////////////////////////////////////////////////////////////
72
+
73
+ // npx . pubkey info
74
+ pubkey
75
+ .command('info')
76
+ .description('Display public key information')
77
+ .action(async (options) => {
78
+ const mod = await import('./commands/pubkey-info.js');
79
+ await mod.pubkeyInfoCommand(options);
80
+ });
81
+
82
+ ////////////////////////////////////////////////////////////////////////////////
83
+ // pubkey fetch
84
+ ////////////////////////////////////////////////////////////////////////////////
85
+
86
+ // npx . pubkey fetch
87
+ addCommonOptions(pubkey.command('fetch'))
88
+ .description('Fetch FHEVM public key')
89
+ .action(async (options) => {
90
+ const mod = await import('./commands/pubkey-fetch.js');
91
+ await mod.pubkeyFetchCommand(options);
92
+ });
93
+
94
+ ////////////////////////////////////////////////////////////////////////////////
95
+ // pubkey delete
96
+ ////////////////////////////////////////////////////////////////////////////////
97
+
98
+ // npx . pubkey delete
99
+ pubkey
100
+ .command('delete')
101
+ .description('Clear FHEVM public key cache')
102
+ .action(async () => {
103
+ const mod = await import('./commands/pubkey-clear.js');
104
+ await mod.pubkeyClearCommand();
105
+ });
106
+
107
+ ////////////////////////////////////////////////////////////////////////////////
108
+ // test fhecounter-get-count
109
+ ////////////////////////////////////////////////////////////////////////////////
110
+
111
+ const test = program.command('test').description('Test operations');
112
+
113
+ // npx . test fhecounter-get-count
114
+ addCommonOptions(test.command('fhecounter-get-count'))
115
+ .description('Call FHECounter.getCount()')
116
+ .action(async (options) => {
117
+ const mod = await import('./commands/test-fhecounter-getcount.js');
118
+ await mod.testFheCounterGetCountCommand(options);
119
+ });
120
+
121
+ ////////////////////////////////////////////////////////////////////////////////
122
+
123
+ // Create 'zkproof' command group
124
+ const zkproof = program.command('zkproof').description('ZKProof operations');
125
+
126
+ addCommonOptions(zkproof.command('generate'))
127
+ .description('Generate ZKProof')
128
+ .requiredOption('--values <value:type-name...>', 'List of values')
129
+ .action(async (options) => {
130
+ const mod = await import('./commands/zkproof-generate.js');
131
+ await mod.zkProofGenerateCommand(options);
66
132
  });
67
133
 
134
+ // addCommonOptions(zkproof.command('verify'))
135
+ // .description('Verify ZKProof')
136
+ // .action(async (options) => {
137
+ // const mod = await import('./commands/zkproof-verify.js');
138
+ // await mod.zkProofVerifyCommand(options);
139
+ // });
140
+
68
141
  program.parseAsync();
package/bin/utils.js CHANGED
@@ -1,12 +1,22 @@
1
- export const prependHttps = (host) => {
2
- if (!/^https?:\/\//i.test(host)) {
3
- return 'https://' + host;
4
- }
5
- return host;
6
- };
1
+ import dotenv from 'dotenv';
2
+ import fs from 'fs';
3
+ import { ethers } from 'ethers';
4
+ import {
5
+ encryptionBitsFromFheTypeName,
6
+ FhevmHandle,
7
+ isChecksummedAddress,
8
+ isFheTypeName,
9
+ } from '../lib/internal.js';
7
10
 
8
- export const toHexString = (bytes) =>
9
- bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
11
+ export function logCLI(message, { json, verbose }) {
12
+ if (json === true) {
13
+ if (verbose === true) {
14
+ process.stderr.write(message + '\n');
15
+ }
16
+ } else {
17
+ console.log(message);
18
+ }
19
+ }
10
20
 
11
21
  export const throwError = (error, cause) => {
12
22
  if (cause) {
@@ -16,3 +26,330 @@ export const throwError = (error, cause) => {
16
26
  }
17
27
  process.exit();
18
28
  };
29
+
30
+ export function getEnv(envName, envFile) {
31
+ if (envName === 'MNEMONIC') {
32
+ envFile = '.env';
33
+ }
34
+ if (!envFile) {
35
+ throwError(`Missing env filename`);
36
+ }
37
+ const parsedEnv = dotenv.parse(fs.readFileSync(envFile));
38
+ return process.env[envName] ?? parsedEnv[envName];
39
+ }
40
+
41
+ export function parseHandles(handles) {
42
+ const fhevmHandles = [];
43
+ for (let i = 0; i < handles.length; ++i) {
44
+ if (handles[i].indexOf(' ') >= 0) {
45
+ const list = handles[i].split(' ');
46
+ for (let j = 0; j < list.length; ++j) {
47
+ fhevmHandles.push(FhevmHandle.fromBytes32Hex(list[j]));
48
+ }
49
+ } else {
50
+ fhevmHandles.push(FhevmHandle.fromBytes32Hex(handles[i]));
51
+ }
52
+ }
53
+ return fhevmHandles;
54
+ }
55
+
56
+ export function createWallet({ mnemonic, path, basePath, index, wordlist }) {
57
+ basePath = basePath || "m/44'/60'/0'/0/";
58
+ index = index || 0;
59
+ const hdNode = ethers.HDNodeWallet.fromPhrase(
60
+ mnemonic,
61
+ undefined, // password
62
+ path || `${basePath}${index}`,
63
+ wordlist,
64
+ );
65
+ return { wallet: hdNode, address: hdNode.address };
66
+ }
67
+
68
+ export function addCommonOptions(command) {
69
+ return command
70
+ .option('--contract-address <contract address>', 'address of the contract')
71
+ .option('--user-address <user address>', 'address of the account')
72
+ .option(
73
+ '--network <testnet|devnet>',
74
+ 'network name, must be "testnet" or "devnet"',
75
+ )
76
+ .option('--acl <ACL contract address>', 'ACL contract address')
77
+ .option(
78
+ '--kms-verifier <KMSVerifier contract address>',
79
+ 'KMSVerifier contract address',
80
+ )
81
+ .option(
82
+ '--input-verifier <InputVerifier contract address>',
83
+ 'InputVerifier contract address',
84
+ )
85
+ .option(
86
+ '--gateway-input-verification <Gateway input verification contract address>',
87
+ 'Gateway input verification contract address',
88
+ )
89
+ .option(
90
+ '--gateway-decryption-verification <Gateway decryption verification contract address>',
91
+ 'Gateway decryption verification contract address',
92
+ )
93
+ .option('--chain <chain ID>', 'The chain ID')
94
+ .option('--gateway-chain <gateway chain ID>', 'The gateway chain ID')
95
+ .option('--rpc-url <rpc url>', 'The rpc url')
96
+ .option('--relayer-url <relayer url>', 'The relayer url')
97
+ .option('--mnemonic <word list>', 'Mnemonic word list')
98
+ .option('--version <route v1 or v2>', 'The default route version: 1|2')
99
+ .option('--clear-cache', 'Clear the FHEVM public key cache')
100
+ .option('--json', 'Ouput in JSON format')
101
+ .option('--verbose', 'Verbose output');
102
+ }
103
+
104
+ /**
105
+ * @param {object} options - Command line options
106
+ * @returns {{
107
+ * config: {
108
+ * name: 'testnet' | 'devnet',
109
+ * version: 1 | 2,
110
+ * walletAddress: string,
111
+ * userAddress: string,
112
+ * contractAddress: string,
113
+ * fhevmInstanceConfig: {
114
+ * aclContractAddress: string,
115
+ * kmsContractAddress: string,
116
+ * inputVerifierContractAddress: string,
117
+ * verifyingContractAddressDecryption: string,
118
+ * verifyingContractAddressInputVerification: string,
119
+ * chainId: number,
120
+ * gatewayChainId: number,
121
+ * network: string,
122
+ * relayerUrl: string,
123
+ * },
124
+ * },
125
+ * wallet: import('ethers').HDNodeWallet | undefined,
126
+ * signer: import('ethers').HDNodeWallet | undefined,
127
+ * provider: import('ethers').JsonRpcProvider,
128
+ * }}
129
+ */
130
+ export function parseCommonOptions(options) {
131
+ const name = options?.network ?? 'devnet';
132
+ if (name !== 'testnet' && name !== 'devnet') {
133
+ throwError(`Invalid network name '${name}'.`);
134
+ }
135
+
136
+ let version = options?.version ?? 1;
137
+ if (version === 'v1' || version === '1') {
138
+ version = 1;
139
+ }
140
+ if (version === 'v2' || version === '2') {
141
+ version = 2;
142
+ }
143
+ if (version !== 1 && version !== 2) {
144
+ throwError(`Invalid relayer route version '${version}'.`);
145
+ }
146
+
147
+ let rpcUrl = options?.rpcUrl;
148
+ if (!rpcUrl) {
149
+ rpcUrl = getEnv('RPC_URL', `.env.${name}`);
150
+ }
151
+ if (!rpcUrl) {
152
+ throwError(`Missing Rpc Url.`);
153
+ }
154
+
155
+ let relayerUrl = options?.relayerUrl;
156
+ if (!relayerUrl) {
157
+ relayerUrl = getEnv('RELAYER_URL', `.env.${name}`);
158
+ }
159
+ if (!relayerUrl) {
160
+ throwError(`Missing relayer Url.`);
161
+ }
162
+
163
+ if (version === 1) {
164
+ if (!relayerUrl.endsWith('/v1')) {
165
+ relayerUrl = relayerUrl + '/v1';
166
+ }
167
+ }
168
+ if (version === 2) {
169
+ if (!relayerUrl.endsWith('/v2')) {
170
+ relayerUrl = relayerUrl + '/v2';
171
+ }
172
+ }
173
+
174
+ let contractAddress = options?.contractAddress;
175
+ if (!contractAddress) {
176
+ contractAddress = getEnv('CONTRACT_ADDRESS', `.env.${name}`);
177
+ }
178
+ if (!contractAddress) {
179
+ contractAddress = getEnv(
180
+ 'FHE_COUNTER_PUBLIC_DECRYPT_ADDRESS',
181
+ `.env.${name}`,
182
+ );
183
+ }
184
+ if (!isChecksummedAddress(contractAddress)) {
185
+ throwError(`Invalid contract address '${contractAddress}'.`);
186
+ }
187
+
188
+ let userAddress = options?.userAddress;
189
+ if (userAddress && !isChecksummedAddress(userAddress)) {
190
+ userAddress = getEnv('USER_ADDRESS', `.env.${name}`);
191
+ }
192
+
193
+ let aclContractAddress = options?.acl;
194
+ if (!aclContractAddress) {
195
+ aclContractAddress = getEnv('ACL_CONTRACT_ADDRESS', `.env.${name}`);
196
+ }
197
+ if (!isChecksummedAddress(aclContractAddress)) {
198
+ throwError(`Invalid ACL address '${aclContractAddress}'.`);
199
+ }
200
+
201
+ let kmsContractAddress = options?.kmsVerifier;
202
+ if (!kmsContractAddress) {
203
+ kmsContractAddress = getEnv(
204
+ 'KMS_VERIFIER_CONTRACT_ADDRESS',
205
+ `.env.${name}`,
206
+ );
207
+ }
208
+ if (!isChecksummedAddress(kmsContractAddress)) {
209
+ throwError(`Invalid KMSVerifier address '${kmsContractAddress}'.`);
210
+ }
211
+
212
+ let inputVerifierContractAddress = options?.inputVerifier;
213
+ if (!inputVerifierContractAddress) {
214
+ inputVerifierContractAddress = getEnv(
215
+ 'INPUT_VERIFIER_CONTRACT_ADDRESS',
216
+ `.env.${name}`,
217
+ );
218
+ }
219
+ if (!isChecksummedAddress(inputVerifierContractAddress)) {
220
+ throwError(
221
+ `Invalid InputVerifier address '${inputVerifierContractAddress}'.`,
222
+ );
223
+ }
224
+
225
+ let verifyingContractAddressInputVerification =
226
+ options?.gatewayInputVerification;
227
+ if (!verifyingContractAddressInputVerification) {
228
+ verifyingContractAddressInputVerification = getEnv(
229
+ 'INPUT_VERIFICATION_ADDRESS',
230
+ `.env.${name}`,
231
+ );
232
+ }
233
+ if (!isChecksummedAddress(kmsContractAddress)) {
234
+ throwError(`Invalid KMSVerifier address '${kmsContractAddress}'.`);
235
+ }
236
+
237
+ let verifyingContractAddressDecryption =
238
+ options?.gatewayDecryptionVerification;
239
+ if (!verifyingContractAddressDecryption) {
240
+ verifyingContractAddressDecryption = getEnv(
241
+ 'DECRYPTION_ADDRESS',
242
+ `.env.${name}`,
243
+ );
244
+ }
245
+ if (!isChecksummedAddress(kmsContractAddress)) {
246
+ throwError(`Invalid KMSVerifier address '${kmsContractAddress}'.`);
247
+ }
248
+
249
+ //
250
+ let chainId = options?.chain;
251
+ if (!chainId) {
252
+ chainId = getEnv('CHAIN_ID', `.env.${name}`);
253
+ }
254
+ chainId = Number.parseInt(chainId);
255
+ if (Number.isNaN(chainId)) {
256
+ throwError(`Invalid chain ID '${chainId}'.`);
257
+ }
258
+
259
+ let gatewayChainId = options?.gatewayChain;
260
+ if (!gatewayChainId) {
261
+ gatewayChainId = getEnv('CHAIN_ID_GATEWAY', `.env.${name}`);
262
+ }
263
+ gatewayChainId = Number.parseInt(gatewayChainId);
264
+ if (Number.isNaN(gatewayChainId)) {
265
+ throwError(`Invalid gateway chain ID '${gatewayChainId}'.`);
266
+ }
267
+
268
+ const mnemonic = options?.mnemonic ?? getEnv('MNEMONIC');
269
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
270
+ const walletResult = mnemonic ? createWallet({ mnemonic }) : undefined;
271
+ const wallet = walletResult?.wallet;
272
+ const signer = wallet?.connect(provider);
273
+
274
+ const config = {
275
+ name: name,
276
+ version,
277
+ walletAddress: wallet?.address,
278
+ userAddress: userAddress ?? wallet?.address,
279
+ contractAddress,
280
+ fhevmInstanceConfig: {
281
+ aclContractAddress,
282
+ kmsContractAddress,
283
+ inputVerifierContractAddress,
284
+ verifyingContractAddressDecryption,
285
+ verifyingContractAddressInputVerification,
286
+ chainId,
287
+ gatewayChainId,
288
+ network: rpcUrl,
289
+ relayerUrl,
290
+ },
291
+ };
292
+
293
+ return { config, provider, wallet, signer };
294
+ }
295
+
296
+ export function valueColumnTypeListToFheTypedValues(list) {
297
+ return list.map((str) => {
298
+ const [valueStr, fheTypeName] = str.split(':');
299
+ if (!isFheTypeName(fheTypeName)) {
300
+ throwError(`Invalid FheType name: ${fheTypeName}`);
301
+ }
302
+ let value;
303
+ if (fheTypeName === 'ebool') {
304
+ value = valueStr === 'true' ? true : false;
305
+ } else if (fheTypeName === 'eaddress') {
306
+ value = valueStr;
307
+ } else if (
308
+ fheTypeName === 'euint8' ||
309
+ fheTypeName === 'euint16' ||
310
+ fheTypeName === 'euint32'
311
+ ) {
312
+ value = Number(valueStr);
313
+ } else {
314
+ value = BigInt(valueStr);
315
+ if (value <= BigInt(Number.MAX_SAFE_INTEGER)) {
316
+ value = Number(value);
317
+ }
318
+ }
319
+ return { fheType: fheTypeName, value };
320
+ });
321
+ }
322
+
323
+ export function fheTypedValuesToBuilderFunctionWithArg(fheTypedValues) {
324
+ return fheTypedValues.map((pair) => {
325
+ const { value, fheType } = pair;
326
+ if (!isFheTypeName(fheType)) {
327
+ throwError(`Invalid FheType name: ${fheType}`);
328
+ }
329
+ let funcName;
330
+ if (fheType === 'ebool') {
331
+ funcName = 'addBool';
332
+ } else if (fheType === 'eaddress') {
333
+ funcName = 'addAddress';
334
+ } else {
335
+ const bits = encryptionBitsFromFheTypeName(fheType);
336
+ funcName = `add${bits}`;
337
+ }
338
+ return { funcName, arg: value };
339
+ });
340
+ }
341
+
342
+ export function jsonParseFheTypedValues(text) {
343
+ return JSON.parse(text, (key, value) => {
344
+ if (value === 'true' || value === true) {
345
+ return true;
346
+ }
347
+ if (value === 'false' || value === true) {
348
+ return false;
349
+ }
350
+ if (typeof value === 'string' && value.startsWith('0x')) {
351
+ return value;
352
+ }
353
+ return BigInt(value);
354
+ });
355
+ }