@devtion/devcli 0.0.0-8b5a17f → 0.0.0-8bb9489

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 CHANGED
@@ -67,7 +67,7 @@ npx @p0tion/phase2cli contribute
67
67
 
68
68
  ### Local Development
69
69
 
70
- **Prerequisities**
70
+ **Prerequisites**
71
71
 
72
72
  - Node.js version 16.0 or higher.
73
73
  - Yarn version 3.5.0 or higher.
package/dist/.env CHANGED
@@ -29,9 +29,9 @@ AUTH_GITHUB_CLIENT_ID=e9f8a5fabdfe0d95618c
29
29
  ### AWS S3 bucket used as storage for ceremony artifacts.
30
30
 
31
31
  # The chunk size to be used when executing multi-part upload or downloads.
32
- # default 50 MBs.
33
- # (e.g. a 200 MB file setting a stream chunk size of 50 MB is going to be splitted and uploaded/downloaded in 4 chunks).
34
- CONFIG_STREAM_CHUNK_SIZE_IN_MB=50
32
+ # default 25 MBs.
33
+ # (e.g. a 200 MB file setting a stream chunk size of 25 MB is going to be splitted and uploaded/downloaded in 4 chunks).
34
+ CONFIG_STREAM_CHUNK_SIZE_IN_MB=25
35
35
  # The postfix string for each ceremony bucket.
36
36
  # default -ph2-ceremony
37
37
  CONFIG_CEREMONY_BUCKET_POSTFIX=-p0tion-development-environment
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
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.0.9
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
@@ -365,6 +365,12 @@ const getVerificationKeyLocalFilePath = (completeFilename) => `${verificationKey
365
365
  * @returns <string> - the complete final verifier contract path to the file.
366
366
  */
367
367
  const getVerifierContractLocalFilePath = (completeFilename) => `${verifierContractsLocalFolderPath}/${completeFilename}`;
368
+ /**
369
+ * Get the complete final attestation file path.
370
+ * @param completeFilename <string> - the complete filename of the file (name.ext).
371
+ * @returns <string> - the complete final final attestation path to the file.
372
+ */
373
+ const getFinalAttestationLocalFilePath = (completeFilename) => `${finalAttestationsLocalFolderPath}/${completeFilename}`;
368
374
  /**
369
375
  * Get the final transcript file path.
370
376
  * @param completeFilename <string> - the complete filename of the file (name.ext).
@@ -579,8 +585,18 @@ const publishGist = async (token, content, ceremonyTitle, ceremonyPrefix) => {
579
585
  * @returns <string> - the ready to share tweet url.
580
586
  */
581
587
  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`;
588
+ ? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
589
+ ceremonyName.toLowerCase().includes("setup") ||
590
+ ceremonyName.toLowerCase().includes("phase2") ||
591
+ ceremonyName.toLowerCase().includes("ceremony")
592
+ ? "!"
593
+ : "%20Phase%202%20Trusted%20Setup%20ceremony!"}%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
594
+ : `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
595
+ ceremonyName.toLowerCase().includes("setup") ||
596
+ ceremonyName.toLowerCase().includes("phase2") ||
597
+ ceremonyName.toLowerCase().includes("ceremony")
598
+ ? "!"
599
+ : "%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
600
  /**
585
601
  * Return a custom progress bar.
586
602
  * @param type <ProgressBarType> - the type of the progress bar.
@@ -708,13 +724,14 @@ const getLatestUpdatesFromParticipant = async (firestoreDatabase, ceremonyId, pa
708
724
  * @param entropyOrBeaconHash <string> - the entropy or beacon hash (only when finalizing) for the contribution.
709
725
  * @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
710
726
  * @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
727
+ * @param circuitsLength <number> - the total number of circuits in the ceremony.
711
728
  */
712
- const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing) => {
729
+ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing, circuitsLength) => {
713
730
  // Extract data.
714
731
  const { prefix: ceremonyPrefix } = ceremony.data;
715
732
  const { waitingQueue, avgTimings, prefix: circuitPrefix, sequencePosition } = circuit.data;
716
733
  const { completedContributions } = waitingQueue; // = current progress.
717
- console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}`)}`)} (Contribution Steps)`);
734
+ console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}/${circuitsLength}`)}`)} (Contribution Steps)`);
718
735
  // Get most up-to-date data from the participant document.
719
736
  let participantData = await getLatestUpdatesFromParticipant(firestoreDatabase, ceremony.id, participant.id);
720
737
  const spinner = customSpinner(`${participantData.contributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */
@@ -760,6 +777,7 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
760
777
  // Download the latest contribution from bucket.
761
778
  await downloadCeremonyArtifact(cloudFunctions, bucketName, lastZkeyStorageFilePath, lastZkeyLocalFilePath);
762
779
  console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${lastZkeyIndex}`)} correctly downloaded`);
780
+ await sleep(3000);
763
781
  // Advance to next contribution step (COMPUTING) if not finalizing.
764
782
  if (!isFinalizing) {
765
783
  spinner.text = `Preparing for contribution computation...`;
@@ -787,11 +805,14 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
787
805
  showError(COMMAND_ERRORS.COMMAND_CONTRIBUTE_FINALIZE_NO_TRANSCRIPT_CONTRIBUTION_HASH_MATCH, true);
788
806
  // Format contribution hash.
789
807
  const contributionHash = matchContributionHash?.at(0)?.replace("\n\t\t", "");
808
+ await sleep(500);
790
809
  // Make request to cloud functions to permanently store the information.
791
810
  await permanentlyStoreCurrentContributionTimeAndHash(cloudFunctions, ceremony.id, computingTime, contributionHash);
792
811
  // Format computing time.
793
812
  const { seconds: computationSeconds, minutes: computationMinutes, hours: computationHours } = getSecondsMinutesHoursFromMillis(computingTime);
794
813
  spinner.succeed(`${isFinalizing ? "Contribution" : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} computation took ${theme.text.bold(`${convertToDoubleDigits(computationHours)}:${convertToDoubleDigits(computationMinutes)}:${convertToDoubleDigits(computationSeconds)}`)}`);
814
+ // ensure the previous step is completed
815
+ await sleep(5000);
795
816
  // Advance to next contribution step (UPLOADING) if not finalizing.
796
817
  if (!isFinalizing) {
797
818
  spinner.text = `Preparing for uploading the contribution...`;
@@ -809,10 +830,15 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
809
830
  if (isFinalizing || participantData.contributionStep === "UPLOADING" /* ParticipantContributionStep.UPLOADING */) {
810
831
  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
832
  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);
833
+ const progressBar = customProgressBar(ProgressBarType.UPLOAD, `your contribution`);
834
+ if (!isFinalizing) {
835
+ await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB), ceremony.id, participantData.tempContributionData, progressBar);
836
+ progressBar.stop();
837
+ }
814
838
  else
815
839
  await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
840
+ // small sleep to ensure the previous step is completed
841
+ await sleep(5000);
816
842
  spinner.succeed(`${isFinalizing ? `Contribution` : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} correctly saved to storage`);
817
843
  // Advance to next contribution step (VERIFYING) if not finalizing.
