@devtion/devcli 0.0.0-56491a8 → 0.0.0-57a8ab9

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/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * @module @devtion/devcli
5
- * @version 1.0.6
4
+ * @module @p0tion/phase2cli
5
+ * @version 1.1.1
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, 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 { Timestamp, onSnapshot } from 'firebase/firestore';
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 erros. Please, try once again later and make sure your Internet connection is stable.`,
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.`,
@@ -250,6 +253,10 @@ const config = new Conf({
250
253
  accessToken: {
251
254
  type: "string",
252
255
  default: ""
256
+ },
257
+ bandadaIdentity: {
258
+ type: "string",
259
+ default: ""
253
260
  }
254
261
  }
255
262
  });
@@ -310,6 +317,25 @@ const setLocalAccessToken = (token) => config.set("accessToken", token);
310
317
  * Delete the stored access token.
311
318
  */
312
319
  const deleteLocalAccessToken = () => config.delete("accessToken");
320
+ /**
321
+ * Return the Bandada identity, if present.
322
+ * @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
323
+ */
324
+ const getLocalBandadaIdentity = () => config.get("bandadaIdentity");
325
+ /**
326
+ * Check if the Bandada identity exists in the local storage.
327
+ * @returns <boolean>
328
+ */
329
+ const checkLocalBandadaIdentity = () => config.has("bandadaIdentity") && !!config.get("bandadaIdentity");
330
+ /**
331
+ * Set the Bandada identity.
332
+ * @param identity <string> - the Bandada identity to be stored.
333
+ */
334
+ const setLocalBandadaIdentity = (identity) => config.set("bandadaIdentity", identity);
335
+ /**
336
+ * Delete the stored Bandada identity.
337
+ */
338
+ const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity");
313
339
  /**
314
340
  * Get the complete local file path.
315
341
  * @param cwd <string> - the current working directory path.
@@ -365,6 +391,12 @@ const getVerificationKeyLocalFilePath = (completeFilename) => `${verificationKey
365
391
  * @returns <string> - the complete final verifier contract path to the file.
366
392
  */
367
393
  const getVerifierContractLocalFilePath = (completeFilename) => `${verifierContractsLocalFolderPath}/${completeFilename}`;
394
+ /**
395
+ * Get the complete final attestation file path.
396
+ * @param completeFilename <string> - the complete filename of the file (name.ext).
397
+ * @returns <string> - the complete final final attestation path to the file.
398
+ */
399
+ const getFinalAttestationLocalFilePath = (completeFilename) => `${finalAttestationsLocalFolderPath}/${completeFilename}`;
368
400
  /**
369
401
  * Get the final transcript file path.
370
402
  * @param completeFilename <string> - the complete filename of the file (name.ext).
@@ -414,7 +446,7 @@ const getGithubAuthenticatedUserGists = async (githubToken, params) => {
414
446
  headers: {
415
447
  authorization: `token ${githubToken}`
416
448
  },
417
- per_page: params.perPage,
449
+ per_page: params.perPage, // max items per page = 100.
418
450
  page: params.page
419
451
  });
420
452
  if (response && response.status === 200)
@@ -462,8 +494,9 @@ const getPublicAttestationGist = async (githubToken, publicAttestationFilename)
462
494
  * @returns <string> - the third-party provider handle of the user.
463
495
  */
464
496
  const getUserHandleFromProviderUserId = (providerUserId) => {
465
- if (providerUserId.indexOf("-") === -1)
466
- showError(THIRD_PARTY_SERVICES_ERRORS.GITHUB_GET_GITHUB_ACCOUNT_INFO, true);
497
+ if (providerUserId.indexOf("-") === -1) {
498
+ return providerUserId;
499
+ }
467
500
  return providerUserId.split("-")[0];
468
501
  };
469
502
  /**
@@ -579,8 +612,18 @@ const publishGist = async (token, content, ceremonyTitle, ceremonyPrefix) => {
579
612
  * @returns <string> - the ready to share tweet url.
580
613
  */
581
614
  const generateCustomUrlToTweetAboutParticipation = (ceremonyName, gistUrl, isFinalizing) => isFinalizing
582
- ? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}%20Phase%202%20Trusted%20Setup%20ceremony!%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
583
- : `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyName}%20Phase%202%20Trusted%20Setup%20ceremony!%20You%20can%20contribute%20here:%20https://github.com/privacy-scaling-explorations/p0tion%20You%20can%20view%20my%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP`;
615
+ ? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
616
+ ceremonyName.toLowerCase().includes("setup") ||
617
+ ceremonyName.toLowerCase().includes("phase2") ||
618
+ ceremonyName.toLowerCase().includes("ceremony")
619
+ ? "!"
620
+ : "%20Phase%202%20Trusted%20Setup%20ceremony!"}%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
621
+ : `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
622
+ ceremonyName.toLowerCase().includes("setup") ||
623
+ ceremonyName.toLowerCase().includes("phase2") ||
624
+ ceremonyName.toLowerCase().includes("ceremony")
625
+ ? "!"
626
+ : "%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`;
584
627
  /**
585
628
  * Return a custom progress bar.
586
629
  * @param type <ProgressBarType> - the type of the progress bar.
@@ -708,13 +751,14 @@ const getLatestUpdatesFromParticipant = async (firestoreDatabase, ceremonyId, pa
708
751
  * @param entropyOrBeaconHash <string> - the entropy or beacon hash (only when finalizing) for the contribution.
709
752
  * @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
710
753
  * @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
754
+ * @param circuitsLength <number> - the total number of circuits in the ceremony.
711
755
  */
