@devtion/devcli 0.0.0-9c50f66 → 0.0.0-a7b749e
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 +1 -1
- package/dist/.env +55 -0
- package/dist/index.js +549 -120
- package/dist/public/mini-semaphore.wasm +0 -0
- package/dist/public/mini-semaphore.zkey +0 -0
- package/dist/types/commands/authBandada.d.ts +2 -0
- package/dist/types/commands/authSIWE.d.ts +7 -0
- package/dist/types/commands/ceremony/index.d.ts +3 -0
- package/dist/types/commands/ceremony/listParticipants.d.ts +2 -0
- package/dist/types/commands/contribute.d.ts +1 -1
- package/dist/types/commands/finalize.d.ts +4 -3
- package/dist/types/commands/index.d.ts +2 -0
- package/dist/types/commands/observe.d.ts +1 -1
- package/dist/types/commands/setup.d.ts +4 -4
- 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 +38 -0
- package/dist/types/lib/prompts.d.ts +7 -7
- package/dist/types/lib/utils.d.ts +3 -2
- package/dist/types/types/index.d.ts +69 -0
- package/package.json +10 -3
- package/src/commands/auth.ts +25 -9
- package/src/commands/authBandada.ts +120 -0
- package/src/commands/authSIWE.ts +185 -0
- package/src/commands/ceremony/index.ts +20 -0
- package/src/commands/ceremony/listParticipants.ts +56 -0
- package/src/commands/contribute.ts +56 -29
- package/src/commands/finalize.ts +27 -14
- package/src/commands/index.ts +3 -1
- package/src/commands/listCeremonies.ts +2 -3
- package/src/commands/logout.ts +3 -1
- package/src/commands/observe.ts +8 -4
- package/src/commands/setup.ts +59 -48
- package/src/commands/validate.ts +2 -3
- package/src/index.ts +35 -13
- package/src/lib/bandada.ts +51 -0
- package/src/lib/errors.ts +2 -2
- package/src/lib/localConfigs.ts +55 -1
- package/src/lib/prompts.ts +23 -26
- package/src/lib/services.ts +39 -16
- package/src/lib/utils.ts +49 -13
- package/src/types/index.ts +75 -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.2.5
|
|
6
6
|
* @file All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies
|
|
7
7
|
* @copyright Ethereum Foundation 2022
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @see [Github]{@link https://github.com/privacy-scaling-explorations/p0tion}
|
|
10
10
|
*/
|
|
11
11
|
import { createCommand } from 'commander';
|
|
12
|
-
import fs, { readFileSync, createWriteStream, renameSync } from 'fs';
|
|
13
|
-
import { dirname } from 'path';
|
|
12
|
+
import fs, { readFileSync, createWriteStream, existsSync, renameSync } from 'fs';
|
|
13
|
+
import path, { dirname } from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import { zKey } from 'snarkjs';
|
|
15
|
+
import { zKey, groth16 } from 'snarkjs';
|
|
16
16
|
import boxen from 'boxen';
|
|
17
17
|
import { pipeline } from 'node:stream';
|
|
18
18
|
import { promisify } from 'node:util';
|
|
19
19
|
import fetch$1 from 'node-fetch';
|
|
20
|
-
import {
|
|
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, contribHashRegex, permanentlyStoreCurrentContributionTimeAndHash, convertToDoubleDigits, multiPartUpload, verifyContribution, generateGetObjectPreSignedUrl, convertBytesOrKbToGb, numExpIterations, getDocumentById, getParticipantsCollectionPath, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs, extractPrefix, autoGenerateEntropy, vmConfigurationTypes, initializeFirebaseCoreServices, signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator, parseCeremonyFile, blake512FromPath, checkIfObjectExist, setupCeremony, genesisZkeyIndex, getR1csStorageFilePath, getWasmStorageFilePath, getPotStorageFilePath, extractPoTFromFilename, potFileDownloadMainUrl, createS3Bucket, potFilenameTemplate, getR1CSInfo, getOpenedCeremonies, getCeremonyCircuits, checkParticipantForCeremony, getCurrentActiveParticipantTimeout, getCircuitBySequencePosition, getCircuitContributionsFromContributor, progressToNextCircuitForContribution, resumeContributionAfterTimeoutExpiration, generateValidContributionsAttestation, getContributionsValidityForContributor, getClosedCeremonies, checkAndPrepareCoordinatorForFinalization, computeSHA256ToHex, finalizeCeremony, getVerificationKeyStorageFilePath, verificationKeyAcronym, getVerifierContractStorageFilePath, verifierSmartContractAcronym, finalizeCircuit, exportVkey, exportVerifierContract, getAllCeremonies } from '@devtion/actions';
|
|
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.`,
|
|
@@ -121,7 +122,7 @@ const COMMAND_ERRORS = {
|
|
|
121
122
|
COMMAND_SETUP_NO_R1CS: `Unable to retrieve R1CS files from current working directory. Please, run this command from a working directory where the R1CS files are located to continue with the setup process. We kindly ask you to run the command from an empty directory containing only the R1CS and WASM files.`,
|
|
122
123
|
COMMAND_SETUP_NO_WASM: `Unable to retrieve WASM files from current working directory. Please, run this command from a working directory where the WASM files are located to continue with the setup process. We kindly ask you to run the command from an empty directory containing only the WASM and R1CS files.`,
|
|
123
124
|
COMMAND_SETUP_MISMATCH_R1CS_WASM: `The folder contains more R1CS files than WASM files (or vice versa). Please, run this command from a working directory where each R1CS is paired with its corresponding file WASM.`,
|
|
124
|
-
COMMAND_SETUP_DOWNLOAD_PTAU: `Unable to download Powers of Tau file from
|
|
125
|
+
COMMAND_SETUP_DOWNLOAD_PTAU: `Unable to download Powers of Tau file from PPoT Phase 1 Trusted Setup. Possible causes may involve an error while making the request (be sure to have a stable internet connection). Please, we kindly ask you to terminate the current session and repeat the process.`,
|
|
125
126
|
COMMAND_SETUP_ABORT: `You chose to abort the setup process.`,
|
|
126
127
|
COMMAND_CONTRIBUTE_NO_OPENED_CEREMONIES: `Unfortunately, there is no ceremony for which you can make a contribution at this time. Please, try again later.`,
|
|
127
128
|
COMMAND_CONTRIBUTE_NO_PARTICIPANT_DATA: `Unable to retrieve your data as ceremony participant. Please, terminate the current session and try again later. If the error persists, please contact the ceremony coordinator.`,
|
|
@@ -237,6 +238,14 @@ const checkAndMakeNewDirectoryIfNonexistent = (directoryLocalPath) => {
|
|
|
237
238
|
const writeLocalJsonFile = (filePath, data) => {
|
|
238
239
|
fs.writeFileSync(filePath, JSON.stringify(data), "utf-8");
|
|
239
240
|
};
|
|
241
|
+
/**
|
|
242
|
+
* Return the local current project directory name.
|
|
243
|
+
* @returns <string> - the local project (e.g., dist/) directory name.
|
|
244
|
+
*/
|
|
245
|
+
const getLocalDirname = () => {
|
|
246
|
+
const filename = fileURLToPath(import.meta.url);
|
|
247
|
+
return path.dirname(filename);
|
|
248
|
+
};
|
|
240
249
|
|
|
241
250
|
// Get npm package name.
|
|
242
251
|
const packagePath$4 = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
@@ -252,6 +261,14 @@ const config = new Conf({
|
|
|
252
261
|
accessToken: {
|
|
253
262
|
type: "string",
|
|
254
263
|
default: ""
|
|
264
|
+
},
|
|
265
|
+
bandadaIdentity: {
|
|
266
|
+
type: "string",
|
|
267
|
+
default: ""
|
|
268
|
+
},
|
|
269
|
+
authMethod: {
|
|
270
|
+
type: "string",
|
|
271
|
+
default: ""
|
|
255
272
|
}
|
|
256
273
|
}
|
|
257
274
|
});
|
|
@@ -312,6 +329,39 @@ const setLocalAccessToken = (token) => config.set("accessToken", token);
|
|
|
312
329
|
* Delete the stored access token.
|
|
313
330
|
*/
|
|
314
331
|
const deleteLocalAccessToken = () => config.delete("accessToken");
|
|
332
|
+
/**
|
|
333
|
+
* Return the Bandada identity, if present.
|
|
334
|
+
* @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
|
|
335
|
+
*/
|
|
336
|
+
const getLocalBandadaIdentity = () => config.get("bandadaIdentity");
|
|
337
|
+
/**
|
|
338
|
+
* Check if the Bandada identity exists in the local storage.
|
|
339
|
+
* @returns <boolean>
|
|
340
|
+
*/
|
|
341
|
+
const checkLocalBandadaIdentity = () => config.has("bandadaIdentity") && !!config.get("bandadaIdentity");
|
|
342
|
+
/**
|
|
343
|
+
* Set the Bandada identity.
|
|
344
|
+
* @param identity <string> - the Bandada identity to be stored.
|
|
345
|
+
*/
|
|
346
|
+
const setLocalBandadaIdentity = (identity) => config.set("bandadaIdentity", identity);
|
|
347
|
+
/**
|
|
348
|
+
* Delete the stored Bandada identity.
|
|
349
|
+
*/
|
|
350
|
+
const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity");
|
|
351
|
+
/**
|
|
352
|
+
* Return the authentication method, if present.
|
|
353
|
+
* @returns <string | undefined> - the authentication method if present, otherwise undefined.
|
|
354
|
+
*/
|
|
355
|
+
const getLocalAuthMethod = () => config.get("authMethod");
|
|
356
|
+
/**
|
|
357
|
+
* Set the authentication method.
|
|
358
|
+
* @param method <string> - the authentication method to be stored.
|
|
359
|
+
*/
|
|
360
|
+
const setLocalAuthMethod = (method) => config.set("authMethod", method);
|
|
361
|
+
/**
|
|
362
|
+
* Delete the stored authentication method.
|
|
363
|
+
*/
|
|
364
|
+
const deleteLocalAuthMethod = () => config.delete("authMethod");
|
|
315
365
|
/**
|
|
316
366
|
* Get the complete local file path.
|
|
317
367
|
* @param cwd <string> - the current working directory path.
|
|
@@ -367,6 +417,12 @@ const getVerificationKeyLocalFilePath = (completeFilename) => `${verificationKey
|
|
|
367
417
|
* @returns <string> - the complete final verifier contract path to the file.
|
|
368
418
|
*/
|
|
369
419
|
const getVerifierContractLocalFilePath = (completeFilename) => `${verifierContractsLocalFolderPath}/${completeFilename}`;
|
|
420
|
+
/**
|
|
421
|
+
* Get the complete final attestation file path.
|
|
422
|
+
* @param completeFilename <string> - the complete filename of the file (name.ext).
|
|
423
|
+
* @returns <string> - the complete final final attestation path to the file.
|
|
424
|
+
*/
|
|
425
|
+
const getFinalAttestationLocalFilePath = (completeFilename) => `${finalAttestationsLocalFolderPath}/${completeFilename}`;
|
|
370
426
|
/**
|
|
371
427
|
* Get the final transcript file path.
|
|
372
428
|
* @param completeFilename <string> - the complete filename of the file (name.ext).
|
|
@@ -416,7 +472,7 @@ const getGithubAuthenticatedUserGists = async (githubToken, params) => {
|
|
|
416
472
|
headers: {
|
|
417
473
|
authorization: `token ${githubToken}`
|
|
418
474
|
},
|
|
419
|
-
per_page: params.perPage,
|
|
475
|
+
per_page: params.perPage, // max items per page = 100.
|
|
420
476
|
page: params.page
|
|
421
477
|
});
|
|
422
478
|
if (response && response.status === 200)
|
|
@@ -464,9 +520,10 @@ const getPublicAttestationGist = async (githubToken, publicAttestationFilename)
|
|
|
464
520
|
* @returns <string> - the third-party provider handle of the user.
|
|
465
521
|
*/
|
|
466
522
|
const getUserHandleFromProviderUserId = (providerUserId) => {
|
|
467
|
-
if (providerUserId.indexOf("-") === -1)
|
|
468
|
-
|
|
469
|
-
|
|
523
|
+
if (providerUserId.indexOf("-") === -1) {
|
|
524
|
+
return providerUserId;
|
|
525
|
+
}
|
|
526
|
+
return providerUserId.substring(0, providerUserId.lastIndexOf("-"));
|
|
470
527
|
};
|
|
471
528
|
/**
|
|
472
529
|
* Return a custom spinner.
|
|
@@ -581,8 +638,18 @@ const publishGist = async (token, content, ceremonyTitle, ceremonyPrefix) => {
|
|
|
581
638
|
* @returns <string> - the ready to share tweet url.
|
|
582
639
|
*/
|
|
583
640
|
const generateCustomUrlToTweetAboutParticipation = (ceremonyName, gistUrl, isFinalizing) => isFinalizing
|
|
584
|
-
? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}
|
|
585
|
-
|
|
641
|
+
? `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
|
|
642
|
+
ceremonyName.toLowerCase().includes("setup") ||
|
|
643
|
+
ceremonyName.toLowerCase().includes("phase2") ||
|
|
644
|
+
ceremonyName.toLowerCase().includes("ceremony")
|
|
645
|
+
? "!"
|
|
646
|
+
: "%20Phase%202%20Trusted%20Setup%20ceremony!"}%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
|
|
647
|
+
: `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyName}${ceremonyName.toLowerCase().includes("trusted") ||
|
|
648
|
+
ceremonyName.toLowerCase().includes("setup") ||
|
|
649
|
+
ceremonyName.toLowerCase().includes("phase2") ||
|
|
650
|
+
ceremonyName.toLowerCase().includes("ceremony")
|
|
651
|
+
? "!"
|
|
652
|
+
: "%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
653
|
/**
|
|
587
654
|
* Return a custom progress bar.
|
|
588
655
|
* @param type <ProgressBarType> - the type of the progress bar.
|
|
@@ -710,13 +777,14 @@ const getLatestUpdatesFromParticipant = async (firestoreDatabase, ceremonyId, pa
|
|
|
710
777
|
* @param entropyOrBeaconHash <string> - the entropy or beacon hash (only when finalizing) for the contribution.
|
|
711
778
|
* @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
|
|
712
779
|
* @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
|
|
780
|
+
* @param circuitsLength <number> - the total number of circuits in the ceremony.
|
|
713
781
|
*/
|
|
714
|
-
const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing) => {
|
|
782
|
+
const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropyOrBeaconHash, contributorOrCoordinatorIdentifier, isFinalizing, circuitsLength) => {
|
|
715
783
|
// Extract data.
|
|
716
784
|
const { prefix: ceremonyPrefix } = ceremony.data;
|
|
717
785
|
const { waitingQueue, avgTimings, prefix: circuitPrefix, sequencePosition } = circuit.data;
|
|
718
786
|
const { completedContributions } = waitingQueue; // = current progress.
|
|
719
|
-
console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}`)}`)} (Contribution Steps)`);
|
|
787
|
+
console.log(`${theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${sequencePosition}/${circuitsLength}`)}`)} (Contribution Steps)`);
|
|
720
788
|
// Get most up-to-date data from the participant document.
|
|
721
789
|
let participantData = await getLatestUpdatesFromParticipant(firestoreDatabase, ceremony.id, participant.id);
|
|
722
790
|
const spinner = customSpinner(`${participantData.contributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */
|
|
@@ -762,6 +830,7 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
762
830
|
// Download the latest contribution from bucket.
|
|
763
831
|
await downloadCeremonyArtifact(cloudFunctions, bucketName, lastZkeyStorageFilePath, lastZkeyLocalFilePath);
|
|
764
832
|
console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${lastZkeyIndex}`)} correctly downloaded`);
|
|
833
|
+
await sleep(3000);
|
|
765
834
|
// Advance to next contribution step (COMPUTING) if not finalizing.
|
|
766
835
|
if (!isFinalizing) {
|
|
767
836
|
spinner.text = `Preparing for contribution computation...`;
|
|
@@ -784,16 +853,19 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
784
853
|
spinner.start();
|
|
785
854
|
// Read local transcript file info to get the contribution hash.
|
|
786
855
|
const transcriptContents = readFile(transcriptLocalFilePath);
|
|
787
|
-
const matchContributionHash = transcriptContents.match(
|
|
856
|
+
const matchContributionHash = transcriptContents.match(contribHashRegex);
|
|
788
857
|
if (!matchContributionHash)
|
|
789
858
|
showError(COMMAND_ERRORS.COMMAND_CONTRIBUTE_FINALIZE_NO_TRANSCRIPT_CONTRIBUTION_HASH_MATCH, true);
|
|
790
859
|
// Format contribution hash.
|
|
791
860
|
const contributionHash = matchContributionHash?.at(0)?.replace("\n\t\t", "");
|
|
861
|
+
await sleep(500);
|
|
792
862
|
// Make request to cloud functions to permanently store the information.
|
|
793
863
|
await permanentlyStoreCurrentContributionTimeAndHash(cloudFunctions, ceremony.id, computingTime, contributionHash);
|
|
794
864
|
// Format computing time.
|
|
795
865
|
const { seconds: computationSeconds, minutes: computationMinutes, hours: computationHours } = getSecondsMinutesHoursFromMillis(computingTime);
|
|
796
866
|
spinner.succeed(`${isFinalizing ? "Contribution" : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} computation took ${theme.text.bold(`${convertToDoubleDigits(computationHours)}:${convertToDoubleDigits(computationMinutes)}:${convertToDoubleDigits(computationSeconds)}`)}`);
|
|
867
|
+
// ensure the previous step is completed
|
|
868
|
+
await sleep(5000);
|
|
797
869
|
// Advance to next contribution step (UPLOADING) if not finalizing.
|
|
798
870
|
if (!isFinalizing) {
|
|
799
871
|
spinner.text = `Preparing for uploading the contribution...`;
|
|
@@ -809,12 +881,17 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
809
881
|
console.log(`${theme.symbols.success} Contribution ${theme.text.bold(`#${nextZkeyIndex}`)} already computed`);
|
|
810
882
|
// Contribution step = UPLOADING.
|
|
811
883
|
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
|
|
884
|
+
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
885
|
spinner.start();
|
|
814
|
-
|
|
815
|
-
|
|
886
|
+
const progressBar = customProgressBar(ProgressBarType.UPLOAD, `your contribution`);
|
|
887
|
+
if (!isFinalizing) {
|
|
888
|
+
await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB), ceremony.id, participantData.tempContributionData, progressBar);
|
|
889
|
+
progressBar.stop();
|
|
890
|
+
}
|
|
816
891
|
else
|
|
817
892
|
await multiPartUpload(cloudFunctions, bucketName, nextZkeyStorageFilePath, nextZkeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
893
|
+
// small sleep to ensure the previous step is completed
|
|
894
|
+
await sleep(5000);
|
|
818
895
|
spinner.succeed(`${isFinalizing ? `Contribution` : `Contribution ${theme.text.bold(`#${nextZkeyIndex}`)}`} correctly saved to storage`);
|
|
819
896
|
// Advance to next contribution step (VERIFYING) if not finalizing.
|
|
820
897
|
if (!isFinalizing) {
|
|
@@ -992,7 +1069,7 @@ const promptCircomCompiler = async () => {
|
|
|
992
1069
|
* Shows a list of circuits for a single option selection.
|
|
993
1070
|
* @dev the circuit names are derived from local R1CS files.
|
|
994
1071
|
* @param options <Array<string>> - an array of circuits names.
|
|
995
|
-
* @returns Promise<string> - the name of the
|
|
1072
|
+
* @returns Promise<string> - the name of the chosen circuit.
|
|
996
1073
|
*/
|
|
997
1074
|
const promptCircuitSelector = async (options) => {
|
|
998
1075
|
const { circuitFilename } = await prompts({
|
|
@@ -1010,7 +1087,7 @@ const promptCircuitSelector = async (options) => {
|
|
|
1010
1087
|
* Shows a list of standard EC2 VM instance types for a single option selection.
|
|
1011
1088
|
* @notice the suggested VM configuration type is calculated based on circuit constraint size.
|
|
1012
1089
|
* @param constraintSize <number> - the amount of circuit constraints
|
|
1013
|
-
* @returns Promise<string> - the name of the
|
|
1090
|
+
* @returns Promise<string> - the name of the chosen VM type.
|
|
1014
1091
|
*/
|
|
1015
1092
|
const promptVMTypeSelector = async (constraintSize) => {
|
|
1016
1093
|
let suggestedConfiguration = 0;
|
|
@@ -1107,7 +1184,7 @@ const promptVMDiskTypeSelector = async () => {
|
|
|
1107
1184
|
/**
|
|
1108
1185
|
* Show a series of questions about the circuits.
|
|
1109
1186
|
* @param constraintSize <number> - the amount of circuit constraints.
|
|
1110
|
-
* @param timeoutMechanismType <CeremonyTimeoutType> - the
|
|
1187
|
+
* @param timeoutMechanismType <CeremonyTimeoutType> - the chosen timeout mechanism type for the ceremony.
|
|
1111
1188
|
* @param needPromptCircomCompiler <boolean> - a boolean value indicating if the questions related to the Circom compiler version and commit hash must be asked.
|
|
1112
1189
|
* @param enforceVM <boolean> - a boolean value indicating if the contribution verification could be supported by VM-only approach or not.
|
|
1113
1190
|
* @returns Promise<Array<Circuit>> - circuit info prompted by the coordinator.
|
|
@@ -1120,7 +1197,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1120
1197
|
let circomVersion = "";
|
|
1121
1198
|
let circomCommitHash = "";
|
|
1122
1199
|
let circuitInputData;
|
|
1123
|
-
let
|
|
1200
|
+
let cfOrVm;
|
|
1124
1201
|
let vmDiskType;
|
|
1125
1202
|
let vmConfigurationType = "";
|
|
1126
1203
|
const questions = [
|
|
@@ -1175,18 +1252,21 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1175
1252
|
circomVersion = version;
|
|
1176
1253
|
circomCommitHash = commitHash;
|
|
1177
1254
|
}
|
|
1178
|
-
// Ask for
|
|
1255
|
+
// Ask for preferred contribution verification method (CF vs VM).
|
|
1179
1256
|
if (!enforceVM) {
|
|
1180
1257
|
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
1258
|
`VM` // eq. false.
|
|
1182
1259
|
);
|
|
1183
|
-
|
|
1260
|
+
cfOrVm = confirmation
|
|
1261
|
+
? "CF" /* CircuitContributionVerificationMechanism.CF */
|
|
1262
|
+
: "VM" /* CircuitContributionVerificationMechanism.VM */;
|
|
1184
1263
|
}
|
|
1185
|
-
else
|
|
1186
|
-
|
|
1187
|
-
|
|
1264
|
+
else {
|
|
1265
|
+
cfOrVm = "VM" /* CircuitContributionVerificationMechanism.VM */;
|
|
1266
|
+
}
|
|
1267
|
+
if (cfOrVm === undefined)
|
|
1188
1268
|
showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
|
|
1189
|
-
if (
|
|
1269
|
+
if (cfOrVm === "VM" /* CircuitContributionVerificationMechanism.VM */) {
|
|
1190
1270
|
// Ask for selecting the specific VM configuration type.
|
|
1191
1271
|
vmConfigurationType = await promptVMTypeSelector(constraintSize);
|
|
1192
1272
|
// Ask for selecting the specific VM disk (volume) type.
|
|
@@ -1220,9 +1300,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1220
1300
|
paramsConfiguration: circuitConfigurationValues
|
|
1221
1301
|
},
|
|
1222
1302
|
verification: {
|
|
1223
|
-
cfOrVm
|
|
1224
|
-
? "CF" /* CircuitContributionVerificationMechanism.CF */
|
|
1225
|
-
: "VM" /* CircuitContributionVerificationMechanism.VM */,
|
|
1303
|
+
cfOrVm,
|
|
1226
1304
|
vm: {
|
|
1227
1305
|
vmConfigurationType,
|
|
1228
1306
|
vmDiskType
|
|
@@ -1258,9 +1336,7 @@ const promptCircuitInputData = async (constraintSize, timeoutMechanismType, same
|
|
|
1258
1336
|
paramsConfiguration: circuitConfigurationValues
|
|
1259
1337
|
},
|
|
1260
1338
|
verification: {
|
|
1261
|
-
cfOrVm
|
|
1262
|
-
? "CF" /* CircuitContributionVerificationMechanism.CF */
|
|
1263
|
-
: "VM" /* CircuitContributionVerificationMechanism.VM */,
|
|
1339
|
+
cfOrVm,
|
|
1264
1340
|
vm: {
|
|
1265
1341
|
vmConfigurationType,
|
|
1266
1342
|
vmDiskType
|
|
@@ -1304,7 +1380,7 @@ const promptCircuitAddition = async () => {
|
|
|
1304
1380
|
* Shows a list of pre-computed zKeys for a single option selection.
|
|
1305
1381
|
* @dev the names are derived from local zKeys files.
|
|
1306
1382
|
* @param options <Array<string>> - an array of pre-computed zKeys names.
|
|
1307
|
-
* @returns Promise<string> - the name of the
|
|
1383
|
+
* @returns Promise<string> - the name of the chosen pre-computed zKey.
|
|
1308
1384
|
*/
|
|
1309
1385
|
const promptPreComputedZkeySelector = async (options) => {
|
|
1310
1386
|
const { preComputedZkeyFilename } = await prompts({
|
|
@@ -1342,13 +1418,13 @@ const promptNeededPowersForCircuit = async (suggestedSmallestNeededPowers) => {
|
|
|
1342
1418
|
* Shows a list of PoT files for a single option selection.
|
|
1343
1419
|
* @dev the names are derived from local PoT files.
|
|
1344
1420
|
* @param options <Array<string>> - an array of PoT file names.
|
|
1345
|
-
* @returns Promise<string> - the name of the
|
|
1421
|
+
* @returns Promise<string> - the name of the chosen PoT.
|
|
1346
1422
|
*/
|
|
1347
1423
|
const promptPotSelector = async (options) => {
|
|
1348
1424
|
const { potFilename } = await prompts({
|
|
1349
1425
|
type: "select",
|
|
1350
1426
|
name: "potFilename",
|
|
1351
|
-
message: theme.text.bold("Select the Powers of Tau file
|
|
1427
|
+
message: theme.text.bold("Select the Powers of Tau file chosen for the circuit"),
|
|
1352
1428
|
choices: options.map((option) => {
|
|
1353
1429
|
console.log(option);
|
|
1354
1430
|
return { title: option, value: option };
|
|
@@ -1366,7 +1442,7 @@ const promptPotSelector = async (options) => {
|
|
|
1366
1442
|
* @param isFinalizing <boolean> - true when the coordinator must select a ceremony for finalization; otherwise false (participant selects a ceremony for contribution).
|
|
1367
1443
|
* @returns Promise<FirebaseDocumentInfo> - the Firestore document of the selected ceremony.
|
|
1368
1444
|
*/
|
|
1369
|
-
const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing) => {
|
|
1445
|
+
const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing, messageToDisplay) => {
|
|
1370
1446
|
// Prepare state.
|
|
1371
1447
|
const choices = [];
|
|
1372
1448
|
// Prepare choices x ceremony.
|
|
@@ -1384,9 +1460,7 @@ const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing) =>
|
|
|
1384
1460
|
const { ceremony } = await prompts({
|
|
1385
1461
|
type: "select",
|
|
1386
1462
|
name: "ceremony",
|
|
1387
|
-
message: theme.text.bold(
|
|
1388
|
-
? "Which ceremony would you like to contribute to?"
|
|
1389
|
-
: "Which ceremony would you like to finalize?"),
|
|
1463
|
+
message: theme.text.bold(messageToDisplay),
|
|
1390
1464
|
choices,
|
|
1391
1465
|
initial: 0
|
|
1392
1466
|
});
|
|
@@ -1418,7 +1492,7 @@ const promptToTypeEntropyOrBeacon = async (isEntropy = true) => {
|
|
|
1418
1492
|
* @return <Promise<string>> - the entropy.
|
|
1419
1493
|
*/
|
|
1420
1494
|
const promptForEntropy = async () => {
|
|
1421
|
-
// Prompt for entropy generation
|
|
1495
|
+
// Prompt for entropy generation preferred method.
|
|
1422
1496
|
const { confirmation } = await askForConfirmation(`Do you prefer to type your entropy or generate it randomly?`, "Manually", "Randomly");
|
|
1423
1497
|
if (confirmation === undefined)
|
|
1424
1498
|
showError(COMMAND_ERRORS.COMMAND_ABORT_PROMPT, true);
|
|
@@ -1537,16 +1611,37 @@ const checkAuth = async (firebaseApp) => {
|
|
|
1537
1611
|
showError(THIRD_PARTY_SERVICES_ERRORS.GITHUB_NOT_AUTHENTICATED, true);
|
|
1538
1612
|
// Retrieve local access token.
|
|
1539
1613
|
const token = String(getLocalAccessToken());
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1614
|
+
let providerUserId;
|
|
1615
|
+
let username;
|
|
1616
|
+
const authMethod = getLocalAuthMethod();
|
|
1617
|
+
switch (authMethod) {
|
|
1618
|
+
case "github": {
|
|
1619
|
+
// Get credentials.
|
|
1620
|
+
const credentials = exchangeGithubTokenForCredentials(token);
|
|
1621
|
+
// Sign in to Firebase using credentials.
|
|
1622
|
+
await signInToFirebase(firebaseApp, credentials);
|
|
1623
|
+
// Get Github unique identifier (handle-id).
|
|
1624
|
+
providerUserId = await getGithubProviderUserId(String(token));
|
|
1625
|
+
username = getUserHandleFromProviderUserId(providerUserId);
|
|
1626
|
+
break;
|
|
1627
|
+
}
|
|
1628
|
+
case "bandada": {
|
|
1629
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1630
|
+
providerUserId = userCredentials.user.uid;
|
|
1631
|
+
username = providerUserId;
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
case "siwe": {
|
|
1635
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1636
|
+
providerUserId = userCredentials.user.uid;
|
|
1637
|
+
username = providerUserId;
|
|
1638
|
+
break;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1544
1641
|
// Get current authenticated user.
|
|
1545
1642
|
const user = getCurrentFirebaseAuthUser(firebaseApp);
|
|
1546
|
-
// Get Github unique identifier (handle-id).
|
|
1547
|
-
const providerUserId = await getGithubProviderUserId(String(token));
|
|
1548
1643
|
// Greet the user.
|
|
1549
|
-
console.log(`Greetings, @${theme.text.bold(
|
|
1644
|
+
console.log(`Greetings, @${theme.text.bold(username)} ${theme.emojis.wave}\n`);
|
|
1550
1645
|
return {
|
|
1551
1646
|
user,
|
|
1552
1647
|
token,
|
|
@@ -1635,7 +1730,7 @@ const handleAdditionOfCircuitsToCeremony = async (r1csOptions, wasmOptions, cere
|
|
|
1635
1730
|
wasmFilename.split(`.${commonTerms.foldersAndPathsTerms.wasm}`)[0]);
|
|
1636
1731
|
if (matchingWasms.length !== 1)
|
|
1637
1732
|
showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true);
|
|
1638
|
-
// Get input data for
|
|
1733
|
+
// Get input data for chosen circuit.
|
|
1639
1734
|
const circuitInputData = await getInputDataToAddCircuitToCeremony(choosenCircuitFilename, matchingWasms[0], ceremonyTimeoutMechanismType, sameCircomCompiler, circuitSequencePosition, sharedCircomCompilerData);
|
|
1640
1735
|
// Store circuit data.
|
|
1641
1736
|
inputDataForCircuits.push(circuitInputData);
|
|
@@ -1688,7 +1783,7 @@ const displayCeremonySummary = (ceremonyInputData, circuits) => {
|
|
|
1688
1783
|
};
|
|
1689
1784
|
/**
|
|
1690
1785
|
* Check if the smallest Powers of Tau has already been downloaded/stored in the correspondent local path
|
|
1691
|
-
* @dev we are downloading the Powers of Tau file from
|
|
1786
|
+
* @dev we are downloading the Powers of Tau file from Perpetual Powers of Tau Phase 1 Trusted Setup.
|
|
1692
1787
|
* @param powers <string> - the smallest amount of powers needed for the given circuit (should be in a 'XY' stringified form).
|
|
1693
1788
|
* @param ptauCompleteFilename <string> - the complete file name of the powers of tau file to be downloaded.
|
|
1694
1789
|
* @returns <Promise<void>>
|
|
@@ -1702,7 +1797,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
|
|
|
1702
1797
|
.map((dirent) => dirent.name);
|
|
1703
1798
|
// Check if already downloaded or not.
|
|
1704
1799
|
if (smallestPtauFileForGivenPowers.length === 0) {
|
|
1705
|
-
const spinner = customSpinner(`Downloading the ${theme.text.bold(`#${powers}`)} smallest PoT file needed from the
|
|
1800
|
+
const spinner = customSpinner(`Downloading the ${theme.text.bold(`#${powers}`)} smallest PoT file needed from the Perpetual Powers of Tau Phase 1 Trusted Setup...`, `clock`);
|
|
1706
1801
|
spinner.start();
|
|
1707
1802
|
// Download smallest Powers of Tau file from remote server.
|
|
1708
1803
|
const streamPipeline = promisify(pipeline);
|
|
@@ -1725,7 +1820,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
|
|
|
1725
1820
|
* number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
|
|
1726
1821
|
* to provide a number of powers manually, ranging from the smallest possible to the largest.
|
|
1727
1822
|
* @param neededPowers <number> - the smallest amount of powers needed by the zKey.
|
|
1728
|
-
* @returns Promise<string, string> - the information about the
|
|
1823
|
+
* @returns Promise<string, string> - the information about the chosen Powers of Tau file for the pre-computed zKey
|
|
1729
1824
|
* along with related powers.
|
|
1730
1825
|
*/
|
|
1731
1826
|
const handlePreComputedZkeyPowersOfTauSelection = async (neededPowers) => {
|
|
@@ -1817,7 +1912,7 @@ const handleCircuitArtifactUploadToStorage = async (firebaseFunctions, bucketNam
|
|
|
1817
1912
|
* @notice The setup command allows the coordinator of the ceremony to prepare the next ceremony by interacting with the CLI.
|
|
1818
1913
|
* @dev For proper execution, the command must be run in a folder containing the R1CS files related to the circuits
|
|
1819
1914
|
* for which the coordinator wants to create the ceremony. The command will download the necessary Tau powers
|
|
1820
|
-
* from
|
|
1915
|
+
* from PPoT ceremony Phase 1 Setup Ceremony.
|
|
1821
1916
|
* @param cmd? <any> - the path to the ceremony setup file.
|
|
1822
1917
|
*/
|
|
1823
1918
|
const setup = async (cmd) => {
|
|
@@ -1826,7 +1921,9 @@ const setup = async (cmd) => {
|
|
|
1826
1921
|
let ceremonyId = ""; // The unique identifier of the ceremony.
|
|
1827
1922
|
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
1828
1923
|
// Check for authentication.
|
|
1829
|
-
const { user, providerUserId } = cmd.auth
|
|
1924
|
+
const { user, providerUserId } = cmd.auth
|
|
1925
|
+
? await authWithToken(firebaseApp, cmd.auth)
|
|
1926
|
+
: await checkAuth(firebaseApp);
|
|
1830
1927
|
// Preserve command execution only for coordinators.
|
|
1831
1928
|
if (!(await isCoordinator(user)))
|
|
1832
1929
|
showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
|
|
@@ -1843,7 +1940,7 @@ const setup = async (cmd) => {
|
|
|
1843
1940
|
// if there is the file option, then set up the non interactively
|
|
1844
1941
|
if (cmd.template) {
|
|
1845
1942
|
// 1. parse the file
|
|
1846
|
-
// tmp data - do not cleanup files as we need them
|
|
1943
|
+
// tmp data - do not cleanup files as we need them
|
|
1847
1944
|
const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template)} setup configuration file...`, `clock`);
|
|
1848
1945
|
spinner.start();
|
|
1849
1946
|
const setupCeremonyData = await parseCeremonyFile(cmd.template);
|
|
@@ -1853,8 +1950,6 @@ const setup = async (cmd) => {
|
|
|
1853
1950
|
// create a new bucket
|
|
1854
1951
|
const bucketName = await handleCeremonyBucketCreation(firebaseFunctions, ceremonySetupData.ceremonyPrefix);
|
|
1855
1952
|
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
1953
|
// loop through each circuit
|
|
1859
1954
|
for await (const circuit of setupCeremonyData.circuits) {
|
|
1860
1955
|
// Local paths.
|
|
@@ -1864,25 +1959,24 @@ const setup = async (cmd) => {
|
|
|
1864
1959
|
const potLocalPathAndFileName = getPotLocalFilePath(circuit.files.potFilename);
|
|
1865
1960
|
const zkeyLocalPathAndFileName = getZkeyLocalFilePath(circuit.files.initialZkeyFilename);
|
|
1866
1961
|
// 2. download the pot and wasm files
|
|
1867
|
-
const streamPipeline = promisify(pipeline);
|
|
1868
1962
|
await checkAndDownloadSmallestPowersOfTau(convertToDoubleDigits(circuit.metadata?.pot), circuit.files.potFilename);
|
|
1869
|
-
//
|
|
1870
|
-
const spinner = customSpinner(`
|
|
1963
|
+
// 3. generate the zKey
|
|
1964
|
+
const spinner = customSpinner(`Generating genesis zKey for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
|
|
1871
1965
|
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.");
|
|
1966
|
+
if (existsSync(zkeyLocalPathAndFileName)) {
|
|
1967
|
+
spinner.succeed(`The genesis zKey for circuit ${theme.text.bold(circuit.name)} is already present on disk`);
|
|
1876
1968
|
}
|
|
1877
|
-
|
|
1878
|
-
await
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1969
|
+
else {
|
|
1970
|
+
await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined);
|
|
1971
|
+
spinner.succeed(`Generation of the genesis zKey for circuit ${theme.text.bold(circuit.name)} completed successfully`);
|
|
1972
|
+
}
|
|
1973
|
+
const hashSpinner = customSpinner(`Calculating hashes for circuit ${theme.text.bold(circuit.name)}...`, `clock`);
|
|
1974
|
+
hashSpinner.start();
|
|
1882
1975
|
// 4. calculate the hashes
|
|
1883
1976
|
const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName);
|
|
1884
1977
|
const potBlake2bHash = await blake512FromPath(getPotLocalFilePath(circuit.files.potFilename));
|
|
1885
1978
|
const initialZkeyBlake2bHash = await blake512FromPath(zkeyLocalPathAndFileName);
|
|
1979
|
+
hashSpinner.succeed(`Hashes for circuit ${theme.text.bold(circuit.name)} calculated successfully`);
|
|
1886
1980
|
// 5. upload the artifacts
|
|
1887
1981
|
// Upload zKey to Storage.
|
|
1888
1982
|
await handleCircuitArtifactUploadToStorage(firebaseFunctions, bucketName, circuit.files.initialZkeyStoragePath, zkeyLocalPathAndFileName, circuit.files.initialZkeyFilename);
|
|
@@ -1900,9 +1994,9 @@ const setup = async (cmd) => {
|
|
|
1900
1994
|
// 6 update the setup data object
|
|
1901
1995
|
ceremonySetupData.circuits[index].files = {
|
|
1902
1996
|
...circuit.files,
|
|
1903
|
-
potBlake2bHash
|
|
1904
|
-
wasmBlake2bHash
|
|
1905
|
-
initialZkeyBlake2bHash
|
|
1997
|
+
potBlake2bHash,
|
|
1998
|
+
wasmBlake2bHash,
|
|
1999
|
+
initialZkeyBlake2bHash
|
|
1906
2000
|
};
|
|
1907
2001
|
ceremonySetupData.circuits[index].zKeySizeInBytes = getFileStats(zkeyLocalPathAndFileName).size;
|
|
1908
2002
|
}
|
|
@@ -2110,17 +2204,29 @@ const expirationCountdownForGithubOAuth = (expirationInSeconds) => {
|
|
|
2110
2204
|
*/
|
|
2111
2205
|
const onVerification = async (verification) => {
|
|
2112
2206
|
// Copy code to clipboard.
|
|
2113
|
-
|
|
2114
|
-
|
|
2207
|
+
let noClipboard = false;
|
|
2208
|
+
try {
|
|
2209
|
+
clipboard.writeSync(verification.user_code);
|
|
2210
|
+
clipboard.readSync();
|
|
2211
|
+
}
|
|
2212
|
+
catch (error) {
|
|
2213
|
+
noClipboard = true;
|
|
2214
|
+
}
|
|
2115
2215
|
// Display data.
|
|
2116
2216
|
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`);
|
|
2117
|
-
console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })),
|
|
2118
|
-
|
|
2217
|
+
console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
|
|
2218
|
+
const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``;
|
|
2219
|
+
console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(verification.user_code)} ${message} ${theme.symbols.success}\n`);
|
|
2119
2220
|
const spinner = customSpinner(`Redirecting to Github...`, `clock`);
|
|
2120
2221
|
spinner.start();
|
|
2121
2222
|
await sleep(10000); // ~10s to make users able to read the CLI.
|
|
2122
|
-
|
|
2123
|
-
|
|
2223
|
+
try {
|
|
2224
|
+
// Automatically open the page (# Step 2).
|
|
2225
|
+
await open(verification.verification_uri);
|
|
2226
|
+
}
|
|
2227
|
+
catch (error) {
|
|
2228
|
+
console.log(`${theme.symbols.info} Please authenticate via GitHub at ${verification.verification_uri}`);
|
|
2229
|
+
}
|
|
2124
2230
|
spinner.stop();
|
|
2125
2231
|
// Countdown for time expiration.
|
|
2126
2232
|
expirationCountdownForGithubOAuth(verification.expires_in);
|
|
@@ -2171,6 +2277,7 @@ const auth = async () => {
|
|
|
2171
2277
|
// Generate a new access token using Github Device Flow (OAuth 2.0).
|
|
2172
2278
|
const newToken = await executeGithubDeviceFlow(String(process.env.AUTH_GITHUB_CLIENT_ID));
|
|
2173
2279
|
// Store the new access token.
|
|
2280
|
+
setLocalAuthMethod("github");
|
|
2174
2281
|
setLocalAccessToken(newToken);
|
|
2175
2282
|
}
|
|
2176
2283
|
else
|
|
@@ -2191,6 +2298,249 @@ const auth = async () => {
|
|
|
2191
2298
|
terminate(providerUserId);
|
|
2192
2299
|
};
|
|
2193
2300
|
|
|
2301
|
+
const { BANDADA_API_URL } = process.env;
|
|
2302
|
+
const bandadaApi = new ApiSdk(BANDADA_API_URL);
|
|
2303
|
+
const addMemberToGroup = async (groupId, dashboardUrl, identity) => {
|
|
2304
|
+
const commitment = identity.commitment.toString();
|
|
2305
|
+
const group = await bandadaApi.getGroup(groupId);
|
|
2306
|
+
const providerName = group.credentials.id.split("_")[0].toLowerCase();
|
|
2307
|
+
// 6. open a new window with the url:
|
|
2308
|
+
const url = `${dashboardUrl}credentials?group=${groupId}&member=${commitment}&provider=${providerName}`;
|
|
2309
|
+
console.log(`${theme.text.bold(`Verification URL:`)} ${theme.text.underlined(url)}`);
|
|
2310
|
+
open(url);
|
|
2311
|
+
const { confirmation } = await askForConfirmation("Did you join the Bandada group in the browser?");
|
|
2312
|
+
if (!confirmation)
|
|
2313
|
+
showError("You must join the Bandada group to continue the login process", true);
|
|
2314
|
+
};
|
|
2315
|
+
const isGroupMember = async (groupId, identity) => {
|
|
2316
|
+
const commitment = identity.commitment.toString();
|
|
2317
|
+
const isMember = await bandadaApi.isGroupMember(groupId, commitment);
|
|
2318
|
+
return isMember;
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2321
|
+
const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env;
|
|
2322
|
+
const authBandada = async () => {
|
|
2323
|
+
try {
|
|
2324
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2325
|
+
const spinner = customSpinner(`Checking identity string for Semaphore...`, `clock`);
|
|
2326
|
+
spinner.start();
|
|
2327
|
+
// 1. check if _identity string exists in local storage
|
|
2328
|
+
let identityString;
|
|
2329
|
+
const isIdentityStringStored = checkLocalBandadaIdentity();
|
|
2330
|
+
if (isIdentityStringStored) {
|
|
2331
|
+
identityString = getLocalBandadaIdentity();
|
|
2332
|
+
spinner.succeed(`Identity seed found\n`);
|
|
2333
|
+
}
|
|
2334
|
+
else {
|
|
2335
|
+
spinner.warn(`Identity seed not found\n`);
|
|
2336
|
+
// 2. generate a random _identity string and save it in local storage
|
|
2337
|
+
const { seed } = await prompts({
|
|
2338
|
+
type: "text",
|
|
2339
|
+
name: "seed",
|
|
2340
|
+
message: theme.text.bold(`Enter a secret string to use as your identity seed in Semaphore:`),
|
|
2341
|
+
initial: false
|
|
2342
|
+
});
|
|
2343
|
+
identityString = seed;
|
|
2344
|
+
setLocalBandadaIdentity(identityString);
|
|
2345
|
+
}
|
|
2346
|
+
// 3. create a semaphore identity with _identity string as a seed
|
|
2347
|
+
const identity = new Identity(identityString);
|
|
2348
|
+
// 4. check if the user is a member of the group
|
|
2349
|
+
console.log(`Checking Bandada membership...`);
|
|
2350
|
+
const isMember = await isGroupMember(BANDADA_GROUP_ID, identity);
|
|
2351
|
+
if (!isMember) {
|
|
2352
|
+
await addMemberToGroup(BANDADA_GROUP_ID, BANDADA_DASHBOARD_URL, identity);
|
|
2353
|
+
}
|
|
2354
|
+
// 5. generate a proof that the user owns the commitment.
|
|
2355
|
+
spinner.text = `Generating proof of identity...`;
|
|
2356
|
+
spinner.start();
|
|
2357
|
+
// publicSignals = [hash(externalNullifier, identityNullifier), commitment]
|
|
2358
|
+
const initDirectoryName = getLocalDirname();
|
|
2359
|
+
const directoryName = initDirectoryName.includes("/src") ? "." : initDirectoryName;
|
|
2360
|
+
const { proof, publicSignals } = await groth16.fullProve({
|
|
2361
|
+
identityTrapdoor: identity.trapdoor,
|
|
2362
|
+
identityNullifier: identity.nullifier,
|
|
2363
|
+
externalNullifier: BANDADA_GROUP_ID
|
|
2364
|
+
}, `${directoryName}/public/mini-semaphore.wasm`, `${directoryName}/public/mini-semaphore.zkey`);
|
|
2365
|
+
spinner.succeed(`Proof generated.\n`);
|
|
2366
|
+
spinner.text = `Sending proof to verification...`;
|
|
2367
|
+
spinner.start();
|
|
2368
|
+
// 6. send proof to a cloud function that verifies it and checks membership
|
|
2369
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.bandadaValidateProof);
|
|
2370
|
+
const result = await cf({
|
|
2371
|
+
proof,
|
|
2372
|
+
publicSignals
|
|
2373
|
+
});
|
|
2374
|
+
const { valid, token, message } = result.data;
|
|
2375
|
+
if (!valid) {
|
|
2376
|
+
showError(message, true);
|
|
2377
|
+
deleteLocalAuthMethod();
|
|
2378
|
+
deleteLocalAccessToken();
|
|
2379
|
+
deleteLocalBandadaIdentity();
|
|
2380
|
+
}
|
|
2381
|
+
spinner.succeed(`Proof verified.\n`);
|
|
2382
|
+
spinner.text = `Authenticating...`;
|
|
2383
|
+
spinner.start();
|
|
2384
|
+
// 7. Auth to p0tion firebase
|
|
2385
|
+
const credentials = await signInWithCustomToken(getAuth(), token);
|
|
2386
|
+
setLocalAuthMethod("bandada");
|
|
2387
|
+
setLocalAccessToken(token);
|
|
2388
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(credentials.user.uid)}.`);
|
|
2389
|
+
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2390
|
+
}
|
|
2391
|
+
catch (error) {
|
|
2392
|
+
// Delete local token.
|
|
2393
|
+
console.log("An error crashed the process. Deleting local token and identity.");
|
|
2394
|
+
console.error(error);
|
|
2395
|
+
deleteLocalAuthMethod();
|
|
2396
|
+
deleteLocalAccessToken();
|
|
2397
|
+
deleteLocalBandadaIdentity();
|
|
2398
|
+
}
|
|
2399
|
+
process.exit(0);
|
|
2400
|
+
};
|
|
2401
|
+
|
|
2402
|
+
const showVerificationCodeAndUri = async (OAuthDeviceCode) => {
|
|
2403
|
+
// Copy code to clipboard.
|
|
2404
|
+
let noClipboard = false;
|
|
2405
|
+
try {
|
|
2406
|
+
clipboard.writeSync(OAuthDeviceCode.user_code);
|
|
2407
|
+
clipboard.readSync();
|
|
2408
|
+
}
|
|
2409
|
+
catch (error) {
|
|
2410
|
+
noClipboard = true;
|
|
2411
|
+
}
|
|
2412
|
+
// Display data.
|
|
2413
|
+
console.log(`${theme.symbols.warning} Visit ${theme.text.bold(theme.text.underlined(OAuthDeviceCode.verification_uri_complete))} on this device to generate a new token and authenticate\n`);
|
|
2414
|
+
console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
|
|
2415
|
+
const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``;
|
|
2416
|
+
console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(OAuthDeviceCode.user_code)} ${message} ${theme.symbols.success}\n`);
|
|
2417
|
+
const spinner = customSpinner(`Redirecting to Sign In With Ethereum...`, `clock`);
|
|
2418
|
+
spinner.start();
|
|
2419
|
+
await sleep(10000); // ~10s to make users able to read the CLI.
|
|
2420
|
+
try {
|
|
2421
|
+
// Automatically open the page (# Step 2).
|
|
2422
|
+
await open(OAuthDeviceCode.verification_uri_complete);
|
|
2423
|
+
}
|
|
2424
|
+
catch (error) {
|
|
2425
|
+
console.log(`${theme.symbols.info} Please authenticate via SIWE at ${OAuthDeviceCode.verification_uri_complete}`);
|
|
2426
|
+
}
|
|
2427
|
+
spinner.stop();
|
|
2428
|
+
};
|
|
2429
|
+
/**
|
|
2430
|
+
* Return the token to sign in to Firebase after passing the SIWE Device Flow
|
|
2431
|
+
* @param clientId <string> - The client id of the Auth0 application.
|
|
2432
|
+
* @param firebaseFunctions <any> - The Firebase functions instance to call the cloud function
|
|
2433
|
+
* @returns <string> - The token to sign in to Firebase
|
|
2434
|
+
*/
|
|
2435
|
+
const executeSIWEDeviceFlow = async (clientId, firebaseFunctions) => {
|
|
2436
|
+
// Call Auth0 endpoint to request device code uri
|
|
2437
|
+
const OAuthDeviceCode = (await fetch$1(`${process.env.AUTH0_APPLICATION_URL}/oauth/device/code`, {
|
|
2438
|
+
method: "POST",
|
|
2439
|
+
headers: { "content-type": "application/json" },
|
|
2440
|
+
body: JSON.stringify({
|
|
2441
|
+
client_id: clientId,
|
|
2442
|
+
scope: "openid",
|
|
2443
|
+
audience: `${process.env.AUTH0_APPLICATION_URL}/api/v2/`
|
|
2444
|
+
})
|
|
2445
|
+
}).then((_res) => _res.json()));
|
|
2446
|
+
if (OAuthDeviceCode.error) {
|
|
2447
|
+
showError(OAuthDeviceCode.error_description, true);
|
|
2448
|
+
deleteLocalAuthMethod();
|
|
2449
|
+
deleteLocalAccessToken();
|
|
2450
|
+
}
|
|
2451
|
+
await showVerificationCodeAndUri(OAuthDeviceCode);
|
|
2452
|
+
// Poll Auth0 endpoint until you get token or request expires
|
|
2453
|
+
let isSignedIn = false;
|
|
2454
|
+
let isExpired = false;
|
|
2455
|
+
let auth0Token = "";
|
|
2456
|
+
while (!isSignedIn && !isExpired) {
|
|
2457
|
+
// Call Auth0 endpoint to request token
|
|
2458
|
+
const OAuthToken = (await fetch$1(`${process.env.AUTH0_APPLICATION_URL}/oauth/token`, {
|
|
2459
|
+
method: "POST",
|
|
2460
|
+
headers: { "content-type": "application/json" },
|
|
2461
|
+
body: JSON.stringify({
|
|
2462
|
+
client_id: clientId,
|
|
2463
|
+
device_code: OAuthDeviceCode.device_code,
|
|
2464
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
2465
|
+
})
|
|
2466
|
+
}).then((_res) => _res.json()));
|
|
2467
|
+
if (OAuthToken.error) {
|
|
2468
|
+
if (OAuthToken.error === "authorization_pending") {
|
|
2469
|
+
// Wait for the user to sign in
|
|
2470
|
+
await sleep(OAuthDeviceCode.interval * 1000);
|
|
2471
|
+
}
|
|
2472
|
+
else if (OAuthToken.error === "slow_down") {
|
|
2473
|
+
// Wait for the user to sign in
|
|
2474
|
+
await sleep(OAuthDeviceCode.interval * 1000 * 2);
|
|
2475
|
+
}
|
|
2476
|
+
else if (OAuthToken.error === "expired_token") {
|
|
2477
|
+
// The user didn't sign in on time
|
|
2478
|
+
isExpired = true;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
else {
|
|
2482
|
+
// The user signed in
|
|
2483
|
+
isSignedIn = true;
|
|
2484
|
+
auth0Token = OAuthToken.access_token;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
// Send token to cloud function to check nonce, create user and retrieve token
|
|
2488
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.checkNonceOfSIWEAddress);
|
|
2489
|
+
const result = await cf({
|
|
2490
|
+
auth0Token
|
|
2491
|
+
});
|
|
2492
|
+
const { token, valid, message } = result.data;
|
|
2493
|
+
if (!valid) {
|
|
2494
|
+
showError(message, true);
|
|
2495
|
+
deleteLocalAuthMethod();
|
|
2496
|
+
deleteLocalAccessToken();
|
|
2497
|
+
}
|
|
2498
|
+
return token;
|
|
2499
|
+
};
|
|
2500
|
+
/**
|
|
2501
|
+
* Auth command using Sign In With Ethereum
|
|
2502
|
+
* @notice The auth command allows a user to make the association of their Ethereum account with the CLI by leveraging SIWE as an authentication mechanism.
|
|
2503
|
+
* @dev Under the hood, the command handles a manual Device Flow following the guidelines in the SIWE documentation.
|
|
2504
|
+
*/
|
|
2505
|
+
const authSIWE = async () => {
|
|
2506
|
+
try {
|
|
2507
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2508
|
+
// Console more context for the user.
|
|
2509
|
+
console.log(`${theme.symbols.info} ${theme.text.bold(`You are about to authenticate on this CLI using your Ethereum address (device flow - OAuth 2.0 mechanism).\n${theme.symbols.warning} Please, note that only a Sign-in With Ethereum signature will be required`)}\n`);
|
|
2510
|
+
const spinner = customSpinner(`Checking authentication token...`, `clock`);
|
|
2511
|
+
spinner.start();
|
|
2512
|
+
await sleep(5000);
|
|
2513
|
+
// Manage OAuth Github or SIWE token.
|
|
2514
|
+
const isLocalTokenStored = checkLocalAccessToken();
|
|
2515
|
+
if (!isLocalTokenStored) {
|
|
2516
|
+
spinner.fail(`No local authentication token found\n`);
|
|
2517
|
+
// Generate a new access token using Github Device Flow (OAuth 2.0).
|
|
2518
|
+
const newToken = await executeSIWEDeviceFlow(String(process.env.AUTH_SIWE_CLIENT_ID), firebaseFunctions);
|
|
2519
|
+
// Store the new access token.
|
|
2520
|
+
setLocalAuthMethod("siwe");
|
|
2521
|
+
setLocalAccessToken(newToken);
|
|
2522
|
+
}
|
|
2523
|
+
else
|
|
2524
|
+
spinner.succeed(`Local authentication token found\n`);
|
|
2525
|
+
// Get access token from local store.
|
|
2526
|
+
const token = String(getLocalAccessToken());
|
|
2527
|
+
spinner.text = `Authenticating...`;
|
|
2528
|
+
spinner.start();
|
|
2529
|
+
// Exchange token for credential.
|
|
2530
|
+
const credentials = await signInWithCustomToken(getAuth(), token);
|
|
2531
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(credentials.user.uid)}.`);
|
|
2532
|
+
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2533
|
+
process.exit(0);
|
|
2534
|
+
}
|
|
2535
|
+
catch (error) {
|
|
2536
|
+
// Delete local token.
|
|
2537
|
+
console.log("An error crashed the process. Deleting local token and identity.");
|
|
2538
|
+
console.error(error);
|
|
2539
|
+
deleteLocalAuthMethod();
|
|
2540
|
+
deleteLocalAccessToken();
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
|
|
2194
2544
|
/**
|
|
2195
2545
|
* Return the verification result for latest contribution.
|
|
2196
2546
|
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
@@ -2313,8 +2663,8 @@ const handleDiskSpaceRequirementForNextContribution = async (cloudFunctions, cer
|
|
|
2313
2663
|
spinner.fail(`You may not have enough memory to calculate the contribution for the Circuit ${theme.colors.magenta(`${circuitSequencePosition}`)}.\n\n${theme.symbols.info} The required amount of disk space is ${contributionDiskSpaceRequirement < 0.01
|
|
2314
2664
|
? theme.text.bold(`< 0.01`)
|
|
2315
2665
|
: theme.text.bold(contributionDiskSpaceRequirement)} GB but you only have ${participantFreeDiskSpace > 0 ? theme.text.bold(participantFreeDiskSpace.toFixed(2)) : theme.text.bold(0)} GB available memory \nThe estimate ${theme.text.bold("may not be 100% correct")} since is based on the aggregate free memory on your disks but some may not be detected!\n`);
|
|
2316
|
-
const {
|
|
2317
|
-
wannaContributeOrHaveEnoughMemory = !!
|
|
2666
|
+
const { confirmationEnoughMemory } = await askForConfirmation(`Please, we kindly ask you to continue with the contribution if you have noticed the estimate is wrong and you have enough memory in your machine`, "Continue", "Exit");
|
|
2667
|
+
wannaContributeOrHaveEnoughMemory = !!confirmationEnoughMemory;
|
|
2318
2668
|
if (circuitSequencePosition > 1) {
|
|
2319
2669
|
console.log(`${theme.symbols.info} Please note, you have time until ceremony ends to free up your memory and complete remaining contributions`);
|
|
2320
2670
|
// Asks the contributor if their wants to terminate contributions for the ceremony.
|
|
@@ -2382,8 +2732,12 @@ const handlePublicAttestation = async (firestoreDatabase, circuits, ceremonyId,
|
|
|
2382
2732
|
// Write public attestation locally.
|
|
2383
2733
|
writeFile(getAttestationLocalFilePath(`${ceremonyPrefix}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
2384
2734
|
await sleep(1000); // workaround for file descriptor unexpected close.
|
|
2385
|
-
|
|
2386
|
-
|
|
2735
|
+
let gistUrl = "";
|
|
2736
|
+
const isGithub = getLocalAuthMethod() === "github";
|
|
2737
|
+
if (isGithub) {
|
|
2738
|
+
gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
|
|
2739
|
+
console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
2740
|
+
}
|
|
2387
2741
|
// Prepare a ready-to-share tweet.
|
|
2388
2742
|
await handleTweetGeneration(ceremonyName, gistUrl);
|
|
2389
2743
|
};
|
|
@@ -2436,6 +2790,7 @@ const listenToCeremonyCircuitDocumentChanges = (firestoreDatabase, ceremonyId, p
|
|
|
2436
2790
|
}
|
|
2437
2791
|
});
|
|
2438
2792
|
};
|
|
2793
|
+
let contributionInProgress = false;
|
|
2439
2794
|
/**
|
|
2440
2795
|
* Listen to current authenticated participant document changes.
|
|
2441
2796
|
* @dev this is the core business logic related to the execution of the contribute command.
|
|
@@ -2563,11 +2918,21 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2563
2918
|
(!noTemporaryContributionData && resumingWithSameTemporaryData);
|
|
2564
2919
|
// Scenario (3.B).
|
|
2565
2920
|
if (isCurrentContributor && hasResumableStep && startingOrResumingContribution) {
|
|
2921
|
+
if (contributionInProgress) {
|
|
2922
|
+
console.warn(`\n${theme.symbols.warning} Received instruction to start/resume contribution but contribution is already in progress...[skipping]`);
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2566
2925
|
// Communicate resume / start of the contribution to participant.
|
|
2567
2926
|
await simpleLoader(`${changedContributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */ ? `Starting` : `Resuming`} your contribution...`, `clock`, 3000);
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2927
|
+
try {
|
|
2928
|
+
contributionInProgress = true;
|
|
2929
|
+
// Start / Resume the contribution for the participant.
|
|
2930
|
+
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, entropy, providerUserId, false, // not finalizing.
|
|
2931
|
+
circuits.length);
|
|
2932
|
+
}
|
|
2933
|
+
finally {
|
|
2934
|
+
contributionInProgress = false;
|
|
2935
|
+
}
|
|
2571
2936
|
}
|
|
2572
2937
|
// Scenario (3.A).
|
|
2573
2938
|
else if (isWaitingForContribution)
|
|
@@ -2616,7 +2981,9 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2616
2981
|
// Get latest contribution verification result.
|
|
2617
2982
|
await getLatestVerificationResult(firestoreDatabase, ceremony.id, circuit.id, participant.id);
|
|
2618
2983
|
// Get next circuit for contribution.
|
|
2619
|
-
const nextCircuit = timeoutExpired
|
|
2984
|
+
const nextCircuit = timeoutExpired
|
|
2985
|
+
? getCircuitBySequencePosition(circuits, changedContributionProgress)
|
|
2986
|
+
: getCircuitBySequencePosition(circuits, changedContributionProgress + 1);
|
|
2620
2987
|
// Check disk space requirements for participant.
|
|
2621
2988
|
const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(cloudFunctions, ceremony.id, nextCircuit.data.sequencePosition, nextCircuit.data.zKeySizeInBytes, timeoutExpired, providerUserId);
|
|
2622
2989
|
// Check if the participant would like to generate a new attestation.
|
|
@@ -2654,11 +3021,12 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2654
3021
|
*/
|
|
2655
3022
|
const contribute = async (opt) => {
|
|
2656
3023
|
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
2657
|
-
// Check for authentication.
|
|
2658
|
-
const { user, providerUserId, token } = await checkAuth(firebaseApp);
|
|
2659
3024
|
// Get options.
|
|
2660
3025
|
const ceremonyOpt = opt.ceremony;
|
|
2661
3026
|
const entropyOpt = opt.entropy;
|
|
3027
|
+
const { auth } = opt;
|
|
3028
|
+
// Check for authentication.
|
|
3029
|
+
const { user, providerUserId, token } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
|
|
2662
3030
|
// Prepare data.
|
|
2663
3031
|
let selectedCeremony;
|
|
2664
3032
|
// Retrieve the opened ceremonies.
|
|
@@ -2684,7 +3052,7 @@ const contribute = async (opt) => {
|
|
|
2684
3052
|
}
|
|
2685
3053
|
else {
|
|
2686
3054
|
// Prompt the user to select a ceremony from the opened ones.
|
|
2687
|
-
selectedCeremony = await promptForCeremonySelection(ceremoniesOpenedForContributions, false);
|
|
3055
|
+
selectedCeremony = await promptForCeremonySelection(ceremoniesOpenedForContributions, false, "Which ceremony would you like to contribute to?");
|
|
2688
3056
|
}
|
|
2689
3057
|
// Get selected ceremony circuit(s) documents.
|
|
2690
3058
|
const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
|
|
@@ -2694,7 +3062,7 @@ const contribute = async (opt) => {
|
|
|
2694
3062
|
const userDoc = await getDocumentById(firestoreDatabase, commonTerms.collections.users.name, user.uid);
|
|
2695
3063
|
const userData = userDoc.data();
|
|
2696
3064
|
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
|
|
3065
|
+
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
3066
|
process.exit(0);
|
|
2699
3067
|
}
|
|
2700
3068
|
// Check the user's current participant readiness for contribution status (eligible, already contributed, timed out).
|
|
@@ -2845,10 +3213,10 @@ const observe = async () => {
|
|
|
2845
3213
|
// Preserve command execution only for coordinators].
|
|
2846
3214
|
if (!(await isCoordinator(user)))
|
|
2847
3215
|
showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
|
|
2848
|
-
// Get running
|
|
3216
|
+
// Get running ceremonies info (if any).
|
|
2849
3217
|
const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase);
|
|
2850
3218
|
// Ask to select a ceremony.
|
|
2851
|
-
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false);
|
|
3219
|
+
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false, "Which ceremony would you like to observe?");
|
|
2852
3220
|
console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`);
|
|
2853
3221
|
let cursorPos = 0; // Keep track of current cursor position.
|
|
2854
3222
|
const spinner = customSpinner(`Getting ready...`, "clock");
|
|
@@ -2894,7 +3262,7 @@ const handleVerificationKey = async (cloudFunctions, bucketName, finalZkeyLocalF
|
|
|
2894
3262
|
spinner.text = "Writing verification key...";
|
|
2895
3263
|
// Write the verification key locally.
|
|
2896
3264
|
writeLocalJsonFile(verificationKeyLocalFilePath, vKey);
|
|
2897
|
-
await sleep(3000); //
|
|
3265
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2898
3266
|
// Upload verification key to storage.
|
|
2899
3267
|
await multiPartUpload(cloudFunctions, bucketName, verificationKeyStorageFilePath, verificationKeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2900
3268
|
spinner.succeed(`Verification key correctly saved on storage`);
|
|
@@ -2914,13 +3282,13 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2914
3282
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}`;
|
|
2915
3283
|
const verifierPath = packagePath.includes(`src/commands`)
|
|
2916
3284
|
? `${dirname(fileURLToPath(import.meta.url))}/../../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
|
|
2917
|
-
: `${dirname(fileURLToPath(import.meta.url))}
|
|
3285
|
+
: `${dirname(fileURLToPath(import.meta.url))}/../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`;
|
|
2918
3286
|
// Export the Solidity verifier smart contract.
|
|
2919
3287
|
const verifierCode = await exportVerifierContract(finalZkeyLocalFilePath, verifierPath);
|
|
2920
3288
|
spinner.text = `Writing verifier smart contract...`;
|
|
2921
3289
|
// Write the verification key locally.
|
|
2922
3290
|
writeFile(verifierContractLocalFilePath, verifierCode);
|
|
2923
|
-
await sleep(3000); //
|
|
3291
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2924
3292
|
// Upload verifier smart contract to storage.
|
|
2925
3293
|
await multiPartUpload(cloudFunctions, bucketName, verifierContractStorageFilePath, verifierContractLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2926
3294
|
spinner.succeed(`Verifier smart contract correctly saved on storage`);
|
|
@@ -2941,11 +3309,12 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2941
3309
|
* @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
|
|
2942
3310
|
* @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
|
|
2943
3311
|
* @param coordinatorIdentifier <string> - the identifier of the coordinator.
|
|
3312
|
+
* @param circuitsLength <number> - the number of circuits in the ceremony.
|
|
2944
3313
|
*/
|
|
2945
|
-
const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier) => {
|
|
3314
|
+
const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier, circuitsLength) => {
|
|
2946
3315
|
// Step (1).
|
|
2947
|
-
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true);
|
|
2948
|
-
await sleep(2000); //
|
|
3316
|
+
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength);
|
|
3317
|
+
await sleep(2000); // workaround for descriptors.
|
|
2949
3318
|
// Extract data.
|
|
2950
3319
|
const { prefix: circuitPrefix } = circuit.data;
|
|
2951
3320
|
const { prefix: ceremonyPrefix } = ceremony.data;
|
|
@@ -2979,10 +3348,11 @@ const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, cere
|
|
|
2979
3348
|
* @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
|
|
2980
3349
|
* handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
|
|
2981
3350
|
*/
|
|
2982
|
-
const finalize = async () => {
|
|
3351
|
+
const finalize = async (opt) => {
|
|
2983
3352
|
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
2984
3353
|
// Check for authentication.
|
|
2985
|
-
const {
|
|
3354
|
+
const { auth } = opt;
|
|
3355
|
+
const { user, providerUserId, token: coordinatorAccessToken } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp);
|
|
2986
3356
|
// Preserve command execution only for coordinators.
|
|
2987
3357
|
if (!(await isCoordinator(user)))
|
|
2988
3358
|
showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true);
|
|
@@ -2993,7 +3363,7 @@ const finalize = async () => {
|
|
|
2993
3363
|
showError(COMMAND_ERRORS.COMMAND_FINALIZED_NO_CLOSED_CEREMONIES, true);
|
|
2994
3364
|
console.log(`${theme.symbols.warning} The computation of the final contribution could take the bulk of your computational resources and memory based on the size of the circuit ${theme.emojis.fire}\n`);
|
|
2995
3365
|
// Prompt for ceremony selection.
|
|
2996
|
-
const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true);
|
|
3366
|
+
const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true, "Which ceremony would you like to finalize?");
|
|
2997
3367
|
// Get coordinator participant document.
|
|
2998
3368
|
let participant = await getDocumentById(firestoreDatabase, getParticipantsCollectionPath(selectedCeremony.id), user.uid);
|
|
2999
3369
|
const isCoordinatorReadyForCeremonyFinalization = await checkAndPrepareCoordinatorForFinalization(firebaseFunctions, selectedCeremony.id);
|
|
@@ -3017,7 +3387,7 @@ const finalize = async () => {
|
|
|
3017
3387
|
const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
|
|
3018
3388
|
// Handle finalization for each ceremony circuit.
|
|
3019
3389
|
for await (const circuit of circuits)
|
|
3020
|
-
await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId);
|
|
3390
|
+
await handleCircuitFinalization(firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId, circuits.length);
|
|
3021
3391
|
process.stdout.write(`\n`);
|
|
3022
3392
|
const spinner = customSpinner(`Wrapping up the finalization of the ceremony...`, "clock");
|
|
3023
3393
|
spinner.start();
|
|
@@ -3032,7 +3402,7 @@ const finalize = async () => {
|
|
|
3032
3402
|
// Generate attestation with final contributions.
|
|
3033
3403
|
const publicAttestation = await generateValidContributionsAttestation(firestoreDatabase, circuits, selectedCeremony.id, participant.id, contributions, providerUserId, ceremonyName, true);
|
|
3034
3404
|
// Write public attestation locally.
|
|
3035
|
-
writeFile(
|
|
3405
|
+
writeFile(getFinalAttestationLocalFilePath(`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
3036
3406
|
await sleep(3000); // workaround for file descriptor unexpected close.
|
|
3037
3407
|
const gistUrl = await publishGist(coordinatorAccessToken, publicAttestation, ceremonyName, prefix);
|
|
3038
3408
|
console.log(`\n${theme.symbols.info} Your public final attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
@@ -3093,7 +3463,9 @@ const logout = async () => {
|
|
|
3093
3463
|
const auth = getAuth();
|
|
3094
3464
|
await signOut(auth);
|
|
3095
3465
|
// Delete local token.
|
|
3466
|
+
deleteLocalAuthMethod();
|
|
3096
3467
|
deleteLocalAccessToken();
|
|
3468
|
+
deleteLocalBandadaIdentity();
|
|
3097
3469
|
await sleep(3000); // ~3s.
|
|
3098
3470
|
spinner.stop();
|
|
3099
3471
|
console.log(`${theme.symbols.success} Logout successfully completed`);
|
|
@@ -3155,6 +3527,55 @@ const listCeremonies = async () => {
|
|
|
3155
3527
|
}
|
|
3156
3528
|
};
|
|
3157
3529
|
|
|
3530
|
+
const listParticipants = async () => {
|
|
3531
|
+
try {
|
|
3532
|
+
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
3533
|
+
const allCeremonies = await getAllCeremonies(firestoreDatabase);
|
|
3534
|
+
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true, "Which ceremony would you like to see participants?");
|
|
3535
|
+
const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id);
|
|
3536
|
+
const participantsRef = collection(docRef, "participants");
|
|
3537
|
+
const participantsSnapshot = await getDocs(participantsRef);
|
|
3538
|
+
const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data());
|
|
3539
|
+
const usersRef = collection(firestoreDatabase, "users");
|
|
3540
|
+
const usersSnapshot = await getDocs(usersRef);
|
|
3541
|
+
const users = usersSnapshot.docs.map((userDoc) => {
|
|
3542
|
+
const data = userDoc.data();
|
|
3543
|
+
return { id: userDoc.id, ...data };
|
|
3544
|
+
});
|
|
3545
|
+
const participantDetails = participants
|
|
3546
|
+
.map((participant) => {
|
|
3547
|
+
const user = users.find((_user) => _user.id === participant.userId);
|
|
3548
|
+
if (!user)
|
|
3549
|
+
return null;
|
|
3550
|
+
return {
|
|
3551
|
+
id: user.name,
|
|
3552
|
+
status: participant.status,
|
|
3553
|
+
contributionStep: participant.contributionStep,
|
|
3554
|
+
lastUpdated: new Date(participant.lastUpdated)
|
|
3555
|
+
};
|
|
3556
|
+
})
|
|
3557
|
+
.filter((user) => user !== null);
|
|
3558
|
+
const participantsDone = participantDetails.filter((participant) => participant.status === "DONE");
|
|
3559
|
+
console.log(participantDetails);
|
|
3560
|
+
console.log(`${theme.text.underlined("Total participants:")} ${participantDetails.length}`);
|
|
3561
|
+
console.log(`${theme.text.underlined("Total participants finished:")} ${participantsDone.length}`);
|
|
3562
|
+
}
|
|
3563
|
+
catch (err) {
|
|
3564
|
+
showError(`Something went wrong: ${err.toString()}`, true);
|
|
3565
|
+
}
|
|
3566
|
+
process.exit(0);
|
|
3567
|
+
};
|
|
3568
|
+
|
|
3569
|
+
const setCeremonyCommands = (program) => {
|
|
3570
|
+
const ceremony = program.command("ceremony").description("manage ceremonies");
|
|
3571
|
+
ceremony
|
|
3572
|
+
.command("participants")
|
|
3573
|
+
.description("retrieve participants list of a ceremony")
|
|
3574
|
+
.requiredOption("-c, --ceremony <string>", "the prefix of the ceremony you want to retrieve information about", "")
|
|
3575
|
+
.action(listParticipants);
|
|
3576
|
+
return ceremony;
|
|
3577
|
+
};
|
|
3578
|
+
|
|
3158
3579
|
// Get pkg info (e.g., name, version).
|
|
3159
3580
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
3160
3581
|
const { description, version, name } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
|
|
@@ -3163,44 +3584,52 @@ const program = createCommand();
|
|
|
3163
3584
|
program.name(name).description(description).version(version);
|
|
3164
3585
|
// User commands.
|
|
3165
3586
|
program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth);
|
|
3587
|
+
program
|
|
3588
|
+
.command("auth-bandada")
|
|
3589
|
+
.description("authenticate yourself in a privacy-perserving manner using Bandada")
|
|
3590
|
+
.action(authBandada);
|
|
3591
|
+
program
|
|
3592
|
+
.command("auth-siwe")
|
|
3593
|
+
.description("authenticate yourself using your Ethereum account (Sign In With Ethereum - SIWE)")
|
|
3594
|
+
.action(authSIWE);
|
|
3166
3595
|
program
|
|
3167
3596
|
.command("contribute")
|
|
3168
3597
|
.description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
|
|
3169
3598
|
.option("-c, --ceremony <string>", "the prefix of the ceremony you want to contribute for", "")
|
|
3170
3599
|
.option("-e, --entropy <string>", "the entropy (aka toxic waste) of your contribution", "")
|
|
3600
|
+
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
3171
3601
|
.action(contribute);
|
|
3172
3602
|
program
|
|
3173
3603
|
.command("clean")
|
|
3174
3604
|
.description("clean up output generated by commands from the current working directory")
|
|
3175
3605
|
.action(clean);
|
|
3176
|
-
program
|
|
3177
|
-
.command("list")
|
|
3178
|
-
.description("List all ceremonies prefixes")
|
|
3179
|
-
.action(listCeremonies);
|
|
3606
|
+
program.command("list").description("List all ceremonies prefixes").action(listCeremonies);
|
|
3180
3607
|
program
|
|
3181
3608
|
.command("logout")
|
|
3182
3609
|
.description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
|
|
3183
3610
|
.action(logout);
|
|
3184
3611
|
program
|
|
3185
3612
|
.command("validate")
|
|
3186
|
-
.description("
|
|
3613
|
+
.description("validate that a Ceremony Setup file is correct")
|
|
3187
3614
|
.requiredOption("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3188
3615
|
.option("-c, --constraints <number>", "The number of constraints to check against")
|
|
3189
3616
|
.action(validate);
|
|
3190
3617
|
// Only coordinator commands.
|
|
3191
|
-
const
|
|
3192
|
-
|
|
3618
|
+
const coordinate = program.command("coordinate").description("commands for coordinating a ceremony");
|
|
3619
|
+
coordinate
|
|
3193
3620
|
.command("setup")
|
|
3194
3621
|
.description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
|
|
3195
|
-
.option(
|
|
3196
|
-
.option(
|
|
3622
|
+
.option("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3623
|
+
.option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
|
|
3197
3624
|
.action(setup);
|
|
3198
|
-
|
|
3625
|
+
coordinate
|
|
3199
3626
|
.command("observe")
|
|
3200
3627
|
.description("observe in real-time the waiting queue of each ceremony circuit")
|
|
3201
3628
|
.action(observe);
|
|
3202
|
-
|
|
3629
|
+
coordinate
|
|
3203
3630
|
.command("finalize")
|
|
3204
3631
|
.description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
|
|
3632
|
+
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
3205
3633
|
.action(finalize);
|
|
3634
|
+
setCeremonyCommands(program);
|
|
3206
3635
|
program.parseAsync(process.argv);
|