818
844
  if (!isFinalizing) {
@@ -990,7 +1016,7 @@ const promptCircomCompiler = async () => {
990
1016
  * Shows a list of circuits for a single option selection.
991
1017
  * @dev the circuit names are derived from local R1CS files.
992
1018
  * @param options <Array<string>> - an array of circuits names.
993
- * @returns Promise<string> - the name of the choosen circuit.
1019
+ * @returns Promise<string> - the name of the chosen circuit.
994
1020
  */
995
1021
  const promptCircuitSelector = async (options) => {
996
1022
  const { circuitFilename } = await prompts({
@@ -1008,7 +1034,7 @@ const promptCircuitSelector = async (options) => {
1008
1034
  * Shows a list of standard EC2 VM instance types for a single option selection.
1009
1035
  * @notice the suggested VM configuration type is calculated based on circuit constraint size.
1010
1036
  * @param constraintSize <number> - the amount of circuit constraints
1011
- * @returns Promise<string> - the name of the choosen VM type.
1037
+ * @returns Promise<string> - the name of the chosen VM type.
1012
1038
  */
1013
1039
  const promptVMTypeSelector = async (constraintSize) => {
1014
1040
  let suggestedConfiguration = 0;
@@ -1105,7 +1131,7 @@ const promptVMDiskTypeSelector = async () => {
1105
1131
  /**
1106
1132
  * Show a series of questions about the circuits.
1107
1133
  * @param constraintSize <number> - the amount of circuit constraints.
1108
- * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
1134
+ * @param timeoutMechanismType <CeremonyTimeoutType> - the chosen timeout mechanism type for the ceremony.
1109
1135
  * @param needPromptCircomCompiler <boolean> - a boolean value indicating if the questions related to the Circom compiler version and commit hash must be asked.
1110
1136
  * @param enforceVM <boolean> - a boolean value indicating if the contribution verification could be supported by VM-only approach or not.
1111
1137
  * @returns Promise<Array<Circuit>> - circuit info prompted by the coordinator.
@@ -1118,7 +1144,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1118
1144
  let circomVersion = "";
1119
1145
  let circomCommitHash = "";
1120
1146
  let circuitInputData;
1121
- let useCfOrVm;
1147
+ let cfOrVm;
1122
1148
  let vmDiskType;
1123
1149
  let vmConfigurationType = "";
1124
1150
  const questions = [
@@ -1173,18 +1199,21 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1173
1199
  circomVersion = version;
1174
1200
  circomCommitHash = commitHash;
1175
1201
  }
1176
- // Ask for prefered contribution verification method (CF vs VM).
1202
+ // Ask for preferred contribution verification method (CF vs VM).
1177
1203
  if (!enforceVM) {
1178
1204
  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
1205
  `VM` // eq. false.
1180
1206
  );
1181
- useCfOrVm = confirmation;
1207
+ cfOrVm = confirmation
1208
+ ? "CF" /* CircuitContributionVerificationMechanism.CF */
1209
+ : "VM" /* CircuitContributionVerificationMechanism.VM */;
1182
1210
  }
1183
- else
1184
- useCfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
1185
- if (useCfOrVm === undefined)
1211
+ else {
1212
+ cfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
1213
+ }
1214
+ if (cfOrVm === undefined)
1186
1215
  showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
1187
- if (!useCfOrVm) {
1216
+ if (cfOrVm === "VM" /* CircuitContributionVerificationMechanism.VM */) {
1188
1217
  // Ask for selecting the specific VM configuration type.
1189
1218
  vmConfigurationType = await promptVMTypeSelector(constraintSize);
1190
1219
  // Ask for selecting the specific VM disk (volume) type.
@@ -1218,9 +1247,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1218
1247
  paramsConfiguration: circuitConfigurationValues
1219
1248
  },
1220
1249
  verification: {
1221
- cfOrVm: useCfOrVm
1222
- ? "CF" /* CircuitContributionVerificationMechanism.CF */
1223
- : "VM" /* CircuitContributionVerificationMechanism.VM */,
1250
+ cfOrVm,
1224
1251
  vm: {
1225
1252
  vmConfigurationType,
1226
1253
  vmDiskType
@@ -1256,9 +1283,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1256
1283
  paramsConfiguration: circuitConfigurationValues
1257
1284
  },
1258
1285
  verification: {
1259
- cfOrVm: useCfOrVm
1260
- ? "CF" /* CircuitContributionVerificationMechanism.CF */
1261
- : "VM" /* CircuitContributionVerificationMechanism.VM */,
1286
+ cfOrVm,
1262
1287
  vm: {
1263
1288
  vmConfigurationType,
1264
1289
  vmDiskType
@@ -1302,7 +1327,7 @@ const promptCircuitAddition = async () => {
1302
1327
  * Shows a list of pre-computed zKeys for a single option selection.
1303
1328
  * @dev the names are derived from local zKeys files.
1304
1329
  * @param options <Array<string>> - an array of pre-computed zKeys names.
1305
- * @returns Promise<string> - the name of the choosen pre-computed zKey.
1330
+ * @returns Promise<string> - the name of the chosen pre-computed zKey.
1306
1331
  */
1307
1332
  const promptPreComputedZkeySelector = async (options) => {
1308
1333
  const { preComputedZkeyFilename } = await prompts({
@@ -1340,13 +1365,13 @@ const promptNeededPowersForCircuit = async (suggestedSmallestNeededPowers) => {
1340
1365
  * Shows a list of PoT files for a single option selection.
1341
1366
  * @dev the names are derived from local PoT files.
1342
1367
  * @param options <Array<string>> - an array of PoT file names.
1343
- * @returns Promise<string> - the name of the choosen PoT.
1368
+ * @returns Promise<string> - the name of the chosen PoT.
1344
1369
  */
1345
1370
  const promptPotSelector = async (options) => {
1346
1371
  const { potFilename } = await prompts({
1347
1372
  type: "select",
1348
1373
  name: "potFilename",
1349
- message: theme.text.bold("Select the Powers of Tau file choosen for the circuit"),
1374
+ message: theme.text.bold("Select the Powers of Tau file chosen for the circuit"),
1350
1375
  choices: options.map((option) => {
1351
1376
  console.log(option);
1352
1377
  return { title: option, value: option };
@@ -1416,7 +1441,7 @@ const promptToTypeEntropyOrBeacon = async (isEntropy = true) => {
1416
1441
  * @return <Promise<string>> - the entropy.
1417
1442
  */
1418
1443
  const promptForEntropy = async () => {
1419
- // Prompt for entropy generation prefered method.
1444
+ // Prompt for entropy generation preferred method.
1420
1445
  const { confirmation } = await askForConfirmation(`Do you prefer to type your entropy or generate it randomly?`, "Manually", "Randomly");
1421
1446
  if (confirmation === undefined)
1422
1447
  showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
@@ -1633,7 +1658,7 @@ const handleAdditionOfCircuitsToCeremony = async (r1csOptions, wasmOptions, cere
1633
1658
  wasmFilename.split(`.${commonTerms.foldersAndPathsTerms.wasm}`)[0]);
1634
1659
  if (matchingWasms.length !== 1)
1635
1660
  showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true);
1636
- // Get input data for choosen circuit.
1661
+ // Get input data for chosen circuit.
1637
1662
  const circuitInputData = await getInputDataToAddCircuitToCeremony(choosenCircuitFilename, matchingWasms[0], ceremonyTimeoutMechanismType, sameCircomCompiler, circuitSequencePosition, sharedCircomCompilerData);
1638
1663
  // Store circuit data.
1639
1664
  inputDataForCircuits.push(circuitInputData);
@@ -1723,7 +1748,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
1723
1748
  * number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
1724
1749
  * to provide a number of powers manually, ranging from the smallest possible to the largest.
1725
1750
  * @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
1751
+ * @returns Promise<string, string> - the information about the chosen Powers of Tau file for the pre-computed zKey
1727
1752
  * along with related powers.
1728
1753
  */
1729
1754
  const handlePreComputedZkeyPowersOfTauSelection = async (neededPowers) => {
@@ -1824,7 +1849,9 @@ const setup = async (cmd) => {
1824
1849
  let ceremonyId = ""; // The unique identifier of the ceremony.
1825
1850
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
1826
1851
  // Check for authentication.
1827
- const { user, providerUserId } = cmd.auth ? await authWithToken(firebaseApp, cmd.auth) : await checkAuth(firebaseApp);
1852
+ const { user, providerUserId } = cmd.auth
1853
+ ? await authWithToken(firebaseApp, cmd.auth)
1854
+ : await checkAuth(firebaseApp);
1828
1855
  // Preserve command execution only for coordinators.
1829
1856
  if (!(await isCoordinator(user)))
1830
1857
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
@@ -1841,7 +1868,7 @@ const setup = async (cmd) => {
1841
1868
  // if there is the file option, then set up the non interactively
1842
1869
  if (cmd.template) {
1843
1870
  // 1. parse the file
1844
- // tmp data - do not cleanup files as we need them
1871
+ // tmp data - do not cleanup files as we need them
1845
1872
  const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template)} setup configuration file...`, `clock`);
1846
1873
  spinner.start();
1847
1874
  const setupCeremonyData = await parseCeremonyFile(cmd.template);
@@ -2097,17 +2124,29 @@ const expirationCountdownForGithubOAuth = (expirationInSeconds) => {
2097
2124
  */
2098
2125
  const onVerification = async (verification) => {
2099
2126
  // Copy code to clipboard.
2100
- clipboard.writeSync(verification.user_code);
2101
- clipboard.readSync();
2127
+ let noClipboard = false;
2128
+ try {
2129
+ clipboard.writeSync(verification.user_code);
2130
+ clipboard.readSync();
2131
+ }
2132
+ catch (error) {
2133
+ noClipboard = true;
2134
+ }
2102
2135
  // Display data.
2103
2136
  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`);
2137
+ console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
2138
+ 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}\n`);
2106
2140
  const spinner = customSpinner(`Redirecting to Github...`, `clock`);
2107
2141
  spinner.start();
2108
2142
  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);
2143
+ try {
2144
+ // Automatically open the page (# Step 2).
2145
+ await open(verification.verification_uri);
2146
+ }
2147
+ catch (error) {
2148
+ console.log(`${theme.symbols.info} Please authenticate via GitHub at ${verification.verification_uri}`);
2149
+ }
2111
2150
  spinner.stop();
2112
2151
  // Countdown for time expiration.
2113
2152
  expirationCountdownForGithubOAuth(verification.expires_in);
@@ -2553,8 +2592,8 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2553
2592
  // Communicate resume / start of the contribution to participant.
2554
2593
  await simpleLoader(`${changedContributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */ ? `Starting` : `Resuming`} your contribution...`, `clock`, 3000);
2555
2594
  // Start / Resume the contribution for the participant.
2556
- await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false // not finalizing.
2557
- );
2595
+ await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false, // not finalizing.
2596
+ circuits.length);
2558
2597
  }
2559
2598
  // Scenario (3.A).
2560
2599
  else if (isWaitingForContribution)
@@ -2603,7 +2642,9 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2603
2642
  // Get latest contribution verification result.
2604
2643
  await getLatestVerificationResult(firestoreDatabase, ceremony.id, circuit.id, participant.id);
2605
2644
  // Get next circuit for contribution.
2606
- const nextCircuit = timeoutExpired ? getCircuitBySequencePosition(circuits, changedContributionProgress) : getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
2645
+ const nextCircuit = timeoutExpired
2646
+ ? getCircuitBySequencePosition(circuits, changedContributionProgress)
2647
+ : getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
2607
2648
  // Check disk space requirements for participant.
2608
2649
  const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(cloudFunctions, ceremony.id, nextCircuit.data.sequencePosition, nextCircuit.data.zKeySizeInBytes, timeoutExpired, providerUserId);
2609
2650
  // Check if the participant would like to generate a new attestation.
@@ -2682,7 +2723,7 @@ const contribute = async (opt) => {
2682
2723
  const userDoc = await getDocumentById(firestoreDatabase, commonTerms.collections.users.name, user.uid);
2683
2724
  const userData = userDoc.data();
2684
2725
  if (!userData) {
2685
- 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.`);
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 elegible 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.`);
2686
2727
  process.exit(0);
2687
2728
  }
2688
2729
  // Check the user's current participant readiness for contribution status (eligible, already contributed, timed out).
@@ -2833,7 +2874,7 @@ const observe = async () => {
2833
2874
  // Preserve command execution only for coordinators].
2834
2875
  if (!(await isCoordinator(user)))
2835
2876
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
2836
- // Get running cerimonies info (if any).
2877
+ // Get running ceremonies info (if any).
2837
2878
  const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase);
2838
2879
  // Ask to select a ceremony.
2839
2880
  const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false);
@@ -2902,7 +2943,7 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
2902
2943
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}`;
2903
2944
  const verifierPath = packagePath.includes(`src/commands`)
2904
2945
  ? `${dirname(fileURLToPath(import.meta.url))}/../../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
2905
- : `${dirname(fileURLToPath(import.meta.url))}/../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
2946
+ : `${dirname(fileURLToPath(import.meta.url))}/../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
2906
2947
  // Export the Solidity verifier smart contract.
2907
2948
  const verifierCode = await exportVerifierContract(finalZkeyLocalFilePath, verifierPath);
2908
2949
  spinner.text = `Writing verifier smart contract...`;
@@ -2929,10 +2970,11 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
2929
2970
  * @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
2930
2971
  * @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
2931
2972
  * @param coordinatorIdentifier <string> - the identifier of the coordinator.
2973
+ * @param circuitsLength <number> - the number of circuits in the ceremony.
2932
2974
  */
2933
- const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier) => {
2975
+ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier, circuitsLength) => {
2934
2976
  // Step (1).
2935
- await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true);
2977
+ await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength);
2936
2978
  await sleep(2000); // workaound for descriptors.
2937
2979
  // Extract data.
2938
2980
  const { prefix: circuitPrefix } = circuit.data;
@@ -2967,10 +3009,11 @@ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, cere
2967
3009
  * @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
2968
3010
  * handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
2969
3011
  */
2970
- const finalize = async () => {
3012
+ const finalize = async (opt) => {
2971
3013
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
2972
3014
  // Check for authentication.
2973
- const { user, providerUserId, token: coordinatorAccessToken } = await checkAuth(firebaseApp);
3015
+ const auth = opt.auth;
3016
+ const { user, providerUserId, token: coordinatorAccessToken } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
2974
3017
  // Preserve command execution only for coordinators.
2975
3018
  if (!(await isCoordinator(user)))
2976
3019
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
@@ -3005,7 +3048,7 @@ const finalize = async () => {
3005
3048
  const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
3006
3049
  // Handle finalization for each ceremony circuit.
3007
3050
  for await (const circuit of circuits)
3008
- await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId);
3051
+ await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId, circuits.length);
3009
3052
  process.stdout.write(`\n`);
3010
3053
  const spinner = customSpinner(`Wrapping up the finalization of the ceremony...`, "clock");
3011
3054
  spinner.start();
@@ -3020,7 +3063,7 @@ const finalize = async () => {
3020
3063
  // Generate attestation with final contributions.
3021
3064
  const publicAttestation = await generateValidContributionsAttestation(firestoreDatabase, circuits, selectedCeremony.id, participant.id, contributions, providerUserId, ceremonyName, true);
3022
3065
  // Write public attestation locally.
3023
- writeFile(getAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
3066
+ writeFile(getFinalAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
3024
3067
  await sleep(3000); // workaround for file descriptor unexpected close.
3025
3068
  const gistUrl = await publishGist(coordinatorAccessToken, publicAttestation, ceremonyName, prefix);
3026
3069
  console.log(`\n${theme.symbols.info} Your public final attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
@@ -3162,10 +3205,7 @@ program
3162
3205
  .command("clean")
3163
3206
  .description("clean up output generated by commands from the current working directory")
3164
3207
  .action(clean);
3165
- program
3166
- .command("list")
3167
- .description("List all ceremonies prefixes")
3168
- .action(listCeremonies);
3208
+ program.command("list").description("List all ceremonies prefixes").action(listCeremonies);
3169
3209
  program
3170
3210
  .command("logout")
3171
3211
  .description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
@@ -3181,8 +3221,8 @@ const ceremony = program.command("coordinate").description("commands for coordin
3181
3221
  ceremony
3182
3222
  .command("setup")
3183
3223
  .description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
3184
- .option('-t, --template <path>', 'The path to the ceremony setup template', '')
3185
- .option('-a, --auth <string>', 'The Github OAuth 2.0 token', '')
3224
+ .option("-t, --template <path>", "The path to the ceremony setup template", "")
3225
+ .option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
3186
3226
  .action(setup);
3187
3227
  ceremony
3188
3228
  .command("observe")
@@ -3191,5 +3231,6 @@ ceremony
3191
3231
  ceremony
3192
3232
  .command("finalize")
3193
3233
  .description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
3234
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
3194
3235
  .action(finalize);
3195
3236
  program.parseAsync(process.argv);
@@ -36,8 +36,9 @@ export declare const handleVerifierSmartContract: (cloudFunctions: Functions, bu
36
36
  * @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
37
37
  * @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
38
38
  * @param coordinatorIdentifier <string> - the identifier of the coordinator.
39
+ * @param circuitsLength <number> - the number of circuits in the ceremony.
39
40
  */
40
- export declare const handleCircuitFinalization: (cloudFunctions: Functions, firestoreDatabase: Firestore, ceremony: FirebaseDocumentInfo, circuit: FirebaseDocumentInfo, participant: FirebaseDocumentInfo, beacon: string, coordinatorIdentifier: string) => Promise<void>;
41
+ export declare const handleCircuitFinalization: (cloudFunctions: Functions, firestoreDatabase: Firestore, ceremony: FirebaseDocumentInfo, circuit: FirebaseDocumentInfo, participant: FirebaseDocumentInfo, beacon: string, coordinatorIdentifier: string, circuitsLength: number) => Promise<void>;
41
42
  /**
42
43
  * Finalize command.
43
44
  * @notice The finalize command allows a coordinator to finalize a Trusted Setup Phase 2 ceremony by providing the final beacon,
@@ -47,5 +48,5 @@ export declare const handleCircuitFinalization: (cloudFunctions: Functions, fire
47
48
  * @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
48
49
  * handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
49
50
  */
50
- declare const finalize: () => Promise<void>;
51
+ declare const finalize: (opt: any) => Promise<void>;
51
52
  export default finalize;
@@ -39,7 +39,7 @@ export declare const checkAndDownloadSmallestPowersOfTau: (powers: string, ptauC
39
39
  * number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
40
40
  * to provide a number of powers manually, ranging from the smallest possible to the largest.
41
41
  * @param neededPowers <number> - the smallest amount of powers needed by the zKey.
42
- * @returns Promise<string, string> - the information about the choosen Powers of Tau file for the pre-computed zKey
42
+ * @returns Promise<string, string> - the information about the chosen Powers of Tau file for the pre-computed zKey
43
43
  * along with related powers.
44
44
  */
45
45
  export declare const handlePreComputedZkeyPowersOfTauSelection: (neededPowers: number) => Promise<{
@@ -24,14 +24,14 @@ export declare const promptCircomCompiler: () => Promise<CircomCompilerData>;
24
24
  * Shows a list of circuits for a single option selection.
25
25
  * @dev the circuit names are derived from local R1CS files.
26
26
  * @param options <Array<string>> - an array of circuits names.
27
- * @returns Promise<string> - the name of the choosen circuit.
27
+ * @returns Promise<string> - the name of the chosen circuit.
28
28
  */
29
29
  export declare const promptCircuitSelector: (options: Array<string>) => Promise<string>;
30
30
  /**
31
31
  * Shows a list of standard EC2 VM instance types for a single option selection.
32
32
  * @notice the suggested VM configuration type is calculated based on circuit constraint size.
33
33
  * @param constraintSize <number> - the amount of circuit constraints
34
- * @returns Promise<string> - the name of the choosen VM type.
34
+ * @returns Promise<string> - the name of the chosen VM type.
35
35
  */
36
36
  export declare const promptVMTypeSelector: (constraintSize: any) => Promise<string>;
37
37
  /**
@@ -42,7 +42,7 @@ export declare const promptVMDiskTypeSelector: () => Promise<DiskTypeForVM>;
42
42
  /**
43
43
  * Show a series of questions about the circuits.
44
44
  * @param constraintSize <number> - the amount of circuit constraints.
45
- * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
45
+ * @param timeoutMechanismType <CeremonyTimeoutType> - the chosen timeout mechanism type for the ceremony.
46
46
  * @param needPromptCircomCompiler <boolean> - a boolean value indicating if the questions related to the Circom compiler version and commit hash must be asked.
47
47
  * @param enforceVM <boolean> - a boolean value indicating if the contribution verification could be supported by VM-only approach or not.
48
48
  * @returns Promise<Array<Circuit>> - circuit info prompted by the coordinator.
@@ -67,7 +67,7 @@ export declare const promptCircuitAddition: () => Promise<boolean>;
67
67
  * Shows a list of pre-computed zKeys for a single option selection.
68
68
  * @dev the names are derived from local zKeys files.
69
69
  * @param options <Array<string>> - an array of pre-computed zKeys names.
70
- * @returns Promise<string> - the name of the choosen pre-computed zKey.
70
+ * @returns Promise<string> - the name of the chosen pre-computed zKey.
71
71
  */
72
72
  export declare const promptPreComputedZkeySelector: (options: Array<string>) => Promise<string>;
73
73
  /**
@@ -80,7 +80,7 @@ export declare const promptNeededPowersForCircuit: (suggestedSmallestNeededPower
80
80
  * Shows a list of PoT files for a single option selection.
81
81
  * @dev the names are derived from local PoT files.
82
82
  * @param options <Array<string>> - an array of PoT file names.
83
- * @returns Promise<string> - the name of the choosen PoT.
83
+ * @returns Promise<string> - the name of the chosen PoT.
84
84
  */
85
85
  export declare const promptPotSelector: (options: Array<string>) => Promise<string>;
86
86
  /**
@@ -154,5 +154,6 @@ export declare const getLatestUpdatesFromParticipant: (firestoreDatabase: Firest
154
154
  * @param entropyOrBeaconHash <string> - the entropy or beacon hash (only when finalizing) for the contribution.
155
155
  * @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
156
156
  * @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
157
+ * @param circuitsLength <number> - the total number of circuits in the ceremony.
157
158
  */
158
- export declare const handleStartOrResumeContribution: (cloudFunctions: Functions, firestoreDatabase: Firestore, ceremony: FirebaseDocumentInfo, circuit: FirebaseDocumentInfo, participant: FirebaseDocumentInfo, entropyOrBeaconHash: any, contributorOrCoordinatorIdentifier: string, isFinalizing: boolean) => Promise<void>;
159
+ export declare const handleStartOrResumeContribution: (cloudFunctions: Functions, firestoreDatabase: Firestore, ceremony: FirebaseDocumentInfo, circuit: FirebaseDocumentInfo, participant: FirebaseDocumentInfo, entropyOrBeaconHash: any, contributorOrCoordinatorIdentifier: string, isFinalizing: boolean, circuitsLength: number) => Promise<void>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devtion/devcli",
3
3
  "type": "module",
4
- "version": "0.0.0-8b5a17f",
4
+ "version": "0.0.0-8bb9489",
5
5
  "description": "All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies",
6
6
  "repository": "git@github.com:privacy-scaling-explorations/p0tion.git",
7
7
  "homepage": "https://github.com/privacy-scaling-explorations/p0tion",
@@ -97,5 +97,5 @@
97
97
  "publishConfig": {
98
98
  "access": "public"
99
99
  },
100
- "gitHead": "df754397c15a7086a163688fb4d3f4e7c98ad379"
100
+ "gitHead": "183d8a9e86db61fe1c713898c0abce990a7e6004"
101
101
  }
@@ -63,8 +63,14 @@ export const expirationCountdownForGithubOAuth = (expirationInSeconds: number) =
63
63
  */
64
64
  export const onVerification = async (verification: Verification): Promise<void> => {
65
65
  // Copy code to clipboard.
66
- clipboard.writeSync(verification.user_code)
67
- clipboard.readSync()
66
+ let noClipboard = false
67
+ try {
68
+ clipboard.writeSync(verification.user_code)
69
+ clipboard.readSync()
70
+ } catch (error) {
71
+ noClipboard = true
72
+ }
73
+
68
74
 
69
75
  // Display data.
70
76
  console.log(
@@ -73,12 +79,13 @@ export const onVerification = async (verification: Verification): Promise<void>
73
79
  )} on this device to generate a new token and authenticate\n`
74
80
  )
75
81
 
76
- console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), '\n')
77
-
82
+ console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n")
83
+
84
+ const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``
78
85
  console.log(
79
- `${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} has been copied to your clipboard (${theme.emojis.clipboard} ${
80
- theme.symbols.success
81
- })\n`
86
+ `${theme.symbols.info} Your auth code: ${theme.text.bold(
87
+ verification.user_code
88
+ )} ${message} ${theme.symbols.success}\n`
82
89
  )
83
90
 
84
91
  const spinner = customSpinner(`Redirecting to Github...`, `clock`)
@@ -86,8 +93,12 @@ export const onVerification = async (verification: Verification): Promise<void>
86
93
 
87
94
  await sleep(10000) // ~10s to make users able to read the CLI.
88
95
 
89
- // Automatically open the page (# Step 2).
90
- await open(verification.verification_uri)
96
+ try {
97
+ // Automatically open the page (# Step 2).
98
+ await open(verification.verification_uri)
99
+ } catch (error: any) {
100
+ console.log(`${theme.symbols.info} Please authenticate via GitHub at ${verification.verification_uri}`)
101
+ }
91
102
 
92
103
  spinner.stop()
93
104
 
@@ -724,7 +724,8 @@ export const listenToParticipantDocumentChanges = async (
724
724
  participant,
725
725
  entropy,
726
726
  providerUserId,
727
- false // not finalizing.
727
+ false, // not finalizing.
728
+ circuits.length
728
729
  )
729
730
  }
730
731
  // Scenario (3.A).
@@ -810,7 +811,9 @@ export const listenToParticipantDocumentChanges = async (
810
811
  await getLatestVerificationResult(firestoreDatabase, ceremony.id, circuit.id, participant.id)
811
812
 
812
813
  // Get next circuit for contribution.
813
- const nextCircuit = timeoutExpired ? getCircuitBySequencePosition(circuits, changedContributionProgress) : getCircuitBySequencePosition(circuits, changedContributionProgress + 1)
814
+ const nextCircuit = timeoutExpired
815
+ ? getCircuitBySequencePosition(circuits, changedContributionProgress)
816
+ : getCircuitBySequencePosition(circuits, changedContributionProgress + 1)
814
817
 
815
818
  // Check disk space requirements for participant.
816
819
  const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(
@@ -894,7 +897,7 @@ const contribute = async (opt: any) => {
894
897
  // Get options.
895
898
  const ceremonyOpt = opt.ceremony
896
899
  const entropyOpt = opt.entropy
897
- const auth = opt.auth
900
+ const auth = opt.auth
898
901
 
899
902
  // Check for authentication.
900
903
  const { user, providerUserId, token } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp)
@@ -949,7 +952,7 @@ const contribute = async (opt: any) => {
949
952
  const userData = userDoc.data()
950
953
  if (!userData) {
951
954
  spinner.fail(
952
- `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.`
955
+ `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. 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.`
953
956
  )
954
957
  process.exit(0)
955
958
  }
@@ -36,9 +36,9 @@ import {
36
36
  sleep,
37
37
  terminate
38
38
  } from "../lib/utils.js"
39
- import { bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
39
+ import { authWithToken, bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
40
40
  import {
41
- getAttestationLocalFilePath,
41
+ getFinalAttestationLocalFilePath,
42
42
  getFinalZkeyLocalFilePath,
43
43
  getVerificationKeyLocalFilePath,
44
44
  getVerifierContractLocalFilePath,
@@ -112,7 +112,7 @@ export const handleVerifierSmartContract = async (
112
112
  ? `${dirname(
113
113
  fileURLToPath(import.meta.url)
114
114
  )}/../../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
115
- : `${dirname(fileURLToPath(import.meta.url))}/../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
115
+ : `${dirname(fileURLToPath(import.meta.url))}/../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
116
116
 
117
117
  // Export the Solidity verifier smart contract.
118
118
  const verifierCode = await exportVerifierContract(finalZkeyLocalFilePath, verifierPath)
@@ -152,6 +152,7 @@ export const handleVerifierSmartContract = async (
152
152
  * @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
153
153
  * @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
154
154
  * @param coordinatorIdentifier <string> - the identifier of the coordinator.
155
+ * @param circuitsLength <number> - the number of circuits in the ceremony.
155
156
  */
156
157
  export const handleCircuitFinalization = async (
157
158
  cloudFunctions: Functions,
@@ -160,7 +161,8 @@ export const handleCircuitFinalization = async (
160
161
  circuit: FirebaseDocumentInfo,
161
162
  participant: FirebaseDocumentInfo,
162
163
  beacon: string,
163
- coordinatorIdentifier: string
164
+ coordinatorIdentifier: string,
165
+ circuitsLength: number
164
166
  ) => {
165
167
  // Step (1).
166
168
  await handleStartOrResumeContribution(
@@ -171,7 +173,8 @@ export const handleCircuitFinalization = async (
171
173
  participant,
172
174
  computeSHA256ToHex(beacon),
173
175
  coordinatorIdentifier,
174
- true
176
+ true,
177
+ circuitsLength
175
178
  )
176
179
 
177
180
  await sleep(2000) // workaound for descriptors.
@@ -241,11 +244,16 @@ export const handleCircuitFinalization = async (
241
244
  * @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
242
245
  * handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
243
246
  */
244
- const finalize = async () => {
247
+ const finalize = async (opt: any) => {
245
248
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
246
249
 
247
250
  // Check for authentication.
248
- const { user, providerUserId, token: coordinatorAccessToken } = await checkAuth(firebaseApp)
251
+ const auth = opt.auth
252
+ const {
253
+ user,
254
+ providerUserId,
255
+ token: coordinatorAccessToken
256
+ } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp)
249
257
 
250
258
  // Preserve command execution only for coordinators.
251
259
  if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
@@ -306,7 +314,8 @@ const finalize = async () => {
306
314
  circuit,
307
315
  participant,
308
316
  beacon,
309
- providerUserId
317
+ providerUserId,
318
+ circuits.length
310
319
  )
311
320
 
312
321
  process.stdout.write(`\n`)
@@ -344,7 +353,7 @@ const finalize = async () => {
344
353
 
345
354
  // Write public attestation locally.
346
355
  writeFile(
347
- getAttestationLocalFilePath(
356
+ getFinalAttestationLocalFilePath(
348
357
  `${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`
349
358
  ),
350
359
  Buffer.from(publicAttestation)
@@ -6,4 +6,4 @@ export { default as finalize } from "./finalize.js"
6
6
  export { default as clean } from "./clean.js"
7
7
  export { default as logout } from "./logout.js"
8
8
  export { default as validate } from "./validate.js"
9
- export { default as listCeremonies} from "./listCeremonies.js"
9
+ export { default as listCeremonies } from "./listCeremonies.js"
@@ -17,11 +17,10 @@ const listCeremonies = async () => {
17
17
 
18
18
  // loop through all ceremonies
19
19
  for (const ceremony of ceremonies) names.push(ceremony.data().prefix)
20
-
20
+
21
21
  // print them to the console
22
22
  console.log(names.join(", "))
23
23
  process.exit(0)
24
-
25
24
  } catch (err: any) {
26
25
  showError(`${err.toString()}`, false)
27
26
  // we want to exit with a non-zero exit code
@@ -15,7 +15,7 @@ import { COMMAND_ERRORS, GENERIC_ERRORS, showError } from "../lib/errors.js"
15
15
  import { promptForCeremonySelection } from "../lib/prompts.js"
16
16
  import { bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
17
17
  import theme from "../lib/theme.js"
18
- import {customSpinner, getSecondsMinutesHoursFromMillis, sleep } from "../lib/utils.js"
18
+ import { customSpinner, getSecondsMinutesHoursFromMillis, sleep } from "../lib/utils.js"
19
19
 
20
20
  /**
21
21
  * Clean cursor lines from current position back to root (default: zero).
@@ -143,7 +143,7 @@ const observe = async () => {
143
143
  // Preserve command execution only for coordinators].
144
144
  if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
145
145
 
146
- // Get running cerimonies info (if any).
146
+ // Get running ceremonies info (if any).
147
147
  const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
148
148
 
149
149
  // Ask to select a ceremony.
@@ -181,7 +181,7 @@ export const handleAdditionOfCircuitsToCeremony = async (
181
181
 
182
182
  if (matchingWasms.length !== 1) showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true)
183
183
 
184
- // Get input data for choosen circuit.
184
+ // Get input data for chosen circuit.
185
185
  const circuitInputData = await getInputDataToAddCircuitToCeremony(
186
186
  choosenCircuitFilename,
187
187
  matchingWasms[0],
@@ -322,7 +322,7 @@ export const checkAndDownloadSmallestPowersOfTau = async (
322
322
  * number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
323
323
  * to provide a number of powers manually, ranging from the smallest possible to the largest.
324
324
  * @param neededPowers <number> - the smallest amount of powers needed by the zKey.
325
- * @returns Promise<string, string> - the information about the choosen Powers of Tau file for the pre-computed zKey
325
+ * @returns Promise<string, string> - the information about the chosen Powers of Tau file for the pre-computed zKey
326
326
  * along with related powers.
327
327
  */
328
328
  export const handlePreComputedZkeyPowersOfTauSelection = async (
@@ -466,7 +466,7 @@ export const handleCircuitArtifactUploadToStorage = async (
466
466
  * from Hermez's ceremony Phase 1 Reliable Setup Ceremony.
467
467
  * @param cmd? <any> - the path to the ceremony setup file.
468
468
  */
469
- const setup = async (cmd: { template?: string, auth?: string}) => {
469
+ const setup = async (cmd: { template?: string; auth?: string }) => {
470
470
  // Setup command state.
471
471
  const circuits: Array<CircuitDocument> = [] // Circuits.
472
472
  let ceremonyId: string = "" // The unique identifier of the ceremony.
@@ -474,8 +474,10 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
474
474
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
475
475
 
476
476
  // Check for authentication.
477
- const { user, providerUserId } = cmd.auth ? await authWithToken(firebaseApp, cmd.auth) : await checkAuth(firebaseApp)
478
-
477
+ const { user, providerUserId } = cmd.auth
478
+ ? await authWithToken(firebaseApp, cmd.auth)
479
+ : await checkAuth(firebaseApp)
480
+
479
481
  // Preserve command execution only for coordinators.
480
482
  if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
481
483
 
@@ -501,7 +503,7 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
501
503
  // if there is the file option, then set up the non interactively
502
504
  if (cmd.template) {
503
505
  // 1. parse the file
504
- // tmp data - do not cleanup files as we need them
506
+ // tmp data - do not cleanup files as we need them
505
507
  const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template!)} setup configuration file...`, `clock`)
506
508
  spinner.start()
507
509
  const setupCeremonyData = await parseCeremonyFile(cmd.template!)
@@ -524,13 +526,26 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
524
526
  const zkeyLocalPathAndFileName = getZkeyLocalFilePath(circuit.files.initialZkeyFilename)
525
527
 
526
528
  // 2. download the pot and wasm files
527
- await checkAndDownloadSmallestPowersOfTau(convertToDoubleDigits(circuit.metadata?.pot!), circuit.files.potFilename)
528
-
529
+ await checkAndDownloadSmallestPowersOfTau(
530
+ convertToDoubleDigits(circuit.metadata?.pot!),
531
+ circuit.files.potFilename
532
+ )
533
+
529
534
  // 3. generate the zKey
530
- const spinner = customSpinner(`Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`, `clock`)
535
+ const spinner = customSpinner(
536
+ `Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`,
537
+ `clock`
538
+ )
531
539
  spinner.start()
532
- await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined)
533
- spinner.succeed(`Generation of the genesis zKey for citcui ${theme.text.bold(circuit.name)} completed successfully`)
540
+ await zKey.newZKey(
541
+ r1csLocalPathAndFileName,
542
+ getPotLocalFilePath(circuit.files.potFilename),
543
+ zkeyLocalPathAndFileName,
544
+ undefined
545
+ )
546
+ spinner.succeed(
547
+ `Generation of the genesis zKey for citcui ${theme.text.bold(circuit.name)} completed successfully`
548
+ )
534
549
 
535
550
  // 4. calculate the hashes
536
551
  const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName)
@@ -547,7 +562,7 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
547
562
  zkeyLocalPathAndFileName,
548
563
  circuit.files.initialZkeyFilename
549
564
  )
550
-
565
+
551
566
  // Check if PoT file has been already uploaded to storage.
552
567
  const alreadyUploadedPot = await checkIfObjectExist(
553
568
  firebaseFunctions,
@@ -595,18 +610,24 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
595
610
 
596
611
  ceremonySetupData.circuits[index].zKeySizeInBytes = getFileStats(zkeyLocalPathAndFileName).size
597
612
  }
598
-
599
613
 
600
614
  // 7. setup the ceremony
601
- const ceremonyId = await setupCeremony(firebaseFunctions, ceremonySetupData.ceremonyInputData, ceremonySetupData.ceremonyPrefix, ceremonySetupData.circuits)
602
- console.log( `Congratulations, the setup of ceremony ${theme.text.bold(
603
- ceremonySetupData.ceremonyInputData.title
604
- )} (${`UID: ${theme.text.bold(ceremonyId)}`}) has been successfully completed ${
605
- theme.emojis.tada
606
- }. You will be able to find all the files and info respectively in the ceremony bucket and database document.`)
607
-
615
+ const ceremonyId = await setupCeremony(
616
+ firebaseFunctions,
617
+ ceremonySetupData.ceremonyInputData,
618
+ ceremonySetupData.ceremonyPrefix,
619
+ ceremonySetupData.circuits
620
+ )
621
+ console.log(
622
+ `Congratulations, the setup of ceremony ${theme.text.bold(
623
+ ceremonySetupData.ceremonyInputData.title
624
+ )} (${`UID: ${theme.text.bold(ceremonyId)}`}) has been successfully completed ${
625
+ theme.emojis.tada
626
+ }. You will be able to find all the files and info respectively in the ceremony bucket and database document.`
627
+ )
628
+
608
629
  terminate(providerUserId)
609
- }
630
+ }
610
631
 
611
632
  // Look for R1CS files.
612
633
  const r1csFilePaths = await filterDirectoryFilesByExtension(cwd, `.r1cs`)
@@ -4,7 +4,7 @@ import { showError } from "../lib/errors.js"
4
4
  /**
5
5
  * Validate ceremony setup command.
6
6
  */
7
- const validate = async (cmd: { template: string, constraints?: number }) => {
7
+ const validate = async (cmd: { template: string; constraints?: number }) => {
8
8
  try {
9
9
  // parse the file and cleanup after
10
10
  const parsedFile = await parseCeremonyFile(cmd.template, true)
@@ -18,7 +18,6 @@ const validate = async (cmd: { template: string, constraints?: number }) => {
18
18
  }
19
19
 
20
20
  console.log(true)
21
-
22
21
  } catch (err: any) {
23
22
  showError(`${err.toString()}`, false)
24
23
  // we want to exit with a non-zero exit code
package/src/index.ts CHANGED
@@ -4,7 +4,17 @@ import { createCommand } from "commander"
4
4
  import { readFileSync } from "fs"
5
5
  import { dirname } from "path"
6
6
  import { fileURLToPath } from "url"
7
- import { setup, auth, contribute, observe, finalize, clean, logout, validate, listCeremonies } from "./commands/index.js"
7
+ import {
8
+ setup,
9
+ auth,
10
+ contribute,
11
+ observe,
12
+ finalize,
13
+ clean,
14
+ logout,
15
+ validate,
16
+ listCeremonies
17
+ } from "./commands/index.js"
8
18
 
9
19
  // Get pkg info (e.g., name, version).
10
20
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
@@ -27,10 +37,7 @@ program
27
37
  .command("clean")
28
38
  .description("clean up output generated by commands from the current working directory")
29
39
  .action(clean)
30
- program
31
- .command("list")
32
- .description("List all ceremonies prefixes")
33
- .action(listCeremonies)
40
+ program.command("list").description("List all ceremonies prefixes").action(listCeremonies)
34
41
  program
35
42
  .command("logout")
36
43
  .description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
@@ -48,10 +55,10 @@ const ceremony = program.command("coordinate").description("commands for coordin
48
55
  ceremony
49
56
  .command("setup")
50
57
  .description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
51
- .option('-t, --template <path>', 'The path to the ceremony setup template', '')
52
- .option('-a, --auth <string>', 'The Github OAuth 2.0 token', '')
58
+ .option("-t, --template <path>", "The path to the ceremony setup template", "")
59
+ .option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
53
60
  .action(setup)
54
-
61
+
55
62
  ceremony
56
63
  .command("observe")
57
64
  .description("observe in real-time the waiting queue of each ceremony circuit")
@@ -62,6 +69,7 @@ ceremony
62
69
  .description(
63
70
  "finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract"
64
71
  )
72
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
65
73
  .action(finalize)
66
74
 
67
75
  program.parseAsync(process.argv)
@@ -203,7 +203,7 @@ export const promptCircomCompiler = async (): Promise<CircomCompilerData> => {
203
203
  * Shows a list of circuits for a single option selection.
204
204
  * @dev the circuit names are derived from local R1CS files.
205
205
  * @param options <Array<string>> - an array of circuits names.
206
- * @returns Promise<string> - the name of the choosen circuit.
206
+ * @returns Promise<string> - the name of the chosen circuit.
207
207
  */
208
208
  export const promptCircuitSelector = async (options: Array<string>): Promise<string> => {
209
209
  const { circuitFilename } = await prompts({
@@ -223,7 +223,7 @@ export const promptCircuitSelector = async (options: Array<string>): Promise<str
223
223
  * Shows a list of standard EC2 VM instance types for a single option selection.
224
224
  * @notice the suggested VM configuration type is calculated based on circuit constraint size.
225
225
  * @param constraintSize <number> - the amount of circuit constraints
226
- * @returns Promise<string> - the name of the choosen VM type.
226
+ * @returns Promise<string> - the name of the chosen VM type.
227
227
  */
228
228
  export const promptVMTypeSelector = async (constraintSize): Promise<string> => {
229
229
  let suggestedConfiguration: number = 0
@@ -325,7 +325,7 @@ export const promptVMDiskTypeSelector = async (): Promise<DiskTypeForVM> => {
325
325
  /**
326
326
  * Show a series of questions about the circuits.
327
327
  * @param constraintSize <number> - the amount of circuit constraints.
328
- * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
328
+ * @param timeoutMechanismType <CeremonyTimeoutType> - the chosen timeout mechanism type for the ceremony.
329
329
  * @param needPromptCircomCompiler <boolean> - a boolean value indicating if the questions related to the Circom compiler version and commit hash must be asked.
330
330
  * @param enforceVM <boolean> - a boolean value indicating if the contribution verification could be supported by VM-only approach or not.
331
331
  * @returns Promise<Array<Circuit>> - circuit info prompted by the coordinator.
@@ -343,7 +343,7 @@ export const promptCircuitInputData = async (
343
343
  let circomVersion: string = ""
344
344
  let circomCommitHash: string = ""
345
345
  let circuitInputData: CircuitInputData
346
- let useCfOrVm: CircuitContributionVerificationMechanism
346
+ let cfOrVm: CircuitContributionVerificationMechanism
347
347
  let vmDiskType: DiskTypeForVM
348
348
  let vmConfigurationType: string = ""
349
349
 
@@ -422,19 +422,24 @@ export const promptCircuitInputData = async (
422
422
  circomCommitHash = commitHash
423
423
  }
424
424
 
425
- // Ask for prefered contribution verification method (CF vs VM).
425
+ // Ask for preferred contribution verification method (CF vs VM).
426
426
  if (!enforceVM) {
427
427
  const { confirmation } = await askForConfirmation(
428
428
  `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`,
429
429
  `CF`, // eq. true.
430
430
  `VM` // eq. false.
431
431
  )
432
- useCfOrVm = confirmation
433
- } else useCfOrVm = CircuitContributionVerificationMechanism.VM
432
+ cfOrVm = confirmation
433
+ ? CircuitContributionVerificationMechanism.CF
434
+ : CircuitContributionVerificationMechanism.VM
434
435
 
435
- if (useCfOrVm === undefined) showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true)
436
+ } else {
437
+ cfOrVm = CircuitContributionVerificationMechanism.VM
438
+ }
439
+
440
+ if (cfOrVm === undefined) showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true)
436
441
 
437
- if (!useCfOrVm) {
442
+ if (cfOrVm === CircuitContributionVerificationMechanism.VM) {
438
443
  // Ask for selecting the specific VM configuration type.
439
444
  vmConfigurationType = await promptVMTypeSelector(constraintSize)
440
445
 
@@ -478,9 +483,7 @@ export const promptCircuitInputData = async (
478
483
  paramsConfiguration: circuitConfigurationValues
479
484
  },
480
485
  verification: {
481
- cfOrVm: useCfOrVm
482
- ? CircuitContributionVerificationMechanism.CF
483
- : CircuitContributionVerificationMechanism.VM,
486
+ cfOrVm,
484
487
  vm: {
485
488
  vmConfigurationType,
486
489
  vmDiskType
@@ -520,9 +523,7 @@ export const promptCircuitInputData = async (
520
523
  paramsConfiguration: circuitConfigurationValues
521
524
  },
522
525
  verification: {
523
- cfOrVm: useCfOrVm
524
- ? CircuitContributionVerificationMechanism.CF
525
- : CircuitContributionVerificationMechanism.VM,
526
+ cfOrVm,
526
527
  vm: {
527
528
  vmConfigurationType,
528
529
  vmDiskType
@@ -586,7 +587,7 @@ export const promptCircuitAddition = async (): Promise<boolean> => {
586
587
  * Shows a list of pre-computed zKeys for a single option selection.
587
588
  * @dev the names are derived from local zKeys files.
588
589
  * @param options <Array<string>> - an array of pre-computed zKeys names.
589
- * @returns Promise<string> - the name of the choosen pre-computed zKey.
590
+ * @returns Promise<string> - the name of the chosen pre-computed zKey.
590
591
  */
591
592
  export const promptPreComputedZkeySelector = async (options: Array<string>): Promise<string> => {
592
593
  const { preComputedZkeyFilename } = await prompts({
@@ -633,13 +634,13 @@ export const promptNeededPowersForCircuit = async (suggestedSmallestNeededPowers
633
634
  * Shows a list of PoT files for a single option selection.
634
635
  * @dev the names are derived from local PoT files.
635
636
  * @param options <Array<string>> - an array of PoT file names.
636
- * @returns Promise<string> - the name of the choosen PoT.
637
+ * @returns Promise<string> - the name of the chosen PoT.
637
638
  */
638
639
  export const promptPotSelector = async (options: Array<string>): Promise<string> => {
639
640
  const { potFilename } = await prompts({
640
641
  type: "select",
641
642
  name: "potFilename",
642
- message: theme.text.bold("Select the Powers of Tau file choosen for the circuit"),
643
+ message: theme.text.bold("Select the Powers of Tau file chosen for the circuit"),
643
644
  choices: options.map((option: string) => {
644
645
  console.log(option)
645
646
  return { title: option, value: option }
@@ -731,7 +732,7 @@ export const promptToTypeEntropyOrBeacon = async (isEntropy = true): Promise<str
731
732
  * @return <Promise<string>> - the entropy.
732
733
  */
733
734
  export const promptForEntropy = async (): Promise<string> => {
734
- // Prompt for entropy generation prefered method.
735
+ // Prompt for entropy generation preferred method.
735
736
  const { confirmation } = await askForConfirmation(
736
737
  `Do you prefer to type your entropy or generate it randomly?`,
737
738
  "Manually",
@@ -117,8 +117,6 @@ export const signInToFirebase = async (firebaseApp: FirebaseApp, credentials: OA
117
117
  }
118
118
  }
119
119
 
120
-
121
-
122
120
  /**
123
121
  * Ensure that the callee is an authenticated user.
124
122
  * @notice The token will be passed as parameter.
package/src/lib/utils.ts CHANGED
@@ -311,8 +311,22 @@ export const generateCustomUrlToTweetAboutParticipation = (
311
311
  isFinalizing: boolean
312
312
  ) =>
313
313
  isFinalizing
314
- ? `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`
315
- : `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`
314
+ ? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}${
315
+ ceremonyName.toLowerCase().includes("trusted") ||
316
+ ceremonyName.toLowerCase().includes("setup") ||
317
+ ceremonyName.toLowerCase().includes("phase2") ||
318
+ ceremonyName.toLowerCase().includes("ceremony")
319
+ ? "!"
320
+ : "%20Phase%202%20Trusted%20Setup%20ceremony!"
321
+ }%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
322
+ : `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyName}${
323
+ ceremonyName.toLowerCase().includes("trusted") ||
324
+ ceremonyName.toLowerCase().includes("setup") ||
325
+ ceremonyName.toLowerCase().includes("phase2") ||
326
+ ceremonyName.toLowerCase().includes("ceremony")
327
+ ? "!"
328
+ : "%20Phase%202%20Trusted%20Setup%20ceremony!"
329
+ }%20You%20can%20view%20the%20steps%20to%20contribute%20here:%20https://ceremony.pse.dev%20You%20can%20view%20my%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP`
316
330
 
317
331
  /**
318
332
  * Return a custom progress bar.
@@ -521,6 +535,7 @@ export const getLatestUpdatesFromParticipant = async (
521
535
  * @param entropyOrBeaconHash <string> - the entropy or beacon hash (only when finalizing) for the contribution.
522
536
  * @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
523
537
  * @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
538
+ * @param circuitsLength <number> - the total number of circuits in the ceremony.
524
539
  */
525
540
  export const handleStartOrResumeContribution = async (
526
541
  cloudFunctions: Functions,
@@ -530,7 +545,8 @@ export const handleStartOrResumeContribution = async (
530
545
  participant: FirebaseDocumentInfo,
531
546
  entropyOrBeaconHash: any,
532
547
  contributorOrCoordinatorIdentifier: string,
533
- isFinalizing: boolean
548
+ isFinalizing: boolean,
549
+ circuitsLength: number
534
550
  ): Promise<void> => {
535
551
  // Extract data.
536
552
  const { prefix: ceremonyPrefix } = ceremony.data
@@ -538,7 +554,9 @@ export const handleStartOrResumeContribution = async (
538
554
  const { completedContributions } = waitingQueue // = current progress.
539
555
 
540
556
  console.log(
541
- `${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}`)}`)} (Contribution Steps)`
557
+ `${theme.text.bold(
558
+ `\n- Circuit # ${theme.colors.magenta(`${sequencePosition}/${circuitsLength}`)}`
559
+ )} (Contribution Steps)`
542
560
  )
543
561
 
544
562
  // Get most up-to-date data from the participant document.
@@ -607,6 +625,8 @@ export const handleStartOrResumeContribution = async (
607
625
  `${theme.symbols.success} Contribution ${theme.text.bold(`#${lastZkeyIndex}`)} correctly downloaded`
608
626
  )
609
627
 
628
+ await sleep(3000)
629
+
610
630
  // Advance to next contribution step (COMPUTING) if not finalizing.
611
631
  if (!isFinalizing) {
612
632
  spinner.text = `Preparing for contribution computation...`
@@ -650,6 +670,8 @@ export const handleStartOrResumeContribution = async (
650
670
  // Format contribution hash.
651
671
  const contributionHash = matchContributionHash?.at(0)?.replace("\n\t\t", "")!
652
672
 
673
+ await sleep(500)
674
+
653
675
  // Make request to cloud functions to permanently store the information.
654
676
  await permanentlyStoreCurrentContributionTimeAndHash(
655
677
  cloudFunctions,
@@ -675,6 +697,9 @@ export const handleStartOrResumeContribution = async (
675
697
  )}`
676
698
  )
677
699
 
700
+ // ensure the previous step is completed
701
+ await sleep(5000)
702
+
678
703
  // Advance to next contribution step (UPLOADING) if not finalizing.
679
704
  if (!isFinalizing) {
680
705
  spinner.text = `Preparing for uploading the contribution...`
@@ -699,7 +724,9 @@ export const handleStartOrResumeContribution = async (
699
724
  } This step may take a while based on circuit size and your internet speed. Everything's fine, just be patient.`
700
725
  spinner.start()
701
726
 
702
- if (!isFinalizing)
727
+ const progressBar = customProgressBar(ProgressBarType.UPLOAD, `your contribution`)
728
+
729
+ if (!isFinalizing) {
703
730
  await multiPartUpload(
704
731
  cloudFunctions,
705
732
  bucketName,
@@ -707,8 +734,12 @@ export const handleStartOrResumeContribution = async (
707
734
  nextZkeyLocalFilePath,
708
735
  Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB),
709
736
  ceremony.id,
710
- participantData.tempContributionData
737
+ participantData.tempContributionData,
738
+ progressBar
711
739
  )
740
+
741
+ progressBar.stop()
742
+ }
712
743
  else
713
744
  await multiPartUpload(
714
745
  cloudFunctions,
@@ -718,6 +749,9 @@ export const handleStartOrResumeContribution = async (
718
749
  Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB)
719
750
  )
720
751
 
752
+ // small sleep to ensure the previous step is completed
753
+ await sleep(5000)
754
+
721
755
  spinner.succeed(
722
756
  `${
723
757
  isFinalizing ? `Contribution` : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`