712
- const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing) => {
756
+ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing, circuitsLength) => {
713
757
  // Extract data.
714
758
  const { prefix: ceremonyPrefix } = ceremony.data;
715
759
  const { waitingQueue, avgTimings, prefix: circuitPrefix, sequencePosition } = circuit.data;
716
760
  const { completedContributions } = waitingQueue; // = current progress.
717
- console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}`)}`)} (Contribution Steps)`);
761
+ console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}/${circuitsLength}`)}`)} (Contribution Steps)`);
718
762
  // Get most up-to-date data from the participant document.
719
763
  let participantData = await getLatestUpdatesFromParticipant(firestoreDatabase, ceremony.id, participant.id);
720
764
  const spinner = customSpinner(`${participantData.contributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */
@@ -760,6 +804,7 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
760
804
  // Download the latest contribution from bucket.
761
805
  await downloadCeremonyArtifact(cloudFunctions, bucketName, lastZkeyStorageFilePath, lastZkeyLocalFilePath);
762
806
  console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${lastZkeyIndex}`)} correctly downloaded`);
807
+ await sleep(3000);
763
808
  // Advance to next contribution step (COMPUTING) if not finalizing.
764
809
  if (!isFinalizing) {
765
810
  spinner.text = `Preparing for contribution computation...`;
@@ -787,11 +832,14 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
787
832
  showError(COMMAND_ERRORS.COMMAND_CONTRIBUTE_FINALIZE_NO_TRANSCRIPT_CONTRIBUTION_HASH_MATCH, true);
788
833
  // Format contribution hash.
789
834
  const contributionHash = matchContributionHash?.at(0)?.replace("\n\t\t", "");
835
+ await sleep(500);
790
836
  // Make request to cloud functions to permanently store the information.
791
837
  await permanentlyStoreCurrentContributionTimeAndHash(cloudFunctions, ceremony.id, computingTime, contributionHash);
792
838
  // Format computing time.
793
839
  const { seconds: computationSeconds, minutes: computationMinutes, hours: computationHours } = getSecondsMinutesHoursFromMillis(computingTime);
794
840
  spinner.succeed(`${isFinalizing ? "Contribution" : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} computation took ${theme.text.bold(`${convertToDoubleDigits(computationHours)}:${convertToDoubleDigits(computationMinutes)}:${convertToDoubleDigits(computationSeconds)}`)}`);
841
+ // ensure the previous step is completed
842
+ await sleep(5000);
795
843
  // Advance to next contribution step (UPLOADING) if not finalizing.
796
844
  if (!isFinalizing) {
797
845
  spinner.text = `Preparing for uploading the contribution...`;
@@ -807,12 +855,17 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
807
855
  console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${nextZkeyIndex}`)} already computed`);
808
856
  // Contribution step = UPLOADING.
809
857
  if (isFinalizing || participantData.contributionStep === "UPLOADING" /* ParticipantContributionStep.UPLOADING */) {
810
- spinner.text = `Uploading ${isFinalizing ? "final" : "your"} contribution ${!isFinalizing ? theme.text.bold(`#${nextZkeyIndex}`) : ""} to storage.\n${theme.symbols.warning} This step may take a while based on circuit size and your contribution speed. Everything's fine, just be patient.`;
858
+ spinner.text = `Uploading ${isFinalizing ? "final" : "your"} contribution ${!isFinalizing ? theme.text.bold(`#${nextZkeyIndex}`) : ""} to storage.\n${theme.symbols.warning} This step may take a while based on circuit size and your internet speed. Everything's fine, just be patient.`;
811
859
  spinner.start();
812
- if (!isFinalizing)
813
- await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB), ceremony.id, participantData.tempContributionData);
860
+ const progressBar = customProgressBar(ProgressBarType.UPLOAD, `your contribution`);
861
+ if (!isFinalizing) {
862
+ await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB), ceremony.id, participantData.tempContributionData, progressBar);
863
+ progressBar.stop();
864
+ }
814
865
  else
815
866
  await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
867
+ // small sleep to ensure the previous step is completed
868
+ await sleep(5000);
816
869
  spinner.succeed(`${isFinalizing ? `Contribution` : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} correctly saved to storage`);
817
870
  // Advance to next contribution step (VERIFYING) if not finalizing.
