UNPKG

@aptos-labs/zk-ceremony

Version:

All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies

331 lines (281 loc) 12.4 kB
#!/usr/bin/env node import open from "open" import { isCoordinator, getClosedCeremonies, getDocumentById, getParticipantsCollectionPath, checkAndPrepareCoordinatorForFinalization, getCeremonyCircuits, getVerificationKeyStorageFilePath, getBucketName, multiPartUpload, finalizeCeremony, generateValidContributionsAttestation, commonTerms, finalContributionIndex, computeSHA256ToHex, finalizeCircuit, verificationKeyAcronym, FirebaseDocumentInfo, exportVkey } from "@aptos-labs/zk-actions" import { Functions } from "firebase/functions" import { Firestore } from "firebase/firestore" import { COMMAND_ERRORS, showError } from "../lib/errors.js" import { customSpinner, generateCustomUrlToTweetAboutParticipation, handleStartOrResumeContribution, publishGist, sleep, terminate } from "../lib/utils.js" import { authWithToken, bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js" import { getFinalAttestationLocalFilePath, getFinalZkeyLocalFilePath, getVerificationKeyLocalFilePath, localPaths } from "../lib/localConfigs.js" import theme from "../lib/theme.js" import { checkAndMakeNewDirectoryIfNonexistent, writeLocalJsonFile, writeFile } from "../lib/files.js" import { promptForCeremonySelection, promptToTypeEntropyOrBeacon } from "../lib/prompts.js" /** * Export and store on the ceremony bucket the verification key for the given final contribution. * @param cloudFunctions <Functions> - the instance of the Firebase cloud functions for the application. * @param bucketName <string> - the name of the ceremony bucket. * @param finalZkeyLocalFilePath <string> - the local file path of the final zKey. * @param verificationKeyLocalFilePath <string> - the local file path of the verification key. * @param verificationKeyStorageFilePath <string> - the storage file path of the verification key. */ export const handleVerificationKey = async ( cloudFunctions: Functions, bucketName: string, finalZkeyLocalFilePath: string, verificationKeyLocalFilePath: string, verificationKeyStorageFilePath: string ) => { const spinner = customSpinner(`Exporting the verification key...`, "clock") spinner.start() // Export the verification key. const vKey = await exportVkey(finalZkeyLocalFilePath) spinner.text = "Writing verification key..." // Write the verification key locally. writeLocalJsonFile(verificationKeyLocalFilePath, vKey) await sleep(3000) // workaround for file descriptor. // Upload verification key to storage. await multiPartUpload( cloudFunctions, bucketName, verificationKeyStorageFilePath, verificationKeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB) ) spinner.succeed(`Verification key correctly saved on storage`) } /** * Handle the process of finalizing a ceremony circuit. * @dev this process results in the extraction of the final ceremony artifacts for the calculation and verification of proofs. * @notice this method must enforce the order among these steps: * 1) Compute the final contribution (zKey). * 2) Extract the verification key (vKey). * 3) Extract the Verifier smart contract (.sol). * 4) Upload the artifacts in the AWS S3 storage. * 5) Complete the final contribution data w/ artifacts references and hashes (cloud function). * @param cloudFunctions <Functions> - the instance of the Firebase cloud functions for the application. * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application. * @param ceremony <FirebaseDocumentInfo> - the Firestore document of the ceremony. * @param circuit <FirebaseDocumentInfo> - the Firestore document of the ceremony circuit. * @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator). * @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony. * @param coordinatorIdentifier <string> - the identifier of the coordinator. * @param circuitsLength <number> - the number of circuits in the ceremony. */ export const handleCircuitFinalization = async ( cloudFunctions: Functions, firestoreDatabase: Firestore, ceremony: FirebaseDocumentInfo, circuit: FirebaseDocumentInfo, participant: FirebaseDocumentInfo, beacon: string, coordinatorIdentifier: string, circuitsLength: number ) => { // Step (1). await handleStartOrResumeContribution( cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength ) await sleep(2000) // workaround for descriptors. // Extract data. const { prefix: circuitPrefix } = circuit.data const { prefix: ceremonyPrefix } = ceremony.data // Prepare local paths. const finalZkeyLocalFilePath = getFinalZkeyLocalFilePath(`${circuitPrefix}_${finalContributionIndex}.zkey`) const verificationKeyLocalFilePath = getVerificationKeyLocalFilePath( `${circuitPrefix}_${verificationKeyAcronym}.json` ) // Prepare storage paths. const verificationKeyStorageFilePath = getVerificationKeyStorageFilePath( circuitPrefix, `${circuitPrefix}_${verificationKeyAcronym}.json` ) // Get ceremony bucket. const bucketName = getBucketName(ceremonyPrefix, String(process.env.CONFIG_CEREMONY_BUCKET_POSTFIX)) // Step (2 & 4). await handleVerificationKey( cloudFunctions, bucketName, finalZkeyLocalFilePath, verificationKeyLocalFilePath, verificationKeyStorageFilePath ) // Step (3 & 4). // await handleVerifierSmartContract( // cloudFunctions, // bucketName, // finalZkeyLocalFilePath, // verifierContractLocalFilePath, // verifierContractStorageFilePath // ) // Step (5). const spinner = customSpinner(`Wrapping up the finalization of the circuit...`, `clock`) spinner.start() // Finalize circuit contribution. await finalizeCircuit(cloudFunctions, ceremony.id, circuit.id, bucketName, beacon) await sleep(2000) spinner.succeed(`Circuit has been finalized correctly`) } /** * Finalize command. * @notice The finalize command allows a coordinator to finalize a Trusted Setup Phase 2 ceremony by providing the final beacon, * computing the final zKeys and extracting the Verifier Smart Contract + Verification Keys per each ceremony circuit. * anyone could use the final zKey to create a proof and everyone else could verify the correctness using the * related verification key (off-chain) or Verifier smart contract (on-chain). * @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to * handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation. */ const finalize = async (opt: any) => { const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices() // Check for authentication. const { auth } = opt const { user, providerUserId, token: coordinatorAccessToken } = auth ? await authWithToken(firebaseApp, auth) : await checkAuth(firebaseApp) // Preserve command execution only for coordinators. if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true) // Retrieve the closed ceremonies (ready for finalization). const ceremoniesClosedForFinalization = await getClosedCeremonies(firestoreDatabase) // Gracefully exit if no ceremonies are closed and ready for finalization. if (!ceremoniesClosedForFinalization.length) showError(COMMAND_ERRORS.COMMAND_FINALIZED_NO_CLOSED_CEREMONIES, true) 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` ) // Prompt for ceremony selection. const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true) // Get coordinator participant document. let participant = await getDocumentById( firestoreDatabase, getParticipantsCollectionPath(selectedCeremony.id), user.uid ) const isCoordinatorReadyForCeremonyFinalization = await checkAndPrepareCoordinatorForFinalization( firebaseFunctions, selectedCeremony.id ) if (!isCoordinatorReadyForCeremonyFinalization) showError(COMMAND_ERRORS.COMMAND_FINALIZED_NOT_READY_FOR_FINALIZATION, true) // Prompt for beacon. const beacon = await promptToTypeEntropyOrBeacon(false) // Compute hash const beaconHash = computeSHA256ToHex(beacon) // Display. console.log(`${theme.symbols.info} Beacon SHA256 hash ${theme.text.bold(beaconHash)}`) // Clean directories. checkAndMakeNewDirectoryIfNonexistent(localPaths.output) checkAndMakeNewDirectoryIfNonexistent(localPaths.finalize) checkAndMakeNewDirectoryIfNonexistent(localPaths.finalZkeys) checkAndMakeNewDirectoryIfNonexistent(localPaths.finalPot) checkAndMakeNewDirectoryIfNonexistent(localPaths.finalAttestations) checkAndMakeNewDirectoryIfNonexistent(localPaths.verificationKeys) checkAndMakeNewDirectoryIfNonexistent(localPaths.verifierContracts) // Get ceremony circuits. const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id) // Handle finalization for each ceremony circuit. for await (const circuit of circuits) await handleCircuitFinalization( firebaseFunctions, firestoreDatabase, selectedCeremony, circuit, participant, beacon, providerUserId, circuits.length ) process.stdout.write(`\n`) const spinner = customSpinner(`Wrapping up the finalization of the ceremony...`, "clock") spinner.start() // Finalize the ceremony. await finalizeCeremony(firebaseFunctions, selectedCeremony.id) spinner.succeed( `Great, you have completed the finalization of the ${theme.text.bold(selectedCeremony.data.title)} ceremony ${ theme.emojis.tada }\n` ) // Get updated coordinator participant document. participant = await getDocumentById(firestoreDatabase, getParticipantsCollectionPath(selectedCeremony.id), user.uid) // Extract updated data. const { contributions } = participant.data()! const { prefix, title: ceremonyName } = selectedCeremony.data // Generate attestation with final contributions. const publicAttestation = await generateValidContributionsAttestation( firestoreDatabase, circuits, selectedCeremony.id, participant.id, contributions, providerUserId, ceremonyName, true ) // Write public attestation locally. writeFile( getFinalAttestationLocalFilePath( `${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log` ), Buffer.from(publicAttestation) ) await sleep(3000) // workaround for file descriptor unexpected close. const gistUrl = await publishGist(coordinatorAccessToken, publicAttestation, ceremonyName, prefix) console.log( `\n${ theme.symbols.info } Your public final attestation has been successfully posted as Github Gist (${theme.text.bold( theme.text.underlined(gistUrl) )})` ) // Generate a ready to share custom url to tweet about ceremony participation. const tweetUrl = generateCustomUrlToTweetAboutParticipation(ceremonyName, gistUrl, true) console.log( `${ theme.symbols.info } We encourage you to tweet about the ceremony finalization by clicking the link below\n\n${theme.text.underlined( tweetUrl )}` ) // Automatically open a webpage with the tweet. await open(tweetUrl) terminate(providerUserId) } export default finalize