@devtion/devcli 0.0.0-5d170d3 → 0.0.0-7cfaa5d

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
@@ -61,6 +61,8 @@ or run specific commands with `npx`:
61
61
  npx @p0tion/phase2cli contribute
62
62
  ```
63
63
 
64
+ > Please note that phase2cli only runs on Linux or Mac systems. If a Windows user, please install [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) first.
65
+
64
66
  ## 📜 Usage
65
67
 
66
68
  ### Local Development
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 @p0tion/phase2cli
5
- * @version 1.0.5
4
+ * @module @devtion/devcli
5
+ * @version 1.0.8
6
6
  * @file All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies
7
7
  * @copyright Ethereum Foundation 2022
8
8
  * @license MIT
@@ -17,8 +17,7 @@ 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 { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
21
- 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 '@p0tion/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 } from '@devtion/actions';
22
21
  import fetch from '@adobe/node-fetch-retry';
23
22
  import { request } from '@octokit/request';
24
23
  import { SingleBar, Presets } from 'cli-progress';
@@ -34,7 +33,6 @@ import Conf from 'conf';
34
33
  import prompts from 'prompts';
35
34
  import clear from 'clear';
36
35
  import figlet from 'figlet';
37
- import { Readable } from 'stream';
38
36
  import { createOAuthDeviceAuth } from '@octokit/auth-oauth-device';
39
37
  import clipboard from 'clipboardy';
40
38
  import open from 'open';
@@ -367,6 +365,12 @@ const getVerificationKeyLocalFilePath = (completeFilename) => `${verificationKey
367
365
  * @returns <string> - the complete final verifier contract path to the file.
368
366
  */
369
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}`;
370
374
  /**
371
375
  * Get the final transcript file path.
372
376
  * @param completeFilename <string> - the complete filename of the file (name.ext).
@@ -581,8 +585,18 @@ const publishGist = async (token, content, ceremonyTitle, ceremonyPrefix) => {
581
585
  * @returns <string> - the ready to share tweet url.
582
586
  */
583
587
  const generateCustomUrlToTweetAboutParticipation = (ceremonyName, gistUrl, isFinalizing) => isFinalizing
584
- ? `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`
585
- : `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`;
586
600
  /**
587
601
  * Return a custom progress bar.
588
602
  * @param type <ProgressBarType> - the type of the progress bar.
@@ -710,13 +724,14 @@ const getLatestUpdatesFromParticipant = async (firestoreDatabase, ceremonyId, pa
710
724
  * @param entropyOrBeaconHash <string> - the entropy or beacon hash (only when finalizing) for the contribution.
711
725
  * @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
712
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.
713
728
  */
714
- const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing) => {
729
+ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing, circuitsLength) => {
715
730
  // Extract data.
716
731
  const { prefix: ceremonyPrefix } = ceremony.data;
717
732
  const { waitingQueue, avgTimings, prefix: circuitPrefix, sequencePosition } = circuit.data;
718
733
  const { completedContributions } = waitingQueue; // = current progress.
719
- 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)`);
720
735
  // Get most up-to-date data from the participant document.
721
736
  let participantData = await getLatestUpdatesFromParticipant(firestoreDatabase, ceremony.id, participant.id);