818
871
  if (!isFinalizing) {
@@ -990,7 +1043,7 @@ const promptCircomCompiler = async () => {
990
1043
  * Shows a list of circuits for a single option selection.
991
1044
  * @dev the circuit names are derived from local R1CS files.
992
1045
  * @param options <Array<string>> - an array of circuits names.
993
- * @returns Promise<string> - the name of the choosen circuit.
1046
+ * @returns Promise<string> - the name of the chosen circuit.
994
1047
  */
995
1048
  const promptCircuitSelector = async (options) => {
996
1049
  const { circuitFilename } = await prompts({
@@ -1008,7 +1061,7 @@ const promptCircuitSelector = async (options) => {
1008
1061
  * Shows a list of standard EC2 VM instance types for a single option selection.
1009
1062
  * @notice the suggested VM configuration type is calculated based on circuit constraint size.
1010
1063
  * @param constraintSize <number> - the amount of circuit constraints
1011
- * @returns Promise<string> - the name of the choosen VM type.
1064
+ * @returns Promise<string> - the name of the chosen VM type.
1012
1065
  */
1013
1066
  const promptVMTypeSelector = async (constraintSize) => {
1014
1067
  let suggestedConfiguration = 0;
@@ -1105,7 +1158,7 @@ const promptVMDiskTypeSelector = async () => {
1105
1158
  /**
1106
1159
  * Show a series of questions about the circuits.
1107
1160
  * @param constraintSize <number> - the amount of circuit constraints.
1108
- * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
1161
+ * @param timeoutMechanismType <CeremonyTimeoutType> - the chosen timeout mechanism type for the ceremony.
1109
1162
  * @param needPromptCircomCompiler <boolean> - a boolean value indicating if the questions related to the Circom compiler version and commit hash must be asked.
1110
1163
  * @param enforceVM <boolean> - a boolean value indicating if the contribution verification could be supported by VM-only approach or not.
1111
1164
  * @returns Promise<Array<Circuit>> - circuit info prompted by the coordinator.
@@ -1118,7 +1171,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1118
1171
  let circomVersion = "";
1119
1172
  let circomCommitHash = "";
1120
1173
  let circuitInputData;
1121
- let useCfOrVm;
1174
+ let cfOrVm;
1122
1175
  let vmDiskType;
1123
1176
  let vmConfigurationType = "";
1124
1177
  const questions = [
@@ -1173,18 +1226,21 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1173
1226
  circomVersion = version;
1174
1227
  circomCommitHash = commitHash;
1175
1228
  }
1176
- // Ask for prefered contribution verification method (CF vs VM).
1229
+ // Ask for preferred contribution verification method (CF vs VM).
1177
1230
  if (!enforceVM) {
1178
1231
  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.
1179
1232
  `VM` // eq. false.
1180
1233
  );
1181
- useCfOrVm = confirmation;
1234
+ cfOrVm = confirmation
1235
+ ? "CF" /* CircuitContributionVerificationMechanism.CF */
1236
+ : "VM" /* CircuitContributionVerificationMechanism.VM */;
1182
1237
  }
1183
- else
1184
- useCfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
1185
- if (useCfOrVm === undefined)
1238
+ else {
1239
+ cfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
1240
+ }
1241
+ if (cfOrVm === undefined)
1186
1242
  showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
1187
- if (!useCfOrVm) {
1243
+ if (cfOrVm === "VM" /* CircuitContributionVerificationMechanism.VM */) {
1188
1244
  // Ask for selecting the specific VM configuration type.
1189
1245
  vmConfigurationType = await promptVMTypeSelector(constraintSize);
1190
1246
  // Ask for selecting the specific VM disk (volume) type.
@@ -1218,9 +1274,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1218
1274
  paramsConfiguration: circuitConfigurationValues
1219
1275
  },
1220
1276
  verification: {
1221
- cfOrVm: useCfOrVm
1222
- ? "CF" /* CircuitContributionVerificationMechanism.CF */
1223
- : "VM" /* CircuitContributionVerificationMechanism.VM */,
1277
+ cfOrVm,
1224
1278
  vm: {
1225
1279
  vmConfigurationType,
1226
1280
  vmDiskType
@@ -1256,9 +1310,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1256
1310
  paramsConfiguration: circuitConfigurationValues
1257
1311
  },
1258
1312
  verification: {
1259
- cfOrVm: useCfOrVm
1260
- ? "CF" /* CircuitContributionVerificationMechanism.CF */
1261
- : "VM" /* CircuitContributionVerificationMechanism.VM */,
1313
+ cfOrVm,
1262
1314
  vm: {
1263
1315
  vmConfigurationType,
1264
1316
  vmDiskType
@@ -1302,7 +1354,7 @@ const promptCircuitAddition = async () => {
1302
1354
  * Shows a list of pre-computed zKeys for a single option selection.
1303
1355
  * @dev the names are derived from local zKeys files.
1304
1356
  * @param options <Array<string>> - an array of pre-computed zKeys names.
1305
- * @returns Promise<string> - the name of the choosen pre-computed zKey.
1357
+ * @returns Promise<string> - the name of the chosen pre-computed zKey.
1306
1358
  */
1307
1359
  const promptPreComputedZkeySelector = async (options) => {
1308
1360
  const { preComputedZkeyFilename } = await prompts({
@@ -1340,13 +1392,13 @@ const promptNeededPowersForCircuit = async (suggestedSmallestNeededPowers) => {
1340
1392
  * Shows a list of PoT files for a single option selection.
1341
1393
  * @dev the names are derived from local PoT files.
1342
1394
  * @param options <Array<string>> - an array of PoT file names.
1343
- * @returns Promise<string> - the name of the choosen PoT.
1395
+ * @returns Promise<string> - the name of the chosen PoT.
1344
1396
  */
1345
1397
  const promptPotSelector = async (options) => {
1346
1398
  const { potFilename } = await prompts({
1347
1399
  type: "select",
1348
1400
  name: "potFilename",
1349
- message: theme.text.bold("Select the Powers of Tau file choosen for the circuit"),
1401
+ message: theme.text.bold("Select the Powers of Tau file chosen for the circuit"),
1350
1402
  choices: options.map((option) => {
1351
1403
  console.log(option);
1352
1404
  return { title: option, value: option };
@@ -1416,7 +1468,7 @@ const promptToTypeEntropyOrBeacon = async (isEntropy = true) => {
1416
1468
  * @return <Promise<string>> - the entropy.
1417
1469
  */
1418
1470
  const promptForEntropy = async () => {
1419
- // Prompt for entropy generation prefered method.
1471
+ // Prompt for entropy generation preferred method.
1420
1472
  const { confirmation } = await askForConfirmation(`Do you prefer to type your entropy or generate it randomly?`, "Manually", "Randomly");
1421
1473
  if (confirmation === undefined)
1422
1474
  showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
@@ -1535,16 +1587,27 @@ const checkAuth = async (firebaseApp) => {
1535
1587
  showError(THIRD_PARTY_SERVICES_ERRORS.GITHUB_NOT_AUTHENTICATED, true);
1536
1588
  // Retrieve local access token.
1537
1589
  const token = String(getLocalAccessToken());
1538
- // Get credentials.
1539
- const credentials = exchangeGithubTokenForCredentials(token);
1540
- // Sign in to Firebase using credentials.
1541
- await signInToFirebase(firebaseApp, credentials);
1590
+ let providerUserId;
1591
+ let username;
1592
+ const isLocalBandadaIdentityStored = checkLocalBandadaIdentity();
1593
+ if (isLocalBandadaIdentityStored) {
1594
+ const userCredentials = await signInWithCustomToken(getAuth(), token);
1595
+ providerUserId = userCredentials.user.uid;
1596
+ username = providerUserId;
1597
+ }
1598
+ else {
1599
+ // Get credentials.
1600
+ const credentials = exchangeGithubTokenForCredentials(token);
1601
+ // Sign in to Firebase using credentials.
1602
+ await signInToFirebase(firebaseApp, credentials);
1603
+ // Get Github unique identifier (handle-id).
1604
+ providerUserId = await getGithubProviderUserId(String(token));
1605
+ username = getUserHandleFromProviderUserId(providerUserId);
1606
+ }
1542
1607
  // Get current authenticated user.
1543
1608
  const user = getCurrentFirebaseAuthUser(firebaseApp);
1544
- // Get Github unique identifier (handle-id).
1545
- const providerUserId = await getGithubProviderUserId(String(token));
1546
1609
  // Greet the user.
1547
- console.log(`Greetings, @${theme.text.bold(getUserHandleFromProviderUserId(providerUserId))} ${theme.emojis.wave}\n`);
1610
+ console.log(`Greetings, @${theme.text.bold(username)} ${theme.emojis.wave}\n`);
1548
1611
  return {
1549
1612
  user,
1550
1613
  token,
@@ -1633,7 +1696,7 @@ const handleAdditionOfCircuitsToCeremony = async (r1csOptions, wasmOptions, cere
1633
1696
  wasmFilename.split(`.${commonTerms.foldersAndPathsTerms.wasm}`)[0]);
1634
1697
  if (matchingWasms.length !== 1)
1635
1698
  showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true);
1636
- // Get input data for choosen circuit.
1699
+ // Get input data for chosen circuit.
1637
1700
  const circuitInputData = await getInputDataToAddCircuitToCeremony(choosenCircuitFilename, matchingWasms[0], ceremonyTimeoutMechanismType, sameCircomCompiler, circuitSequencePosition, sharedCircomCompilerData);
1638
1701
  // Store circuit data.
1639
1702
  inputDataForCircuits.push(circuitInputData);
@@ -1723,7 +1786,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
1723
1786
  * number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
1724
1787
  * to provide a number of powers manually, ranging from the smallest possible to the largest.
1725
1788
  * @param neededPowers <number> - the smallest amount of powers needed by the zKey.
1726
- * @returns Promise<string, string> - the information about the choosen Powers of Tau file for the pre-computed zKey
1789
+ * @returns Promise<string, string> - the information about the chosen Powers of Tau file for the pre-computed zKey
1727
1790
  * along with related powers.
1728
1791
  */
1729
1792
  const handlePreComputedZkeyPowersOfTauSelection = async (neededPowers) => {
@@ -1824,7 +1887,9 @@ const setup = async (cmd) => {
1824
1887
  let ceremonyId = ""; // The unique identifier of the ceremony.
1825
1888
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
1826
1889
  // Check for authentication.
1827
- const { user, providerUserId } = cmd.auth ? await authWithToken(firebaseApp, cmd.auth) : await checkAuth(firebaseApp);
1890
+ const { user, providerUserId } = cmd.auth
1891
+ ? await authWithToken(firebaseApp, cmd.auth)
1892
+ : await checkAuth(firebaseApp);
1828
1893
  // Preserve command execution only for coordinators.
1829
1894
  if (!(await isCoordinator(user)))
1830
1895
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
@@ -1841,7 +1906,7 @@ const setup = async (cmd) => {
1841
1906
  // if there is the file option, then set up the non interactively
1842
1907
  if (cmd.template) {
1843
1908
  // 1. parse the file
1844
- // tmp data - do not cleanup files as we need them
1909
+ // tmp data - do not cleanup files as we need them
1845
1910
  const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template)} setup configuration file...`, `clock`);
1846
1911
  spinner.start();
1847
1912
  const setupCeremonyData = await parseCeremonyFile(cmd.template);
@@ -1864,12 +1929,20 @@ const setup = async (cmd) => {
1864
1929
  // 3. generate the zKey
1865
1930
  const spinner = customSpinner(`Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
1866
1931
  spinner.start();
1867
- await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined);
1868
- spinner.succeed(`Generation of the genesis zKey for citcui ${theme.text.bold(circuit.name)} completed successfully`);
1932
+ if (existsSync(zkeyLocalPathAndFileName)) {
1933
+ spinner.succeed(`The genesis zKey for circuit ${theme.text.bold(circuit.name)} is already present on disk`);
1934
+ }
1935
+ else {
1936
+ await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined);
1937
+ spinner.succeed(`Generation of the genesis zKey for circuit ${theme.text.bold(circuit.name)} completed successfully`);
1938
+ }
1939
+ const hashSpinner = customSpinner(`Calculating hashes for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
1940
+ hashSpinner.start();
1869
1941
  // 4. calculate the hashes
1870
1942
  const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName);
1871
1943
  const potBlake2bHash = await blake512FromPath(getPotLocalFilePath(circuit.files.potFilename));
1872
1944
  const initialZkeyBlake2bHash = await blake512FromPath(zkeyLocalPathAndFileName);
1945
+ hashSpinner.succeed(`Hashes for circuit ${theme.text.bold(circuit.name)} calculated successfully`);
1873
1946
  // 5. upload the artifacts
1874
1947
  // Upload zKey to Storage.
1875
1948
  await handleCircuitArtifactUploadToStorage(firebaseFunctions, bucketName, circuit.files.initialZkeyStoragePath, zkeyLocalPathAndFileName, circuit.files.initialZkeyFilename);
@@ -1887,9 +1960,9 @@ const setup = async (cmd) => {
1887
1960
  // 6 update the setup data object
1888
1961
  ceremonySetupData.circuits[index].files = {
1889
1962
  ...circuit.files,
1890
- potBlake2bHash: potBlake2bHash,
1891
- wasmBlake2bHash: wasmBlake2bHash,
1892
- initialZkeyBlake2bHash: initialZkeyBlake2bHash
1963
+ potBlake2bHash,
1964
+ wasmBlake2bHash,
1965
+ initialZkeyBlake2bHash
1893
1966
  };
1894
1967
  ceremonySetupData.circuits[index].zKeySizeInBytes = getFileStats(zkeyLocalPathAndFileName).size;
1895
1968
  }
@@ -2097,17 +2170,29 @@ const expirationCountdownForGithubOAuth = (expirationInSeconds) => {
2097
2170
  */
2098
2171
  const onVerification = async (verification) => {
2099
2172
  // Copy code to clipboard.
2100
- clipboard.writeSync(verification.user_code);
2101
- clipboard.readSync();
2173
+ let noClipboard = false;
2174
+ try {
2175
+ clipboard.writeSync(verification.user_code);
2176
+ clipboard.readSync();
2177
+ }
2178
+ catch (error) {
2179
+ noClipboard = true;
2180
+ }
2102
2181
  // Display data.
2103
2182
  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`);
2104
- console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), '\n');
2105
- console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} has been copied to your clipboard (${theme.emojis.clipboard} ${theme.symbols.success})\n`);
2183
+ console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
2184
+ const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``;
2185
+ console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} ${message} ${theme.symbols.success}\n`);
2106
2186
  const spinner = customSpinner(`Redirecting to Github...`, `clock`);
2107
2187
  spinner.start();
2108
2188
  await sleep(10000); // ~10s to make users able to read the CLI.
2109
- // Automatically open the page (# Step 2).
2110
- await open(verification.verification_uri);
2189
+ try {
2190
+ // Automatically open the page (# Step 2).
2191
+ await open(verification.verification_uri);
2192
+ }
2193
+ catch (error) {
2194
+ console.log(`${theme.symbols.info} Please authenticate via GitHub at ${verification.verification_uri}`);
2195
+ }
2111
2196
  spinner.stop();
2112
2197
  // Countdown for time expiration.
2113
2198
  expirationCountdownForGithubOAuth(verification.expires_in);
@@ -2178,6 +2263,91 @@ const auth = async () => {
2178
2263
  terminate(providerUserId);
2179
2264
  };
2180
2265
 
2266
+ const { BANDADA_API_URL } = process.env;
2267
+ const bandadaApi = new ApiSdk(BANDADA_API_URL);
2268
+ const addMemberToGroup = async (groupId, dashboardUrl, identity) => {
2269
+ const commitment = identity.commitment.toString();
2270
+ const group = await bandadaApi.getGroup(groupId);
2271
+ const providerName = group.credentials.id.split("_")[0].toLowerCase();
2272
+ // 6. open a new window with the url:
2273
+ const url = `${dashboardUrl}credentials?group=${groupId}&member=${commitment}&provider=${providerName}`;
2274
+ console.log(`${theme.text.bold(`Verification URL:`)} ${theme.text.underlined(url)}`);
2275
+ open(url);
2276
+ const { confirmation } = await askForConfirmation("Did you join the Bandada group in the browser?");
2277
+ if (!confirmation)
2278
+ showError("You must join the Bandada group to continue the login process", true);
2279
+ };
2280
+ const isGroupMember = async (groupId, identity) => {
2281
+ const commitment = identity.commitment.toString();
2282
+ const isMember = await bandadaApi.isGroupMember(groupId, commitment);
2283
+ return isMember;
2284
+ };
2285
+
2286
+ const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env;
2287
+ const authBandada = async () => {
2288
+ const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
2289
+ const spinner = customSpinner(`Checking identity string for Semaphore...`, `clock`);
2290
+ spinner.start();
2291
+ // 1. check if _identity string exists in local storage
2292
+ let identityString;
2293
+ const isIdentityStringStored = checkLocalBandadaIdentity();
2294
+ if (isIdentityStringStored) {
2295
+ identityString = getLocalBandadaIdentity();
2296
+ spinner.succeed(`Identity seed found\n`);
2297
+ }
2298
+ else {
2299
+ spinner.warn(`Identity seed not found\n`);
2300
+ // 2. generate a random _identity string and save it in local storage
2301
+ const { seed } = await prompts({
2302
+ type: "text",
2303
+ name: "seed",
2304
+ message: theme.text.bold(`Enter a secret string to use as your identity seed in Semaphore:`),
2305
+ initial: false
2306
+ });
2307
+ identityString = seed;
2308
+ setLocalBandadaIdentity(identityString);
2309
+ }
2310
+ // 3. create a semaphore identity with _identity string as a seed
2311
+ const identity = new Identity(identityString);
2312
+ // 4. check if the user is a member of the group
2313
+ console.log(`Checking Bandada membership...`);
2314
+ const isMember = await isGroupMember(BANDADA_GROUP_ID, identity);
2315
+ if (!isMember) {
2316
+ await addMemberToGroup(BANDADA_GROUP_ID, BANDADA_DASHBOARD_URL, identity);
2317
+ }
2318
+ // 5. generate a proof that the user owns the commitment.
2319
+ spinner.text = `Generating proof of identity...`;
2320
+ spinner.start();
2321
+ // publicSignals = [hash(externalNullifier, identityNullifier), commitment]
2322
+ const { proof, publicSignals } = await groth16.fullProve({
2323
+ identityTrapdoor: identity.trapdoor,
2324
+ identityNullifier: identity.nullifier,
2325
+ externalNullifier: BANDADA_GROUP_ID
2326
+ }, path.join(path.resolve(), "/public/mini-semaphore.wasm"), path.join(path.resolve(), "/public/mini-semaphore.zkey"));
2327
+ spinner.succeed(`Proof generated.\n`);
2328
+ spinner.text = `Sending proof to verification...`;
2329
+ spinner.start();
2330
+ // 6. send proof to a cloud function that verifies it and checks membership
2331
+ const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.bandadaValidateProof);
2332
+ const result = await cf({
2333
+ proof,
2334
+ publicSignals
2335
+ });
2336
+ const { valid, token, message } = result.data;
2337
+ if (!valid) {
2338
+ showError(message, true);
2339
+ }
2340
+ spinner.succeed(`Proof verified.\n`);
2341
+ spinner.text = `Authenticating...`;
2342
+ spinner.start();
2343
+ // 7. Auth to p0tion firebase
2344
+ const userCredentials = await signInWithCustomToken(getAuth(), token);
2345
+ setLocalAccessToken(token);
2346
+ spinner.succeed(`Authenticated as ${theme.text.bold(userCredentials.user.uid)}.`);
2347
+ console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
2348
+ process.exit(0);
2349
+ };
2350
+
2181
2351
  /**
2182
2352
  * Return the verification result for latest contribution.
2183
2353
  * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
@@ -2369,8 +2539,12 @@ const handlePublicAttestation = async (firestoreDatabase, circuits, ceremonyId,
2369
2539
  // Write public attestation locally.
2370
2540
  writeFile(getAttestationLocalFilePath(`${ceremonyPrefix}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
2371
2541
  await sleep(1000); // workaround for file descriptor unexpected close.
2372
- const gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
2373
- console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
2542
+ let gistUrl = "";
2543
+ const isBandada = checkLocalBandadaIdentity();
2544
+ if (!isBandada) {
2545
+ gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
2546
+ console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
2547
+ }
2374
2548
  // Prepare a ready-to-share tweet.
2375
2549
  await handleTweetGeneration(ceremonyName, gistUrl);
2376
2550
  };
@@ -2553,8 +2727,8 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2553
2727
  // Communicate resume / start of the contribution to participant.
2554
2728
  await simpleLoader(`${changedContributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */ ? `Starting` : `Resuming`} your contribution...`, `clock`, 3000);
2555
2729
  // Start / Resume the contribution for the participant.
2556
- await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false // not finalizing.
2557
- );
2730
+ await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false, // not finalizing.
2731
+ circuits.length);
2558
2732
  }
2559
2733
  // Scenario (3.A).
2560
2734
  else if (isWaitingForContribution)
@@ -2603,7 +2777,9 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2603
2777
  // Get latest contribution verification result.
2604
2778
  await getLatestVerificationResult(firestoreDatabase, ceremony.id, circuit.id, participant.id);
2605
2779
  // Get next circuit for contribution.
2606
- const nextCircuit = timeoutExpired ? getCircuitBySequencePosition(circuits, changedContributionProgress) : getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
2780
+ const nextCircuit = timeoutExpired
2781
+ ? getCircuitBySequencePosition(circuits, changedContributionProgress)
2782
+ : getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
2607
2783
  // Check disk space requirements for participant.
2608
2784
  const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(cloudFunctions, ceremony.id, nextCircuit.data.sequencePosition, nextCircuit.data.zKeySizeInBytes, timeoutExpired, providerUserId);
2609
2785
  // Check if the participant would like to generate a new attestation.
@@ -2641,11 +2817,12 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2641
2817
  */
2642
2818
  const contribute = async (opt) => {
2643
2819
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
2644
- // Check for authentication.
2645
- const { user, providerUserId, token } = await checkAuth(firebaseApp);
2646
2820
  // Get options.
2647
2821
  const ceremonyOpt = opt.ceremony;
2648
2822
  const entropyOpt = opt.entropy;
2823
+ const { auth } = opt;
2824
+ // Check for authentication.
2825
+ const { user, providerUserId, token } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
2649
2826
  // Prepare data.
2650
2827
  let selectedCeremony;
2651
2828
  // Retrieve the opened ceremonies.
@@ -2681,7 +2858,7 @@ const contribute = async (opt) => {
2681
2858
  const userDoc = await getDocumentById(firestoreDatabase, commonTerms.collections.users.name, user.uid);
2682
2859
  const userData = userDoc.data();
2683
2860
  if (!userData) {
2684
- 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 elegible to contribute to any ceremony. Please contact the coordinator if you believe this to be an error.`);
2861
+ 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.`);
2685
2862
  process.exit(0);
2686
2863
  }
2687
2864
  // Check the user's current participant readiness for contribution status (eligible, already contributed, timed out).
@@ -2832,7 +3009,7 @@ const observe = async () => {
2832
3009
  // Preserve command execution only for coordinators].
2833
3010
  if (!(await isCoordinator(user)))
2834
3011
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
2835
- // Get running cerimonies info (if any).
3012
+ // Get running ceremonies info (if any).
2836
3013
  const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase);
2837
3014
  // Ask to select a ceremony.
2838
3015
  const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false);
@@ -2881,7 +3058,7 @@ const handleVerificationKey = async (cloudFunctions, bucketName, finalZkeyLocalF
2881
3058
  spinner.text = "Writing verification key...";
2882
3059
  // Write the verification key locally.
2883
3060
  writeLocalJsonFile(verificationKeyLocalFilePath, vKey);
2884
- await sleep(3000); // workaound for file descriptor.
3061
+ await sleep(3000); // workaround for file descriptor.
2885
3062
  // Upload verification key to storage.
2886
3063
  await multiPartUpload(cloudFunctions, bucketName, verificationKeyStorageFilePath, verificationKeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
2887
3064
  spinner.succeed(`Verification key correctly saved on storage`);
@@ -2901,13 +3078,13 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
2901
3078
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}`;
2902
3079
  const verifierPath = packagePath.includes(`src/commands`)
2903
3080
  ? `${dirname(fileURLToPath(import.meta.url))}/../../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
2904
- : `${dirname(fileURLToPath(import.meta.url))}/../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
3081
+ : `${dirname(fileURLToPath(import.meta.url))}/../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
2905
3082
  // Export the Solidity verifier smart contract.
2906
3083
  const verifierCode = await exportVerifierContract(finalZkeyLocalFilePath, verifierPath);
2907
3084
  spinner.text = `Writing verifier smart contract...`;
2908
3085
  // Write the verification key locally.
2909
3086
  writeFile(verifierContractLocalFilePath, verifierCode);
2910
- await sleep(3000); // workaound for file descriptor.
3087
+ await sleep(3000); // workaround for file descriptor.
2911
3088
  // Upload verifier smart contract to storage.
2912
3089
  await multiPartUpload(cloudFunctions, bucketName, verifierContractStorageFilePath, verifierContractLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
2913
3090
  spinner.succeed(`Verifier smart contract correctly saved on storage`);
@@ -2928,11 +3105,12 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
2928
3105
  * @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
2929
3106
  * @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
2930
3107
  * @param coordinatorIdentifier <string> - the identifier of the coordinator.
3108
+ * @param circuitsLength <number> - the number of circuits in the ceremony.
2931
3109
  */
2932
- const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier) => {
3110
+ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier, circuitsLength) => {
2933
3111
  // Step (1).
2934
- await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true);
2935
- await sleep(2000); // workaound for descriptors.
3112
+ await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength);
3113
+ await sleep(2000); // workaround for descriptors.
2936
3114
  // Extract data.
2937
3115
  const { prefix: circuitPrefix } = circuit.data;
2938
3116
  const { prefix: ceremonyPrefix } = ceremony.data;
@@ -2966,10 +3144,11 @@ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, cere
2966
3144
  * @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
2967
3145
  * handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
2968
3146
  */
2969
- const finalize = async () => {
3147
+ const finalize = async (opt) => {
2970
3148
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
2971
3149
  // Check for authentication.
2972
- const { user, providerUserId, token: coordinatorAccessToken } = await checkAuth(firebaseApp);
3150
+ const { auth } = opt;
3151
+ const { user, providerUserId, token: coordinatorAccessToken } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
2973
3152
  // Preserve command execution only for coordinators.
2974
3153
  if (!(await isCoordinator(user)))
2975
3154
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
@@ -3004,7 +3183,7 @@ const finalize = async () => {
3004
3183
  const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
3005
3184
  // Handle finalization for each ceremony circuit.
3006
3185
  for await (const circuit of circuits)
3007
- await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId);
3186
+ await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId, circuits.length);
3008
3187
  process.stdout.write(`\n`);
3009
3188
  const spinner = customSpinner(`Wrapping up the finalization of the ceremony...`, "clock");
3010
3189
  spinner.start();
@@ -3019,7 +3198,7 @@ const finalize = async () => {
3019
3198
  // Generate attestation with final contributions.
3020
3199
  const publicAttestation = await generateValidContributionsAttestation(firestoreDatabase, circuits, selectedCeremony.id, participant.id, contributions, providerUserId, ceremonyName, true);
3021
3200
  // Write public attestation locally.
3022
- writeFile(getAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
3201
+ writeFile(getFinalAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
3023
3202
  await sleep(3000); // workaround for file descriptor unexpected close.
3024
3203
  const gistUrl = await publishGist(coordinatorAccessToken, publicAttestation, ceremonyName, prefix);
3025
3204
  console.log(`\n${theme.symbols.info} Your public final attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
@@ -3081,6 +3260,7 @@ const logout = async () => {
3081
3260
  await signOut(auth);
3082
3261
  // Delete local token.
3083
3262
  deleteLocalAccessToken();
3263
+ deleteLocalBandadaIdentity();
3084
3264
  await sleep(3000); // ~3s.
3085
3265
  spinner.stop();
3086
3266
  console.log(`${theme.symbols.success} Logout successfully completed`);
@@ -3142,6 +3322,37 @@ const listCeremonies = async () => {
3142
3322
  }
3143
3323
  };
3144
3324
 
3325
+ const listParticipants = async () => {
3326
+ try {
3327
+ const { firestoreDatabase } = await bootstrapCommandExecutionAndServices();
3328
+ const allCeremonies = await getAllCeremonies(firestoreDatabase);
3329
+ const selectedCeremony = await promptForCeremonySelection(allCeremonies, true);
3330
+ const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id);
3331
+ const participantsRef = collection(docRef, "participants");
3332
+ const participantsSnapshot = await getDocs(participantsRef);
3333
+ const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data().userId);
3334
+ console.log(participants);
3335
+ /* const usersRef = collection(firestoreDatabase, "users")
3336
+ const usersSnapshot = await getDocs(usersRef)
3337
+ const users = usersSnapshot.docs.map((userDoc) => userDoc.data())
3338
+ console.log(users) */
3339
+ }
3340
+ catch (err) {
3341
+ showError(`Something went wrong: ${err.toString()}`, true);
3342
+ }
3343
+ process.exit(0);
3344
+ };
3345
+
3346
+ const setCeremonyCommands = (program) => {
3347
+ const ceremony = program.command("ceremony").description("manage ceremonies");
3348
+ ceremony
3349
+ .command("participants")
3350
+ .description("retrieve participants list of a ceremony")
3351
+ .requiredOption("-c, --ceremony <string>", "the prefix of the ceremony you want to retrieve information about", "")
3352
+ .action(listParticipants);
3353
+ return ceremony;
3354
+ };
3355
+
3145
3356
  // Get pkg info (e.g., name, version).
3146
3357
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
3147
3358
  const { description, version, name } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
@@ -3150,44 +3361,48 @@ const program = createCommand();
3150
3361
  program.name(name).description(description).version(version);
3151
3362
  // User commands.
3152
3363
  program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth);
3364
+ program
3365
+ .command("auth-bandada")
3366
+ .description("authenticate yourself in a privacy-perserving manner using Bandada")
3367
+ .action(authBandada);
3153
3368
  program
3154
3369
  .command("contribute")
3155
3370
  .description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
3156
3371
  .option("-c, --ceremony <string>", "the prefix of the ceremony you want to contribute for", "")
3157
3372
  .option("-e, --entropy <string>", "the entropy (aka toxic waste) of your contribution", "")
3373
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
3158
3374
  .action(contribute);
3159
3375
  program
3160
3376
  .command("clean")
3161
3377
  .description("clean up output generated by commands from the current working directory")
3162
3378
  .action(clean);
3163
- program
3164
- .command("list")
3165
- .description("List all ceremonies prefixes")
3166
- .action(listCeremonies);
3379
+ program.command("list").description("List all ceremonies prefixes").action(listCeremonies);
3167
3380
  program
3168
3381
  .command("logout")
3169
3382
  .description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
3170
3383
  .action(logout);
3171
3384
  program
3172
3385
  .command("validate")
3173
- .description("Validate that a Ceremony Setup file is correct")
3386
+ .description("validate that a Ceremony Setup file is correct")
3174
3387
  .requiredOption("-t, --template <path>", "The path to the ceremony setup template", "")
3175
3388
  .option("-c, --constraints <number>", "The number of constraints to check against")
3176
3389
  .action(validate);
3177
3390
  // Only coordinator commands.
3178
- const ceremony = program.command("coordinate").description("commands for coordinating a ceremony");
3179
- ceremony
3391
+ const coordinate = program.command("coordinate").description("commands for coordinating a ceremony");
3392
+ coordinate
3180
3393
  .command("setup")
3181
3394
  .description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
3182
- .option('-t, --template <path>', 'The path to the ceremony setup template', '')
3183
- .option('-a, --auth <string>', 'The Github OAuth 2.0 token', '')
3395
+ .option("-t, --template <path>", "The path to the ceremony setup template", "")
3396
+ .option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
3184
3397
  .action(setup);
3185
- ceremony
3398
+ coordinate
3186
3399
  .command("observe")
3187
3400
  .description("observe in real-time the waiting queue of each ceremony circuit")
3188
3401
  .action(observe);
3189
- ceremony
3402
+ coordinate
3190
3403
  .command("finalize")
3191
3404
  .description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
3405
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
3192
3406
  .action(finalize);
3407
+ setCeremonyCommands(program);
3193
3408
  program.parseAsync(process.argv);