@devtion/devcli 0.0.0-5d170d3 → 0.0.0-67a4629
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 +3 -1
- package/dist/.env +10 -3
- package/dist/index.js +315 -104
- package/dist/public/mini-semaphore.wasm +0 -0
- package/dist/public/mini-semaphore.zkey +0 -0
- package/dist/types/commands/authBandada.d.ts +2 -0
- package/dist/types/commands/ceremony/index.d.ts +3 -0
- package/dist/types/commands/ceremony/listParticipants.d.ts +2 -0
- package/dist/types/commands/contribute.d.ts +1 -1
- package/dist/types/commands/finalize.d.ts +4 -3
- package/dist/types/commands/index.d.ts +1 -0
- package/dist/types/commands/observe.d.ts +1 -1
- package/dist/types/commands/setup.d.ts +2 -2
- package/dist/types/lib/bandada.d.ts +6 -0
- package/dist/types/lib/files.d.ts +1 -0
- package/dist/types/lib/localConfigs.d.ts +19 -0
- package/dist/types/lib/prompts.d.ts +6 -6
- package/dist/types/lib/utils.d.ts +3 -2
- package/dist/types/types/index.d.ts +12 -0
- package/package.json +11 -4
- package/src/commands/auth.ts +21 -7
- package/src/commands/authBandada.ts +110 -0
- package/src/commands/ceremony/index.ts +20 -0
- package/src/commands/ceremony/listParticipants.ts +30 -0
- package/src/commands/contribute.ts +25 -17
- package/src/commands/finalize.ts +22 -13
- package/src/commands/index.ts +2 -1
- package/src/commands/listCeremonies.ts +2 -3
- package/src/commands/logout.ts +2 -1
- package/src/commands/observe.ts +3 -3
- package/src/commands/setup.ts +56 -45
- package/src/commands/validate.ts +2 -3
- package/src/index.ts +30 -13
- package/src/lib/bandada.ts +51 -0
- package/src/lib/errors.ts +1 -1
- package/src/lib/localConfigs.ts +28 -1
- package/src/lib/prompts.ts +20 -20
- package/src/lib/services.ts +27 -16
- package/src/lib/utils.ts +45 -10
- package/src/types/index.ts +13 -0
package/dist/index.js
CHANGED
|
@@ -2,28 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @module @p0tion/phase2cli
|
|
5
|
-
* @version 1.
|
|
5
|
+
* @version 1.1.1
|
|
6
6
|
* @file All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies
|
|
7
7
|
* @copyright Ethereum Foundation 2022
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @see [Github]{@link https://github.com/privacy-scaling-explorations/p0tion}
|
|
10
10
|
*/
|
|
11
11
|
import { createCommand } from 'commander';
|
|
12
|
-
import fs, { readFileSync, createWriteStream, renameSync } from 'fs';
|
|
12
|
+
import fs, { readFileSync, createWriteStream, existsSync, renameSync } from 'fs';
|
|
13
13
|
import { dirname } from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import { zKey } from 'snarkjs';
|
|
15
|
+
import { zKey, groth16 } from 'snarkjs';
|
|
16
16
|
import boxen from 'boxen';
|
|
17
17
|
import { pipeline } from 'node:stream';
|
|
18
18
|
import { promisify } from 'node:util';
|
|
19
19
|
import fetch$1 from 'node-fetch';
|
|
20
|
-
import {
|
|
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, getAllCeremonies } 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';
|
|
25
24
|
import dotenv from 'dotenv';
|
|
26
|
-
import { GithubAuthProvider, getAuth, signOut } from 'firebase/auth';
|
|
25
|
+
import { GithubAuthProvider, signInWithCustomToken, getAuth, signOut } from 'firebase/auth';
|
|
27
26
|
import { getDiskInfoSync } from 'node-disk-info';
|
|
28
27
|
import ora from 'ora';
|
|
29
28
|
import { Timer } from 'timer-node';
|
|
@@ -34,11 +33,13 @@ 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';
|
|
41
|
-
import {
|
|
39
|
+
import { Identity } from '@semaphore-protocol/identity';
|
|
40
|
+
import { httpsCallable } from 'firebase/functions';
|
|
41
|
+
import { ApiSdk } from '@bandada/api-sdk';
|
|
42
|
+
import { Timestamp, onSnapshot, doc, collection, getDocs } from 'firebase/firestore';
|
|
42
43
|
import readline from 'readline';
|
|
43
44
|
|
|
44
45
|
/**
|
|
@@ -99,7 +100,7 @@ const CORE_SERVICES_ERRORS = {
|
|
|
99
100
|
FIREBASE_TOKEN_EXPIRED_REMOVED_PERMISSIONS: `The Github authorization has failed due to lack of association between your account and the CLI`,
|
|
100
101
|
FIREBASE_USER_DISABLED: `The Github account has been suspended by the ceremony coordinator(s), blocking the possibility of contribution. Please, contact them to understand the motivation behind it.`,
|
|
101
102
|
FIREBASE_FAILED_CREDENTIALS_VERIFICATION: `Firebase cannot verify your Github credentials due to network errors. Please, try once again later.`,
|
|
102
|
-
FIREBASE_NETWORK_ERROR: `Unable to reach Firebase due to network
|
|
103
|
+
FIREBASE_NETWORK_ERROR: `Unable to reach Firebase due to network errors. Please, try once again later and make sure your Internet connection is stable.`,
|
|
103
104
|
FIREBASE_CEREMONY_NOT_OPENED: `There are no ceremonies opened to contributions`,
|
|
104
105
|
FIREBASE_CEREMONY_NOT_CLOSED: `There are no ceremonies ready to finalization`,
|
|
105
106
|
AWS_CEREMONY_BUCKET_CREATION: `Unable to create a new bucket for the ceremony. Something went wrong during the creation. Please, repeat the process by providing a new ceremony name of the ceremony.`,
|
|
@@ -252,6 +253,10 @@ const config = new Conf({
|
|
|
252
253
|
accessToken: {
|
|
253
254
|
type: "string",
|
|
254
255
|
default: ""
|
|
256
|
+
},
|
|
257
|
+
bandadaIdentity: {
|
|
258
|
+
type: "string",
|
|
259
|
+
default: ""
|
|
255
260
|
}
|
|
256
261
|
}
|
|
257
262
|
});
|
|
@@ -312,6 +317,25 @@ const setLocalAccessToken = (token) => config.set("accessToken", token);
|
|
|
312
317
|
* Delete the stored access token.
|
|
313
318
|
*/
|
|
314
319
|
const deleteLocalAccessToken = () => config.delete("accessToken");
|
|
320
|
+
/**
|
|
321
|
+
* Return the Bandada identity, if present.
|
|
322
|
+
* @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
|
|
323
|
+
*/
|
|
324
|
+
const getLocalBandadaIdentity = () => config.get("bandadaIdentity");
|
|
325
|
+
/**
|
|
326
|
+
* Check if the Bandada identity exists in the local storage.
|
|
327
|
+
* @returns <boolean>
|
|
328
|
+
*/
|
|
329
|
+
const checkLocalBandadaIdentity = () => config.has("bandadaIdentity") && !!config.get("bandadaIdentity");
|
|
330
|
+
/**
|
|
331
|
+
* Set the Bandada identity.
|
|
332
|
+
* @param identity <string> - the Bandada identity to be stored.
|
|
333
|
+
*/
|
|
334
|
+
const setLocalBandadaIdentity = (identity) => config.set("bandadaIdentity", identity);
|
|
335
|
+
/**
|
|
336
|
+
* Delete the stored Bandada identity.
|
|
337
|
+
*/
|
|
338
|
+
const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity");
|
|
315
339
|
/**
|
|
316
340
|
* Get the complete local file path.
|
|
317
341
|
* @param cwd <string> - the current working directory path.
|
|
@@ -367,6 +391,12 @@ const getVerificationKeyLocalFilePath = (completeFilename) => `${verificationKey
|
|
|
367
391
|
* @returns <string> - the complete final verifier contract path to the file.
|
|
368
392
|
*/
|
|
369
393
|
const getVerifierContractLocalFilePath = (completeFilename) => `${verifierContractsLocalFolderPath}/${completeFilename}`;
|
|
394
|
+
/**
|
|
395
|
+
* Get the complete final attestation file path.
|
|
396
|
+
* @param completeFilename <string> - the complete filename of the file (name.ext).
|
|
397
|
+
* @returns <string> - the complete final final attestation path to the file.
|
|
398
|
+
*/
|
|
399
|
+
const getFinalAttestationLocalFilePath = (completeFilename) => `${finalAttestationsLocalFolderPath}/${completeFilename}`;
|
|
370
400
|
/**
|
|
371
401
|
* Get the final transcript file path.
|
|
372
402
|
* @param completeFilename <string> - the complete filename of the file (name.ext).
|
|
@@ -416,7 +446,7 @@ const getGithubAuthenticatedUserGists = async (githubToken, params) => {
|
|
|
416
446
|
headers: {
|
|
417
447
|
authorization: `token ${githubToken}`
|
|
418
448
|
},
|
|
419
|
-
per_page: params.perPage,
|
|
449
|
+
per_page: params.perPage, // max items per page = 100.
|
|
420
450
|
page: params.page
|
|
421
451
|
});
|
|
422
452
|
if (response && response.status === 200)
|
|
@@ -464,8 +494,9 @@ const getPublicAttestationGist = async (githubToken, publicAttestationFilename)
|
|
|
464
494
|
* @returns <string> - the third-party provider handle of the user.
|
|
465
495
|
*/
|
|
466
496
|
const getUserHandleFromProviderUserId = (providerUserId) => {
|
|
467
|
-
if (providerUserId.indexOf("-") === -1)
|
|
468
|
-
|
|
497
|
+
if (providerUserId.indexOf("-") === -1) {
|
|
498
|
+
return providerUserId;
|
|
499
|
+
}
|
|
469
500
|
return providerUserId.split("-")[0];
|
|
470
501
|
};
|
|
471
502
|
/**
|
|
@@ -581,8 +612,18 @@ const publishGist = async (token, content, ceremonyTitle, ceremonyPrefix) => {
|
|
|
581
612
|
* @returns <string> - the ready to share tweet url.
|
|
582
613
|
*/
|
|
583
614
|
const generateCustomUrlToTweetAboutParticipation = (ceremonyName, gistUrl, isFinalizing) => isFinalizing
|
|
584
|
-
? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}
|
|
585
|
-
|
|
615
|
+
? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
|
|
616
|
+
ceremonyName.toLowerCase().includes("setup") ||
|
|
617
|
+
ceremonyName.toLowerCase().includes("phase2") ||
|
|
618
|
+
ceremonyName.toLowerCase().includes("ceremony")
|
|
619
|
+
? "!"
|
|
620
|
+
: "%20Phase%202%20Trusted%20Setup%20ceremony!"}%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
|
|
621
|
+
: `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
|
|
622
|
+
ceremonyName.toLowerCase().includes("setup") ||
|
|
623
|
+
ceremonyName.toLowerCase().includes("phase2") ||
|
|
624
|
+
ceremonyName.toLowerCase().includes("ceremony")
|
|
625
|
+
? "!"
|
|
626
|
+
: "%20Phase%202%20Trusted%20Setup%20ceremony!"}%20You%20can%20view%20the%20steps%20to%20contribute%20here:%20https://ceremony.pse.dev%20You%20can%20view%20my%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP`;
|
|
586
627
|
/**
|
|
587
628
|
* Return a custom progress bar.
|
|
588
629
|
* @param type <ProgressBarType> - the type of the progress bar.
|
|
@@ -710,13 +751,14 @@ const getLatestUpdatesFromParticipant = async (firestoreDatabase, ceremonyId, pa
|
|
|
710
751
|
* @param entropyOrBeaconHash <string> - the entropy or beacon hash (only when finalizing) for the contribution.
|
|
711
752
|
* @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
|
|
712
753
|
* @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
|
|
754
|
+
* @param circuitsLength <number> - the total number of circuits in the ceremony.
|
|
713
755
|
*/
|
|
714
|
-
const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing) => {
|
|
756
|
+
const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing, circuitsLength) => {
|
|
715
757
|
// Extract data.
|
|
716
758
|
const { prefix: ceremonyPrefix } = ceremony.data;
|
|
717
759
|
const { waitingQueue, avgTimings, prefix: circuitPrefix, sequencePosition } = circuit.data;
|
|
718
760
|
const { completedContributions } = waitingQueue; // = current progress.
|
|
719
|
-
console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}`)}`)} (Contribution Steps)`);
|
|
761
|
+
console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}/${circuitsLength}`)}`)} (Contribution Steps)`);
|
|
720
762
|
// Get most up-to-date data from the participant document.
|
|
721
763
|
let participantData = await getLatestUpdatesFromParticipant(firestoreDatabase, ceremony.id, participant.id);
|
|
722
764
|
const spinner = customSpinner(`${participantData.contributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */
|
|
@@ -762,6 +804,7 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
762
804
|
// Download the latest contribution from bucket.
|
|
763
805
|
await downloadCeremonyArtifact(cloudFunctions, bucketName, lastZkeyStorageFilePath, lastZkeyLocalFilePath);
|
|
764
806
|
console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${lastZkeyIndex}`)} correctly downloaded`);
|
|
807
|
+
await sleep(3000);
|
|
765
808
|
// Advance to next contribution step (COMPUTING) if not finalizing.
|
|
766
809
|
if (!isFinalizing) {
|
|
767
810
|
spinner.text = `Preparing for contribution computation...`;
|
|
@@ -789,11 +832,14 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
789
832
|
showError(COMMAND_ERRORS.COMMAND_CONTRIBUTE_FINALIZE_NO_TRANSCRIPT_CONTRIBUTION_HASH_MATCH, true);
|
|
790
833
|
// Format contribution hash.
|
|
791
834
|
const contributionHash = matchContributionHash?.at(0)?.replace("\n\t\t", "");
|
|
835
|
+
await sleep(500);
|
|
792
836
|
// Make request to cloud functions to permanently store the information.
|
|
793
837
|
await permanentlyStoreCurrentContributionTimeAndHash(cloudFunctions, ceremony.id, computingTime, contributionHash);
|
|
794
838
|
// Format computing time.
|
|
795
839
|
const { seconds: computationSeconds, minutes: computationMinutes, hours: computationHours } = getSecondsMinutesHoursFromMillis(computingTime);
|
|
796
840
|
spinner.succeed(`${isFinalizing ? "Contribution" : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} computation took ${theme.text.bold(`${convertToDoubleDigits(computationHours)}:${convertToDoubleDigits(computationMinutes)}:${convertToDoubleDigits(computationSeconds)}`)}`);
|
|
841
|
+
// ensure the previous step is completed
|
|
842
|
+
await sleep(5000);
|
|
797
843
|
// Advance to next contribution step (UPLOADING) if not finalizing.
|
|
798
844
|
if (!isFinalizing) {
|
|
799
845
|
spinner.text = `Preparing for uploading the contribution...`;
|
|
@@ -809,12 +855,17 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
809
855
|
console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${nextZkeyIndex}`)} already computed`);
|
|
810
856
|
// Contribution step = UPLOADING.
|
|
811
857
|
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
|
|
858
|
+
spinner.text = `Uploading ${isFinalizing ? "final" : "your"} contribution ${!isFinalizing ? theme.text.bold(`#${nextZkeyIndex}`) : ""} to storage.\n${theme.symbols.warning} This step may take a while based on circuit size and your internet speed. Everything's fine, just be patient.`;
|
|
813
859
|
spinner.start();
|
|
814
|
-
|
|
815
|
-
|
|
860
|
+
const progressBar = customProgressBar(ProgressBarType.UPLOAD, `your contribution`);
|
|
861
|
+
if (!isFinalizing) {
|
|
862
|
+
await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB), ceremony.id, participantData.tempContributionData, progressBar);
|
|
863
|
+
progressBar.stop();
|
|
864
|
+
}
|
|
816
865
|
else
|
|
817
866
|
await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
867
|
+
// small sleep to ensure the previous step is completed
|
|
868
|
+
await sleep(5000);
|
|
818
869
|
spinner.succeed(`${isFinalizing ? `Contribution` : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} correctly saved to storage`);
|
|
819
870
|
// Advance to next contribution step (VERIFYING) if not finalizing.
|
|
820
871
|
if (!isFinalizing) {
|
|
@@ -992,7 +1043,7 @@ const promptCircomCompiler = async () => {
|
|
|
992
1043
|
* Shows a list of circuits for a single option selection.
|
|
993
1044
|
* @dev the circuit names are derived from local R1CS files.
|
|
994
1045
|
* @param options <Array<string>> - an array of circuits names.
|
|
995
|
-
* @returns Promise<string> - the name of the
|
|
1046
|
+
* @returns Promise<string> - the name of the chosen circuit.
|
|
996
1047
|
*/
|
|
997
1048
|
const promptCircuitSelector = async (options) => {
|
|
998
1049
|
const { circuitFilename } = await prompts({
|
|
@@ -1010,7 +1061,7 @@ const promptCircuitSelector = async (options) => {
|
|
|
1010
1061
|
* Shows a list of standard EC2 VM instance types for a single option selection.
|
|
1011
1062
|
* @notice the suggested VM configuration type is calculated based on circuit constraint size.
|
|
1012
1063
|
* @param constraintSize <number> - the amount of circuit constraints
|
|
1013
|
-
* @returns Promise<string> - the name of the
|
|
1064
|
+
* @returns Promise<string> - the name of the chosen VM type.
|
|
1014
1065
|
*/
|
|
1015
1066
|
const promptVMTypeSelector = async (constraintSize) => {
|
|
1016
1067
|
let suggestedConfiguration = 0;
|
|
@@ -1107,7 +1158,7 @@ const promptVMDiskTypeSelector = async () => {
|
|
|
1107
1158
|
/**
|
|
1108
1159
|
* Show a series of questions about the circuits.
|
|
1109
1160
|
* @param constraintSize <number> - the amount of circuit constraints.
|
|
1110
|
-
* @param timeoutMechanismType <CeremonyTimeoutType> - the
|
|
1161
|
+
* @param timeoutMechanismType <CeremonyTimeoutType> - the chosen timeout mechanism type for the ceremony.
|
|
1111
1162
|
* @param needPromptCircomCompiler <boolean> - a boolean value indicating if the questions related to the Circom compiler version and commit hash must be asked.
|
|
1112
1163
|
* @param enforceVM <boolean> - a boolean value indicating if the contribution verification could be supported by VM-only approach or not.
|
|
1113
1164
|
* @returns Promise<Array<Circuit>> - circuit info prompted by the coordinator.
|
|
@@ -1120,7 +1171,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1120
1171
|
let circomVersion = "";
|
|
1121
1172
|
let circomCommitHash = "";
|
|
1122
1173
|
let circuitInputData;
|
|
1123
|
-
let
|
|
1174
|
+
let cfOrVm;
|
|
1124
1175
|
let vmDiskType;
|
|
1125
1176
|
let vmConfigurationType = "";
|
|
1126
1177
|
const questions = [
|
|
@@ -1175,18 +1226,21 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1175
1226
|
circomVersion = version;
|
|
1176
1227
|
circomCommitHash = commitHash;
|
|
1177
1228
|
}
|
|
1178
|
-
// Ask for
|
|
1229
|
+
// Ask for preferred contribution verification method (CF vs VM).
|
|
1179
1230
|
if (!enforceVM) {
|
|
1180
1231
|
const { confirmation } = await askForConfirmation(`The contribution verification can be performed using Cloud Functions (CF, cheaper for small contributions but limited to 1M constraints) or custom virtual machines (expensive but could scale up to 30M constraints). Be aware about VM costs and if you wanna learn more, please visit the documentation to have a complete overview about cost estimation of the two mechanisms.\nChoose the contribution verification mechanism`, `CF`, // eq. true.
|
|
1181
1232
|
`VM` // eq. false.
|
|
1182
1233
|
);
|
|
1183
|
-
|
|
1234
|
+
cfOrVm = confirmation
|
|
1235
|
+
? "CF" /* CircuitContributionVerificationMechanism.CF */
|
|
1236
|
+
: "VM" /* CircuitContributionVerificationMechanism.VM */;
|
|
1184
1237
|
}
|
|
1185
|
-
else
|
|
1186
|
-
|
|
1187
|
-
|
|
1238
|
+
else {
|
|
1239
|
+
cfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
|
|
1240
|
+
}
|
|
1241
|
+
if (cfOrVm === undefined)
|
|
1188
1242
|
showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
|
|
1189
|
-
if (
|
|
1243
|
+
if (cfOrVm === "VM" /* CircuitContributionVerificationMechanism.VM */) {
|
|
1190
1244
|
// Ask for selecting the specific VM configuration type.
|
|
1191
1245
|
vmConfigurationType = await promptVMTypeSelector(constraintSize);
|
|
1192
1246
|
// Ask for selecting the specific VM disk (volume) type.
|
|
@@ -1220,9 +1274,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1220
1274
|
paramsConfiguration: circuitConfigurationValues
|
|
1221
1275
|
},
|
|
1222
1276
|
verification: {
|
|
1223
|
-
cfOrVm
|
|
1224
|
-
? "CF" /* CircuitContributionVerificationMechanism.CF */
|
|
1225
|
-
: "VM" /* CircuitContributionVerificationMechanism.VM */,
|
|
1277
|
+
cfOrVm,
|
|
1226
1278
|
vm: {
|
|
1227
1279
|
vmConfigurationType,
|
|
1228
1280
|
vmDiskType
|
|
@@ -1258,9 +1310,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1258
1310
|
paramsConfiguration: circuitConfigurationValues
|
|
1259
1311
|
},
|
|
1260
1312
|
verification: {
|
|
1261
|
-
cfOrVm
|
|
1262
|
-
? "CF" /* CircuitContributionVerificationMechanism.CF */
|
|
1263
|
-
: "VM" /* CircuitContributionVerificationMechanism.VM */,
|
|
1313
|
+
cfOrVm,
|
|
1264
1314
|
vm: {
|
|
1265
1315
|
vmConfigurationType,
|
|
1266
1316
|
vmDiskType
|
|
@@ -1304,7 +1354,7 @@ const promptCircuitAddition = async () => {
|
|
|
1304
1354
|
* Shows a list of pre-computed zKeys for a single option selection.
|
|
1305
1355
|
* @dev the names are derived from local zKeys files.
|
|
1306
1356
|
* @param options <Array<string>> - an array of pre-computed zKeys names.
|
|
1307
|
-
* @returns Promise<string> - the name of the
|
|
1357
|
+
* @returns Promise<string> - the name of the chosen pre-computed zKey.
|
|
1308
1358
|
*/
|
|
1309
1359
|
const promptPreComputedZkeySelector = async (options) => {
|
|
1310
1360
|
const { preComputedZkeyFilename } = await prompts({
|
|
@@ -1342,13 +1392,13 @@ const promptNeededPowersForCircuit = async (suggestedSmallestNeededPowers) => {
|
|
|
1342
1392
|
* Shows a list of PoT files for a single option selection.
|
|
1343
1393
|
* @dev the names are derived from local PoT files.
|
|
1344
1394
|
* @param options <Array<string>> - an array of PoT file names.
|
|
1345
|
-
* @returns Promise<string> - the name of the
|
|
1395
|
+
* @returns Promise<string> - the name of the chosen PoT.
|
|
1346
1396
|
*/
|
|
1347
1397
|
const promptPotSelector = async (options) => {
|
|
1348
1398
|
const { potFilename } = await prompts({
|
|
1349
1399
|
type: "select",
|
|
1350
1400
|
name: "potFilename",
|
|
1351
|
-
message: theme.text.bold("Select the Powers of Tau file
|
|
1401
|
+
message: theme.text.bold("Select the Powers of Tau file chosen for the circuit"),
|
|
1352
1402
|
choices: options.map((option) => {
|
|
1353
1403
|
console.log(option);
|
|
1354
1404
|
return { title: option, value: option };
|
|
@@ -1418,7 +1468,7 @@ const promptToTypeEntropyOrBeacon = async (isEntropy = true) => {
|
|
|
1418
1468
|
* @return <Promise<string>> - the entropy.
|
|
1419
1469
|
*/
|
|
1420
1470
|
const promptForEntropy = async () => {
|
|
1421
|
-
// Prompt for entropy generation
|
|
1471
|
+
// Prompt for entropy generation preferred method.
|
|
1422
1472
|
const { confirmation } = await askForConfirmation(`Do you prefer to type your entropy or generate it randomly?`, "Manually", "Randomly");
|
|
1423
1473
|
if (confirmation === undefined)
|
|
1424
1474
|
showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
|
|
@@ -1537,16 +1587,27 @@ const checkAuth = async (firebaseApp) => {
|
|
|
1537
1587
|
showError(THIRD_PARTY_SERVICES_ERRORS.GITHUB_NOT_AUTHENTICATED, true);
|
|
1538
1588
|
// Retrieve local access token.
|
|
1539
1589
|
const token = String(getLocalAccessToken());
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1590
|
+
let providerUserId;
|
|
1591
|
+
let username;
|
|
1592
|
+
const isLocalBandadaIdentityStored = checkLocalBandadaIdentity();
|
|
1593
|
+
if (isLocalBandadaIdentityStored) {
|
|
1594
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1595
|
+
providerUserId = userCredentials.user.uid;
|
|
1596
|
+
username = providerUserId;
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
// Get credentials.
|
|
1600
|
+
const credentials = exchangeGithubTokenForCredentials(token);
|
|
1601
|
+
// Sign in to Firebase using credentials.
|
|
1602
|
+
await signInToFirebase(firebaseApp, credentials);
|
|
1603
|
+
// Get Github unique identifier (handle-id).
|
|
1604
|
+
providerUserId = await getGithubProviderUserId(String(token));
|
|
1605
|
+
username = getUserHandleFromProviderUserId(providerUserId);
|
|
1606
|
+
}
|
|
1544
1607
|
// Get current authenticated user.
|
|
1545
1608
|
const user = getCurrentFirebaseAuthUser(firebaseApp);
|
|
1546
|
-
// Get Github unique identifier (handle-id).
|
|
1547
|
-
const providerUserId = await getGithubProviderUserId(String(token));
|
|
1548
1609
|
// Greet the user.
|
|
1549
|
-
console.log(`Greetings, @${theme.text.bold(
|
|
1610
|
+
console.log(`Greetings, @${theme.text.bold(username)} ${theme.emojis.wave}\n`);
|
|
1550
1611
|
return {
|
|
1551
1612
|
user,
|
|
1552
1613
|
token,
|
|
@@ -1635,7 +1696,7 @@ const handleAdditionOfCircuitsToCeremony = async (r1csOptions, wasmOptions, cere
|
|
|
1635
1696
|
wasmFilename.split(`.${commonTerms.foldersAndPathsTerms.wasm}`)[0]);
|
|
1636
1697
|
if (matchingWasms.length !== 1)
|
|
1637
1698
|
showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true);
|
|
1638
|
-
// Get input data for
|
|
1699
|
+
// Get input data for chosen circuit.
|
|
1639
1700
|
const circuitInputData = await getInputDataToAddCircuitToCeremony(choosenCircuitFilename, matchingWasms[0], ceremonyTimeoutMechanismType, sameCircomCompiler, circuitSequencePosition, sharedCircomCompilerData);
|
|
1640
1701
|
// Store circuit data.
|
|
1641
1702
|
inputDataForCircuits.push(circuitInputData);
|
|
@@ -1725,7 +1786,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
|
|
|
1725
1786
|
* number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
|
|
1726
1787
|
* to provide a number of powers manually, ranging from the smallest possible to the largest.
|
|
1727
1788
|
* @param neededPowers <number> - the smallest amount of powers needed by the zKey.
|
|
1728
|
-
* @returns Promise<string, string> - the information about the
|
|
1789
|
+
* @returns Promise<string, string> - the information about the chosen Powers of Tau file for the pre-computed zKey
|
|
1729
1790
|
* along with related powers.
|
|
1730
1791
|
*/
|
|
1731
1792
|
const handlePreComputedZkeyPowersOfTauSelection = async (neededPowers) => {
|
|
@@ -1826,7 +1887,9 @@ const setup = async (cmd) => {
|
|
|
1826
1887
|
let ceremonyId = ""; // The unique identifier of the ceremony.
|
|
1827
1888
|
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
1828
1889
|
// Check for authentication.
|
|
1829
|
-
const { user, providerUserId } = cmd.auth
|
|
1890
|
+
const { user, providerUserId } = cmd.auth
|
|
1891
|
+
? await authWithToken(firebaseApp, cmd.auth)
|
|
1892
|
+
: await checkAuth(firebaseApp);
|
|
1830
1893
|
// Preserve command execution only for coordinators.
|
|
1831
1894
|
if (!(await isCoordinator(user)))
|
|
1832
1895
|
showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
|
|
@@ -1843,7 +1906,7 @@ const setup = async (cmd) => {
|
|
|
1843
1906
|
// if there is the file option, then set up the non interactively
|
|
1844
1907
|
if (cmd.template) {
|
|
1845
1908
|
// 1. parse the file
|
|
1846
|
-
// tmp data - do not cleanup files as we need them
|
|
1909
|
+
// tmp data - do not cleanup files as we need them
|
|
1847
1910
|
const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template)} setup configuration file...`, `clock`);
|
|
1848
1911
|
spinner.start();
|
|
1849
1912
|
const setupCeremonyData = await parseCeremonyFile(cmd.template);
|
|
@@ -1853,8 +1916,6 @@ const setup = async (cmd) => {
|
|
|
1853
1916
|
// create a new bucket
|
|
1854
1917
|
const bucketName = await handleCeremonyBucketCreation(firebaseFunctions, ceremonySetupData.ceremonyPrefix);
|
|
1855
1918
|
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
1919
|
// loop through each circuit
|
|
1859
1920
|
for await (const circuit of setupCeremonyData.circuits) {
|
|
1860
1921
|
// Local paths.
|
|
@@ -1864,25 +1925,24 @@ const setup = async (cmd) => {
|
|
|
1864
1925
|
const potLocalPathAndFileName = getPotLocalFilePath(circuit.files.potFilename);
|
|
1865
1926
|
const zkeyLocalPathAndFileName = getZkeyLocalFilePath(circuit.files.initialZkeyFilename);
|
|
1866
1927
|
// 2. download the pot and wasm files
|
|
1867
|
-
const streamPipeline = promisify(pipeline);
|
|
1868
1928
|
await checkAndDownloadSmallestPowersOfTau(convertToDoubleDigits(circuit.metadata?.pot), circuit.files.potFilename);
|
|
1869
|
-
//
|
|
1870
|
-
const spinner = customSpinner(`
|
|
1929
|
+
// 3. generate the zKey
|
|
1930
|
+
const spinner = customSpinner(`Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
|
|
1871
1931
|
spinner.start();
|
|
1872
|
-
|
|
1873
|
-
|
|
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.");
|
|
1932
|
+
if (existsSync(zkeyLocalPathAndFileName)) {
|
|
1933
|
+
spinner.succeed(`The genesis zKey for circuit ${theme.text.bold(circuit.name)} is already present on disk`);
|
|
1876
1934
|
}
|
|
1877
|
-
|
|
1878
|
-
await
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1935
|
+
else {
|
|
1936
|
+
await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined);
|
|
1937
|
+
spinner.succeed(`Generation of the genesis zKey for circuit ${theme.text.bold(circuit.name)} completed successfully`);
|
|
1938
|
+
}
|
|
1939
|
+
const hashSpinner = customSpinner(`Calculating hashes for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
|
|
1940
|
+
hashSpinner.start();
|
|
1882
1941
|
// 4. calculate the hashes
|
|
1883
1942
|
const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName);
|
|
1884
1943
|
const potBlake2bHash = await blake512FromPath(getPotLocalFilePath(circuit.files.potFilename));
|
|
1885
1944
|
const initialZkeyBlake2bHash = await blake512FromPath(zkeyLocalPathAndFileName);
|
|
1945
|
+
hashSpinner.succeed(`Hashes for circuit ${theme.text.bold(circuit.name)} calculated successfully`);
|
|
1886
1946
|
// 5. upload the artifacts
|
|
1887
1947
|
// Upload zKey to Storage.
|
|
1888
1948
|
await handleCircuitArtifactUploadToStorage(firebaseFunctions, bucketName, circuit.files.initialZkeyStoragePath, zkeyLocalPathAndFileName, circuit.files.initialZkeyFilename);
|
|
@@ -1900,9 +1960,9 @@ const setup = async (cmd) => {
|
|
|
1900
1960
|
// 6 update the setup data object
|
|
1901
1961
|
ceremonySetupData.circuits[index].files = {
|
|
1902
1962
|
...circuit.files,
|
|
1903
|
-
potBlake2bHash
|
|
1904
|
-
wasmBlake2bHash
|
|
1905
|
-
initialZkeyBlake2bHash
|
|
1963
|
+
potBlake2bHash,
|
|
1964
|
+
wasmBlake2bHash,
|
|
1965
|
+
initialZkeyBlake2bHash
|
|
1906
1966
|
};
|
|
1907
1967
|
ceremonySetupData.circuits[index].zKeySizeInBytes = getFileStats(zkeyLocalPathAndFileName).size;
|
|
1908
1968
|
}
|
|
@@ -2110,16 +2170,29 @@ const expirationCountdownForGithubOAuth = (expirationInSeconds) => {
|
|
|
2110
2170
|
*/
|
|
2111
2171
|
const onVerification = async (verification) => {
|
|
2112
2172
|
// Copy code to clipboard.
|
|
2113
|
-
|
|
2114
|
-
|
|
2173
|
+
let noClipboard = false;
|
|
2174
|
+
try {
|
|
2175
|
+
clipboard.writeSync(verification.user_code);
|
|
2176
|
+
clipboard.readSync();
|
|
2177
|
+
}
|
|
2178
|
+
catch (error) {
|
|
2179
|
+
noClipboard = true;
|
|
2180
|
+
}
|
|
2115
2181
|
// 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(
|
|
2182
|
+
console.log(`${theme.symbols.warning} Visit ${theme.text.bold(theme.text.underlined(verification.verification_uri))} on this device to generate a new token and authenticate\n`);
|
|
2183
|
+
console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
|
|
2184
|
+
const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``;
|
|
2185
|
+
console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} ${message} ${theme.symbols.success}\n`);
|
|
2118
2186
|
const spinner = customSpinner(`Redirecting to Github...`, `clock`);
|
|
2119
2187
|
spinner.start();
|
|
2120
2188
|
await sleep(10000); // ~10s to make users able to read the CLI.
|
|
2121
|
-
|
|
2122
|
-
|
|
2189
|
+
try {
|
|
2190
|
+
// Automatically open the page (# Step 2).
|
|
2191
|
+
await open(verification.verification_uri);
|
|
2192
|
+
}
|
|
2193
|
+
catch (error) {
|
|
2194
|
+
console.log(`${theme.symbols.info} Please authenticate via GitHub at ${verification.verification_uri}`);
|
|
2195
|
+
}
|
|
2123
2196
|
spinner.stop();
|
|
2124
2197
|
// Countdown for time expiration.
|
|
2125
2198
|
expirationCountdownForGithubOAuth(verification.expires_in);
|
|
@@ -2190,6 +2263,100 @@ const auth = async () => {
|
|
|
2190
2263
|
terminate(providerUserId);
|
|
2191
2264
|
};
|
|
2192
2265
|
|
|
2266
|
+
const { BANDADA_API_URL } = process.env;
|
|
2267
|
+
const bandadaApi = new ApiSdk(BANDADA_API_URL);
|
|
2268
|
+
const addMemberToGroup = async (groupId, dashboardUrl, identity) => {
|
|
2269
|
+
const commitment = identity.commitment.toString();
|
|
2270
|
+
const group = await bandadaApi.getGroup(groupId);
|
|
2271
|
+
const providerName = group.credentials.id.split("_")[0].toLowerCase();
|
|
2272
|
+
// 6. open a new window with the url:
|
|
2273
|
+
const url = `${dashboardUrl}credentials?group=${groupId}&member=${commitment}&provider=${providerName}`;
|
|
2274
|
+
console.log(`${theme.text.bold(`Verification URL:`)} ${theme.text.underlined(url)}`);
|
|
2275
|
+
open(url);
|
|
2276
|
+
const { confirmation } = await askForConfirmation("Did you join the Bandada group in the browser?");
|
|
2277
|
+
if (!confirmation)
|
|
2278
|
+
showError("You must join the Bandada group to continue the login process", true);
|
|
2279
|
+
};
|
|
2280
|
+
const isGroupMember = async (groupId, identity) => {
|
|
2281
|
+
const commitment = identity.commitment.toString();
|
|
2282
|
+
const isMember = await bandadaApi.isGroupMember(groupId, commitment);
|
|
2283
|
+
return isMember;
|
|
2284
|
+
};
|
|
2285
|
+
|
|
2286
|
+
const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env;
|
|
2287
|
+
const authBandada = async () => {
|
|
2288
|
+
try {
|
|
2289
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2290
|
+
const spinner = customSpinner(`Checking identity string for Semaphore...`, `clock`);
|
|
2291
|
+
spinner.start();
|
|
2292
|
+
// 1. check if _identity string exists in local storage
|
|
2293
|
+
let identityString;
|
|
2294
|
+
const isIdentityStringStored = checkLocalBandadaIdentity();
|
|
2295
|
+
if (isIdentityStringStored) {
|
|
2296
|
+
identityString = getLocalBandadaIdentity();
|
|
2297
|
+
spinner.succeed(`Identity seed found\n`);
|
|
2298
|
+
}
|
|
2299
|
+
else {
|
|
2300
|
+
spinner.warn(`Identity seed not found\n`);
|
|
2301
|
+
// 2. generate a random _identity string and save it in local storage
|
|
2302
|
+
const { seed } = await prompts({
|
|
2303
|
+
type: "text",
|
|
2304
|
+
name: "seed",
|
|
2305
|
+
message: theme.text.bold(`Enter a secret string to use as your identity seed in Semaphore:`),
|
|
2306
|
+
initial: false
|
|
2307
|
+
});
|
|
2308
|
+
identityString = seed;
|
|
2309
|
+
setLocalBandadaIdentity(identityString);
|
|
2310
|
+
}
|
|
2311
|
+
// 3. create a semaphore identity with _identity string as a seed
|
|
2312
|
+
const identity = new Identity(identityString);
|
|
2313
|
+
// 4. check if the user is a member of the group
|
|
2314
|
+
console.log(`Checking Bandada membership...`);
|
|
2315
|
+
const isMember = await isGroupMember(BANDADA_GROUP_ID, identity);
|
|
2316
|
+
if (!isMember) {
|
|
2317
|
+
await addMemberToGroup(BANDADA_GROUP_ID, BANDADA_DASHBOARD_URL, identity);
|
|
2318
|
+
}
|
|
2319
|
+
// 5. generate a proof that the user owns the commitment.
|
|
2320
|
+
spinner.text = `Generating proof of identity...`;
|
|
2321
|
+
spinner.start();
|
|
2322
|
+
// publicSignals = [hash(externalNullifier, identityNullifier), commitment]
|
|
2323
|
+
const { proof, publicSignals } = await groth16.fullProve({
|
|
2324
|
+
identityTrapdoor: identity.trapdoor,
|
|
2325
|
+
identityNullifier: identity.nullifier,
|
|
2326
|
+
externalNullifier: BANDADA_GROUP_ID
|
|
2327
|
+
}, `${dirname(fileURLToPath(import.meta.url))}/public/mini-semaphore.wasm`, `${dirname(fileURLToPath(import.meta.url))}/public/mini-semaphore.zkey`);
|
|
2328
|
+
spinner.succeed(`Proof generated.\n`);
|
|
2329
|
+
spinner.text = `Sending proof to verification...`;
|
|
2330
|
+
spinner.start();
|
|
2331
|
+
// 6. send proof to a cloud function that verifies it and checks membership
|
|
2332
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.bandadaValidateProof);
|
|
2333
|
+
const result = await cf({
|
|
2334
|
+
proof,
|
|
2335
|
+
publicSignals
|
|
2336
|
+
});
|
|
2337
|
+
const { valid, token, message } = result.data;
|
|
2338
|
+
if (!valid) {
|
|
2339
|
+
showError(message, true);
|
|
2340
|
+
}
|
|
2341
|
+
spinner.succeed(`Proof verified.\n`);
|
|
2342
|
+
spinner.text = `Authenticating...`;
|
|
2343
|
+
spinner.start();
|
|
2344
|
+
// 7. Auth to p0tion firebase
|
|
2345
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
2346
|
+
setLocalAccessToken(token);
|
|
2347
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(userCredentials.user.uid)}.`);
|
|
2348
|
+
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2349
|
+
}
|
|
2350
|
+
catch (error) {
|
|
2351
|
+
// Delete local token.
|
|
2352
|
+
console.log("An error crashed the process. Deleting local token and identity.");
|
|
2353
|
+
console.error(error);
|
|
2354
|
+
deleteLocalAccessToken();
|
|
2355
|
+
deleteLocalBandadaIdentity();
|
|
2356
|
+
}
|
|
2357
|
+
process.exit(0);
|
|
2358
|
+
};
|
|
2359
|
+
|
|
2193
2360
|
/**
|
|
2194
2361
|
* Return the verification result for latest contribution.
|
|
2195
2362
|
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
@@ -2381,8 +2548,12 @@ const handlePublicAttestation = async (firestoreDatabase, circuits, ceremonyId,
|
|
|
2381
2548
|
// Write public attestation locally.
|
|
2382
2549
|
writeFile(getAttestationLocalFilePath(`${ceremonyPrefix}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
2383
2550
|
await sleep(1000); // workaround for file descriptor unexpected close.
|
|
2384
|
-
|
|
2385
|
-
|
|
2551
|
+
let gistUrl = "";
|
|
2552
|
+
const isBandada = checkLocalBandadaIdentity();
|
|
2553
|
+
if (!isBandada) {
|
|
2554
|
+
gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
|
|
2555
|
+
console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
2556
|
+
}
|
|
2386
2557
|
// Prepare a ready-to-share tweet.
|
|
2387
2558
|
await handleTweetGeneration(ceremonyName, gistUrl);
|
|
2388
2559
|
};
|
|
@@ -2407,7 +2578,6 @@ const listenToCeremonyCircuitDocumentChanges = (firestoreDatabase, ceremonyId, p
|
|
|
2407
2578
|
const { avgTimings, waitingQueue } = changedCircuit.data();
|
|
2408
2579
|
const { fullContribution, verifyCloudFunction } = avgTimings;
|
|
2409
2580
|
const { currentContributor } = waitingQueue;
|
|
2410
|
-
// Get circuit current contributor participant document.
|
|
2411
2581
|
const circuitCurrentContributor = await getDocumentById(firestoreDatabase, getParticipantsCollectionPath(ceremonyId), currentContributor);
|
|
2412
2582
|
// Check data.
|
|
2413
2583
|
if (!circuitCurrentContributor.data())
|
|
@@ -2566,8 +2736,8 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2566
2736
|
// Communicate resume / start of the contribution to participant.
|
|
2567
2737
|
await simpleLoader(`${changedContributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */ ? `Starting` : `Resuming`} your contribution...`, `clock`, 3000);
|
|
2568
2738
|
// Start / Resume the contribution for the participant.
|
|
2569
|
-
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false // not finalizing.
|
|
2570
|
-
);
|
|
2739
|
+
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false, // not finalizing.
|
|
2740
|
+
circuits.length);
|
|
2571
2741
|
}
|
|
2572
2742
|
// Scenario (3.A).
|
|
2573
2743
|
else if (isWaitingForContribution)
|
|
@@ -2616,7 +2786,9 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2616
2786
|
// Get latest contribution verification result.
|
|
2617
2787
|
await getLatestVerificationResult(firestoreDatabase, ceremony.id, circuit.id, participant.id);
|
|
2618
2788
|
// Get next circuit for contribution.
|
|
2619
|
-
const nextCircuit =
|
|
2789
|
+
const nextCircuit = timeoutExpired
|
|
2790
|
+
? getCircuitBySequencePosition(circuits, changedContributionProgress)
|
|
2791
|
+
: getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
|
|
2620
2792
|
// Check disk space requirements for participant.
|
|
2621
2793
|
const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(cloudFunctions, ceremony.id, nextCircuit.data.sequencePosition, nextCircuit.data.zKeySizeInBytes, timeoutExpired, providerUserId);
|
|
2622
2794
|
// Check if the participant would like to generate a new attestation.
|
|
@@ -2654,11 +2826,12 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2654
2826
|
*/
|
|
2655
2827
|
const contribute = async (opt) => {
|
|
2656
2828
|
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
2657
|
-
// Check for authentication.
|
|
2658
|
-
const { user, providerUserId, token } = await checkAuth(firebaseApp);
|
|
2659
2829
|
// Get options.
|
|
2660
2830
|
const ceremonyOpt = opt.ceremony;
|
|
2661
2831
|
const entropyOpt = opt.entropy;
|
|
2832
|
+
const { auth } = opt;
|
|
2833
|
+
// Check for authentication.
|
|
2834
|
+
const { user, providerUserId, token } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
|
|
2662
2835
|
// Prepare data.
|
|
2663
2836
|
let selectedCeremony;
|
|
2664
2837
|
// Retrieve the opened ceremonies.
|
|
@@ -2694,7 +2867,7 @@ const contribute = async (opt) => {
|
|
|
2694
2867
|
const userDoc = await getDocumentById(firestoreDatabase, commonTerms.collections.users.name, user.uid);
|
|
2695
2868
|
const userData = userDoc.data();
|
|
2696
2869
|
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
|
|
2870
|
+
spinner.fail(`Unfortunately we could not find a user document with your information. This likely means that you did not pass the GitHub reputation checks and therefore are not eligible to contribute to any ceremony. If you believe you pass the requirements, it might be possible that your profile is private and we were not able to fetch your real statistics, in this case please consider making your profile public for the duration of the contribution. Please contact the coordinator if you believe this to be an error.`);
|
|
2698
2871
|
process.exit(0);
|
|
2699
2872
|
}
|
|
2700
2873
|
// Check the user's current participant readiness for contribution status (eligible, already contributed, timed out).
|
|
@@ -2845,7 +3018,7 @@ const observe = async () => {
|
|
|
2845
3018
|
// Preserve command execution only for coordinators].
|
|
2846
3019
|
if (!(await isCoordinator(user)))
|
|
2847
3020
|
showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
|
|
2848
|
-
// Get running
|
|
3021
|
+
// Get running ceremonies info (if any).
|
|
2849
3022
|
const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase);
|
|
2850
3023
|
// Ask to select a ceremony.
|
|
2851
3024
|
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false);
|
|
@@ -2894,7 +3067,7 @@ const handleVerificationKey = async (cloudFunctions, bucketName, finalZkeyLocalF
|
|
|
2894
3067
|
spinner.text = "Writing verification key...";
|
|
2895
3068
|
// Write the verification key locally.
|
|
2896
3069
|
writeLocalJsonFile(verificationKeyLocalFilePath, vKey);
|
|
2897
|
-
await sleep(3000); //
|
|
3070
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2898
3071
|
// Upload verification key to storage.
|
|
2899
3072
|
await multiPartUpload(cloudFunctions, bucketName, verificationKeyStorageFilePath, verificationKeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2900
3073
|
spinner.succeed(`Verification key correctly saved on storage`);
|
|
@@ -2914,13 +3087,13 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2914
3087
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}`;
|
|
2915
3088
|
const verifierPath = packagePath.includes(`src/commands`)
|
|
2916
3089
|
? `${dirname(fileURLToPath(import.meta.url))}/../../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
|
|
2917
|
-
: `${dirname(fileURLToPath(import.meta.url))}
|
|
3090
|
+
: `${dirname(fileURLToPath(import.meta.url))}/../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
|
|
2918
3091
|
// Export the Solidity verifier smart contract.
|
|
2919
3092
|
const verifierCode = await exportVerifierContract(finalZkeyLocalFilePath, verifierPath);
|
|
2920
3093
|
spinner.text = `Writing verifier smart contract...`;
|
|
2921
3094
|
// Write the verification key locally.
|
|
2922
3095
|
writeFile(verifierContractLocalFilePath, verifierCode);
|
|
2923
|
-
await sleep(3000); //
|
|
3096
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2924
3097
|
// Upload verifier smart contract to storage.
|
|
2925
3098
|
await multiPartUpload(cloudFunctions, bucketName, verifierContractStorageFilePath, verifierContractLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2926
3099
|
spinner.succeed(`Verifier smart contract correctly saved on storage`);
|
|
@@ -2941,11 +3114,12 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2941
3114
|
* @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
|
|
2942
3115
|
* @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
|
|
2943
3116
|
* @param coordinatorIdentifier <string> - the identifier of the coordinator.
|
|
3117
|
+
* @param circuitsLength <number> - the number of circuits in the ceremony.
|
|
2944
3118
|
*/
|
|
2945
|
-
const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier) => {
|
|
3119
|
+
const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier, circuitsLength) => {
|
|
2946
3120
|
// Step (1).
|
|
2947
|
-
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true);
|
|
2948
|
-
await sleep(2000); //
|
|
3121
|
+
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength);
|
|
3122
|
+
await sleep(2000); // workaround for descriptors.
|
|
2949
3123
|
// Extract data.
|
|
2950
3124
|
const { prefix: circuitPrefix } = circuit.data;
|
|
2951
3125
|
const { prefix: ceremonyPrefix } = ceremony.data;
|
|
@@ -2979,10 +3153,11 @@ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, cere
|
|
|
2979
3153
|
* @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
|
|
2980
3154
|
* handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
|
|
2981
3155
|
*/
|
|
2982
|
-
const finalize = async () => {
|
|
3156
|
+
const finalize = async (opt) => {
|
|
2983
3157
|
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
2984
3158
|
// Check for authentication.
|
|
2985
|
-
const {
|
|
3159
|
+
const { auth } = opt;
|
|
3160
|
+
const { user, providerUserId, token: coordinatorAccessToken } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
|
|
2986
3161
|
// Preserve command execution only for coordinators.
|
|
2987
3162
|
if (!(await isCoordinator(user)))
|
|
2988
3163
|
showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
|
|
@@ -3017,7 +3192,7 @@ const finalize = async () => {
|
|
|
3017
3192
|
const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
|
|
3018
3193
|
// Handle finalization for each ceremony circuit.
|
|
3019
3194
|
for await (const circuit of circuits)
|
|
3020
|
-
await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId);
|
|
3195
|
+
await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId, circuits.length);
|
|
3021
3196
|
process.stdout.write(`\n`);
|
|
3022
3197
|
const spinner = customSpinner(`Wrapping up the finalization of the ceremony...`, "clock");
|
|
3023
3198
|
spinner.start();
|
|
@@ -3032,7 +3207,7 @@ const finalize = async () => {
|
|
|
3032
3207
|
// Generate attestation with final contributions.
|
|
3033
3208
|
const publicAttestation = await generateValidContributionsAttestation(firestoreDatabase, circuits, selectedCeremony.id, participant.id, contributions, providerUserId, ceremonyName, true);
|
|
3034
3209
|
// Write public attestation locally.
|
|
3035
|
-
writeFile(
|
|
3210
|
+
writeFile(getFinalAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
3036
3211
|
await sleep(3000); // workaround for file descriptor unexpected close.
|
|
3037
3212
|
const gistUrl = await publishGist(coordinatorAccessToken, publicAttestation, ceremonyName, prefix);
|
|
3038
3213
|
console.log(`\n${theme.symbols.info} Your public final attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
@@ -3094,6 +3269,7 @@ const logout = async () => {
|
|
|
3094
3269
|
await signOut(auth);
|
|
3095
3270
|
// Delete local token.
|
|
3096
3271
|
deleteLocalAccessToken();
|
|
3272
|
+
deleteLocalBandadaIdentity();
|
|
3097
3273
|
await sleep(3000); // ~3s.
|
|
3098
3274
|
spinner.stop();
|
|
3099
3275
|
console.log(`${theme.symbols.success} Logout successfully completed`);
|
|
@@ -3155,6 +3331,37 @@ const listCeremonies = async () => {
|
|
|
3155
3331
|
}
|
|
3156
3332
|
};
|
|
3157
3333
|
|
|
3334
|
+
const listParticipants = async () => {
|
|
3335
|
+
try {
|
|
3336
|
+
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
3337
|
+
const allCeremonies = await getAllCeremonies(firestoreDatabase);
|
|
3338
|
+
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true);
|
|
3339
|
+
const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id);
|
|
3340
|
+
const participantsRef = collection(docRef, "participants");
|
|
3341
|
+
const participantsSnapshot = await getDocs(participantsRef);
|
|
3342
|
+
const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data().userId);
|
|
3343
|
+
console.log(participants);
|
|
3344
|
+
/* const usersRef = collection(firestoreDatabase, "users")
|
|
3345
|
+
const usersSnapshot = await getDocs(usersRef)
|
|
3346
|
+
const users = usersSnapshot.docs.map((userDoc) => userDoc.data())
|
|
3347
|
+
console.log(users) */
|
|
3348
|
+
}
|
|
3349
|
+
catch (err) {
|
|
3350
|
+
showError(`Something went wrong: ${err.toString()}`, true);
|
|
3351
|
+
}
|
|
3352
|
+
process.exit(0);
|
|
3353
|
+
};
|
|
3354
|
+
|
|
3355
|
+
const setCeremonyCommands = (program) => {
|
|
3356
|
+
const ceremony = program.command("ceremony").description("manage ceremonies");
|
|
3357
|
+
ceremony
|
|
3358
|
+
.command("participants")
|
|
3359
|
+
.description("retrieve participants list of a ceremony")
|
|
3360
|
+
.requiredOption("-c, --ceremony <string>", "the prefix of the ceremony you want to retrieve information about", "")
|
|
3361
|
+
.action(listParticipants);
|
|
3362
|
+
return ceremony;
|
|
3363
|
+
};
|
|
3364
|
+
|
|
3158
3365
|
// Get pkg info (e.g., name, version).
|
|
3159
3366
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
3160
3367
|
const { description, version, name } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
|
|
@@ -3163,44 +3370,48 @@ const program = createCommand();
|
|
|
3163
3370
|
program.name(name).description(description).version(version);
|
|
3164
3371
|
// User commands.
|
|
3165
3372
|
program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth);
|
|
3373
|
+
program
|
|
3374
|
+
.command("auth-bandada")
|
|
3375
|
+
.description("authenticate yourself in a privacy-perserving manner using Bandada")
|
|
3376
|
+
.action(authBandada);
|
|
3166
3377
|
program
|
|
3167
3378
|
.command("contribute")
|
|
3168
3379
|
.description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
|
|
3169
3380
|
.option("-c, --ceremony <string>", "the prefix of the ceremony you want to contribute for", "")
|
|
3170
3381
|
.option("-e, --entropy <string>", "the entropy (aka toxic waste) of your contribution", "")
|
|
3382
|
+
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
3171
3383
|
.action(contribute);
|
|
3172
3384
|
program
|
|
3173
3385
|
.command("clean")
|
|
3174
3386
|
.description("clean up output generated by commands from the current working directory")
|
|
3175
3387
|
.action(clean);
|
|
3176
|
-
program
|
|
3177
|
-
.command("list")
|
|
3178
|
-
.description("List all ceremonies prefixes")
|
|
3179
|
-
.action(listCeremonies);
|
|
3388
|
+
program.command("list").description("List all ceremonies prefixes").action(listCeremonies);
|
|
3180
3389
|
program
|
|
3181
3390
|
.command("logout")
|
|
3182
3391
|
.description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
|
|
3183
3392
|
.action(logout);
|
|
3184
3393
|
program
|
|
3185
3394
|
.command("validate")
|
|
3186
|
-
.description("
|
|
3395
|
+
.description("validate that a Ceremony Setup file is correct")
|
|
3187
3396
|
.requiredOption("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3188
3397
|
.option("-c, --constraints <number>", "The number of constraints to check against")
|
|
3189
3398
|
.action(validate);
|
|
3190
3399
|
// Only coordinator commands.
|
|
3191
|
-
const
|
|
3192
|
-
|
|
3400
|
+
const coordinate = program.command("coordinate").description("commands for coordinating a ceremony");
|
|
3401
|
+
coordinate
|
|
3193
3402
|
.command("setup")
|
|
3194
3403
|
.description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
|
|
3195
|
-
.option(
|
|
3196
|
-
.option(
|
|
3404
|
+
.option("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3405
|
+
.option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
|
|
3197
3406
|
.action(setup);
|
|
3198
|
-
|
|
3407
|
+
coordinate
|
|
3199
3408
|
.command("observe")
|
|
3200
3409
|
.description("observe in real-time the waiting queue of each ceremony circuit")
|
|
3201
3410
|
.action(observe);
|
|
3202
|
-
|
|
3411
|
+
coordinate
|
|
3203
3412
|
.command("finalize")
|
|
3204
3413
|
.description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
|
|
3414
|
+
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
3205
3415
|
.action(finalize);
|
|
3416
|
+
setCeremonyCommands(program);
|
|
3206
3417
|
program.parseAsync(process.argv);
|