722
737
  const spinner = customSpinner(`${participantData.contributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */
@@ -809,7 +824,7 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
809
824
  console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${nextZkeyIndex}`)} already computed`);
810
825
  // Contribution step = UPLOADING.
811
826
  if (isFinalizing || participantData.contributionStep === "UPLOADING" /* ParticipantContributionStep.UPLOADING */) {
812
- 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.`;
827
+ 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.`;
813
828
  spinner.start();
814
829
  if (!isFinalizing)
815
830
  await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB), ceremony.id, participantData.tempContributionData);
@@ -1120,7 +1135,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1120
1135
  let circomVersion = "";
1121
1136
  let circomCommitHash = "";
1122
1137
  let circuitInputData;
1123
- let useCfOrVm;
1138
+ let cfOrVm;
1124
1139
  let vmDiskType;
1125
1140
  let vmConfigurationType = "";
1126
1141
  const questions = [
@@ -1180,13 +1195,16 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1180
1195
  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.
1181
1196
  `VM` // eq. false.
1182
1197
  );
1183
- useCfOrVm = confirmation;
1198
+ cfOrVm = confirmation
1199
+ ? "CF" /* CircuitContributionVerificationMechanism.CF */
1200
+ : "VM" /* CircuitContributionVerificationMechanism.VM */;
1184
1201
  }
1185
- else
1186
- useCfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
1187
- if (useCfOrVm === undefined)
1202
+ else {
1203
+ cfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
1204
+ }
1205
+ if (cfOrVm === undefined)
1188
1206
  showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
1189
- if (!useCfOrVm) {
1207
+ if (cfOrVm === "VM" /* CircuitContributionVerificationMechanism.VM */) {
1190
1208
  // Ask for selecting the specific VM configuration type.
1191
1209
  vmConfigurationType = await promptVMTypeSelector(constraintSize);
1192
1210
  // Ask for selecting the specific VM disk (volume) type.
@@ -1220,9 +1238,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1220
1238
  paramsConfiguration: circuitConfigurationValues
1221
1239
  },
1222
1240
  verification: {
1223
- cfOrVm: useCfOrVm
1224
- ? "CF" /* CircuitContributionVerificationMechanism.CF */
1225
- : "VM" /* CircuitContributionVerificationMechanism.VM */,
1241
+ cfOrVm,
1226
1242
  vm: {
1227
1243
  vmConfigurationType,
1228
1244
  vmDiskType
@@ -1258,9 +1274,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
1258
1274
  paramsConfiguration: circuitConfigurationValues
1259
1275
  },
1260
1276
  verification: {
1261
- cfOrVm: useCfOrVm
1262
- ? "CF" /* CircuitContributionVerificationMechanism.CF */
1263
- : "VM" /* CircuitContributionVerificationMechanism.VM */,
1277
+ cfOrVm,
1264
1278
  vm: {
1265
1279
  vmConfigurationType,
1266
1280
  vmDiskType
@@ -1826,7 +1840,9 @@ const setup = async (cmd) => {
1826
1840
  let ceremonyId = ""; // The unique identifier of the ceremony.
1827
1841
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
1828
1842
  // Check for authentication.
1829
- const { user, providerUserId } = cmd.auth ? await authWithToken(firebaseApp, cmd.auth) : await checkAuth(firebaseApp);
1843
+ const { user, providerUserId } = cmd.auth
1844
+ ? await authWithToken(firebaseApp, cmd.auth)
1845
+ : await checkAuth(firebaseApp);
1830
1846
  // Preserve command execution only for coordinators.
1831
1847
  if (!(await isCoordinator(user)))
1832
1848
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
@@ -1843,7 +1859,7 @@ const setup = async (cmd) => {
1843
1859
  // if there is the file option, then set up the non interactively
1844
1860
  if (cmd.template) {
1845
1861
  // 1. parse the file
1846
- // tmp data - do not cleanup files as we need them
1862
+ // tmp data - do not cleanup files as we need them
1847
1863
  const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template)} setup configuration file...`, `clock`);
1848
1864
  spinner.start();
1849
1865
  const setupCeremonyData = await parseCeremonyFile(cmd.template);
