@devtion/devcli 0.0.0-3df1645 → 0.0.0-477457c
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/README.md +2 -2
- package/dist/.env +14 -0
- package/dist/index.js +487 -82
- package/dist/public/mini-semaphore.wasm +0 -0
- package/dist/public/mini-semaphore.zkey +0 -0
- package/dist/types/commands/authBandada.d.ts +2 -0
- package/dist/types/commands/authSIWE.d.ts +7 -0
- package/dist/types/commands/ceremony/index.d.ts +3 -0
- package/dist/types/commands/ceremony/listParticipants.d.ts +2 -0
- package/dist/types/commands/index.d.ts +2 -0
- package/dist/types/commands/setup.d.ts +3 -3
- package/dist/types/lib/bandada.d.ts +6 -0
- package/dist/types/lib/files.d.ts +0 -1
- package/dist/types/lib/localConfigs.d.ts +38 -0
- package/dist/types/lib/prompts.d.ts +6 -6
- package/dist/types/types/index.d.ts +69 -0
- package/package.json +12 -5
- package/src/commands/auth.ts +18 -8
- package/src/commands/authBandada.ts +120 -0
- package/src/commands/authSIWE.ts +185 -0
- package/src/commands/ceremony/index.ts +20 -0
- package/src/commands/ceremony/listParticipants.ts +56 -0
- package/src/commands/contribute.ts +48 -25
- package/src/commands/finalize.ts +9 -5
- package/src/commands/index.ts +2 -0
- package/src/commands/logout.ts +3 -1
- package/src/commands/observe.ts +6 -2
- package/src/commands/setup.ts +32 -18
- package/src/index.ts +18 -5
- package/src/lib/bandada.ts +51 -0
- package/src/lib/errors.ts +2 -2
- package/src/lib/localConfigs.ts +54 -0
- package/src/lib/prompts.ts +11 -15
- package/src/lib/services.ts +38 -13
- package/src/lib/utils.ts +13 -9
- package/src/types/index.ts +75 -0
package/dist/index.js
CHANGED
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @module @p0tion/phase2cli
|
|
5
|
-
* @version 1.
|
|
5
|
+
* @version 1.2.8
|
|
6
6
|
* @file All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies
|
|
7
7
|
* @copyright Ethereum Foundation 2022
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @see [Github]{@link https://github.com/privacy-scaling-explorations/p0tion}
|
|
10
10
|
*/
|
|
11
11
|
import { createCommand } from 'commander';
|
|
12
|
-
import fs, { readFileSync, createWriteStream, renameSync } from 'fs';
|
|
13
|
-
import { dirname } from 'path';
|
|
12
|
+
import fs, { readFileSync, createWriteStream, existsSync, renameSync } from 'fs';
|
|
13
|
+
import path, { dirname } from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import { zKey } from 'snarkjs';
|
|
15
|
+
import { zKey, groth16 } from 'snarkjs';
|
|
16
16
|
import boxen from 'boxen';
|
|
17
17
|
import { pipeline } from 'node:stream';
|
|
18
18
|
import { promisify } from 'node:util';
|
|
19
19
|
import fetch$1 from 'node-fetch';
|
|
20
|
-
import { commonTerms, formatZkeyIndex, getZkeyStorageFilePath, finalContributionIndex, createCustomLoggerForFile, getBucketName, progressToNextContributionStep, permanentlyStoreCurrentContributionTimeAndHash, convertToDoubleDigits, multiPartUpload, verifyContribution, generateGetObjectPreSignedUrl, convertBytesOrKbToGb, numExpIterations, getDocumentById, getParticipantsCollectionPath, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs, extractPrefix, autoGenerateEntropy, vmConfigurationTypes, initializeFirebaseCoreServices, signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator, parseCeremonyFile, blake512FromPath, checkIfObjectExist, setupCeremony, genesisZkeyIndex, getR1csStorageFilePath, getWasmStorageFilePath, getPotStorageFilePath, extractPoTFromFilename, potFileDownloadMainUrl, createS3Bucket, potFilenameTemplate, getR1CSInfo, getOpenedCeremonies, getCeremonyCircuits, checkParticipantForCeremony, getCurrentActiveParticipantTimeout, getCircuitBySequencePosition, getCircuitContributionsFromContributor, progressToNextCircuitForContribution, resumeContributionAfterTimeoutExpiration, generateValidContributionsAttestation, getContributionsValidityForContributor, getClosedCeremonies, checkAndPrepareCoordinatorForFinalization, computeSHA256ToHex, finalizeCeremony, getVerificationKeyStorageFilePath, verificationKeyAcronym, getVerifierContractStorageFilePath, verifierSmartContractAcronym, finalizeCircuit, exportVkey, exportVerifierContract } from '@devtion/actions';
|
|
20
|
+
import { commonTerms, formatZkeyIndex, getZkeyStorageFilePath, finalContributionIndex, createCustomLoggerForFile, getBucketName, progressToNextContributionStep, contribHashRegex, permanentlyStoreCurrentContributionTimeAndHash, convertToDoubleDigits, multiPartUpload, verifyContribution, generateGetObjectPreSignedUrl, convertBytesOrKbToGb, numExpIterations, getDocumentById, getParticipantsCollectionPath, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs, extractPrefix, autoGenerateEntropy, vmConfigurationTypes, initializeFirebaseCoreServices, signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator, parseCeremonyFile, blake512FromPath, checkIfObjectExist, setupCeremony, genesisZkeyIndex, getR1csStorageFilePath, getWasmStorageFilePath, getPotStorageFilePath, extractPoTFromFilename, potFileDownloadMainUrl, createS3Bucket, potFilenameTemplate, getR1CSInfo, getOpenedCeremonies, getCeremonyCircuits, checkParticipantForCeremony, getCurrentActiveParticipantTimeout, getCircuitBySequencePosition, getCircuitContributionsFromContributor, progressToNextCircuitForContribution, resumeContributionAfterTimeoutExpiration, generateValidContributionsAttestation, getContributionsValidityForContributor, getClosedCeremonies, checkAndPrepareCoordinatorForFinalization, computeSHA256ToHex, finalizeCeremony, getVerificationKeyStorageFilePath, verificationKeyAcronym, getVerifierContractStorageFilePath, verifierSmartContractAcronym, finalizeCircuit, exportVkey, exportVerifierContract, getAllCeremonies } from '@devtion/actions';
|
|
21
21
|
import fetch from '@adobe/node-fetch-retry';
|
|
22
22
|
import { request } from '@octokit/request';
|
|
23
23
|
import { SingleBar, Presets } from 'cli-progress';
|
|
24
24
|
import dotenv from 'dotenv';
|
|
25
|
-
import { GithubAuthProvider, getAuth, signOut } from 'firebase/auth';
|
|
25
|
+
import { GithubAuthProvider, signInWithCustomToken, getAuth, signOut } from 'firebase/auth';
|
|
26
26
|
import { getDiskInfoSync } from 'node-disk-info';
|
|
27
27
|
import ora from 'ora';
|
|
28
28
|
import { Timer } from 'timer-node';
|
|
@@ -36,7 +36,10 @@ import figlet from 'figlet';
|
|
|
36
36
|
import { createOAuthDeviceAuth } from '@octokit/auth-oauth-device';
|
|
37
37
|
import clipboard from 'clipboardy';
|
|
38
38
|
import open from 'open';
|
|
39
|
-
import {
|
|
39
|
+
import { Identity } from '@semaphore-protocol/identity';
|
|
40
|
+
import { httpsCallable } from 'firebase/functions';
|
|
41
|
+
import { ApiSdk } from '@bandada/api-sdk';
|
|
42
|
+
import { Timestamp, onSnapshot, doc, collection, getDocs } from 'firebase/firestore';
|
|
40
43
|
import readline from 'readline';
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -97,7 +100,7 @@ const CORE_SERVICES_ERRORS = {
|
|
|
97
100
|
FIREBASE_TOKEN_EXPIRED_REMOVED_PERMISSIONS: `The Github authorization has failed due to lack of association between your account and the CLI`,
|
|
98
101
|
FIREBASE_USER_DISABLED: `The Github account has been suspended by the ceremony coordinator(s), blocking the possibility of contribution. Please, contact them to understand the motivation behind it.`,
|
|
99
102
|
FIREBASE_FAILED_CREDENTIALS_VERIFICATION: `Firebase cannot verify your Github credentials due to network errors. Please, try once again later.`,
|
|
100
|
-
FIREBASE_NETWORK_ERROR: `Unable to reach Firebase due to network
|
|
103
|
+
FIREBASE_NETWORK_ERROR: `Unable to reach Firebase due to network errors. Please, try once again later and make sure your Internet connection is stable.`,
|
|
101
104
|
FIREBASE_CEREMONY_NOT_OPENED: `There are no ceremonies opened to contributions`,
|
|
102
105
|
FIREBASE_CEREMONY_NOT_CLOSED: `There are no ceremonies ready to finalization`,
|
|
103
106
|
AWS_CEREMONY_BUCKET_CREATION: `Unable to create a new bucket for the ceremony. Something went wrong during the creation. Please, repeat the process by providing a new ceremony name of the ceremony.`,
|
|
@@ -119,7 +122,7 @@ const COMMAND_ERRORS = {
|
|
|
119
122
|
COMMAND_SETUP_NO_R1CS: `Unable to retrieve R1CS files from current working directory. Please, run this command from a working directory where the R1CS files are located to continue with the setup process. We kindly ask you to run the command from an empty directory containing only the R1CS and WASM files.`,
|
|
120
123
|
COMMAND_SETUP_NO_WASM: `Unable to retrieve WASM files from current working directory. Please, run this command from a working directory where the WASM files are located to continue with the setup process. We kindly ask you to run the command from an empty directory containing only the WASM and R1CS files.`,
|
|
121
124
|
COMMAND_SETUP_MISMATCH_R1CS_WASM: `The folder contains more R1CS files than WASM files (or vice versa). Please, run this command from a working directory where each R1CS is paired with its corresponding file WASM.`,
|
|
122
|
-
COMMAND_SETUP_DOWNLOAD_PTAU: `Unable to download Powers of Tau file from
|
|
125
|
+
COMMAND_SETUP_DOWNLOAD_PTAU: `Unable to download Powers of Tau file from PPoT Phase 1 Trusted Setup. Possible causes may involve an error while making the request (be sure to have a stable internet connection). Please, we kindly ask you to terminate the current session and repeat the process.`,
|
|
123
126
|
COMMAND_SETUP_ABORT: `You chose to abort the setup process.`,
|
|
124
127
|
COMMAND_CONTRIBUTE_NO_OPENED_CEREMONIES: `Unfortunately, there is no ceremony for which you can make a contribution at this time. Please, try again later.`,
|
|
125
128
|
COMMAND_CONTRIBUTE_NO_PARTICIPANT_DATA: `Unable to retrieve your data as ceremony participant. Please, terminate the current session and try again later. If the error persists, please contact the ceremony coordinator.`,
|
|
@@ -235,6 +238,14 @@ const checkAndMakeNewDirectoryIfNonexistent = (directoryLocalPath) => {
|
|
|
235
238
|
const writeLocalJsonFile = (filePath, data) => {
|
|
236
239
|
fs.writeFileSync(filePath, JSON.stringify(data), "utf-8");
|
|
237
240
|
};
|
|
241
|
+
/**
|
|
242
|
+
* Return the local current project directory name.
|
|
243
|
+
* @returns <string> - the local project (e.g., dist/) directory name.
|
|
244
|
+
*/
|
|
245
|
+
const getLocalDirname = () => {
|
|
246
|
+
const filename = fileURLToPath(import.meta.url);
|
|
247
|
+
return path.dirname(filename);
|
|
248
|
+
};
|
|
238
249
|
|
|
239
250
|
// Get npm package name.
|
|
240
251
|
const packagePath$4 = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
@@ -250,6 +261,14 @@ const config = new Conf({
|
|
|
250
261
|
accessToken: {
|
|
251
262
|
type: "string",
|
|
252
263
|
default: ""
|
|
264
|
+
},
|
|
265
|
+
bandadaIdentity: {
|
|
266
|
+
type: "string",
|
|
267
|
+
default: ""
|
|
268
|
+
},
|
|
269
|
+
authMethod: {
|
|
270
|
+
type: "string",
|
|
271
|
+
default: ""
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
274
|
});
|
|
@@ -310,6 +329,39 @@ const setLocalAccessToken = (token) => config.set("accessToken", token);
|
|
|
310
329
|
* Delete the stored access token.
|
|
311
330
|
*/
|
|
312
331
|
const deleteLocalAccessToken = () => config.delete("accessToken");
|
|
332
|
+
/**
|
|
333
|
+
* Return the Bandada identity, if present.
|
|
334
|
+
* @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
|
|
335
|
+
*/
|
|
336
|
+
const getLocalBandadaIdentity = () => config.get("bandadaIdentity");
|
|
337
|
+
/**
|
|
338
|
+
* Check if the Bandada identity exists in the local storage.
|
|
339
|
+
* @returns <boolean>
|
|
340
|
+
*/
|
|
341
|
+
const checkLocalBandadaIdentity = () => config.has("bandadaIdentity") && !!config.get("bandadaIdentity");
|
|
342
|
+
/**
|
|
343
|
+
* Set the Bandada identity.
|
|
344
|
+
* @param identity <string> - the Bandada identity to be stored.
|
|
345
|
+
*/
|
|
346
|
+
const setLocalBandadaIdentity = (identity) => config.set("bandadaIdentity", identity);
|
|
347
|
+
/**
|
|
348
|
+
* Delete the stored Bandada identity.
|
|
349
|
+
*/
|
|
350
|
+
const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity");
|
|
351
|
+
/**
|
|
352
|
+
* Return the authentication method, if present.
|
|
353
|
+
* @returns <string | undefined> - the authentication method if present, otherwise undefined.
|
|
354
|
+
*/
|
|
355
|
+
const getLocalAuthMethod = () => config.get("authMethod");
|
|
356
|
+
/**
|
|
357
|
+
* Set the authentication method.
|
|
358
|
+
* @param method <string> - the authentication method to be stored.
|
|
359
|
+
*/
|
|
360
|
+
const setLocalAuthMethod = (method) => config.set("authMethod", method);
|
|
361
|
+
/**
|
|
362
|
+
* Delete the stored authentication method.
|
|
363
|
+
*/
|
|
364
|
+
const deleteLocalAuthMethod = () => config.delete("authMethod");
|
|
313
365
|
/**
|
|
314
366
|
* Get the complete local file path.
|
|
315
367
|
* @param cwd <string> - the current working directory path.
|
|
@@ -420,7 +472,7 @@ const getGithubAuthenticatedUserGists = async (githubToken, params) => {
|
|
|
420
472
|
headers: {
|
|
421
473
|
authorization: `token ${githubToken}`
|
|
422
474
|
},
|
|
423
|
-
per_page: params.perPage,
|
|
475
|
+
per_page: params.perPage, // max items per page = 100.
|
|
424
476
|
page: params.page
|
|
425
477
|
});
|
|
426
478
|
if (response && response.status === 200)
|
|
@@ -468,9 +520,10 @@ const getPublicAttestationGist = async (githubToken, publicAttestationFilename)
|
|
|
468
520
|
* @returns <string> - the third-party provider handle of the user.
|
|
469
521
|
*/
|
|
470
522
|
const getUserHandleFromProviderUserId = (providerUserId) => {
|
|
471
|
-
if (providerUserId.indexOf("-") === -1)
|
|
472
|
-
|
|
473
|
-
|
|
523
|
+
if (providerUserId.indexOf("-") === -1) {
|
|
524
|
+
return providerUserId;
|
|
525
|
+
}
|
|
526
|
+
return providerUserId.substring(0, providerUserId.lastIndexOf("-"));
|
|
474
527
|
};
|
|
475
528
|
/**
|
|
476
529
|
* Return a custom spinner.
|
|
@@ -584,19 +637,22 @@ const publishGist = async (token, content, ceremonyTitle, ceremonyPrefix) => {
|
|
|
584
637
|
* @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
|
|
585
638
|
* @returns <string> - the ready to share tweet url.
|
|
586
639
|
*/
|
|
587
|
-
const generateCustomUrlToTweetAboutParticipation = (ceremonyName, gistUrl, isFinalizing) =>
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
ceremonyName.toLowerCase().includes("
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
ceremonyName.toLowerCase().includes("
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
640
|
+
const generateCustomUrlToTweetAboutParticipation = (ceremonyName, gistUrl, isFinalizing) => {
|
|
641
|
+
ceremonyName = ceremonyName.replace(/ /g, "%20");
|
|
642
|
+
return isFinalizing
|
|
643
|
+
? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
|
|
644
|
+
ceremonyName.toLowerCase().includes("setup") ||
|
|
645
|
+
ceremonyName.toLowerCase().includes("phase2") ||
|
|
646
|
+
ceremonyName.toLowerCase().includes("ceremony")
|
|
647
|
+
? "!"
|
|
648
|
+
: "%20Phase%202%20Trusted%20Setup%20ceremony!"}%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
|
|
649
|
+
: `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
|
|
650
|
+
ceremonyName.toLowerCase().includes("setup") ||
|
|
651
|
+
ceremonyName.toLowerCase().includes("phase2") ||
|
|
652
|
+
ceremonyName.toLowerCase().includes("ceremony")
|
|
653
|
+
? "!"
|
|
654
|
+
: "%20Phase%202%20Trusted%20Setup%20ceremony!"}%20You%20can%20view%20the%20steps%20to%20contribute%20here:%20https://ceremony.pse.dev%20You%20can%20view%20my%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP`;
|
|
655
|
+
};
|
|
600
656
|
/**
|
|
601
657
|
* Return a custom progress bar.
|
|
602
658
|
* @param type <ProgressBarType> - the type of the progress bar.
|
|
@@ -800,7 +856,7 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
800
856
|
spinner.start();
|
|
801
857
|
// Read local transcript file info to get the contribution hash.
|
|
802
858
|
const transcriptContents = readFile(transcriptLocalFilePath);
|
|
803
|
-
const matchContributionHash = transcriptContents.match(
|
|
859
|
+
const matchContributionHash = transcriptContents.match(contribHashRegex);
|
|
804
860
|
if (!matchContributionHash)
|
|
805
861
|
showError(COMMAND_ERRORS.COMMAND_CONTRIBUTE_FINALIZE_NO_TRANSCRIPT_CONTRIBUTION_HASH_MATCH, true);
|
|
806
862
|
// Format contribution hash.
|
|
@@ -1016,7 +1072,7 @@ const promptCircomCompiler = async () => {
|
|
|
1016
1072
|
* Shows a list of circuits for a single option selection.
|
|
1017
1073
|
* @dev the circuit names are derived from local R1CS files.
|
|
1018
1074
|
* @param options <Array<string>> - an array of circuits names.
|
|
1019
|
-
* @returns Promise<string> - the name of the
|
|
1075
|
+
* @returns Promise<string> - the name of the chosen circuit.
|
|
1020
1076
|
*/
|
|
1021
1077
|
const promptCircuitSelector = async (options) => {
|
|
1022
1078
|
const { circuitFilename } = await prompts({
|
|
@@ -1034,7 +1090,7 @@ const promptCircuitSelector = async (options) => {
|
|
|
1034
1090
|
* Shows a list of standard EC2 VM instance types for a single option selection.
|
|
1035
1091
|
* @notice the suggested VM configuration type is calculated based on circuit constraint size.
|
|
1036
1092
|
* @param constraintSize <number> - the amount of circuit constraints
|
|
1037
|
-
* @returns Promise<string> - the name of the
|
|
1093
|
+
* @returns Promise<string> - the name of the chosen VM type.
|
|
1038
1094
|
*/
|
|
1039
1095
|
const promptVMTypeSelector = async (constraintSize) => {
|
|
1040
1096
|
let suggestedConfiguration = 0;
|
|
@@ -1131,7 +1187,7 @@ const promptVMDiskTypeSelector = async () => {
|
|
|
1131
1187
|
/**
|
|
1132
1188
|
* Show a series of questions about the circuits.
|
|
1133
1189
|
* @param constraintSize <number> - the amount of circuit constraints.
|
|
1134
|
-
* @param timeoutMechanismType <CeremonyTimeoutType> - the
|
|
1190
|
+
* @param timeoutMechanismType <CeremonyTimeoutType> - the chosen timeout mechanism type for the ceremony.
|
|
1135
1191
|
* @param needPromptCircomCompiler <boolean> - a boolean value indicating if the questions related to the Circom compiler version and commit hash must be asked.
|
|
1136
1192
|
* @param enforceVM <boolean> - a boolean value indicating if the contribution verification could be supported by VM-only approach or not.
|
|
1137
1193
|
* @returns Promise<Array<Circuit>> - circuit info prompted by the coordinator.
|
|
@@ -1199,7 +1255,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1199
1255
|
circomVersion = version;
|
|
1200
1256
|
circomCommitHash = commitHash;
|
|
1201
1257
|
}
|
|
1202
|
-
// Ask for
|
|
1258
|
+
// Ask for preferred contribution verification method (CF vs VM).
|
|
1203
1259
|
if (!enforceVM) {
|
|
1204
1260
|
const { confirmation } = await askForConfirmation(`The contribution verification can be performed using Cloud Functions (CF, cheaper for small contributions but limited to 1M constraints) or custom virtual machines (expensive but could scale up to 30M constraints). Be aware about VM costs and if you wanna learn more, please visit the documentation to have a complete overview about cost estimation of the two mechanisms.\nChoose the contribution verification mechanism`, `CF`, // eq. true.
|
|
1205
1261
|
`VM` // eq. false.
|
|
@@ -1327,7 +1383,7 @@ const promptCircuitAddition = async () => {
|
|
|
1327
1383
|
* Shows a list of pre-computed zKeys for a single option selection.
|
|
1328
1384
|
* @dev the names are derived from local zKeys files.
|
|
1329
1385
|
* @param options <Array<string>> - an array of pre-computed zKeys names.
|
|
1330
|
-
* @returns Promise<string> - the name of the
|
|
1386
|
+
* @returns Promise<string> - the name of the chosen pre-computed zKey.
|
|
1331
1387
|
*/
|
|
1332
1388
|
const promptPreComputedZkeySelector = async (options) => {
|
|
1333
1389
|
const { preComputedZkeyFilename } = await prompts({
|
|
@@ -1365,13 +1421,13 @@ const promptNeededPowersForCircuit = async (suggestedSmallestNeededPowers) => {
|
|
|
1365
1421
|
* Shows a list of PoT files for a single option selection.
|
|
1366
1422
|
* @dev the names are derived from local PoT files.
|
|
1367
1423
|
* @param options <Array<string>> - an array of PoT file names.
|
|
1368
|
-
* @returns Promise<string> - the name of the
|
|
1424
|
+
* @returns Promise<string> - the name of the chosen PoT.
|
|
1369
1425
|
*/
|
|
1370
1426
|
const promptPotSelector = async (options) => {
|
|
1371
1427
|
const { potFilename } = await prompts({
|
|
1372
1428
|
type: "select",
|
|
1373
1429
|
name: "potFilename",
|
|
1374
|
-
message: theme.text.bold("Select the Powers of Tau file
|
|
1430
|
+
message: theme.text.bold("Select the Powers of Tau file chosen for the circuit"),
|
|
1375
1431
|
choices: options.map((option) => {
|
|
1376
1432
|
console.log(option);
|
|
1377
1433
|
return { title: option, value: option };
|
|
@@ -1389,7 +1445,7 @@ const promptPotSelector = async (options) => {
|
|
|
1389
1445
|
* @param isFinalizing <boolean> - true when the coordinator must select a ceremony for finalization; otherwise false (participant selects a ceremony for contribution).
|
|
1390
1446
|
* @returns Promise<FirebaseDocumentInfo> - the Firestore document of the selected ceremony.
|
|
1391
1447
|
*/
|
|
1392
|
-
const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing) => {
|
|
1448
|
+
const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing, messageToDisplay) => {
|
|
1393
1449
|
// Prepare state.
|
|
1394
1450
|
const choices = [];
|
|
1395
1451
|
// Prepare choices x ceremony.
|
|
@@ -1407,9 +1463,7 @@ const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing) =>
|
|
|
1407
1463
|
const { ceremony } = await prompts({
|
|
1408
1464
|
type: "select",
|
|
1409
1465
|
name: "ceremony",
|
|
1410
|
-
message: theme.text.bold(
|
|
1411
|
-
? "Which ceremony would you like to contribute to?"
|
|
1412
|
-
: "Which ceremony would you like to finalize?"),
|
|
1466
|
+
message: theme.text.bold(messageToDisplay),
|
|
1413
1467
|
choices,
|
|
1414
1468
|
initial: 0
|
|
1415
1469
|
});
|
|
@@ -1441,7 +1495,7 @@ const promptToTypeEntropyOrBeacon = async (isEntropy = true) => {
|
|
|
1441
1495
|
* @return <Promise<string>> - the entropy.
|
|
1442
1496
|
*/
|
|
1443
1497
|
const promptForEntropy = async () => {
|
|
1444
|
-
// Prompt for entropy generation
|
|
1498
|
+
// Prompt for entropy generation preferred method.
|
|
1445
1499
|
const { confirmation } = await askForConfirmation(`Do you prefer to type your entropy or generate it randomly?`, "Manually", "Randomly");
|
|
1446
1500
|
if (confirmation === undefined)
|
|
1447
1501
|
showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
|
|
@@ -1560,16 +1614,37 @@ const checkAuth = async (firebaseApp) => {
|
|
|
1560
1614
|
showError(THIRD_PARTY_SERVICES_ERRORS.GITHUB_NOT_AUTHENTICATED, true);
|
|
1561
1615
|
// Retrieve local access token.
|
|
1562
1616
|
const token = String(getLocalAccessToken());
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1617
|
+
let providerUserId;
|
|
1618
|
+
let username;
|
|
1619
|
+
const authMethod = getLocalAuthMethod();
|
|
1620
|
+
switch (authMethod) {
|
|
1621
|
+
case "github": {
|
|
1622
|
+
// Get credentials.
|
|
1623
|
+
const credentials = exchangeGithubTokenForCredentials(token);
|
|
1624
|
+
// Sign in to Firebase using credentials.
|
|
1625
|
+
await signInToFirebase(firebaseApp, credentials);
|
|
1626
|
+
// Get Github unique identifier (handle-id).
|
|
1627
|
+
providerUserId = await getGithubProviderUserId(String(token));
|
|
1628
|
+
username = getUserHandleFromProviderUserId(providerUserId);
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
case "bandada": {
|
|
1632
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1633
|
+
providerUserId = userCredentials.user.uid;
|
|
1634
|
+
username = providerUserId;
|
|
1635
|
+
break;
|
|
1636
|
+
}
|
|
1637
|
+
case "siwe": {
|
|
1638
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1639
|
+
providerUserId = userCredentials.user.uid;
|
|
1640
|
+
username = providerUserId;
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1567
1644
|
// Get current authenticated user.
|
|
1568
1645
|
const user = getCurrentFirebaseAuthUser(firebaseApp);
|
|
1569
|
-
// Get Github unique identifier (handle-id).
|
|
1570
|
-
const providerUserId = await getGithubProviderUserId(String(token));
|
|
1571
1646
|
// Greet the user.
|
|
1572
|
-
console.log(`Greetings, @${theme.text.bold(
|
|
1647
|
+
console.log(`Greetings, @${theme.text.bold(username)} ${theme.emojis.wave}\n`);
|
|
1573
1648
|
return {
|
|
1574
1649
|
user,
|
|
1575
1650
|
token,
|
|
@@ -1658,7 +1733,7 @@ const handleAdditionOfCircuitsToCeremony = async (r1csOptions, wasmOptions, cere
|
|
|
1658
1733
|
wasmFilename.split(`.${commonTerms.foldersAndPathsTerms.wasm}`)[0]);
|
|
1659
1734
|
if (matchingWasms.length !== 1)
|
|
1660
1735
|
showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true);
|
|
1661
|
-
// Get input data for
|
|
1736
|
+
// Get input data for chosen circuit.
|
|
1662
1737
|
const circuitInputData = await getInputDataToAddCircuitToCeremony(choosenCircuitFilename, matchingWasms[0], ceremonyTimeoutMechanismType, sameCircomCompiler, circuitSequencePosition, sharedCircomCompilerData);
|
|
1663
1738
|
// Store circuit data.
|
|
1664
1739
|
inputDataForCircuits.push(circuitInputData);
|
|
@@ -1711,7 +1786,7 @@ const displayCeremonySummary = (ceremonyInputData, circuits) => {
|
|
|
1711
1786
|
};
|
|
1712
1787
|
/**
|
|
1713
1788
|
* Check if the smallest Powers of Tau has already been downloaded/stored in the correspondent local path
|
|
1714
|
-
* @dev we are downloading the Powers of Tau file from
|
|
1789
|
+
* @dev we are downloading the Powers of Tau file from Perpetual Powers of Tau Phase 1 Trusted Setup.
|
|
1715
1790
|
* @param powers <string> - the smallest amount of powers needed for the given circuit (should be in a 'XY' stringified form).
|
|
1716
1791
|
* @param ptauCompleteFilename <string> - the complete file name of the powers of tau file to be downloaded.
|
|
1717
1792
|
* @returns <Promise<void>>
|
|
@@ -1725,7 +1800,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
|
|
|
1725
1800
|
.map((dirent) => dirent.name);
|
|
1726
1801
|
// Check if already downloaded or not.
|
|
1727
1802
|
if (smallestPtauFileForGivenPowers.length === 0) {
|
|
1728
|
-
const spinner = customSpinner(`Downloading the ${theme.text.bold(`#${powers}`)} smallest PoT file needed from the
|
|
1803
|
+
const spinner = customSpinner(`Downloading the ${theme.text.bold(`#${powers}`)} smallest PoT file needed from the Perpetual Powers of Tau Phase 1 Trusted Setup...`, `clock`);
|
|
1729
1804
|
spinner.start();
|
|
1730
1805
|
// Download smallest Powers of Tau file from remote server.
|
|
1731
1806
|
const streamPipeline = promisify(pipeline);
|
|
@@ -1748,7 +1823,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
|
|
|
1748
1823
|
* number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
|
|
1749
1824
|
* to provide a number of powers manually, ranging from the smallest possible to the largest.
|
|
1750
1825
|
* @param neededPowers <number> - the smallest amount of powers needed by the zKey.
|
|
1751
|
-
* @returns Promise<string, string> - the information about the
|
|
1826
|
+
* @returns Promise<string, string> - the information about the chosen Powers of Tau file for the pre-computed zKey
|
|
1752
1827
|
* along with related powers.
|
|
1753
1828
|
*/
|
|
1754
1829
|
const handlePreComputedZkeyPowersOfTauSelection = async (neededPowers) => {
|
|
@@ -1811,6 +1886,7 @@ const handleCeremonyBucketCreation = async (firebaseFunctions, ceremonyPrefix) =
|
|
|
1811
1886
|
spinner.start();
|
|
1812
1887
|
try {
|
|
1813
1888
|
// Make the call to create the bucket.
|
|
1889
|
+
spinner.info(`Creating bucket ${bucketName}`);
|
|
1814
1890
|
await createS3Bucket(firebaseFunctions, bucketName);
|
|
1815
1891
|
}
|
|
1816
1892
|
catch (error) {
|
|
@@ -1840,7 +1916,7 @@ const handleCircuitArtifactUploadToStorage = async (firebaseFunctions, bucketNam
|
|
|
1840
1916
|
* @notice The setup command allows the coordinator of the ceremony to prepare the next ceremony by interacting with the CLI.
|
|
1841
1917
|
* @dev For proper execution, the command must be run in a folder containing the R1CS files related to the circuits
|
|
1842
1918
|
* for which the coordinator wants to create the ceremony. The command will download the necessary Tau powers
|
|
1843
|
-
* from
|
|
1919
|
+
* from PPoT ceremony Phase 1 Setup Ceremony.
|
|
1844
1920
|
* @param cmd? <any> - the path to the ceremony setup file.
|
|
1845
1921
|
*/
|
|
1846
1922
|
const setup = async (cmd) => {
|
|
@@ -1891,12 +1967,20 @@ const setup = async (cmd) => {
|
|
|
1891
1967
|
// 3. generate the zKey
|
|
1892
1968
|
const spinner = customSpinner(`Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
|
|
1893
1969
|
spinner.start();
|
|
1894
|
-
|
|
1895
|
-
|
|
1970
|
+
if (existsSync(zkeyLocalPathAndFileName)) {
|
|
1971
|
+
spinner.succeed(`The genesis zKey for circuit ${theme.text.bold(circuit.name)} is already present on disk`);
|
|
1972
|
+
}
|
|
1973
|
+
else {
|
|
1974
|
+
await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined);
|
|
1975
|
+
spinner.succeed(`Generation of the genesis zKey for circuit ${theme.text.bold(circuit.name)} completed successfully`);
|
|
1976
|
+
}
|
|
1977
|
+
const hashSpinner = customSpinner(`Calculating hashes for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
|
|
1978
|
+
hashSpinner.start();
|
|
1896
1979
|
// 4. calculate the hashes
|
|
1897
1980
|
const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName);
|
|
1898
1981
|
const potBlake2bHash = await blake512FromPath(getPotLocalFilePath(circuit.files.potFilename));
|
|
1899
1982
|
const initialZkeyBlake2bHash = await blake512FromPath(zkeyLocalPathAndFileName);
|
|
1983
|
+
hashSpinner.succeed(`Hashes for circuit ${theme.text.bold(circuit.name)} calculated successfully`);
|
|
1900
1984
|
// 5. upload the artifacts
|
|
1901
1985
|
// Upload zKey to Storage.
|
|
1902
1986
|
await handleCircuitArtifactUploadToStorage(firebaseFunctions, bucketName, circuit.files.initialZkeyStoragePath, zkeyLocalPathAndFileName, circuit.files.initialZkeyFilename);
|
|
@@ -1914,9 +1998,9 @@ const setup = async (cmd) => {
|
|
|
1914
1998
|
// 6 update the setup data object
|
|
1915
1999
|
ceremonySetupData.circuits[index].files = {
|
|
1916
2000
|
...circuit.files,
|
|
1917
|
-
potBlake2bHash
|
|
1918
|
-
wasmBlake2bHash
|
|
1919
|
-
initialZkeyBlake2bHash
|
|
2001
|
+
potBlake2bHash,
|
|
2002
|
+
wasmBlake2bHash,
|
|
2003
|
+
initialZkeyBlake2bHash
|
|
1920
2004
|
};
|
|
1921
2005
|
ceremonySetupData.circuits[index].zKeySizeInBytes = getFileStats(zkeyLocalPathAndFileName).size;
|
|
1922
2006
|
}
|
|
@@ -2107,10 +2191,11 @@ const expirationCountdownForGithubOAuth = (expirationInSeconds) => {
|
|
|
2107
2191
|
// Update time and seconds counter.
|
|
2108
2192
|
expirationInSeconds -= interval;
|
|
2109
2193
|
secondsCounter -= interval;
|
|
2110
|
-
if (secondsCounter
|
|
2111
|
-
secondsCounter =
|
|
2194
|
+
if (secondsCounter === 0) {
|
|
2195
|
+
secondsCounter = 59;
|
|
2196
|
+
}
|
|
2112
2197
|
// Notify user.
|
|
2113
|
-
process.stdout.write(`${theme.symbols.warning} Expires in ${theme.text.bold(theme.colors.magenta(`00:${Math.floor(expirationInSeconds / 60)}:${secondsCounter}`))}\r`);
|
|
2198
|
+
process.stdout.write(`${theme.symbols.warning} Expires in ${theme.text.bold(theme.colors.magenta(`00:${Math.floor(expirationInSeconds / 60)}:${secondsCounter.toString().padStart(2, "0")}`))}\r`);
|
|
2114
2199
|
}
|
|
2115
2200
|
else {
|
|
2116
2201
|
process.stdout.write(`\n\n`); // workaround to \r.
|
|
@@ -2123,6 +2208,7 @@ const expirationCountdownForGithubOAuth = (expirationInSeconds) => {
|
|
|
2123
2208
|
* @param verification <Verification> - the data from Github OAuth2.0 device flow.
|
|
2124
2209
|
*/
|
|
2125
2210
|
const onVerification = async (verification) => {
|
|
2211
|
+
verification.interval = 7; // overwrite Github interval with 7 seconds
|
|
2126
2212
|
// Copy code to clipboard.
|
|
2127
2213
|
let noClipboard = false;
|
|
2128
2214
|
try {
|
|
@@ -2136,7 +2222,7 @@ const onVerification = async (verification) => {
|
|
|
2136
2222
|
console.log(`${theme.symbols.warning} Visit ${theme.text.bold(theme.text.underlined(verification.verification_uri))} on this device to generate a new token and authenticate\n`);
|
|
2137
2223
|
console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
|
|
2138
2224
|
const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``;
|
|
2139
|
-
console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} ${message} ${theme.symbols.success}
|
|
2225
|
+
console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} ${message} ${theme.symbols.success}\n`);
|
|
2140
2226
|
const spinner = customSpinner(`Redirecting to Github...`, `clock`);
|
|
2141
2227
|
spinner.start();
|
|
2142
2228
|
await sleep(10000); // ~10s to make users able to read the CLI.
|
|
@@ -2197,6 +2283,7 @@ const auth = async () => {
|
|
|
2197
2283
|
// Generate a new access token using Github Device Flow (OAuth 2.0).
|
|
2198
2284
|
const newToken = await executeGithubDeviceFlow(String(process.env.AUTH_GITHUB_CLIENT_ID));
|
|
2199
2285
|
// Store the new access token.
|
|
2286
|
+
setLocalAuthMethod("github");
|
|
2200
2287
|
setLocalAccessToken(newToken);
|
|
2201
2288
|
}
|
|
2202
2289
|
else
|
|
@@ -2217,6 +2304,249 @@ const auth = async () => {
|
|
|
2217
2304
|
terminate(providerUserId);
|
|
2218
2305
|
};
|
|
2219
2306
|
|
|
2307
|
+
const { BANDADA_API_URL } = process.env;
|
|
2308
|
+
const bandadaApi = new ApiSdk(BANDADA_API_URL);
|
|
2309
|
+
const addMemberToGroup = async (groupId, dashboardUrl, identity) => {
|
|
2310
|
+
const commitment = identity.commitment.toString();
|
|
2311
|
+
const group = await bandadaApi.getGroup(groupId);
|
|
2312
|
+
const providerName = group.credentials.id.split("_")[0].toLowerCase();
|
|
2313
|
+
// 6. open a new window with the url:
|
|
2314
|
+
const url = `${dashboardUrl}credentials?group=${groupId}&member=${commitment}&provider=${providerName}`;
|
|
2315
|
+
console.log(`${theme.text.bold(`Verification URL:`)} ${theme.text.underlined(url)}`);
|
|
2316
|
+
open(url);
|
|
2317
|
+
const { confirmation } = await askForConfirmation("Did you join the Bandada group in the browser?");
|
|
2318
|
+
if (!confirmation)
|
|
2319
|
+
showError("You must join the Bandada group to continue the login process", true);
|
|
2320
|
+
};
|
|
2321
|
+
const isGroupMember = async (groupId, identity) => {
|
|
2322
|
+
const commitment = identity.commitment.toString();
|
|
2323
|
+
const isMember = await bandadaApi.isGroupMember(groupId, commitment);
|
|
2324
|
+
return isMember;
|
|
2325
|
+
};
|
|
2326
|
+
|
|
2327
|
+
const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env;
|
|
2328
|
+
const authBandada = async () => {
|
|
2329
|
+
try {
|
|
2330
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2331
|
+
const spinner = customSpinner(`Checking identity string for Semaphore...`, `clock`);
|
|
2332
|
+
spinner.start();
|
|
2333
|
+
// 1. check if _identity string exists in local storage
|
|
2334
|
+
let identityString;
|
|
2335
|
+
const isIdentityStringStored = checkLocalBandadaIdentity();
|
|
2336
|
+
if (isIdentityStringStored) {
|
|
2337
|
+
identityString = getLocalBandadaIdentity();
|
|
2338
|
+
spinner.succeed(`Identity seed found\n`);
|
|
2339
|
+
}
|
|
2340
|
+
else {
|
|
2341
|
+
spinner.warn(`Identity seed not found\n`);
|
|
2342
|
+
// 2. generate a random _identity string and save it in local storage
|
|
2343
|
+
const { seed } = await prompts({
|
|
2344
|
+
type: "text",
|
|
2345
|
+
name: "seed",
|
|
2346
|
+
message: theme.text.bold(`Enter a secret string to use as your identity seed in Semaphore:`),
|
|
2347
|
+
initial: false
|
|
2348
|
+
});
|
|
2349
|
+
identityString = seed;
|
|
2350
|
+
setLocalBandadaIdentity(identityString);
|
|
2351
|
+
}
|
|
2352
|
+
// 3. create a semaphore identity with _identity string as a seed
|
|
2353
|
+
const identity = new Identity(identityString);
|
|
2354
|
+
// 4. check if the user is a member of the group
|
|
2355
|
+
console.log(`Checking Bandada membership...`);
|
|
2356
|
+
const isMember = await isGroupMember(BANDADA_GROUP_ID, identity);
|
|
2357
|
+
if (!isMember) {
|
|
2358
|
+
await addMemberToGroup(BANDADA_GROUP_ID, BANDADA_DASHBOARD_URL, identity);
|
|
2359
|
+
}
|
|
2360
|
+
// 5. generate a proof that the user owns the commitment.
|
|
2361
|
+
spinner.text = `Generating proof of identity...`;
|
|
2362
|
+
spinner.start();
|
|
2363
|
+
// publicSignals = [hash(externalNullifier, identityNullifier), commitment]
|
|
2364
|
+
const initDirectoryName = getLocalDirname();
|
|
2365
|
+
const directoryName = initDirectoryName.includes("/src") ? "." : initDirectoryName;
|
|
2366
|
+
const { proof, publicSignals } = await groth16.fullProve({
|
|
2367
|
+
identityTrapdoor: identity.trapdoor,
|
|
2368
|
+
identityNullifier: identity.nullifier,
|
|
2369
|
+
externalNullifier: BANDADA_GROUP_ID
|
|
2370
|
+
}, `${directoryName}/public/mini-semaphore.wasm`, `${directoryName}/public/mini-semaphore.zkey`);
|
|
2371
|
+
spinner.succeed(`Proof generated.\n`);
|
|
2372
|
+
spinner.text = `Sending proof to verification...`;
|
|
2373
|
+
spinner.start();
|
|
2374
|
+
// 6. send proof to a cloud function that verifies it and checks membership
|
|
2375
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.bandadaValidateProof);
|
|
2376
|
+
const result = await cf({
|
|
2377
|
+
proof,
|
|
2378
|
+
publicSignals
|
|
2379
|
+
});
|
|
2380
|
+
const { valid, token, message } = result.data;
|
|
2381
|
+
if (!valid) {
|
|
2382
|
+
showError(message, true);
|
|
2383
|
+
deleteLocalAuthMethod();
|
|
2384
|
+
deleteLocalAccessToken();
|
|
2385
|
+
deleteLocalBandadaIdentity();
|
|
2386
|
+
}
|
|
2387
|
+
spinner.succeed(`Proof verified.\n`);
|
|
2388
|
+
spinner.text = `Authenticating...`;
|
|
2389
|
+
spinner.start();
|
|
2390
|
+
// 7. Auth to p0tion firebase
|
|
2391
|
+
const credentials = await signInWithCustomToken(getAuth(), token);
|
|
2392
|
+
setLocalAuthMethod("bandada");
|
|
2393
|
+
setLocalAccessToken(token);
|
|
2394
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(credentials.user.uid)}.`);
|
|
2395
|
+
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2396
|
+
}
|
|
2397
|
+
catch (error) {
|
|
2398
|
+
// Delete local token.
|
|
2399
|
+
console.log("An error crashed the process. Deleting local token and identity.");
|
|
2400
|
+
console.error(error);
|
|
2401
|
+
deleteLocalAuthMethod();
|
|
2402
|
+
deleteLocalAccessToken();
|
|
2403
|
+
deleteLocalBandadaIdentity();
|
|
2404
|
+
}
|
|
2405
|
+
process.exit(0);
|
|
2406
|
+
};
|
|
2407
|
+
|
|
2408
|
+
const showVerificationCodeAndUri = async (OAuthDeviceCode) => {
|
|
2409
|
+
// Copy code to clipboard.
|
|
2410
|
+
let noClipboard = false;
|
|
2411
|
+
try {
|
|
2412
|
+
clipboard.writeSync(OAuthDeviceCode.user_code);
|
|
2413
|
+
clipboard.readSync();
|
|
2414
|
+
}
|
|
2415
|
+
catch (error) {
|
|
2416
|
+
noClipboard = true;
|
|
2417
|
+
}
|
|
2418
|
+
// Display data.
|
|
2419
|
+
console.log(`${theme.symbols.warning} Visit ${theme.text.bold(theme.text.underlined(OAuthDeviceCode.verification_uri_complete))} on this device to generate a new token and authenticate\n`);
|
|
2420
|
+
console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
|
|
2421
|
+
const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``;
|
|
2422
|
+
console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(OAuthDeviceCode.user_code)} ${message} ${theme.symbols.success}\n`);
|
|
2423
|
+
const spinner = customSpinner(`Redirecting to Sign In With Ethereum...`, `clock`);
|
|
2424
|
+
spinner.start();
|
|
2425
|
+
await sleep(10000); // ~10s to make users able to read the CLI.
|
|
2426
|
+
try {
|
|
2427
|
+
// Automatically open the page (# Step 2).
|
|
2428
|
+
await open(OAuthDeviceCode.verification_uri_complete);
|
|
2429
|
+
}
|
|
2430
|
+
catch (error) {
|
|
2431
|
+
console.log(`${theme.symbols.info} Please authenticate via SIWE at ${OAuthDeviceCode.verification_uri_complete}`);
|
|
2432
|
+
}
|
|
2433
|
+
spinner.stop();
|
|
2434
|
+
};
|
|
2435
|
+
/**
|
|
2436
|
+
* Return the token to sign in to Firebase after passing the SIWE Device Flow
|
|
2437
|
+
* @param clientId <string> - The client id of the Auth0 application.
|
|
2438
|
+
* @param firebaseFunctions <any> - The Firebase functions instance to call the cloud function
|
|
2439
|
+
* @returns <string> - The token to sign in to Firebase
|
|
2440
|
+
*/
|
|
2441
|
+
const executeSIWEDeviceFlow = async (clientId, firebaseFunctions) => {
|
|
2442
|
+
// Call Auth0 endpoint to request device code uri
|
|
2443
|
+
const OAuthDeviceCode = (await fetch$1(`${process.env.AUTH0_APPLICATION_URL}/oauth/device/code`, {
|
|
2444
|
+
method: "POST",
|
|
2445
|
+
headers: { "content-type": "application/json" },
|
|
2446
|
+
body: JSON.stringify({
|
|
2447
|
+
client_id: clientId,
|
|
2448
|
+
scope: "openid",
|
|
2449
|
+
audience: `${process.env.AUTH0_APPLICATION_URL}/api/v2/`
|
|
2450
|
+
})
|
|
2451
|
+
}).then((_res) => _res.json()));
|
|
2452
|
+
if (OAuthDeviceCode.error) {
|
|
2453
|
+
showError(OAuthDeviceCode.error_description, true);
|
|
2454
|
+
deleteLocalAuthMethod();
|
|
2455
|
+
deleteLocalAccessToken();
|
|
2456
|
+
}
|
|
2457
|
+
await showVerificationCodeAndUri(OAuthDeviceCode);
|
|
2458
|
+
// Poll Auth0 endpoint until you get token or request expires
|
|
2459
|
+
let isSignedIn = false;
|
|
2460
|
+
let isExpired = false;
|
|
2461
|
+
let auth0Token = "";
|
|
2462
|
+
while (!isSignedIn && !isExpired) {
|
|
2463
|
+
// Call Auth0 endpoint to request token
|
|
2464
|
+
const OAuthToken = (await fetch$1(`${process.env.AUTH0_APPLICATION_URL}/oauth/token`, {
|
|
2465
|
+
method: "POST",
|
|
2466
|
+
headers: { "content-type": "application/json" },
|
|
2467
|
+
body: JSON.stringify({
|
|
2468
|
+
client_id: clientId,
|
|
2469
|
+
device_code: OAuthDeviceCode.device_code,
|
|
2470
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
2471
|
+
})
|
|
2472
|
+
}).then((_res) => _res.json()));
|
|
2473
|
+
if (OAuthToken.error) {
|
|
2474
|
+
if (OAuthToken.error === "authorization_pending") {
|
|
2475
|
+
// Wait for the user to sign in
|
|
2476
|
+
await sleep(OAuthDeviceCode.interval * 1000);
|
|
2477
|
+
}
|
|
2478
|
+
else if (OAuthToken.error === "slow_down") {
|
|
2479
|
+
// Wait for the user to sign in
|
|
2480
|
+
await sleep(OAuthDeviceCode.interval * 1000 * 2);
|
|
2481
|
+
}
|
|
2482
|
+
else if (OAuthToken.error === "expired_token") {
|
|
2483
|
+
// The user didn't sign in on time
|
|
2484
|
+
isExpired = true;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
else {
|
|
2488
|
+
// The user signed in
|
|
2489
|
+
isSignedIn = true;
|
|
2490
|
+
auth0Token = OAuthToken.access_token;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
// Send token to cloud function to check nonce, create user and retrieve token
|
|
2494
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.checkNonceOfSIWEAddress);
|
|
2495
|
+
const result = await cf({
|
|
2496
|
+
auth0Token
|
|
2497
|
+
});
|
|
2498
|
+
const { token, valid, message } = result.data;
|
|
2499
|
+
if (!valid) {
|
|
2500
|
+
showError(message, true);
|
|
2501
|
+
deleteLocalAuthMethod();
|
|
2502
|
+
deleteLocalAccessToken();
|
|
2503
|
+
}
|
|
2504
|
+
return token;
|
|
2505
|
+
};
|
|
2506
|
+
/**
|
|
2507
|
+
* Auth command using Sign In With Ethereum
|
|
2508
|
+
* @notice The auth command allows a user to make the association of their Ethereum account with the CLI by leveraging SIWE as an authentication mechanism.
|
|
2509
|
+
* @dev Under the hood, the command handles a manual Device Flow following the guidelines in the SIWE documentation.
|
|
2510
|
+
*/
|
|
2511
|
+
const authSIWE = async () => {
|
|
2512
|
+
try {
|
|
2513
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2514
|
+
// Console more context for the user.
|
|
2515
|
+
console.log(`${theme.symbols.info} ${theme.text.bold(`You are about to authenticate on this CLI using your Ethereum address (device flow - OAuth 2.0 mechanism).\n${theme.symbols.warning} Please, note that only a Sign-in With Ethereum signature will be required`)}\n`);
|
|
2516
|
+
const spinner = customSpinner(`Checking authentication token...`, `clock`);
|
|
2517
|
+
spinner.start();
|
|
2518
|
+
await sleep(5000);
|
|
2519
|
+
// Manage OAuth Github or SIWE token.
|
|
2520
|
+
const isLocalTokenStored = checkLocalAccessToken();
|
|
2521
|
+
if (!isLocalTokenStored) {
|
|
2522
|
+
spinner.fail(`No local authentication token found\n`);
|
|
2523
|
+
// Generate a new access token using Github Device Flow (OAuth 2.0).
|
|
2524
|
+
const newToken = await executeSIWEDeviceFlow(String(process.env.AUTH_SIWE_CLIENT_ID), firebaseFunctions);
|
|
2525
|
+
// Store the new access token.
|
|
2526
|
+
setLocalAuthMethod("siwe");
|
|
2527
|
+
setLocalAccessToken(newToken);
|
|
2528
|
+
}
|
|
2529
|
+
else
|
|
2530
|
+
spinner.succeed(`Local authentication token found\n`);
|
|
2531
|
+
// Get access token from local store.
|
|
2532
|
+
const token = String(getLocalAccessToken());
|
|
2533
|
+
spinner.text = `Authenticating...`;
|
|
2534
|
+
spinner.start();
|
|
2535
|
+
// Exchange token for credential.
|
|
2536
|
+
const credentials = await signInWithCustomToken(getAuth(), token);
|
|
2537
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(credentials.user.uid)}.`);
|
|
2538
|
+
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2539
|
+
process.exit(0);
|
|
2540
|
+
}
|
|
2541
|
+
catch (error) {
|
|
2542
|
+
// Delete local token.
|
|
2543
|
+
console.log("An error crashed the process. Deleting local token and identity.");
|
|
2544
|
+
console.error(error);
|
|
2545
|
+
deleteLocalAuthMethod();
|
|
2546
|
+
deleteLocalAccessToken();
|
|
2547
|
+
}
|
|
2548
|
+
};
|
|
2549
|
+
|
|
2220
2550
|
/**
|
|
2221
2551
|
* Return the verification result for latest contribution.
|
|
2222
2552
|
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
@@ -2339,8 +2669,8 @@ const handleDiskSpaceRequirementForNextContribution = async (cloudFunctions, cer
|
|
|
2339
2669
|
spinner.fail(`You may not have enough memory to calculate the contribution for the Circuit ${theme.colors.magenta(`${circuitSequencePosition}`)}.\n\n${theme.symbols.info} The required amount of disk space is ${contributionDiskSpaceRequirement < 0.01
|
|
2340
2670
|
? theme.text.bold(`< 0.01`)
|
|
2341
2671
|
: theme.text.bold(contributionDiskSpaceRequirement)} GB but you only have ${participantFreeDiskSpace > 0 ? theme.text.bold(participantFreeDiskSpace.toFixed(2)) : theme.text.bold(0)} GB available memory \nThe estimate ${theme.text.bold("may not be 100% correct")} since is based on the aggregate free memory on your disks but some may not be detected!\n`);
|
|
2342
|
-
const {
|
|
2343
|
-
wannaContributeOrHaveEnoughMemory = !!
|
|
2672
|
+
const { confirmationEnoughMemory } = await askForConfirmation(`Please, we kindly ask you to continue with the contribution if you have noticed the estimate is wrong and you have enough memory in your machine`, "Continue", "Exit");
|
|
2673
|
+
wannaContributeOrHaveEnoughMemory = !!confirmationEnoughMemory;
|
|
2344
2674
|
if (circuitSequencePosition > 1) {
|
|
2345
2675
|
console.log(`${theme.symbols.info} Please note, you have time until ceremony ends to free up your memory and complete remaining contributions`);
|
|
2346
2676
|
// Asks the contributor if their wants to terminate contributions for the ceremony.
|
|
@@ -2408,8 +2738,12 @@ const handlePublicAttestation = async (firestoreDatabase, circuits, ceremonyId,
|
|
|
2408
2738
|
// Write public attestation locally.
|
|
2409
2739
|
writeFile(getAttestationLocalFilePath(`${ceremonyPrefix}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
2410
2740
|
await sleep(1000); // workaround for file descriptor unexpected close.
|
|
2411
|
-
|
|
2412
|
-
|
|
2741
|
+
let gistUrl = "";
|
|
2742
|
+
const isGithub = getLocalAuthMethod() === "github";
|
|
2743
|
+
if (isGithub) {
|
|
2744
|
+
gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
|
|
2745
|
+
console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
2746
|
+
}
|
|
2413
2747
|
// Prepare a ready-to-share tweet.
|
|
2414
2748
|
await handleTweetGeneration(ceremonyName, gistUrl);
|
|
2415
2749
|
};
|
|
@@ -2462,6 +2796,7 @@ const listenToCeremonyCircuitDocumentChanges = (firestoreDatabase, ceremonyId, p
|
|
|
2462
2796
|
}
|
|
2463
2797
|
});
|
|
2464
2798
|
};
|
|
2799
|
+
let contributionInProgress = false;
|
|
2465
2800
|
/**
|
|
2466
2801
|
* Listen to current authenticated participant document changes.
|
|
2467
2802
|
* @dev this is the core business logic related to the execution of the contribute command.
|
|
@@ -2589,11 +2924,21 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2589
2924
|
(!noTemporaryContributionData && resumingWithSameTemporaryData);
|
|
2590
2925
|
// Scenario (3.B).
|
|
2591
2926
|
if (isCurrentContributor && hasResumableStep && startingOrResumingContribution) {
|
|
2927
|
+
if (contributionInProgress) {
|
|
2928
|
+
console.warn(`\n${theme.symbols.warning} Received instruction to start/resume contribution but contribution is already in progress...[skipping]`);
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2592
2931
|
// Communicate resume / start of the contribution to participant.
|
|
2593
2932
|
await simpleLoader(`${changedContributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */ ? `Starting` : `Resuming`} your contribution...`, `clock`, 3000);
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2933
|
+
try {
|
|
2934
|
+
contributionInProgress = true;
|
|
2935
|
+
// Start / Resume the contribution for the participant.
|
|
2936
|
+
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false, // not finalizing.
|
|
2937
|
+
circuits.length);
|
|
2938
|
+
}
|
|
2939
|
+
finally {
|
|
2940
|
+
contributionInProgress = false;
|
|
2941
|
+
}
|
|
2597
2942
|
}
|
|
2598
2943
|
// Scenario (3.A).
|
|
2599
2944
|
else if (isWaitingForContribution)
|
|
@@ -2685,7 +3030,7 @@ const contribute = async (opt) => {
|
|
|
2685
3030
|
// Get options.
|
|
2686
3031
|
const ceremonyOpt = opt.ceremony;
|
|
2687
3032
|
const entropyOpt = opt.entropy;
|
|
2688
|
-
const auth = opt
|
|
3033
|
+
const { auth } = opt;
|
|
2689
3034
|
// Check for authentication.
|
|
2690
3035
|
const { user, providerUserId, token } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
|
|
2691
3036
|
// Prepare data.
|
|
@@ -2713,7 +3058,7 @@ const contribute = async (opt) => {
|
|
|
2713
3058
|
}
|
|
2714
3059
|
else {
|
|
2715
3060
|
// Prompt the user to select a ceremony from the opened ones.
|
|
2716
|
-
selectedCeremony = await promptForCeremonySelection(ceremoniesOpenedForContributions, false);
|
|
3061
|
+
selectedCeremony = await promptForCeremonySelection(ceremoniesOpenedForContributions, false, "Which ceremony would you like to contribute to?");
|
|
2717
3062
|
}
|
|
2718
3063
|
// Get selected ceremony circuit(s) documents.
|
|
2719
3064
|
const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
|
|
@@ -2723,7 +3068,7 @@ const contribute = async (opt) => {
|
|
|
2723
3068
|
const userDoc = await getDocumentById(firestoreDatabase, commonTerms.collections.users.name, user.uid);
|
|
2724
3069
|
const userData = userDoc.data();
|
|
2725
3070
|
if (!userData) {
|
|
2726
|
-
spinner.fail(`Unfortunately we could not find a user document with your information. This likely means that you did not pass the GitHub reputation checks and therefore are not
|
|
3071
|
+
spinner.fail(`Unfortunately we could not find a user document with your information. This likely means that you did not pass the GitHub reputation checks and therefore are not eligible to contribute to any ceremony. If you believe you pass the requirements, it might be possible that your profile is private and we were not able to fetch your real statistics, in this case please consider making your profile public for the duration of the contribution. Please contact the coordinator if you believe this to be an error.`);
|
|
2727
3072
|
process.exit(0);
|
|
2728
3073
|
}
|
|
2729
3074
|
// Check the user's current participant readiness for contribution status (eligible, already contributed, timed out).
|
|
@@ -2874,10 +3219,10 @@ const observe = async () => {
|
|
|
2874
3219
|
// Preserve command execution only for coordinators].
|
|
2875
3220
|
if (!(await isCoordinator(user)))
|
|
2876
3221
|
showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
|
|
2877
|
-
// Get running
|
|
3222
|
+
// Get running ceremonies info (if any).
|
|
2878
3223
|
const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase);
|
|
2879
3224
|
// Ask to select a ceremony.
|
|
2880
|
-
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false);
|
|
3225
|
+
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false, "Which ceremony would you like to observe?");
|
|
2881
3226
|
console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`);
|
|
2882
3227
|
let cursorPos = 0; // Keep track of current cursor position.
|
|
2883
3228
|
const spinner = customSpinner(`Getting ready...`, "clock");
|
|
@@ -2923,7 +3268,7 @@ const handleVerificationKey = async (cloudFunctions, bucketName, finalZkeyLocalF
|
|
|
2923
3268
|
spinner.text = "Writing verification key...";
|
|
2924
3269
|
// Write the verification key locally.
|
|
2925
3270
|
writeLocalJsonFile(verificationKeyLocalFilePath, vKey);
|
|
2926
|
-
await sleep(3000); //
|
|
3271
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2927
3272
|
// Upload verification key to storage.
|
|
2928
3273
|
await multiPartUpload(cloudFunctions, bucketName, verificationKeyStorageFilePath, verificationKeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2929
3274
|
spinner.succeed(`Verification key correctly saved on storage`);
|
|
@@ -2949,7 +3294,7 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2949
3294
|
spinner.text = `Writing verifier smart contract...`;
|
|
2950
3295
|
// Write the verification key locally.
|
|
2951
3296
|
writeFile(verifierContractLocalFilePath, verifierCode);
|
|
2952
|
-
await sleep(3000); //
|
|
3297
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2953
3298
|
// Upload verifier smart contract to storage.
|
|
2954
3299
|
await multiPartUpload(cloudFunctions, bucketName, verifierContractStorageFilePath, verifierContractLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2955
3300
|
spinner.succeed(`Verifier smart contract correctly saved on storage`);
|
|
@@ -2975,7 +3320,7 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2975
3320
|
const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier, circuitsLength) => {
|
|
2976
3321
|
// Step (1).
|
|
2977
3322
|
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength);
|
|
2978
|
-
await sleep(2000); //
|
|
3323
|
+
await sleep(2000); // workaround for descriptors.
|
|
2979
3324
|
// Extract data.
|
|
2980
3325
|
const { prefix: circuitPrefix } = circuit.data;
|
|
2981
3326
|
const { prefix: ceremonyPrefix } = ceremony.data;
|
|
@@ -3012,7 +3357,7 @@ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, cere
|
|
|
3012
3357
|
const finalize = async (opt) => {
|
|
3013
3358
|
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
3014
3359
|
// Check for authentication.
|
|
3015
|
-
const auth = opt
|
|
3360
|
+
const { auth } = opt;
|
|
3016
3361
|
const { user, providerUserId, token: coordinatorAccessToken } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
|
|
3017
3362
|
// Preserve command execution only for coordinators.
|
|
3018
3363
|
if (!(await isCoordinator(user)))
|
|
@@ -3024,7 +3369,7 @@ const finalize = async (opt) => {
|
|
|
3024
3369
|
showError(COMMAND_ERRORS.COMMAND_FINALIZED_NO_CLOSED_CEREMONIES, true);
|
|
3025
3370
|
console.log(`${theme.symbols.warning} The computation of the final contribution could take the bulk of your computational resources and memory based on the size of the circuit ${theme.emojis.fire}\n`);
|
|
3026
3371
|
// Prompt for ceremony selection.
|
|
3027
|
-
const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true);
|
|
3372
|
+
const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true, "Which ceremony would you like to finalize?");
|
|
3028
3373
|
// Get coordinator participant document.
|
|
3029
3374
|
let participant = await getDocumentById(firestoreDatabase, getParticipantsCollectionPath(selectedCeremony.id), user.uid);
|
|
3030
3375
|
const isCoordinatorReadyForCeremonyFinalization = await checkAndPrepareCoordinatorForFinalization(firebaseFunctions, selectedCeremony.id);
|
|
@@ -3124,7 +3469,9 @@ const logout = async () => {
|
|
|
3124
3469
|
const auth = getAuth();
|
|
3125
3470
|
await signOut(auth);
|
|
3126
3471
|
// Delete local token.
|
|
3472
|
+
deleteLocalAuthMethod();
|
|
3127
3473
|
deleteLocalAccessToken();
|
|
3474
|
+
deleteLocalBandadaIdentity();
|
|
3128
3475
|
await sleep(3000); // ~3s.
|
|
3129
3476
|
spinner.stop();
|
|
3130
3477
|
console.log(`${theme.symbols.success} Logout successfully completed`);
|
|
@@ -3186,6 +3533,55 @@ const listCeremonies = async () => {
|
|
|
3186
3533
|
}
|
|
3187
3534
|
};
|
|
3188
3535
|
|
|
3536
|
+
const listParticipants = async () => {
|
|
3537
|
+
try {
|
|
3538
|
+
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
3539
|
+
const allCeremonies = await getAllCeremonies(firestoreDatabase);
|
|
3540
|
+
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true, "Which ceremony would you like to see participants?");
|
|
3541
|
+
const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id);
|
|
3542
|
+
const participantsRef = collection(docRef, "participants");
|
|
3543
|
+
const participantsSnapshot = await getDocs(participantsRef);
|
|
3544
|
+
const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data());
|
|
3545
|
+
const usersRef = collection(firestoreDatabase, "users");
|
|
3546
|
+
const usersSnapshot = await getDocs(usersRef);
|
|
3547
|
+
const users = usersSnapshot.docs.map((userDoc) => {
|
|
3548
|
+
const data = userDoc.data();
|
|
3549
|
+
return { id: userDoc.id, ...data };
|
|
3550
|
+
});
|
|
3551
|
+
const participantDetails = participants
|
|
3552
|
+
.map((participant) => {
|
|
3553
|
+
const user = users.find((_user) => _user.id === participant.userId);
|
|
3554
|
+
if (!user)
|
|
3555
|
+
return null;
|
|
3556
|
+
return {
|
|
3557
|
+
id: user.name,
|
|
3558
|
+
status: participant.status,
|
|
3559
|
+
contributionStep: participant.contributionStep,
|
|
3560
|
+
lastUpdated: new Date(participant.lastUpdated)
|
|
3561
|
+
};
|
|
3562
|
+
})
|
|
3563
|
+
.filter((user) => user !== null);
|
|
3564
|
+
const participantsDone = participantDetails.filter((participant) => participant.status === "DONE");
|
|
3565
|
+
console.log(participantDetails);
|
|
3566
|
+
console.log(`${theme.text.underlined("Total participants:")} ${participantDetails.length}`);
|
|
3567
|
+
console.log(`${theme.text.underlined("Total participants finished:")} ${participantsDone.length}`);
|
|
3568
|
+
}
|
|
3569
|
+
catch (err) {
|
|
3570
|
+
showError(`Something went wrong: ${err.toString()}`, true);
|
|
3571
|
+
}
|
|
3572
|
+
process.exit(0);
|
|
3573
|
+
};
|
|
3574
|
+
|
|
3575
|
+
const setCeremonyCommands = (program) => {
|
|
3576
|
+
const ceremony = program.command("ceremony").description("manage ceremonies");
|
|
3577
|
+
ceremony
|
|
3578
|
+
.command("participants")
|
|
3579
|
+
.description("retrieve participants list of a ceremony")
|
|
3580
|
+
.requiredOption("-c, --ceremony <string>", "the prefix of the ceremony you want to retrieve information about", "")
|
|
3581
|
+
.action(listParticipants);
|
|
3582
|
+
return ceremony;
|
|
3583
|
+
};
|
|
3584
|
+
|
|
3189
3585
|
// Get pkg info (e.g., name, version).
|
|
3190
3586
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
3191
3587
|
const { description, version, name } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
|
|
@@ -3194,6 +3590,14 @@ const program = createCommand();
|
|
|
3194
3590
|
program.name(name).description(description).version(version);
|
|
3195
3591
|
// User commands.
|
|
3196
3592
|
program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth);
|
|
3593
|
+
program
|
|
3594
|
+
.command("auth-bandada")
|
|
3595
|
+
.description("authenticate yourself in a privacy-perserving manner using Bandada")
|
|
3596
|
+
.action(authBandada);
|
|
3597
|
+
program
|
|
3598
|
+
.command("auth-siwe")
|
|
3599
|
+
.description("authenticate yourself using your Ethereum account (Sign In With Ethereum - SIWE)")
|
|
3600
|
+
.action(authSIWE);
|
|
3197
3601
|
program
|
|
3198
3602
|
.command("contribute")
|
|
3199
3603
|
.description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
|
|
@@ -3212,25 +3616,26 @@ program
|
|
|
3212
3616
|
.action(logout);
|
|
3213
3617
|
program
|
|
3214
3618
|
.command("validate")
|
|
3215
|
-
.description("
|
|
3619
|
+
.description("validate that a Ceremony Setup file is correct")
|
|
3216
3620
|
.requiredOption("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3217
3621
|
.option("-c, --constraints <number>", "The number of constraints to check against")
|
|
3218
3622
|
.action(validate);
|
|
3219
3623
|
// Only coordinator commands.
|
|
3220
|
-
const
|
|
3221
|
-
|
|
3624
|
+
const coordinate = program.command("coordinate").description("commands for coordinating a ceremony");
|
|
3625
|
+
coordinate
|
|
3222
3626
|
.command("setup")
|
|
3223
3627
|
.description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
|
|
3224
3628
|
.option("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3225
3629
|
.option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
|
|
3226
3630
|
.action(setup);
|
|
3227
|
-
|
|
3631
|
+
coordinate
|
|
3228
3632
|
.command("observe")
|
|
3229
3633
|
.description("observe in real-time the waiting queue of each ceremony circuit")
|
|
3230
3634
|
.action(observe);
|
|
3231
|
-
|
|
3635
|
+
coordinate
|
|
3232
3636
|
.command("finalize")
|
|
3233
3637
|
.description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
|
|
3234
3638
|
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
3235
3639
|
.action(finalize);
|
|
3640
|
+
setCeremonyCommands(program);
|
|
3236
3641
|
program.parseAsync(process.argv);
|