@devtion/devcli 0.0.0-56ecf35 → 0.0.0-57a8ab9
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 +186 -22
- package/dist/types/commands/authBandada.d.ts +2 -0
- package/dist/types/commands/ceremony/index.d.ts +3 -0
- package/dist/types/commands/ceremony/listParticipants.d.ts +2 -0
- package/dist/types/commands/index.d.ts +1 -0
- package/dist/types/lib/bandada.d.ts +6 -0
- package/dist/types/lib/files.d.ts +1 -0
- package/dist/types/lib/localConfigs.d.ts +19 -0
- package/dist/types/types/index.d.ts +12 -0
- package/package.json +8 -3
- package/src/commands/authBandada.ts +99 -0
- package/src/commands/ceremony/index.ts +20 -0
- package/src/commands/ceremony/listParticipants.ts +30 -0
- package/src/commands/contribute.ts +13 -8
- package/src/commands/index.ts +1 -0
- package/src/commands/logout.ts +2 -1
- package/src/index.ts +13 -5
- package/src/lib/bandada.ts +51 -0
- package/src/lib/localConfigs.ts +27 -0
- package/src/lib/services.ts +26 -13
- package/src/lib/utils.ts +3 -1
- package/src/types/index.ts +13 -0
package/dist/.env
CHANGED
|
@@ -23,6 +23,13 @@ FIREBASE_CF_URL_VERIFY_CONTRIBUTION=https://verifycontribution-mq4aqokliq-ew.a.r
|
|
|
23
23
|
|
|
24
24
|
# The unique identifier for the Github client associated to the OAuth Application.
|
|
25
25
|
AUTH_GITHUB_CLIENT_ID=e9f8a5fabdfe0d95618c
|
|
26
|
+
### BANDADA AUTHENTICATION ###
|
|
27
|
+
# The Bandada API URL to be used for authentication.
|
|
28
|
+
BANDADA_API_URL=https://api.bandada.pse.dev/
|
|
29
|
+
# The Bandada group id that will be used for the credentials criteria (e.g. GH followers)
|
|
30
|
+
BANDADA_GROUP_ID=40887196405294111455930028907236
|
|
31
|
+
# The Bandada dashboard URL to administrate the groups
|
|
32
|
+
BANDADA_DASHBOARD_URL=https://bandada.pse.dev/
|
|
26
33
|
|
|
27
34
|
### AWS S3 STORAGE ###
|
|
28
35
|
### These configs are related to the configuration of the interaction with the
|
package/dist/index.js
CHANGED
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createCommand } from 'commander';
|
|
12
12
|
import fs, { readFileSync, createWriteStream, existsSync, renameSync } from 'fs';
|
|
13
|
-
import { dirname } from 'path';
|
|
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 { commonTerms, formatZkeyIndex, getZkeyStorageFilePath, finalContributionIndex, createCustomLoggerForFile, getBucketName, progressToNextContributionStep, permanentlyStoreCurrentContributionTimeAndHash, convertToDoubleDigits, multiPartUpload, verifyContribution, generateGetObjectPreSignedUrl, convertBytesOrKbToGb, numExpIterations, getDocumentById, getParticipantsCollectionPath, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs, extractPrefix, autoGenerateEntropy, vmConfigurationTypes, initializeFirebaseCoreServices, signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator, parseCeremonyFile, blake512FromPath, checkIfObjectExist, setupCeremony, genesisZkeyIndex, getR1csStorageFilePath, getWasmStorageFilePath, getPotStorageFilePath, extractPoTFromFilename, potFileDownloadMainUrl, createS3Bucket, potFilenameTemplate, getR1CSInfo, getOpenedCeremonies, getCeremonyCircuits, checkParticipantForCeremony, getCurrentActiveParticipantTimeout, getCircuitBySequencePosition, getCircuitContributionsFromContributor, progressToNextCircuitForContribution, resumeContributionAfterTimeoutExpiration, generateValidContributionsAttestation, getContributionsValidityForContributor, getClosedCeremonies, checkAndPrepareCoordinatorForFinalization, computeSHA256ToHex, finalizeCeremony, getVerificationKeyStorageFilePath, verificationKeyAcronym, getVerifierContractStorageFilePath, verifierSmartContractAcronym, finalizeCircuit, exportVkey, exportVerifierContract } from '@devtion/actions';
|
|
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';
|
|
21
21
|
import fetch from '@adobe/node-fetch-retry';
|
|
22
22
|
import { request } from '@octokit/request';
|
|
23
23
|
import { SingleBar, Presets } from 'cli-progress';
|
|
24
24
|
import dotenv from 'dotenv';
|
|
25
|
-
import { GithubAuthProvider, getAuth, signOut } from 'firebase/auth';
|
|
25
|
+
import { GithubAuthProvider, signInWithCustomToken, getAuth, signOut } from 'firebase/auth';
|
|
26
26
|
import { getDiskInfoSync } from 'node-disk-info';
|
|
27
27
|
import ora from 'ora';
|
|
28
28
|
import { Timer } from 'timer-node';
|
|
@@ -36,7 +36,10 @@ import figlet from 'figlet';
|
|
|
36
36
|
import { createOAuthDeviceAuth } from '@octokit/auth-oauth-device';
|
|
37
37
|
import clipboard from 'clipboardy';
|
|
38
38
|
import open from 'open';
|
|
39
|
-
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';
|
|
40
43
|
import readline from 'readline';
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -250,6 +253,10 @@ const config = new Conf({
|
|
|
250
253
|
accessToken: {
|
|
251
254
|
type: "string",
|
|
252
255
|
default: ""
|
|
256
|
+
},
|
|
257
|
+
bandadaIdentity: {
|
|
258
|
+
type: "string",
|
|
259
|
+
default: ""
|
|
253
260
|
}
|
|
254
261
|
}
|
|
255
262
|
});
|
|
@@ -310,6 +317,25 @@ const setLocalAccessToken = (token) => config.set("accessToken", token);
|
|
|
310
317
|
* Delete the stored access token.
|
|
311
318
|
*/
|
|
312
319
|
const deleteLocalAccessToken = () => config.delete("accessToken");
|
|
320
|
+
/**
|
|
321
|
+
* Return the Bandada identity, if present.
|
|
322
|
+
* @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
|
|
323
|
+
*/
|
|
324
|
+
const getLocalBandadaIdentity = () => config.get("bandadaIdentity");
|
|
325
|
+
/**
|
|
326
|
+
* Check if the Bandada identity exists in the local storage.
|
|
327
|
+
* @returns <boolean>
|
|
328
|
+
*/
|
|
329
|
+
const checkLocalBandadaIdentity = () => config.has("bandadaIdentity") && !!config.get("bandadaIdentity");
|
|
330
|
+
/**
|
|
331
|
+
* Set the Bandada identity.
|
|
332
|
+
* @param identity <string> - the Bandada identity to be stored.
|
|
333
|
+
*/
|
|
334
|
+
const setLocalBandadaIdentity = (identity) => config.set("bandadaIdentity", identity);
|
|
335
|
+
/**
|
|
336
|
+
* Delete the stored Bandada identity.
|
|
337
|
+
*/
|
|
338
|
+
const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity");
|
|
313
339
|
/**
|
|
314
340
|
* Get the complete local file path.
|
|
315
341
|
* @param cwd <string> - the current working directory path.
|
|
@@ -420,7 +446,7 @@ const getGithubAuthenticatedUserGists = async (githubToken, params) => {
|
|
|
420
446
|
headers: {
|
|
421
447
|
authorization: `token ${githubToken}`
|
|
422
448
|
},
|
|
423
|
-
per_page: params.perPage,
|
|
449
|
+
per_page: params.perPage, // max items per page = 100.
|
|
424
450
|
page: params.page
|
|
425
451
|
});
|
|
426
452
|
if (response && response.status === 200)
|
|
@@ -468,8 +494,9 @@ const getPublicAttestationGist = async (githubToken, publicAttestationFilename)
|
|
|
468
494
|
* @returns <string> - the third-party provider handle of the user.
|
|
469
495
|
*/
|
|
470
496
|
const getUserHandleFromProviderUserId = (providerUserId) => {
|
|
471
|
-
if (providerUserId.indexOf("-") === -1)
|
|
472
|
-
|
|
497
|
+
if (providerUserId.indexOf("-") === -1) {
|
|
498
|
+
return providerUserId;
|
|
499
|
+
}
|
|
473
500
|
return providerUserId.split("-")[0];
|
|
474
501
|
};
|
|
475
502
|
/**
|
|
@@ -1560,16 +1587,27 @@ const checkAuth = async (firebaseApp) => {
|
|
|
1560
1587
|
showError(THIRD_PARTY_SERVICES_ERRORS.GITHUB_NOT_AUTHENTICATED, true);
|
|
1561
1588
|
// Retrieve local access token.
|
|
1562
1589
|
const token = String(getLocalAccessToken());
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1590
|
+
let providerUserId;
|
|
1591
|
+
let username;
|
|
1592
|
+
const isLocalBandadaIdentityStored = checkLocalBandadaIdentity();
|
|
1593
|
+
if (isLocalBandadaIdentityStored) {
|
|
1594
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1595
|
+
providerUserId = userCredentials.user.uid;
|
|
1596
|
+
username = providerUserId;
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
// Get credentials.
|
|
1600
|
+
const credentials = exchangeGithubTokenForCredentials(token);
|
|
1601
|
+
// Sign in to Firebase using credentials.
|
|
1602
|
+
await signInToFirebase(firebaseApp, credentials);
|
|
1603
|
+
// Get Github unique identifier (handle-id).
|
|
1604
|
+
providerUserId = await getGithubProviderUserId(String(token));
|
|
1605
|
+
username = getUserHandleFromProviderUserId(providerUserId);
|
|
1606
|
+
}
|
|
1567
1607
|
// Get current authenticated user.
|
|
1568
1608
|
const user = getCurrentFirebaseAuthUser(firebaseApp);
|
|
1569
|
-
// Get Github unique identifier (handle-id).
|
|
1570
|
-
const providerUserId = await getGithubProviderUserId(String(token));
|
|
1571
1609
|
// Greet the user.
|
|
1572
|
-
console.log(`Greetings, @${theme.text.bold(
|
|
1610
|
+
console.log(`Greetings, @${theme.text.bold(username)} ${theme.emojis.wave}\n`);
|
|
1573
1611
|
return {
|
|
1574
1612
|
user,
|
|
1575
1613
|
token,
|
|
@@ -2225,6 +2263,91 @@ const auth = async () => {
|
|
|
2225
2263
|
terminate(providerUserId);
|
|
2226
2264
|
};
|
|
2227
2265
|
|
|
2266
|
+
const { BANDADA_API_URL } = process.env;
|
|
2267
|
+
const bandadaApi = new ApiSdk(BANDADA_API_URL);
|
|
2268
|
+
const addMemberToGroup = async (groupId, dashboardUrl, identity) => {
|
|
2269
|
+
const commitment = identity.commitment.toString();
|
|
2270
|
+
const group = await bandadaApi.getGroup(groupId);
|
|
2271
|
+
const providerName = group.credentials.id.split("_")[0].toLowerCase();
|
|
2272
|
+
// 6. open a new window with the url:
|
|
2273
|
+
const url = `${dashboardUrl}credentials?group=${groupId}&member=${commitment}&provider=${providerName}`;
|
|
2274
|
+
console.log(`${theme.text.bold(`Verification URL:`)} ${theme.text.underlined(url)}`);
|
|
2275
|
+
open(url);
|
|
2276
|
+
const { confirmation } = await askForConfirmation("Did you join the Bandada group in the browser?");
|
|
2277
|
+
if (!confirmation)
|
|
2278
|
+
showError("You must join the Bandada group to continue the login process", true);
|
|
2279
|
+
};
|
|
2280
|
+
const isGroupMember = async (groupId, identity) => {
|
|
2281
|
+
const commitment = identity.commitment.toString();
|
|
2282
|
+
const isMember = await bandadaApi.isGroupMember(groupId, commitment);
|
|
2283
|
+
return isMember;
|
|
2284
|
+
};
|
|
2285
|
+
|
|
2286
|
+
const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env;
|
|
2287
|
+
const authBandada = async () => {
|
|
2288
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2289
|
+
const spinner = customSpinner(`Checking identity string for Semaphore...`, `clock`);
|
|
2290
|
+
spinner.start();
|
|
2291
|
+
// 1. check if _identity string exists in local storage
|
|
2292
|
+
let identityString;
|
|
2293
|
+
const isIdentityStringStored = checkLocalBandadaIdentity();
|
|
2294
|
+
if (isIdentityStringStored) {
|
|
2295
|
+
identityString = getLocalBandadaIdentity();
|
|
2296
|
+
spinner.succeed(`Identity seed found\n`);
|
|
2297
|
+
}
|
|
2298
|
+
else {
|
|
2299
|
+
spinner.warn(`Identity seed not found\n`);
|
|
2300
|
+
// 2. generate a random _identity string and save it in local storage
|
|
2301
|
+
const { seed } = await prompts({
|
|
2302
|
+
type: "text",
|
|
2303
|
+
name: "seed",
|
|
2304
|
+
message: theme.text.bold(`Enter a secret string to use as your identity seed in Semaphore:`),
|
|
2305
|
+
initial: false
|
|
2306
|
+
});
|
|
2307
|
+
identityString = seed;
|
|
2308
|
+
setLocalBandadaIdentity(identityString);
|
|
2309
|
+
}
|
|
2310
|
+
// 3. create a semaphore identity with _identity string as a seed
|
|
2311
|
+
const identity = new Identity(identityString);
|
|
2312
|
+
// 4. check if the user is a member of the group
|
|
2313
|
+
console.log(`Checking Bandada membership...`);
|
|
2314
|
+
const isMember = await isGroupMember(BANDADA_GROUP_ID, identity);
|
|
2315
|
+
if (!isMember) {
|
|
2316
|
+
await addMemberToGroup(BANDADA_GROUP_ID, BANDADA_DASHBOARD_URL, identity);
|
|
2317
|
+
}
|
|
2318
|
+
// 5. generate a proof that the user owns the commitment.
|
|
2319
|
+
spinner.text = `Generating proof of identity...`;
|
|
2320
|
+
spinner.start();
|
|
2321
|
+
// publicSignals = [hash(externalNullifier, identityNullifier), commitment]
|
|
2322
|
+
const { proof, publicSignals } = await groth16.fullProve({
|
|
2323
|
+
identityTrapdoor: identity.trapdoor,
|
|
2324
|
+
identityNullifier: identity.nullifier,
|
|
2325
|
+
externalNullifier: BANDADA_GROUP_ID
|
|
2326
|
+
}, path.join(path.resolve(), "/public/mini-semaphore.wasm"), path.join(path.resolve(), "/public/mini-semaphore.zkey"));
|
|
2327
|
+
spinner.succeed(`Proof generated.\n`);
|
|
2328
|
+
spinner.text = `Sending proof to verification...`;
|
|
2329
|
+
spinner.start();
|
|
2330
|
+
// 6. send proof to a cloud function that verifies it and checks membership
|
|
2331
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.bandadaValidateProof);
|
|
2332
|
+
const result = await cf({
|
|
2333
|
+
proof,
|
|
2334
|
+
publicSignals
|
|
2335
|
+
});
|
|
2336
|
+
const { valid, token, message } = result.data;
|
|
2337
|
+
if (!valid) {
|
|
2338
|
+
showError(message, true);
|
|
2339
|
+
}
|
|
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
|
+
};
|
|
2350
|
+
|
|
2228
2351
|
/**
|
|
2229
2352
|
* Return the verification result for latest contribution.
|
|
2230
2353
|
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
@@ -2416,8 +2539,12 @@ const handlePublicAttestation = async (firestoreDatabase, circuits, ceremonyId,
|
|
|
2416
2539
|
// Write public attestation locally.
|
|
2417
2540
|
writeFile(getAttestationLocalFilePath(`${ceremonyPrefix}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
2418
2541
|
await sleep(1000); // workaround for file descriptor unexpected close.
|
|
2419
|
-
|
|
2420
|
-
|
|
2542
|
+
let gistUrl = "";
|
|
2543
|
+
const isBandada = checkLocalBandadaIdentity();
|
|
2544
|
+
if (!isBandada) {
|
|
2545
|
+
gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
|
|
2546
|
+
console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
2547
|
+
}
|
|
2421
2548
|
// Prepare a ready-to-share tweet.
|
|
2422
2549
|
await handleTweetGeneration(ceremonyName, gistUrl);
|
|
2423
2550
|
};
|
|
@@ -3133,6 +3260,7 @@ const logout = async () => {
|
|
|
3133
3260
|
await signOut(auth);
|
|
3134
3261
|
// Delete local token.
|
|
3135
3262
|
deleteLocalAccessToken();
|
|
3263
|
+
deleteLocalBandadaIdentity();
|
|
3136
3264
|
await sleep(3000); // ~3s.
|
|
3137
3265
|
spinner.stop();
|
|
3138
3266
|
console.log(`${theme.symbols.success} Logout successfully completed`);
|
|
@@ -3194,6 +3322,37 @@ const listCeremonies = async () => {
|
|
|
3194
3322
|
}
|
|
3195
3323
|
};
|
|
3196
3324
|
|
|
3325
|
+
const listParticipants = async () => {
|
|
3326
|
+
try {
|
|
3327
|
+
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
3328
|
+
const allCeremonies = await getAllCeremonies(firestoreDatabase);
|
|
3329
|
+
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true);
|
|
3330
|
+
const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id);
|
|
3331
|
+
const participantsRef = collection(docRef, "participants");
|
|
3332
|
+
const participantsSnapshot = await getDocs(participantsRef);
|
|
3333
|
+
const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data().userId);
|
|
3334
|
+
console.log(participants);
|
|
3335
|
+
/* const usersRef = collection(firestoreDatabase, "users")
|
|
3336
|
+
const usersSnapshot = await getDocs(usersRef)
|
|
3337
|
+
const users = usersSnapshot.docs.map((userDoc) => userDoc.data())
|
|
3338
|
+
console.log(users) */
|
|
3339
|
+
}
|
|
3340
|
+
catch (err) {
|
|
3341
|
+
showError(`Something went wrong: ${err.toString()}`, true);
|
|
3342
|
+
}
|
|
3343
|
+
process.exit(0);
|
|
3344
|
+
};
|
|
3345
|
+
|
|
3346
|
+
const setCeremonyCommands = (program) => {
|
|
3347
|
+
const ceremony = program.command("ceremony").description("manage ceremonies");
|
|
3348
|
+
ceremony
|
|
3349
|
+
.command("participants")
|
|
3350
|
+
.description("retrieve participants list of a ceremony")
|
|
3351
|
+
.requiredOption("-c, --ceremony <string>", "the prefix of the ceremony you want to retrieve information about", "")
|
|
3352
|
+
.action(listParticipants);
|
|
3353
|
+
return ceremony;
|
|
3354
|
+
};
|
|
3355
|
+
|
|
3197
3356
|
// Get pkg info (e.g., name, version).
|
|
3198
3357
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
3199
3358
|
const { description, version, name } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
|
|
@@ -3202,6 +3361,10 @@ const program = createCommand();
|
|
|
3202
3361
|
program.name(name).description(description).version(version);
|
|
3203
3362
|
// User commands.
|
|
3204
3363
|
program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth);
|
|
3364
|
+
program
|
|
3365
|
+
.command("auth-bandada")
|
|
3366
|
+
.description("authenticate yourself in a privacy-perserving manner using Bandada")
|
|
3367
|
+
.action(authBandada);
|
|
3205
3368
|
program
|
|
3206
3369
|
.command("contribute")
|
|
3207
3370
|
.description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
|
|
@@ -3220,25 +3383,26 @@ program
|
|
|
3220
3383
|
.action(logout);
|
|
3221
3384
|
program
|
|
3222
3385
|
.command("validate")
|
|
3223
|
-
.description("
|
|
3386
|
+
.description("validate that a Ceremony Setup file is correct")
|
|
3224
3387
|
.requiredOption("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3225
3388
|
.option("-c, --constraints <number>", "The number of constraints to check against")
|
|
3226
3389
|
.action(validate);
|
|
3227
3390
|
// Only coordinator commands.
|
|
3228
|
-
const
|
|
3229
|
-
|
|
3391
|
+
const coordinate = program.command("coordinate").description("commands for coordinating a ceremony");
|
|
3392
|
+
coordinate
|
|
3230
3393
|
.command("setup")
|
|
3231
3394
|
.description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
|
|
3232
3395
|
.option("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3233
3396
|
.option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
|
|
3234
3397
|
.action(setup);
|
|
3235
|
-
|
|
3398
|
+
coordinate
|
|
3236
3399
|
.command("observe")
|
|
3237
3400
|
.description("observe in real-time the waiting queue of each ceremony circuit")
|
|
3238
3401
|
.action(observe);
|
|
3239
|
-
|
|
3402
|
+
coordinate
|
|
3240
3403
|
.command("finalize")
|
|
3241
3404
|
.description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
|
|
3242
3405
|
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
3243
3406
|
.action(finalize);
|
|
3407
|
+
setCeremonyCommands(program);
|
|
3244
3408
|
program.parseAsync(process.argv);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as setup } from "./setup.js";
|
|
2
2
|
export { default as auth } from "./auth.js";
|
|
3
|
+
export { default as authBandada } from "./authBandada.js";
|
|
3
4
|
export { default as contribute } from "./contribute.js";
|
|
4
5
|
export { default as observe } from "./observe.js";
|
|
5
6
|
export { default as finalize } from "./finalize.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { GroupResponse } from "@bandada/api-sdk";
|
|
2
|
+
import { Identity } from "@semaphore-protocol/identity";
|
|
3
|
+
export declare const getGroup: (groupId: string) => Promise<GroupResponse | null>;
|
|
4
|
+
export declare const getMembersOfGroup: (groupId: string) => Promise<string[] | null>;
|
|
5
|
+
export declare const addMemberToGroup: (groupId: string, dashboardUrl: string, identity: Identity) => Promise<void>;
|
|
6
|
+
export declare const isGroupMember: (groupId: string, identity: Identity) => Promise<boolean>;
|
|
@@ -35,6 +35,25 @@ export declare const setLocalAccessToken: (token: string) => void;
|
|
|
35
35
|
* Delete the stored access token.
|
|
36
36
|
*/
|
|
37
37
|
export declare const deleteLocalAccessToken: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Return the Bandada identity, if present.
|
|
40
|
+
* @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
|
|
41
|
+
*/
|
|
42
|
+
export declare const getLocalBandadaIdentity: () => string | unknown;
|
|
43
|
+
/**
|
|
44
|
+
* Check if the Bandada identity exists in the local storage.
|
|
45
|
+
* @returns <boolean>
|
|
46
|
+
*/
|
|
47
|
+
export declare const checkLocalBandadaIdentity: () => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Set the Bandada identity.
|
|
50
|
+
* @param identity <string> - the Bandada identity to be stored.
|
|
51
|
+
*/
|
|
52
|
+
export declare const setLocalBandadaIdentity: (identity: string) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Delete the stored Bandada identity.
|
|
55
|
+
*/
|
|
56
|
+
export declare const deleteLocalBandadaIdentity: () => void;
|
|
38
57
|
/**
|
|
39
58
|
* Get the complete local file path.
|
|
40
59
|
* @param cwd <string> - the current working directory path.
|
|
@@ -63,3 +63,15 @@ export type GithubGistFile = {
|
|
|
63
63
|
raw_url: string;
|
|
64
64
|
size: number;
|
|
65
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* Define the return object of the function that verifies the Bandada membership and proof.
|
|
68
|
+
* @typedef {Object} VerifiedBandadaResponse
|
|
69
|
+
* @property {boolean} valid - true if the proof is valid and the user is a member of the group; otherwise false.
|
|
70
|
+
* @property {string} message - a message describing the result of the verification.
|
|
71
|
+
* @property {string} token - the custom access token.
|
|
72
|
+
*/
|
|
73
|
+
export type VerifiedBandadaResponse = {
|
|
74
|
+
valid: boolean;
|
|
75
|
+
message: string;
|
|
76
|
+
token: string;
|
|
77
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devtion/devcli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.0-
|
|
4
|
+
"version": "0.0.0-57a8ab9",
|
|
5
5
|
"description": "All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies",
|
|
6
6
|
"repository": "git@github.com:privacy-scaling-explorations/p0tion.git",
|
|
7
7
|
"homepage": "https://github.com/privacy-scaling-explorations/p0tion",
|
|
@@ -34,11 +34,13 @@
|
|
|
34
34
|
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
|
|
35
35
|
"start": "ts-node --esm ./src/index.ts",
|
|
36
36
|
"auth": "yarn start auth",
|
|
37
|
+
"auth:bandada": "yarn start auth-bandada",
|
|
37
38
|
"contribute": "yarn start contribute",
|
|
38
39
|
"clean": "yarn start clean",
|
|
39
40
|
"list": "yarn start list",
|
|
40
41
|
"logout": "yarn start logout",
|
|
41
42
|
"validate": "yarn start validate",
|
|
43
|
+
"ceremony:participants": "yarn start ceremony participants",
|
|
42
44
|
"coordinate:setup": "yarn start coordinate setup",
|
|
43
45
|
"coordinate:observe": "yarn start coordinate observe",
|
|
44
46
|
"coordinate:finalize": "yarn start coordinate finalize",
|
|
@@ -65,10 +67,12 @@
|
|
|
65
67
|
"dependencies": {
|
|
66
68
|
"@adobe/node-fetch-retry": "^2.2.0",
|
|
67
69
|
"@aws-sdk/client-s3": "^3.329.0",
|
|
70
|
+
"@bandada/api-sdk": "^1.0.0-beta.1",
|
|
68
71
|
"@devtion/actions": "latest",
|
|
69
72
|
"@octokit/auth-oauth-app": "^5.0.5",
|
|
70
73
|
"@octokit/auth-oauth-device": "^4.0.4",
|
|
71
74
|
"@octokit/request": "^6.2.3",
|
|
75
|
+
"@semaphore-protocol/identity": "^3.15.1",
|
|
72
76
|
"blakejs": "^1.2.1",
|
|
73
77
|
"boxen": "^7.1.0",
|
|
74
78
|
"chalk": "^5.2.0",
|
|
@@ -78,6 +82,7 @@
|
|
|
78
82
|
"commander": "^10.0.1",
|
|
79
83
|
"conf": "^11.0.1",
|
|
80
84
|
"dotenv": "^16.0.3",
|
|
85
|
+
"ethers": "^6.9.0",
|
|
81
86
|
"figlet": "^1.6.0",
|
|
82
87
|
"firebase": "^9.21.0",
|
|
83
88
|
"log-symbols": "^5.1.0",
|
|
@@ -90,12 +95,12 @@
|
|
|
90
95
|
"prompts": "^2.4.2",
|
|
91
96
|
"rimraf": "^5.0.0",
|
|
92
97
|
"rollup": "^3.21.6",
|
|
93
|
-
"snarkjs": "
|
|
98
|
+
"snarkjs": "0.7.3",
|
|
94
99
|
"timer-node": "^5.0.7",
|
|
95
100
|
"winston": "^3.8.2"
|
|
96
101
|
},
|
|
97
102
|
"publishConfig": {
|
|
98
103
|
"access": "public"
|
|
99
104
|
},
|
|
100
|
-
"gitHead": "
|
|
105
|
+
"gitHead": "6bddf60f1121786c19ad4437e0448de4c859b829"
|
|
101
106
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Identity } from "@semaphore-protocol/identity"
|
|
2
|
+
|
|
3
|
+
import { commonTerms } from "@devtion/actions"
|
|
4
|
+
import { httpsCallable } from "firebase/functions"
|
|
5
|
+
import { groth16 } from "snarkjs"
|
|
6
|
+
import path from "path"
|
|
7
|
+
import { getAuth, signInWithCustomToken } from "firebase/auth"
|
|
8
|
+
import theme from "../lib/theme.js"
|
|
9
|
+
import { customSpinner } from "../lib/utils.js"
|
|
10
|
+
import { VerifiedBandadaResponse } from "../types/index.js"
|
|
11
|
+
import { showError } from "../lib/errors.js"
|
|
12
|
+
import { bootstrapCommandExecutionAndServices } from "../lib/services.js"
|
|
13
|
+
import { addMemberToGroup, isGroupMember } from "../lib/bandada.js"
|
|
14
|
+
import {
|
|
15
|
+
checkLocalBandadaIdentity,
|
|
16
|
+
getLocalBandadaIdentity,
|
|
17
|
+
setLocalAccessToken,
|
|
18
|
+
setLocalBandadaIdentity
|
|
19
|
+
} from "../lib/localConfigs.js"
|
|
20
|
+
import prompts from "prompts"
|
|
21
|
+
|
|
22
|
+
const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env
|
|
23
|
+
|
|
24
|
+
const authBandada = async () => {
|
|
25
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices()
|
|
26
|
+
const spinner = customSpinner(`Checking identity string for Semaphore...`, `clock`)
|
|
27
|
+
spinner.start()
|
|
28
|
+
// 1. check if _identity string exists in local storage
|
|
29
|
+
let identityString: string | unknown
|
|
30
|
+
const isIdentityStringStored = checkLocalBandadaIdentity()
|
|
31
|
+
if (isIdentityStringStored) {
|
|
32
|
+
identityString = getLocalBandadaIdentity()
|
|
33
|
+
spinner.succeed(`Identity seed found\n`)
|
|
34
|
+
} else {
|
|
35
|
+
spinner.warn(`Identity seed not found\n`)
|
|
36
|
+
// 2. generate a random _identity string and save it in local storage
|
|
37
|
+
const { seed } = await prompts({
|
|
38
|
+
type: "text",
|
|
39
|
+
name: "seed",
|
|
40
|
+
message: theme.text.bold(`Enter a secret string to use as your identity seed in Semaphore:`),
|
|
41
|
+
initial: false
|
|
42
|
+
})
|
|
43
|
+
identityString = seed as string
|
|
44
|
+
setLocalBandadaIdentity(identityString as string)
|
|
45
|
+
}
|
|
46
|
+
// 3. create a semaphore identity with _identity string as a seed
|
|
47
|
+
const identity = new Identity(identityString as string)
|
|
48
|
+
|
|
49
|
+
// 4. check if the user is a member of the group
|
|
50
|
+
console.log(`Checking Bandada membership...`)
|
|
51
|
+
const isMember = await isGroupMember(BANDADA_GROUP_ID, identity)
|
|
52
|
+
if (!isMember) {
|
|
53
|
+
await addMemberToGroup(BANDADA_GROUP_ID, BANDADA_DASHBOARD_URL, identity)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 5. generate a proof that the user owns the commitment.
|
|
57
|
+
spinner.text = `Generating proof of identity...`
|
|
58
|
+
spinner.start()
|
|
59
|
+
// publicSignals = [hash(externalNullifier, identityNullifier), commitment]
|
|
60
|
+
const { proof, publicSignals } = await groth16.fullProve(
|
|
61
|
+
{
|
|
62
|
+
identityTrapdoor: identity.trapdoor,
|
|
63
|
+
identityNullifier: identity.nullifier,
|
|
64
|
+
externalNullifier: BANDADA_GROUP_ID
|
|
65
|
+
},
|
|
66
|
+
path.join(path.resolve(), "/public/mini-semaphore.wasm"),
|
|
67
|
+
path.join(path.resolve(), "/public/mini-semaphore.zkey")
|
|
68
|
+
)
|
|
69
|
+
spinner.succeed(`Proof generated.\n`)
|
|
70
|
+
spinner.text = `Sending proof to verification...`
|
|
71
|
+
spinner.start()
|
|
72
|
+
// 6. send proof to a cloud function that verifies it and checks membership
|
|
73
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.bandadaValidateProof)
|
|
74
|
+
const result = await cf({
|
|
75
|
+
proof,
|
|
76
|
+
publicSignals
|
|
77
|
+
})
|
|
78
|
+
const { valid, token, message } = result.data as VerifiedBandadaResponse
|
|
79
|
+
if (!valid) {
|
|
80
|
+
showError(message, true)
|
|
81
|
+
}
|
|
82
|
+
spinner.succeed(`Proof verified.\n`)
|
|
83
|
+
spinner.text = `Authenticating...`
|
|
84
|
+
spinner.start()
|
|
85
|
+
// 7. Auth to p0tion firebase
|
|
86
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token)
|
|
87
|
+
setLocalAccessToken(token)
|
|
88
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(userCredentials.user.uid)}.`)
|
|
89
|
+
|
|
90
|
+
console.log(
|
|
91
|
+
`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(
|
|
92
|
+
`phase2cli logout`
|
|
93
|
+
)} command`
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
process.exit(0)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default authBandada
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from "commander"
|
|
2
|
+
import listParticipants from "./listParticipants.js"
|
|
3
|
+
|
|
4
|
+
const setCeremonyCommands = (program: Command) => {
|
|
5
|
+
const ceremony = program.command("ceremony").description("manage ceremonies")
|
|
6
|
+
|
|
7
|
+
ceremony
|
|
8
|
+
.command("participants")
|
|
9
|
+
.description("retrieve participants list of a ceremony")
|
|
10
|
+
.requiredOption(
|
|
11
|
+
"-c, --ceremony <string>",
|
|
12
|
+
"the prefix of the ceremony you want to retrieve information about",
|
|
13
|
+
""
|
|
14
|
+
)
|
|
15
|
+
.action(listParticipants)
|
|
16
|
+
|
|
17
|
+
return ceremony
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default setCeremonyCommands
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { collection, doc, getDocs } from "firebase/firestore"
|
|
2
|
+
import { commonTerms, getAllCeremonies } from "@devtion/actions"
|
|
3
|
+
import { bootstrapCommandExecutionAndServices } from "../../lib/services.js"
|
|
4
|
+
import { showError } from "../../lib/errors.js"
|
|
5
|
+
import { promptForCeremonySelection } from "../../lib/prompts.js"
|
|
6
|
+
|
|
7
|
+
const listParticipants = async () => {
|
|
8
|
+
try {
|
|
9
|
+
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices()
|
|
10
|
+
|
|
11
|
+
const allCeremonies = await getAllCeremonies(firestoreDatabase)
|
|
12
|
+
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true)
|
|
13
|
+
|
|
14
|
+
const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id)
|
|
15
|
+
const participantsRef = collection(docRef, "participants")
|
|
16
|
+
const participantsSnapshot = await getDocs(participantsRef)
|
|
17
|
+
const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data().userId)
|
|
18
|
+
console.log(participants)
|
|
19
|
+
|
|
20
|
+
/* const usersRef = collection(firestoreDatabase, "users")
|
|
21
|
+
const usersSnapshot = await getDocs(usersRef)
|
|
22
|
+
const users = usersSnapshot.docs.map((userDoc) => userDoc.data())
|
|
23
|
+
console.log(users) */
|
|
24
|
+
} catch (err: any) {
|
|
25
|
+
showError(`Something went wrong: ${err.toString()}`, true)
|
|
26
|
+
}
|
|
27
|
+
process.exit(0)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default listParticipants
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
} from "../lib/utils.js"
|
|
42
42
|
import { COMMAND_ERRORS, showError } from "../lib/errors.js"
|
|
43
43
|
import { authWithToken, bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
|
|
44
|
-
import { getAttestationLocalFilePath, localPaths } from "../lib/localConfigs.js"
|
|
44
|
+
import { checkLocalBandadaIdentity, getAttestationLocalFilePath, localPaths } from "../lib/localConfigs.js"
|
|
45
45
|
import theme from "../lib/theme.js"
|
|
46
46
|
import { checkAndMakeNewDirectoryIfNonexistent, writeFile } from "../lib/files.js"
|
|
47
47
|
|
|
@@ -419,14 +419,19 @@ export const handlePublicAttestation = async (
|
|
|
419
419
|
|
|
420
420
|
await sleep(1000) // workaround for file descriptor unexpected close.
|
|
421
421
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
theme.text.underlined(gistUrl)
|
|
427
|
-
)})`
|
|
428
|
-
)
|
|
422
|
+
let gistUrl = ""
|
|
423
|
+
const isBandada = checkLocalBandadaIdentity()
|
|
424
|
+
if (!isBandada) {
|
|
425
|
+
gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix)
|
|
429
426
|
|
|
427
|
+
console.log(
|
|
428
|
+
`\n${
|
|
429
|
+
theme.symbols.info
|
|
430
|
+
} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(
|
|
431
|
+
theme.text.underlined(gistUrl)
|
|
432
|
+
)})`
|
|
433
|
+
)
|
|
434
|
+
}
|
|
430
435
|
// Prepare a ready-to-share tweet.
|
|
431
436
|
await handleTweetGeneration(ceremonyName, gistUrl)
|
|
432
437
|
}
|
package/src/commands/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as setup } from "./setup.js"
|
|
2
2
|
export { default as auth } from "./auth.js"
|
|
3
|
+
export { default as authBandada } from "./authBandada.js"
|
|
3
4
|
export { default as contribute } from "./contribute.js"
|
|
4
5
|
export { default as observe } from "./observe.js"
|
|
5
6
|
export { default as finalize } from "./finalize.js"
|
package/src/commands/logout.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { showError } from "../lib/errors.js"
|
|
|
6
6
|
import { askForConfirmation } from "../lib/prompts.js"
|
|
7
7
|
import { customSpinner, sleep, terminate } from "../lib/utils.js"
|
|
8
8
|
import theme from "../lib/theme.js"
|
|
9
|
-
import { deleteLocalAccessToken } from "../lib/localConfigs.js"
|
|
9
|
+
import { deleteLocalAccessToken, deleteLocalBandadaIdentity } from "../lib/localConfigs.js"
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Logout command.
|
|
@@ -53,6 +53,7 @@ const logout = async () => {
|
|
|
53
53
|
|
|
54
54
|
// Delete local token.
|
|
55
55
|
deleteLocalAccessToken()
|
|
56
|
+
deleteLocalBandadaIdentity()
|
|
56
57
|
|
|
57
58
|
await sleep(3000) // ~3s.
|
|
58
59
|
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "url"
|
|
|
7
7
|
import {
|
|
8
8
|
setup,
|
|
9
9
|
auth,
|
|
10
|
+
authBandada,
|
|
10
11
|
contribute,
|
|
11
12
|
observe,
|
|
12
13
|
finalize,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
validate,
|
|
16
17
|
listCeremonies
|
|
17
18
|
} from "./commands/index.js"
|
|
19
|
+
import setCeremonyCommands from "./commands/ceremony/index.js"
|
|
18
20
|
|
|
19
21
|
// Get pkg info (e.g., name, version).
|
|
20
22
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
|
|
@@ -26,6 +28,10 @@ program.name(name).description(description).version(version)
|
|
|
26
28
|
|
|
27
29
|
// User commands.
|
|
28
30
|
program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth)
|
|
31
|
+
program
|
|
32
|
+
.command("auth-bandada")
|
|
33
|
+
.description("authenticate yourself in a privacy-perserving manner using Bandada")
|
|
34
|
+
.action(authBandada)
|
|
29
35
|
program
|
|
30
36
|
.command("contribute")
|
|
31
37
|
.description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
|
|
@@ -44,27 +50,27 @@ program
|
|
|
44
50
|
.action(logout)
|
|
45
51
|
program
|
|
46
52
|
.command("validate")
|
|
47
|
-
.description("
|
|
53
|
+
.description("validate that a Ceremony Setup file is correct")
|
|
48
54
|
.requiredOption("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
49
55
|
.option("-c, --constraints <number>", "The number of constraints to check against")
|
|
50
56
|
.action(validate)
|
|
51
57
|
|
|
52
58
|
// Only coordinator commands.
|
|
53
|
-
const
|
|
59
|
+
const coordinate = program.command("coordinate").description("commands for coordinating a ceremony")
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
coordinate
|
|
56
62
|
.command("setup")
|
|
57
63
|
.description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
|
|
58
64
|
.option("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
59
65
|
.option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
|
|
60
66
|
.action(setup)
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
coordinate
|
|
63
69
|
.command("observe")
|
|
64
70
|
.description("observe in real-time the waiting queue of each ceremony circuit")
|
|
65
71
|
.action(observe)
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
coordinate
|
|
68
74
|
.command("finalize")
|
|
69
75
|
.description(
|
|
70
76
|
"finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract"
|
|
@@ -72,4 +78,6 @@ ceremony
|
|
|
72
78
|
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
73
79
|
.action(finalize)
|
|
74
80
|
|
|
81
|
+
setCeremonyCommands(program)
|
|
82
|
+
|
|
75
83
|
program.parseAsync(process.argv)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ApiSdk, GroupResponse } from "@bandada/api-sdk"
|
|
2
|
+
import { Identity } from "@semaphore-protocol/identity"
|
|
3
|
+
import open from "open"
|
|
4
|
+
|
|
5
|
+
import { askForConfirmation } from "../lib/prompts.js"
|
|
6
|
+
import { showError } from "./errors.js"
|
|
7
|
+
import theme from "../lib/theme.js"
|
|
8
|
+
|
|
9
|
+
const { BANDADA_API_URL } = process.env
|
|
10
|
+
|
|
11
|
+
const bandadaApi = new ApiSdk(BANDADA_API_URL)
|
|
12
|
+
|
|
13
|
+
export const getGroup = async (groupId: string): Promise<GroupResponse | null> => {
|
|
14
|
+
try {
|
|
15
|
+
const group = await bandadaApi.getGroup(groupId)
|
|
16
|
+
return group
|
|
17
|
+
} catch (error: any) {
|
|
18
|
+
showError(`Bandada getGroup error: ${error}`, true)
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const getMembersOfGroup = async (groupId: string): Promise<string[] | null> => {
|
|
24
|
+
try {
|
|
25
|
+
const group = await bandadaApi.getGroup(groupId)
|
|
26
|
+
return group.members
|
|
27
|
+
} catch (error: any) {
|
|
28
|
+
showError(`Bandada getMembersOfGroup error: ${error}`, true)
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const addMemberToGroup = async (groupId: string, dashboardUrl: string, identity: Identity) => {
|
|
34
|
+
const commitment = identity.commitment.toString()
|
|
35
|
+
const group = await bandadaApi.getGroup(groupId)
|
|
36
|
+
const providerName = group.credentials.id.split("_")[0].toLowerCase()
|
|
37
|
+
|
|
38
|
+
// 6. open a new window with the url:
|
|
39
|
+
const url = `${dashboardUrl}credentials?group=${groupId}&member=${commitment}&provider=${providerName}`
|
|
40
|
+
console.log(`${theme.text.bold(`Verification URL:`)} ${theme.text.underlined(url)}`)
|
|
41
|
+
open(url)
|
|
42
|
+
|
|
43
|
+
const { confirmation } = await askForConfirmation("Did you join the Bandada group in the browser?")
|
|
44
|
+
if (!confirmation) showError("You must join the Bandada group to continue the login process", true)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const isGroupMember = async (groupId: string, identity: Identity): Promise<boolean> => {
|
|
48
|
+
const commitment = identity.commitment.toString()
|
|
49
|
+
const isMember: boolean = await bandadaApi.isGroupMember(groupId, commitment)
|
|
50
|
+
return isMember
|
|
51
|
+
}
|
package/src/lib/localConfigs.ts
CHANGED
|
@@ -24,6 +24,10 @@ const config = new Conf({
|
|
|
24
24
|
accessToken: {
|
|
25
25
|
type: "string",
|
|
26
26
|
default: ""
|
|
27
|
+
},
|
|
28
|
+
bandadaIdentity: {
|
|
29
|
+
type: "string",
|
|
30
|
+
default: ""
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
})
|
|
@@ -91,6 +95,29 @@ export const setLocalAccessToken = (token: string) => config.set("accessToken",
|
|
|
91
95
|
*/
|
|
92
96
|
export const deleteLocalAccessToken = () => config.delete("accessToken")
|
|
93
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Return the Bandada identity, if present.
|
|
100
|
+
* @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
|
|
101
|
+
*/
|
|
102
|
+
export const getLocalBandadaIdentity = (): string | unknown => config.get("bandadaIdentity")
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if the Bandada identity exists in the local storage.
|
|
106
|
+
* @returns <boolean>
|
|
107
|
+
*/
|
|
108
|
+
export const checkLocalBandadaIdentity = (): boolean => config.has("bandadaIdentity") && !!config.get("bandadaIdentity")
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Set the Bandada identity.
|
|
112
|
+
* @param identity <string> - the Bandada identity to be stored.
|
|
113
|
+
*/
|
|
114
|
+
export const setLocalBandadaIdentity = (identity: string) => config.set("bandadaIdentity", identity)
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Delete the stored Bandada identity.
|
|
118
|
+
*/
|
|
119
|
+
export const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity")
|
|
120
|
+
|
|
94
121
|
/**
|
|
95
122
|
* Get the complete local file path.
|
|
96
123
|
* @param cwd <string> - the current working directory path.
|
package/src/lib/services.ts
CHANGED
|
@@ -6,13 +6,18 @@ import {
|
|
|
6
6
|
import clear from "clear"
|
|
7
7
|
import figlet from "figlet"
|
|
8
8
|
import { FirebaseApp } from "firebase/app"
|
|
9
|
-
import { OAuthCredential } from "firebase/auth"
|
|
9
|
+
import { OAuthCredential, getAuth, signInWithCustomToken } from "firebase/auth"
|
|
10
10
|
import dotenv from "dotenv"
|
|
11
11
|
import { fileURLToPath } from "url"
|
|
12
12
|
import { dirname } from "path"
|
|
13
13
|
import { AuthUser } from "../types/index.js"
|
|
14
14
|
import { CONFIG_ERRORS, CORE_SERVICES_ERRORS, showError, THIRD_PARTY_SERVICES_ERRORS } from "./errors.js"
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
checkLocalAccessToken,
|
|
17
|
+
checkLocalBandadaIdentity,
|
|
18
|
+
deleteLocalAccessToken,
|
|
19
|
+
getLocalAccessToken
|
|
20
|
+
} from "./localConfigs.js"
|
|
16
21
|
import theme from "./theme.js"
|
|
17
22
|
import { exchangeGithubTokenForCredentials, getGithubProviderUserId, getUserHandleFromProviderUserId } from "./utils.js"
|
|
18
23
|
|
|
@@ -164,22 +169,30 @@ export const checkAuth = async (firebaseApp: FirebaseApp): Promise<AuthUser> =>
|
|
|
164
169
|
// Retrieve local access token.
|
|
165
170
|
const token = String(getLocalAccessToken())
|
|
166
171
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
let providerUserId: string
|
|
173
|
+
let username: string
|
|
174
|
+
const isLocalBandadaIdentityStored = checkLocalBandadaIdentity()
|
|
175
|
+
if (isLocalBandadaIdentityStored) {
|
|
176
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token)
|
|
177
|
+
providerUserId = userCredentials.user.uid
|
|
178
|
+
username = providerUserId
|
|
179
|
+
} else {
|
|
180
|
+
// Get credentials.
|
|
181
|
+
const credentials = exchangeGithubTokenForCredentials(token)
|
|
182
|
+
|
|
183
|
+
// Sign in to Firebase using credentials.
|
|
184
|
+
await signInToFirebase(firebaseApp, credentials)
|
|
185
|
+
|
|
186
|
+
// Get Github unique identifier (handle-id).
|
|
187
|
+
providerUserId = await getGithubProviderUserId(String(token))
|
|
188
|
+
username = getUserHandleFromProviderUserId(providerUserId)
|
|
189
|
+
}
|
|
172
190
|
|
|
173
191
|
// Get current authenticated user.
|
|
174
192
|
const user = getCurrentFirebaseAuthUser(firebaseApp)
|
|
175
193
|
|
|
176
|
-
// Get Github unique identifier (handle-id).
|
|
177
|
-
const providerUserId = await getGithubProviderUserId(String(token))
|
|
178
|
-
|
|
179
194
|
// Greet the user.
|
|
180
|
-
console.log(
|
|
181
|
-
`Greetings, @${theme.text.bold(getUserHandleFromProviderUserId(providerUserId))} ${theme.emojis.wave}\n`
|
|
182
|
-
)
|
|
195
|
+
console.log(`Greetings, @${theme.text.bold(username)} ${theme.emojis.wave}\n`)
|
|
183
196
|
|
|
184
197
|
return {
|
|
185
198
|
user,
|
package/src/lib/utils.ts
CHANGED
|
@@ -155,7 +155,9 @@ export const getPublicAttestationGist = async (
|
|
|
155
155
|
* @returns <string> - the third-party provider handle of the user.
|
|
156
156
|
*/
|
|
157
157
|
export const getUserHandleFromProviderUserId = (providerUserId: string): string => {
|
|
158
|
-
if (providerUserId.indexOf("-") === -1)
|
|
158
|
+
if (providerUserId.indexOf("-") === -1) {
|
|
159
|
+
return providerUserId
|
|
160
|
+
}
|
|
159
161
|
|
|
160
162
|
return providerUserId.split("-")[0]
|
|
161
163
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -68,3 +68,16 @@ export type GithubGistFile = {
|
|
|
68
68
|
raw_url: string
|
|
69
69
|
size: number
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Define the return object of the function that verifies the Bandada membership and proof.
|
|
74
|
+
* @typedef {Object} VerifiedBandadaResponse
|
|
75
|
+
* @property {boolean} valid - true if the proof is valid and the user is a member of the group; otherwise false.
|
|
76
|
+
* @property {string} message - a message describing the result of the verification.
|
|
77
|
+
* @property {string} token - the custom access token.
|
|
78
|
+
*/
|
|
79
|
+
export type VerifiedBandadaResponse = {
|
|
80
|
+
valid: boolean
|
|
81
|
+
message: string
|
|
82
|
+
token: string
|
|
83
|
+
}
|