@devtion/devcli 0.0.0-57a8ab9 → 0.0.0-5fad82d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.env +7 -0
- package/dist/index.js +324 -97
- package/dist/public/mini-semaphore.wasm +0 -0
- package/dist/public/mini-semaphore.zkey +0 -0
- package/dist/types/commands/authSIWE.d.ts +7 -0
- package/dist/types/commands/index.d.ts +1 -0
- package/dist/types/commands/setup.d.ts +2 -2
- package/dist/types/lib/localConfigs.d.ts +19 -0
- package/dist/types/lib/prompts.d.ts +1 -1
- package/dist/types/types/index.d.ts +57 -0
- package/package.json +4 -2
- package/src/commands/auth.ts +7 -1
- package/src/commands/authBandada.ts +90 -69
- package/src/commands/authSIWE.ts +185 -0
- package/src/commands/ceremony/listParticipants.ts +33 -7
- package/src/commands/contribute.ts +36 -18
- package/src/commands/finalize.ts +5 -1
- package/src/commands/index.ts +1 -0
- package/src/commands/logout.ts +2 -1
- package/src/commands/observe.ts +5 -1
- package/src/commands/setup.ts +3 -3
- package/src/index.ts +5 -0
- package/src/lib/errors.ts +1 -1
- package/src/lib/localConfigs.ts +27 -0
- package/src/lib/prompts.ts +3 -6
- package/src/lib/services.ts +29 -17
- package/src/lib/utils.ts +4 -3
- package/src/types/index.ts +62 -0
package/dist/.env
CHANGED
|
@@ -45,4 +45,11 @@ CONFIG_CEREMONY_BUCKET_POSTFIX=-p0tion-development-environment
|
|
|
45
45
|
# The amount of time in seconds which indicates the duration about the validity of a pre-signed URL.
|
|
46
46
|
# default: 7200 seconds = 2 hours.
|
|
47
47
|
CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS=7200
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Sign In With Ethereum
|
|
51
|
+
# Auth0 client id
|
|
52
|
+
AUTH_SIWE_CLIENT_ID=tRuFnJNoPTJtKr1RynYfty6uJ16QzHXA
|
|
53
|
+
# The Auth0 application url that support SIWE + Device Flow Authentication
|
|
54
|
+
AUTH0_APPLICATION_URL=https://dev-l0tyk1agsmopw1xa.us.auth0.com
|
|
48
55
|
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
|
|
@@ -17,7 +17,7 @@ import boxen from 'boxen';
|
|
|
17
17
|
import { pipeline } from 'node:stream';
|
|
18
18
|
import { promisify } from 'node:util';
|
|
19
19
|
import fetch$1 from 'node-fetch';
|
|
20
|
-
import { 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';
|
|
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';
|
|
21
21
|
import fetch from '@adobe/node-fetch-retry';
|
|
22
22
|
import { request } from '@octokit/request';
|
|
23
23
|
import { SingleBar, Presets } from 'cli-progress';
|
|
@@ -122,7 +122,7 @@ const COMMAND_ERRORS = {
|
|
|
122
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.`,
|
|
123
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.`,
|
|
124
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.`,
|
|
125
|
-
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.`,
|
|
126
126
|
COMMAND_SETUP_ABORT: `You chose to abort the setup process.`,
|
|
127
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.`,
|
|
128
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.`,
|
|
@@ -238,6 +238,14 @@ const checkAndMakeNewDirectoryIfNonexistent = (directoryLocalPath) => {
|
|
|
238
238
|
const writeLocalJsonFile = (filePath, data) => {
|
|
239
239
|
fs.writeFileSync(filePath, JSON.stringify(data), "utf-8");
|
|
240
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
|
+
};
|
|
241
249
|
|
|
242
250
|
// Get npm package name.
|
|
243
251
|
const packagePath$4 = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
@@ -257,6 +265,10 @@ const config = new Conf({
|
|
|
257
265
|
bandadaIdentity: {
|
|
258
266
|
type: "string",
|
|
259
267
|
default: ""
|
|
268
|
+
},
|
|
269
|
+
authMethod: {
|
|
270
|
+
type: "string",
|
|
271
|
+
default: ""
|
|
260
272
|
}
|
|
261
273
|
}
|
|
262
274
|
});
|
|
@@ -336,6 +348,20 @@ const setLocalBandadaIdentity = (identity) => config.set("bandadaIdentity", iden
|
|
|
336
348
|
* Delete the stored Bandada identity.
|
|
337
349
|
*/
|
|
338
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");
|
|
339
365
|
/**
|
|
340
366
|
* Get the complete local file path.
|
|
341
367
|
* @param cwd <string> - the current working directory path.
|
|
@@ -497,7 +523,7 @@ const getUserHandleFromProviderUserId = (providerUserId) => {
|
|
|
497
523
|
if (providerUserId.indexOf("-") === -1) {
|
|
498
524
|
return providerUserId;
|
|
499
525
|
}
|
|
500
|
-
return providerUserId.
|
|
526
|
+
return providerUserId.substring(0, providerUserId.lastIndexOf("-"));
|
|
501
527
|
};
|
|
502
528
|
/**
|
|
503
529
|
* Return a custom spinner.
|
|
@@ -827,7 +853,7 @@ const handleStartOrResumeContribution = async (cloudFunctions, firestoreDatabase
|
|
|
827
853
|
spinner.start();
|
|
828
854
|
// Read local transcript file info to get the contribution hash.
|
|
829
855
|
const transcriptContents = readFile(transcriptLocalFilePath);
|
|
830
|
-
const matchContributionHash = transcriptContents.match(
|
|
856
|
+
const matchContributionHash = transcriptContents.match(contribHashRegex);
|
|
831
857
|
if (!matchContributionHash)
|
|
832
858
|
showError(COMMAND_ERRORS.COMMAND_CONTRIBUTE_FINALIZE_NO_TRANSCRIPT_CONTRIBUTION_HASH_MATCH, true);
|
|
833
859
|
// Format contribution hash.
|
|
@@ -1416,7 +1442,7 @@ const promptPotSelector = async (options) => {
|
|
|
1416
1442
|
* @param isFinalizing <boolean> - true when the coordinator must select a ceremony for finalization; otherwise false (participant selects a ceremony for contribution).
|
|
1417
1443
|
* @returns Promise<FirebaseDocumentInfo> - the Firestore document of the selected ceremony.
|
|
1418
1444
|
*/
|
|
1419
|
-
const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing) => {
|
|
1445
|
+
const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing, messageToDisplay) => {
|
|
1420
1446
|
// Prepare state.
|
|
1421
1447
|
const choices = [];
|
|
1422
1448
|
// Prepare choices x ceremony.
|
|
@@ -1434,9 +1460,7 @@ const promptForCeremonySelection = async (ceremoniesDocuments, isFinalizing) =>
|
|
|
1434
1460
|
const { ceremony } = await prompts({
|
|
1435
1461
|
type: "select",
|
|
1436
1462
|
name: "ceremony",
|
|
1437
|
-
message: theme.text.bold(
|
|
1438
|
-
? "Which ceremony would you like to contribute to?"
|
|
1439
|
-
: "Which ceremony would you like to finalize?"),
|
|
1463
|
+
message: theme.text.bold(messageToDisplay),
|
|
1440
1464
|
choices,
|
|
1441
1465
|
initial: 0
|
|
1442
1466
|
});
|
|
@@ -1589,20 +1613,30 @@ const checkAuth = async (firebaseApp) => {
|
|
|
1589
1613
|
const token = String(getLocalAccessToken());
|
|
1590
1614
|
let providerUserId;
|
|
1591
1615
|
let username;
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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
|
+
}
|
|
1606
1640
|
}
|
|
1607
1641
|
// Get current authenticated user.
|
|
1608
1642
|
const user = getCurrentFirebaseAuthUser(firebaseApp);
|
|
@@ -1749,7 +1783,7 @@ const displayCeremonySummary = (ceremonyInputData, circuits) => {
|
|
|
1749
1783
|
};
|
|
1750
1784
|
/**
|
|
1751
1785
|
* Check if the smallest Powers of Tau has already been downloaded/stored in the correspondent local path
|
|
1752
|
-
* @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.
|
|
1753
1787
|
* @param powers <string> - the smallest amount of powers needed for the given circuit (should be in a 'XY' stringified form).
|
|
1754
1788
|
* @param ptauCompleteFilename <string> - the complete file name of the powers of tau file to be downloaded.
|
|
1755
1789
|
* @returns <Promise<void>>
|
|
@@ -1763,7 +1797,7 @@ const checkAndDownloadSmallestPowersOfTau = async (powers, ptauCompleteFilename)
|
|
|
1763
1797
|
.map((dirent) => dirent.name);
|
|
1764
1798
|
// Check if already downloaded or not.
|
|
1765
1799
|
if (smallestPtauFileForGivenPowers.length === 0) {
|
|
1766
|
-
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`);
|
|
1767
1801
|
spinner.start();
|
|
1768
1802
|
// Download smallest Powers of Tau file from remote server.
|
|
1769
1803
|
const streamPipeline = promisify(pipeline);
|
|
@@ -1878,7 +1912,7 @@ const handleCircuitArtifactUploadToStorage = async (firebaseFunctions, bucketNam
|
|
|
1878
1912
|
* @notice The setup command allows the coordinator of the ceremony to prepare the next ceremony by interacting with the CLI.
|
|
1879
1913
|
* @dev For proper execution, the command must be run in a folder containing the R1CS files related to the circuits
|
|
1880
1914
|
* for which the coordinator wants to create the ceremony. The command will download the necessary Tau powers
|
|
1881
|
-
* from
|
|
1915
|
+
* from PPoT ceremony Phase 1 Setup Ceremony.
|
|
1882
1916
|
* @param cmd? <any> - the path to the ceremony setup file.
|
|
1883
1917
|
*/
|
|
1884
1918
|
const setup = async (cmd) => {
|
|
@@ -2243,6 +2277,7 @@ const auth = async () => {
|
|
|
2243
2277
|
// Generate a new access token using Github Device Flow (OAuth 2.0).
|
|
2244
2278
|
const newToken = await executeGithubDeviceFlow(String(process.env.AUTH_GITHUB_CLIENT_ID));
|
|
2245
2279
|
// Store the new access token.
|
|
2280
|
+
setLocalAuthMethod("github");
|
|
2246
2281
|
setLocalAccessToken(newToken);
|
|
2247
2282
|
}
|
|
2248
2283
|
else
|
|
@@ -2285,67 +2320,225 @@ const isGroupMember = async (groupId, identity) => {
|
|
|
2285
2320
|
|
|
2286
2321
|
const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env;
|
|
2287
2322
|
const authBandada = async () => {
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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
|
|
2306
2373
|
});
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
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`);
|
|
2329
2418
|
spinner.start();
|
|
2330
|
-
|
|
2331
|
-
|
|
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);
|
|
2332
2489
|
const result = await cf({
|
|
2333
|
-
|
|
2334
|
-
publicSignals
|
|
2490
|
+
auth0Token
|
|
2335
2491
|
});
|
|
2336
|
-
const {
|
|
2492
|
+
const { token, valid, message } = result.data;
|
|
2337
2493
|
if (!valid) {
|
|
2338
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();
|
|
2339
2541
|
}
|
|
2340
|
-
spinner.succeed(`Proof verified.\n`);
|
|
2341
|
-
spinner.text = `Authenticating...`;
|
|
2342
|
-
spinner.start();
|
|
2343
|
-
// 7. Auth to p0tion firebase
|
|
2344
|
-
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
2345
|
-
setLocalAccessToken(token);
|
|
2346
|
-
spinner.succeed(`Authenticated as ${theme.text.bold(userCredentials.user.uid)}.`);
|
|
2347
|
-
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2348
|
-
process.exit(0);
|
|
2349
2542
|
};
|
|
2350
2543
|
|
|
2351
2544
|
/**
|
|
@@ -2470,8 +2663,8 @@ const handleDiskSpaceRequirementForNextContribution = async (cloudFunctions, cer
|
|
|
2470
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
|
|
2471
2664
|
? theme.text.bold(`< 0.01`)
|
|
2472
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`);
|
|
2473
|
-
const {
|
|
2474
|
-
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;
|
|
2475
2668
|
if (circuitSequencePosition > 1) {
|
|
2476
2669
|
console.log(`${theme.symbols.info} Please note, you have time until ceremony ends to free up your memory and complete remaining contributions`);
|
|
2477
2670
|
// Asks the contributor if their wants to terminate contributions for the ceremony.
|
|
@@ -2540,8 +2733,8 @@ const handlePublicAttestation = async (firestoreDatabase, circuits, ceremonyId,
|
|
|
2540
2733
|
writeFile(getAttestationLocalFilePath(`${ceremonyPrefix}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
2541
2734
|
await sleep(1000); // workaround for file descriptor unexpected close.
|
|
2542
2735
|
let gistUrl = "";
|
|
2543
|
-
const
|
|
2544
|
-
if (
|
|
2736
|
+
const isGithub = getLocalAuthMethod() === "github";
|
|
2737
|
+
if (isGithub) {
|
|
2545
2738
|
gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
|
|
2546
2739
|
console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
2547
2740
|
}
|
|
@@ -2597,6 +2790,7 @@ const listenToCeremonyCircuitDocumentChanges = (firestoreDatabase, ceremonyId, p
|
|
|
2597
2790
|
}
|
|
2598
2791
|
});
|
|
2599
2792
|
};
|
|
2793
|
+
let contributionInProgress = false;
|
|
2600
2794
|
/**
|
|
2601
2795
|
* Listen to current authenticated participant document changes.
|
|
2602
2796
|
* @dev this is the core business logic related to the execution of the contribute command.
|
|
@@ -2724,11 +2918,21 @@ const listenToParticipantDocumentChanges = async (firestoreDatabase, cloudFuncti
|
|
|
2724
2918
|
(!noTemporaryContributionData && resumingWithSameTemporaryData);
|
|
2725
2919
|
// Scenario (3.B).
|
|
2726
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
|
+
}
|
|
2727
2925
|
// Communicate resume / start of the contribution to participant.
|
|
2728
2926
|
await simpleLoader(`${changedContributionStep === "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */ ? `Starting` : `Resuming`} your contribution...`, `clock`, 3000);
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
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
|
+
}
|
|
2732
2936
|
}
|
|
2733
2937
|
// Scenario (3.A).
|
|
2734
2938
|
else if (isWaitingForContribution)
|
|
@@ -2848,7 +3052,7 @@ const contribute = async (opt) => {
|
|
|
2848
3052
|
}
|
|
2849
3053
|
else {
|
|
2850
3054
|
// Prompt the user to select a ceremony from the opened ones.
|
|
2851
|
-
selectedCeremony = await promptForCeremonySelection(ceremoniesOpenedForContributions, false);
|
|
3055
|
+
selectedCeremony = await promptForCeremonySelection(ceremoniesOpenedForContributions, false, "Which ceremony would you like to contribute to?");
|
|
2852
3056
|
}
|
|
2853
3057
|
// Get selected ceremony circuit(s) documents.
|
|
2854
3058
|
const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id);
|
|
@@ -3012,7 +3216,7 @@ const observe = async () => {
|
|
|
3012
3216
|
// Get running ceremonies info (if any).
|
|
3013
3217
|
const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase);
|
|
3014
3218
|
// Ask to select a ceremony.
|
|
3015
|
-
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false);
|
|
3219
|
+
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false, "Which ceremony would you like to observe?");
|
|
3016
3220
|
console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`);
|
|
3017
3221
|
let cursorPos = 0; // Keep track of current cursor position.
|
|
3018
3222
|
const spinner = customSpinner(`Getting ready...`, "clock");
|
|
@@ -3159,7 +3363,7 @@ const finalize = async (opt) => {
|
|
|
3159
3363
|
showError(COMMAND_ERRORS.COMMAND_FINALIZED_NO_CLOSED_CEREMONIES, true);
|
|
3160
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`);
|
|
3161
3365
|
// Prompt for ceremony selection.
|
|
3162
|
-
const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true);
|
|
3366
|
+
const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true, "Which ceremony would you like to finalize?");
|
|
3163
3367
|
// Get coordinator participant document.
|
|
3164
3368
|
let participant = await getDocumentById(firestoreDatabase, getParticipantsCollectionPath(selectedCeremony.id), user.uid);
|
|
3165
3369
|
const isCoordinatorReadyForCeremonyFinalization = await checkAndPrepareCoordinatorForFinalization(firebaseFunctions, selectedCeremony.id);
|
|
@@ -3259,6 +3463,7 @@ const logout = async () => {
|
|
|
3259
3463
|
const auth = getAuth();
|
|
3260
3464
|
await signOut(auth);
|
|
3261
3465
|
// Delete local token.
|
|
3466
|
+
deleteLocalAuthMethod();
|
|
3262
3467
|
deleteLocalAccessToken();
|
|
3263
3468
|
deleteLocalBandadaIdentity();
|
|
3264
3469
|
await sleep(3000); // ~3s.
|
|
@@ -3326,16 +3531,34 @@ const listParticipants = async () => {
|
|
|
3326
3531
|
try {
|
|
3327
3532
|
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
3328
3533
|
const allCeremonies = await getAllCeremonies(firestoreDatabase);
|
|
3329
|
-
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true);
|
|
3534
|
+
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true, "Which ceremony would you like to see participants?");
|
|
3330
3535
|
const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id);
|
|
3331
3536
|
const participantsRef = collection(docRef, "participants");
|
|
3332
3537
|
const participantsSnapshot = await getDocs(participantsRef);
|
|
3333
|
-
const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data()
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
const
|
|
3337
|
-
|
|
3338
|
-
|
|
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}`);
|
|
3339
3562
|
}
|
|
3340
3563
|
catch (err) {
|
|
3341
3564
|
showError(`Something went wrong: ${err.toString()}`, true);
|
|
@@ -3365,6 +3588,10 @@ program
|
|
|
3365
3588
|
.command("auth-bandada")
|
|
3366
3589
|
.description("authenticate yourself in a privacy-perserving manner using Bandada")
|
|
3367
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);
|
|
3368
3595
|
program
|
|
3369
3596
|
.command("contribute")
|
|
3370
3597
|
.description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth command using Sign In With Ethereum
|
|
3
|
+
* @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.
|
|
4
|
+
* @dev Under the hood, the command handles a manual Device Flow following the guidelines in the SIWE documentation.
|
|
5
|
+
*/
|
|
6
|
+
declare const authSIWE: () => Promise<void>;
|
|
7
|
+
export default authSIWE;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as setup } from "./setup.js";
|
|
2
2
|
export { default as auth } from "./auth.js";
|
|
3
3
|
export { default as authBandada } from "./authBandada.js";
|
|
4
|
+
export { default as authSIWE } from "./authSIWE.js";
|
|
4
5
|
export { default as contribute } from "./contribute.js";
|
|
5
6
|
export { default as observe } from "./observe.js";
|
|
6
7
|
export { default as finalize } from "./finalize.js";
|