@@ -1853,8 +1869,6 @@ const setup = async (cmd) => {
1853
1869
  // create a new bucket
1854
1870
  const bucketName = await handleCeremonyBucketCreation(firebaseFunctions, ceremonySetupData.ceremonyPrefix);
1855
1871
  console.log(`\n${theme.symbols.success} Ceremony bucket name: ${theme.text.bold(bucketName)}`);
1856
- // create S3 clienbt
1857
- const s3 = new S3Client({ region: 'us-east-1' });
1858
1872
  // loop through each circuit
1859
1873
  for await (const circuit of setupCeremonyData.circuits) {
1860
1874
  // Local paths.
@@ -1864,21 +1878,12 @@ const setup = async (cmd) => {
1864
1878
  const potLocalPathAndFileName = getPotLocalFilePath(circuit.files.potFilename);
1865
1879
  const zkeyLocalPathAndFileName = getZkeyLocalFilePath(circuit.files.initialZkeyFilename);
1866
1880
  // 2. download the pot and wasm files
1867
- const streamPipeline = promisify(pipeline);
1868
1881
  await checkAndDownloadSmallestPowersOfTau(convertToDoubleDigits(circuit.metadata?.pot), circuit.files.potFilename);
1869
- // download the wasm to calculate the hash
1870
- const spinner = customSpinner(`Downloading the ${theme.text.bold(`#${circuit.name}`)} WASM file from the project's bucket...`, `clock`);
1871
- spinner.start();
1872
- const command = new GetObjectCommand({ Bucket: ceremonySetupData.circuitArtifacts[index].artifacts.bucket, Key: ceremonySetupData.circuitArtifacts[index].artifacts.wasmStoragePath });
1873
- const response = await s3.send(command);
1874
- if (response.$metadata.httpStatusCode !== 200) {
1875
- throw new Error("There was an error while trying to download the wasm file. Please check that the file has the correct permissions (public) set.");
1876
- }
1877
- if (response.Body instanceof Readable)
1878
- await streamPipeline(response.Body, createWriteStream(wasmLocalPathAndFileName));
1879
- spinner.stop();
1880
1882
  // 3. generate the zKey
1883
+ const spinner = customSpinner(`Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
1884
+ spinner.start();
1881
1885
  await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined);
1886
+ spinner.succeed(`Generation of the genesis zKey for citcui ${theme.text.bold(circuit.name)} completed successfully`);
1882
1887
  // 4. calculate the hashes
1883
1888
  const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName);
1884
1889
  const potBlake2bHash = await blake512FromPath(getPotLocalFilePath(circuit.files.potFilename));
@@ -2113,8 +2118,9 @@ const onVerification = async (verification) => {
2113
2118
  clipboard.writeSync(verification.user_code);
2114
2119
  clipboard.readSync();
2115
2120
  // Display data.
2116
- 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`);
2117
- console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} (${theme.emojis.clipboard} ${theme.symbols.success})\n`);
2121
+ 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`);
2122
+ console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
2123
+ 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`);
2118
2124
  const spinner = customSpinner(`Redirecting to Github...`, `clock`);
2119
2125
  spinner.start();
2120
2126
  await sleep(10000); // ~10s to make users able to read the CLI.
@@ -2407,7 +2413,6 @@ const listenToCeremonyCircuitDocumentChanges = (firestoreDatabase, ceremonyId, p
2407
2413
  const { avgTimings, waitingQueue } = changedCircuit.data();
2408
2414
  const { fullContribution, verifyCloudFunction } = avgTimings;
2409
2415
  const { currentContributor } = waitingQueue;
2410
- // Get circuit current contributor participant document.
2411
2416
  const circuitCurrentContributor = await getDocumentById(firestoreDatabase, getParticipantsCollectionPath(ceremonyId), currentContributor);
2412
2417
  // Check data.
2413
2418
  if (!circuitCurrentContributor.data())
@@ -2566,8 +2571,8 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2566
2571
  // Communicate resume / start of the contribution to participant.
2567
2572
  await simpleLoader(`${changedContributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */ ? `Starting` : `Resuming`} your contribution...`, `clock`, 3000);
2568
2573
  // Start / Resume the contribution for the participant.
2569
- await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false // not finalizing.
2570
- );
2574
+ await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false, // not finalizing.
2575
+ circuits.length);
2571
2576
  }
2572
2577
  // Scenario (3.A).
2573
2578
  else if (isWaitingForContribution)
@@ -2616,7 +2621,9 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2616
2621
  // Get latest contribution verification result.
2617
2622
  await getLatestVerificationResult(firestoreDatabase, ceremony.id, circuit.id, participant.id);
2618
2623
  // Get next circuit for contribution.
2619
- const nextCircuit = getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
2624
+ const nextCircuit = timeoutExpired
2625
+ ? getCircuitBySequencePosition(circuits, changedContributionProgress)
2626
+ : getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
2620
2627
  // Check disk space requirements for participant.
2621
2628
  const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(cloudFunctions, ceremony.id, nextCircuit.data.sequencePosition, nextCircuit.data.zKeySizeInBytes, timeoutExpired, providerUserId);
2622
2629
  // Check if the participant would like to generate a new attestation.
@@ -2654,11 +2661,12 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
2654
2661
  */
2655
2662
  const contribute = async (opt) => {
2656
2663
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
2657
- // Check for authentication.
2658
- const { user, providerUserId, token } = await checkAuth(firebaseApp);
2659
2664
  // Get options.
2660
2665
  const ceremonyOpt = opt.ceremony;
2661
2666
  const entropyOpt = opt.entropy;
2667
+ const auth = opt.auth;
2668
+ // Check for authentication.
2669
+ const { user, providerUserId, token } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
2662
2670
  // Prepare data.
2663
2671
  let selectedCeremony;
2664
2672
  // Retrieve the opened ceremonies.
@@ -2694,7 +2702,7 @@ const contribute = async (opt) => {
2694
2702
  const userDoc = await getDocumentById(firestoreDatabase, commonTerms.collections.users.name, user.uid);
2695
2703
  const userData = userDoc.data();
2696
2704
  if (!userData) {
2697
- 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.`);
2705
+ 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.`);
2698
2706
  process.exit(0);
2699
2707
  }
2700
2708
  // Check the user's current participant readiness for contribution status (eligible, already contributed, timed out).
@@ -2914,7 +2922,7 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
2914
2922
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}`;
2915
2923
  const verifierPath = packagePath.includes(`src/commands`)
2916
2924
  ? `${dirname(fileURLToPath(import.meta.url))}/../../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
2917
- : `${dirname(fileURLToPath(import.meta.url))}/../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
2925
+ : `${dirname(fileURLToPath(import.meta.url))}/../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
2918
2926
  // Export the Solidity verifier smart contract.
2919
2927
  const verifierCode = await exportVerifierContract(finalZkeyLocalFilePath, verifierPath);
2920
2928
  spinner.text = `Writing verifier smart contract...`;
@@ -2941,10 +2949,11 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
2941
2949
  * @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
2942
2950
  * @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
2943
2951
  * @param coordinatorIdentifier <string> - the identifier of the coordinator.
2952
+ * @param circuitsLength <number> - the number of circuits in the ceremony.
2944
2953
  */
2945
- const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier) => {
2954
+ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier, circuitsLength) => {
2946
2955
  // Step (1).
2947
- await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true);
2956
+ await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength);
2948
2957
  await sleep(2000); // workaound for descriptors.
2949
2958
  // Extract data.
2950
2959
  const { prefix: circuitPrefix } = circuit.data;
@@ -2979,10 +2988,11 @@ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, cere
2979
2988
  * @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
2980
2989
  * handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
2981
2990
  */
2982
- const finalize = async () => {
2991
+ const finalize = async (opt) => {
2983
2992
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
2984
2993
  // Check for authentication.
2985
- const { user, providerUserId, token: coordinatorAccessToken } = await checkAuth(firebaseApp);
2994
+ const auth = opt.auth;
2995
+ const { user, providerUserId, token: coordinatorAccessToken } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
2986
2996
  // Preserve command execution only for coordinators.
2987
2997
  if (!(await isCoordinator(user)))
2988
2998
  showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
@@ -3017,7 +3027,7 @@ const finalize = async () => {
3017
3027
  const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
3018
3028
  // Handle finalization for each ceremony circuit.
3019
3029
  for await (const circuit of circuits)
3020
- await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId);
3030
+ await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId, circuits.length);
3021
3031
  process.stdout.write(`\n`);
3022
3032
  const spinner = customSpinner(`Wrapping up the finalization of the ceremony...`, "clock");
3023
3033
  spinner.start();
@@ -3032,7 +3042,7 @@ const finalize = async () => {
3032
3042
  // Generate attestation with final contributions.
3033
3043
  const publicAttestation = await generateValidContributionsAttestation(firestoreDatabase, circuits, selectedCeremony.id, participant.id, contributions, providerUserId, ceremonyName, true);
3034
3044
  // Write public attestation locally.
3035
- writeFile(getAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
3045
+ writeFile(getFinalAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
3036
3046
  await sleep(3000); // workaround for file descriptor unexpected close.
3037
3047
  const gistUrl = await publishGist(coordinatorAccessToken, publicAttestation, ceremonyName, prefix);
3038
3048
  console.log(`\n${theme.symbols.info} Your public final attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
@@ -3168,15 +3178,13 @@ program
3168
3178
  .description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
3169
3179
  .option("-c, --ceremony <string>", "the prefix of the ceremony you want to contribute for", "")
3170
3180
  .option("-e, --entropy <string>", "the entropy (aka toxic waste) of your contribution", "")
3181
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
3171
3182
  .action(contribute);
3172
3183
  program
3173
3184
  .command("clean")
3174
3185
  .description("clean up output generated by commands from the current working directory")
3175
3186
  .action(clean);
3176
- program
3177
- .command("list")
3178
- .description("List all ceremonies prefixes")
3179
- .action(listCeremonies);
3187
+ program.command("list").description("List all ceremonies prefixes").action(listCeremonies);
3180
3188
  program
3181
3189
  .command("logout")
3182
3190
  .description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
@@ -3192,8 +3200,8 @@ const ceremony = program.command("coordinate").description("commands for coordin
3192
3200
  ceremony
3193
3201
  .command("setup")
3194
3202
  .description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
3195
- .option('-t, --template <path>', 'The path to the ceremony setup template', '')
3196
- .option('-a, --auth <string>', 'The Github OAuth 2.0 token', '')
3203
+ .option("-t, --template <path>", "The path to the ceremony setup template", "")
3204
+ .option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
3197
3205
  .action(setup);
3198
3206
  ceremony
3199
3207
  .command("observe")
@@ -3202,5 +3210,6 @@ ceremony
3202
3210
  ceremony
3203
3211
  .command("finalize")
3204
3212
  .description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
3213
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
3205
3214
  .action(finalize);
3206
3215
  program.parseAsync(process.argv);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Contribution, ContributionValidity, FirebaseDocumentInfo } from "@p0tion/actions";
2
+ import { Contribution, ContributionValidity, FirebaseDocumentInfo } from "@devtion/actions";
3
3
  import { DocumentSnapshot, DocumentData, Firestore } from "firebase/firestore";
4
4
  import { Functions } from "firebase/functions";
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { FirebaseDocumentInfo } from "@p0tion/actions";
2
+ import { FirebaseDocumentInfo } from "@devtion/actions";
3
3
  import { Functions } from "firebase/functions";
4
4
  import { Firestore } from "firebase/firestore";
5
5
  /**
@@ -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;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { FirebaseDocumentInfo } from "@p0tion/actions";
2
+ import { FirebaseDocumentInfo } from "@devtion/actions";
3
3
  import { Firestore } from "firebase/firestore";
4
4
  /**
5
5
  * Clean cursor lines from current position back to root (default: zero).
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Functions } from "firebase/functions";
3
- import { CeremonyTimeoutType, CircomCompilerData, CircuitInputData, CeremonyInputData, CircuitDocument } from "@p0tion/actions";
3
+ import { CeremonyTimeoutType, CircomCompilerData, CircuitInputData, CeremonyInputData, CircuitDocument } from "@devtion/actions";
4
4
  /**
5
5
  * Handle whatever is needed to obtain the input data for a circuit that the coordinator would like to add to the ceremony.
6
6
  * @param choosenCircuitFilename <string> - the name of the circuit to add.
@@ -1,6 +1,6 @@
1
1
  import { Answers } from "prompts";
2
2
  import { Firestore } from "firebase/firestore";
3
- import { CeremonyInputData, FirebaseDocumentInfo, CircomCompilerData, CircuitInputData, CeremonyTimeoutType, DiskTypeForVM } from "@p0tion/actions";
3
+ import { CeremonyInputData, FirebaseDocumentInfo, CircomCompilerData, CircuitInputData, CeremonyTimeoutType, DiskTypeForVM } from "@devtion/actions";
4
4
  /**
5
5
  * Ask a binary (yes/no or true/false) customizable question.
6
6
  * @param question <string> - the question to be answered.
@@ -1,4 +1,4 @@
1
- import { FirebaseDocumentInfo } from "@p0tion/actions";
1
+ import { FirebaseDocumentInfo } from "@devtion/actions";
2
2
  import { OAuthCredential } from "firebase/auth";
3
3
  import { DocumentData, Firestore } from "firebase/firestore";
4
4
  import { Functions } from "firebase/functions";
@@ -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-5d170d3",
4
+ "version": "0.0.0-7cfaa5d",
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",
@@ -64,10 +64,11 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@adobe/node-fetch-retry": "^2.2.0",
67
+ "@aws-sdk/client-s3": "^3.329.0",
68
+ "@devtion/actions": "latest",
67
69
  "@octokit/auth-oauth-app": "^5.0.5",
68
70
  "@octokit/auth-oauth-device": "^4.0.4",
69
71
  "@octokit/request": "^6.2.3",
70
- "@p0tion/actions": "^1.0.5",
71
72
  "blakejs": "^1.2.1",
72
73
  "boxen": "^7.1.0",
73
74
  "chalk": "^5.2.0",
@@ -96,5 +97,5 @@
96
97
  "publishConfig": {
97
98
  "access": "public"
98
99
  },
99
- "gitHead": "ff032e8717f4aac3f66730e0a256660c2e741505"
100
+ "gitHead": "53164d43af65410911da5fa33f1ac0cade724872"
100
101
  }
@@ -4,6 +4,7 @@ import { Verification } from "@octokit/auth-oauth-device/dist-types/types.js"
4
4
  import clipboard from "clipboardy"
5
5
  import dotenv from "dotenv"
6
6
  import open from "open"
7
+ import figlet from "figlet"
7
8
  import { fileURLToPath } from "url"
8
9
  import { dirname } from "path"
9
10
  import { GENERIC_ERRORS, showError } from "../lib/errors.js"
@@ -69,12 +70,15 @@ export const onVerification = async (verification: Verification): Promise<void>
69
70
  console.log(
70
71
  `${theme.symbols.warning} Visit ${theme.text.bold(
71
72
  theme.text.underlined(verification.verification_uri)
72
- )} on this device to generate a new token and authenticate`
73
+ )} on this device to generate a new token and authenticate\n`
73
74
  )
75
+
76
+ console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n")
77
+
74
78
  console.log(
75
- `${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} (${theme.emojis.clipboard} ${
76
- theme.symbols.success
77
- })\n`
79
+ `${theme.symbols.info} Your auth code: ${theme.text.bold(
80
+ verification.user_code
81
+ )} has been copied to your clipboard (${theme.emojis.clipboard} ${theme.symbols.success})\n`
78
82
  )
79
83
 
80
84
  const spinner = customSpinner(`Redirecting to Github...`, `clock`)
@@ -22,7 +22,7 @@ import {
22
22
  generateValidContributionsAttestation,
23
23
  commonTerms,
24
24
  convertToDoubleDigits
25
- } from "@p0tion/actions"
25
+ } from "@devtion/actions"
26
26
  import { DocumentSnapshot, DocumentData, Firestore, onSnapshot, Timestamp } from "firebase/firestore"
27
27
  import { Functions } from "firebase/functions"
28
28
  import open from "open"
@@ -40,7 +40,7 @@ import {
40
40
  estimateParticipantFreeGlobalDiskSpace
41
41
  } from "../lib/utils.js"
42
42
  import { COMMAND_ERRORS, showError } from "../lib/errors.js"
43
- import { bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
43
+ import { authWithToken, bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
44
44
  import { getAttestationLocalFilePath, localPaths } from "../lib/localConfigs.js"
45
45
  import theme from "../lib/theme.js"
46
46
  import { checkAndMakeNewDirectoryIfNonexistent, writeFile } from "../lib/files.js"
@@ -464,7 +464,6 @@ export const listenToCeremonyCircuitDocumentChanges = (
464
464
  const { fullContribution, verifyCloudFunction } = avgTimings
465
465
  const { currentContributor } = waitingQueue
466
466
 
467
- // Get circuit current contributor participant document.
468
467
  const circuitCurrentContributor = await getDocumentById(
469
468
  firestoreDatabase,
470
469
  getParticipantsCollectionPath(ceremonyId),
@@ -725,7 +724,8 @@ export const listenToParticipantDocumentChanges = async (
725
724
  participant,
726
725
  entropy,
727
726
  providerUserId,
728
- false // not finalizing.
727
+ false, // not finalizing.
728
+ circuits.length
729
729
  )
730
730
  }
731
731
  // Scenario (3.A).
@@ -811,7 +811,9 @@ export const listenToParticipantDocumentChanges = async (
811
811
  await getLatestVerificationResult(firestoreDatabase, ceremony.id, circuit.id, participant.id)
812
812
 
813
813
  // Get next circuit for contribution.
814
- const nextCircuit = getCircuitBySequencePosition(circuits, changedContributionProgress + 1)
814
+ const nextCircuit = timeoutExpired
815
+ ? getCircuitBySequencePosition(circuits, changedContributionProgress)
816
+ : getCircuitBySequencePosition(circuits, changedContributionProgress + 1)
815
817
 
816
818
  // Check disk space requirements for participant.
817
819
  const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(
@@ -892,12 +894,13 @@ export const listenToParticipantDocumentChanges = async (
892
894
  const contribute = async (opt: any) => {
893
895
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
894
896
 
895
- // Check for authentication.
896
- const { user, providerUserId, token } = await checkAuth(firebaseApp)
897
-
898
897
  // Get options.
899
898
  const ceremonyOpt = opt.ceremony
900
899
  const entropyOpt = opt.entropy
900
+ const auth = opt.auth
901
+
902
+ // Check for authentication.
903
+ const { user, providerUserId, token } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp)
901
904
 
902
905
  // Prepare data.
903
906
  let selectedCeremony: FirebaseDocumentInfo
@@ -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
  }
@@ -22,7 +22,7 @@ import {
22
22
  exportVerifierContract,
23
23
  FirebaseDocumentInfo,
24
24
  exportVkey
25
- } from "@p0tion/actions"
25
+ } from "@devtion/actions"
26
26
  import { Functions } from "firebase/functions"
27
27
  import { Firestore } from "firebase/firestore"
28
28
  import { dirname } from "path"
@@ -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"
@@ -1,4 +1,4 @@
1
- import { commonTerms, getAllCollectionDocs } from "@p0tion/actions"
1
+ import { commonTerms, getAllCollectionDocs } from "@devtion/actions"
2
2
  import { showError } from "../lib/errors.js"
3
3
  import { bootstrapCommandExecutionAndServices } from "../lib/services.js"
4
4
 
@@ -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
@@ -7,7 +7,7 @@ import {
7
7
  getOpenedCeremonies,
8
8
  isCoordinator,
9
9
  convertToDoubleDigits
10
- } from "@p0tion/actions"
10
+ } from "@devtion/actions"
11
11
  import { Firestore } from "firebase/firestore"
12
12
  import logSymbols from "log-symbols"
13
13
  import readline from "readline"
@@ -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).
@@ -7,7 +7,6 @@ import { pipeline } from "node:stream"
7
7
  import { promisify } from "node:util"
8
8
  import fetch from "node-fetch"
9
9
  import { Functions } from "firebase/functions"
10
- import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
11
10
  import {
12
11
  CeremonyTimeoutType,
13
12
  CircomCompilerData,
@@ -37,7 +36,7 @@ import {
37
36
  setupCeremony,
38
37
  parseCeremonyFile,
39
38
  CircuitContributionVerificationMechanism
40
- } from "@p0tion/actions"
39
+ } from "@devtion/actions"
41
40
  import { customSpinner, simpleLoader, sleep, terminate } from "../lib/utils.js"
42
41
  import {
43
42
  promptCeremonyInputData,
@@ -63,7 +62,6 @@ import {
63
62
  getFileStats,
64
63
  checkAndMakeNewDirectoryIfNonexistent
65
64
  } from "../lib/files.js"
66
- import { Readable } from "stream"
67
65
 
68
66
  /**
69
67
  * Handle whatever is needed to obtain the input data for a circuit that the coordinator would like to add to the ceremony.
@@ -468,7 +466,7 @@ export const handleCircuitArtifactUploadToStorage = async (
468
466
  * from Hermez's ceremony Phase 1 Reliable Setup Ceremony.
469
467
  * @param cmd? <any> - the path to the ceremony setup file.
470
468
  */
471
- const setup = async (cmd: { template?: string, auth?: string}) => {
469
+ const setup = async (cmd: { template?: string; auth?: string }) => {
472
470
  // Setup command state.
473
471
  const circuits: Array<CircuitDocument> = [] // Circuits.
474
472
  let ceremonyId: string = "" // The unique identifier of the ceremony.
@@ -476,8 +474,10 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
476
474
  const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
477
475
 
478
476
  // Check for authentication.
479
- const { user, providerUserId } = cmd.auth ? await authWithToken(firebaseApp, cmd.auth) : await checkAuth(firebaseApp)
480
-
477
+ const { user, providerUserId } = cmd.auth
478
+ ? await authWithToken(firebaseApp, cmd.auth)
479
+ : await checkAuth(firebaseApp)
480
+
481
481
  // Preserve command execution only for coordinators.
482
482
  if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
483
483
 
@@ -503,7 +503,7 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
503
503
  // if there is the file option, then set up the non interactively
504
504
  if (cmd.template) {
505
505
  // 1. parse the file
506
- // tmp data - do not cleanup files as we need them
506
+ // tmp data - do not cleanup files as we need them
507
507
  const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template!)} setup configuration file...`, `clock`)
508
508
  spinner.start()
509
509
  const setupCeremonyData = await parseCeremonyFile(cmd.template!)
@@ -516,9 +516,6 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
516
516
  const bucketName = await handleCeremonyBucketCreation(firebaseFunctions, ceremonySetupData.ceremonyPrefix)
517
517
  console.log(`\n${theme.symbols.success} Ceremony bucket name: ${theme.text.bold(bucketName)}`)
518
518
 
519
- // create S3 clienbt
520
- const s3 = new S3Client({region: 'us-east-1'})
521
-
522
519
  // loop through each circuit
523
520
  for await (const circuit of setupCeremonyData.circuits) {
524
521
  // Local paths.
@@ -529,32 +526,27 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
529
526
  const zkeyLocalPathAndFileName = getZkeyLocalFilePath(circuit.files.initialZkeyFilename)
530
527
 
531
528
  // 2. download the pot and wasm files
532
- const streamPipeline = promisify(pipeline)
533
- await checkAndDownloadSmallestPowersOfTau(convertToDoubleDigits(circuit.metadata?.pot!), circuit.files.potFilename)
534
-
535
- // download the wasm to calculate the hash
529
+ await checkAndDownloadSmallestPowersOfTau(
530
+ convertToDoubleDigits(circuit.metadata?.pot!),
531
+ circuit.files.potFilename
532
+ )
533
+
534
+ // 3. generate the zKey
536
535
  const spinner = customSpinner(
537
- `Downloading the ${theme.text.bold(
538
- `#${circuit.name}`
539
- )} WASM file from the project's bucket...`,
536
+ `Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`,
540
537
  `clock`
541
538
  )
542
539
  spinner.start()
543
- const command = new GetObjectCommand({ Bucket: ceremonySetupData.circuitArtifacts[index].artifacts.bucket, Key: ceremonySetupData.circuitArtifacts[index].artifacts.wasmStoragePath })
544
-
545
- const response = await s3.send(command)
546
-
547
- if (response.$metadata.httpStatusCode !== 200) {
548
- throw new Error("There was an error while trying to download the wasm file. Please check that the file has the correct permissions (public) set.")
549
- }
550
-
551
- if (response.Body instanceof Readable)
552
- await streamPipeline(response.Body, createWriteStream(wasmLocalPathAndFileName))
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
+ )
553
549
 
554
- spinner.stop()
555
- // 3. generate the zKey
556
- await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined)
557
-
558
550
  // 4. calculate the hashes
559
551
  const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName)
560
552
  const potBlake2bHash = await blake512FromPath(getPotLocalFilePath(circuit.files.potFilename))
@@ -570,7 +562,7 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
570
562
  zkeyLocalPathAndFileName,
571
563
  circuit.files.initialZkeyFilename
572
564
  )
573
-
565
+
574
566
  // Check if PoT file has been already uploaded to storage.
575
567
  const alreadyUploadedPot = await checkIfObjectExist(
576
568
  firebaseFunctions,
@@ -618,18 +610,24 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
618
610
 
619
611
  ceremonySetupData.circuits[index].zKeySizeInBytes = getFileStats(zkeyLocalPathAndFileName).size
620
612
  }
621
-
622
613
 
623
614
  // 7. setup the ceremony
624
- const ceremonyId = await setupCeremony(firebaseFunctions, ceremonySetupData.ceremonyInputData, ceremonySetupData.ceremonyPrefix, ceremonySetupData.circuits)
625
- console.log( `Congratulations, the setup of ceremony ${theme.text.bold(
626
- ceremonySetupData.ceremonyInputData.title
627
- )} (${`UID: ${theme.text.bold(ceremonyId)}`}) has been successfully completed ${
628
- theme.emojis.tada
629
- }. You will be able to find all the files and info respectively in the ceremony bucket and database document.`)
630
-
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
+
631
629
  terminate(providerUserId)
632
- }
630
+ }
633
631
 
634
632
  // Look for R1CS files.
635
633
  const r1csFilePaths = await filterDirectoryFilesByExtension(cwd, `.r1cs`)
@@ -1,10 +1,10 @@
1
- import { parseCeremonyFile } from "@p0tion/actions"
1
+ import { parseCeremonyFile } from "@devtion/actions"
2
2
  import { showError } from "../lib/errors.js"
3
3
 
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))}/..`
@@ -21,15 +31,13 @@ program
21
31
  .description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
22
32
  .option("-c, --ceremony <string>", "the prefix of the ceremony you want to contribute for", "")
23
33
  .option("-e, --entropy <string>", "the entropy (aka toxic waste) of your contribution", "")
34
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
24
35
  .action(contribute)
25
36
  program
26
37
  .command("clean")
27
38
  .description("clean up output generated by commands from the current working directory")
28
39
  .action(clean)
29
- program
30
- .command("list")
31
- .description("List all ceremonies prefixes")
32
- .action(listCeremonies)
40
+ program.command("list").description("List all ceremonies prefixes").action(listCeremonies)
33
41
  program
34
42
  .command("logout")
35
43
  .description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
@@ -47,10 +55,10 @@ const ceremony = program.command("coordinate").description("commands for coordin
47
55
  ceremony
48
56
  .command("setup")
49
57
  .description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
50
- .option('-t, --template <path>', 'The path to the ceremony setup template', '')
51
- .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", "")
52
60
  .action(setup)
53
-
61
+
54
62
  ceremony
55
63
  .command("observe")
56
64
  .description("observe in real-time the waiting queue of each ceremony circuit")
@@ -61,6 +69,7 @@ ceremony
61
69
  .description(
62
70
  "finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract"
63
71
  )
72
+ .option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
64
73
  .action(finalize)
65
74
 
66
75
  program.parseAsync(process.argv)
@@ -1,4 +1,4 @@
1
- import { commonTerms } from "@p0tion/actions"
1
+ import { commonTerms } from "@devtion/actions"
2
2
  import Conf from "conf"
3
3
  import { dirname } from "path"
4
4
  import { readFileSync } from "fs"
@@ -14,7 +14,7 @@ import {
14
14
  CircuitContributionVerificationMechanism,
15
15
  vmConfigurationTypes,
16
16
  DiskTypeForVM
17
- } from "@p0tion/actions"
17
+ } from "@devtion/actions"
18
18
  import theme from "./theme.js"
19
19
  import { COMMAND_ERRORS, showError } from "./errors.js"
20
20
 
@@ -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
 
@@ -429,12 +429,17 @@ export const promptCircuitInputData = async (
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
@@ -2,7 +2,7 @@ import {
2
2
  getCurrentFirebaseAuthUser,
3
3
  initializeFirebaseCoreServices,
4
4
  signInToFirebaseWithCredentials
5
- } from "@p0tion/actions"
5
+ } from "@devtion/actions"
6
6
  import clear from "clear"
7
7
  import figlet from "figlet"
8
8
  import { FirebaseApp } from "firebase/app"
@@ -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
@@ -19,7 +19,7 @@ import {
19
19
  permanentlyStoreCurrentContributionTimeAndHash,
20
20
  progressToNextContributionStep,
21
21
  verifyContribution
22
- } from "@p0tion/actions"
22
+ } from "@devtion/actions"
23
23
  import { Presets, SingleBar } from "cli-progress"
24
24
  import dotenv from "dotenv"
25
25
  import { GithubAuthProvider, OAuthCredential } from "firebase/auth"
@@ -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.
@@ -696,7 +714,7 @@ export const handleStartOrResumeContribution = async (
696
714
  !isFinalizing ? theme.text.bold(`#${nextZkeyIndex}`) : ""
697
715
  } to storage.\n${
698
716
  theme.symbols.warning
699
- } This step may take a while based on circuit size and your contribution speed. Everything's fine, just be patient.`
717
+ } This step may take a while based on circuit size and your internet speed. Everything's fine, just be patient.`
700
718
  spinner.start()
701
719
 
702
720
  if (!isFinalizing)