diff --git a/packages/actions/src/helpers/verification.ts b/packages/actions/src/helpers/verification.ts index fe9039db..1a89f128 100644 --- a/packages/actions/src/helpers/verification.ts +++ b/packages/actions/src/helpers/verification.ts @@ -318,6 +318,7 @@ export const verifyCeremony = async ( solidityVersion: string, wasmPath: string, circuitInputs: object, + verifierTemplatePath: string, logger?: any ): Promise => { // download all ceremony artifacts @@ -342,15 +343,16 @@ export const verifyCeremony = async ( ) // 2. extract the verifier and the vKey - const templatePath = `${cwd()}/node_modules/snarkjs/templates/verifier_groth16.sol.ejs` - const verifierLocalPath = `${cwd()}/packages/actions/contracts/Verifier_${ceremonyArtifact.circuitPrefix}.sol` + const verifierLocalPath = `${cwd()}/packages/actions/test/data/artifacts/Verifier_${ + ceremonyArtifact.circuitPrefix + }.sol` const vKeyLocalPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_vkey.json` await exportVerifierAndVKey( solidityVersion, ceremonyArtifact.finalZkeyLocalFilePath, verifierLocalPath, vKeyLocalPath, - templatePath + verifierTemplatePath ) // 3. generate a proof and verify it locally diff --git a/packages/actions/src/index.ts b/packages/actions/src/index.ts index fc6916e0..da0a03d5 100644 --- a/packages/actions/src/index.ts +++ b/packages/actions/src/index.ts @@ -33,7 +33,8 @@ export { generateGROTH16Proof, generateZkeyFromScratch, verifyGROTH16Proof, - verifyZKey + verifyZKey, + verifyCeremony } from "./helpers/verification" export { initializeFirebaseCoreServices } from "./helpers/services" export { signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator } from "./helpers/authentication" diff --git a/packages/actions/test/e2e/03-finalization.test.ts b/packages/actions/test/e2e/03-finalization.test.ts index ee50ed5e..084c77f8 100644 --- a/packages/actions/test/e2e/03-finalization.test.ts +++ b/packages/actions/test/e2e/03-finalization.test.ts @@ -70,10 +70,10 @@ describe("Finalization e2e", () => { const verificationKeyFilename = `${finalizizationCircuit?.data.prefix}_vkey.json` const verifierContractFilename = `${finalizizationCircuit?.data.prefix}_verifier.sol` - const verificationKeyLocalPath = `${cwd()}/packages/actions/test/data/${ + const verificationKeyLocalPath = `${cwd()}/packages/actions/test/data/artifacts/${ finalizizationCircuit?.data.prefix }_vkey.json` - const verifierContractLocalPath = `${cwd()}/packages/actions/test/data/${ + const verifierContractLocalPath = `${cwd()}/packages/actions/test/data/artifacts/${ finalizizationCircuit?.data.prefix }_verifier.sol` @@ -149,6 +149,7 @@ describe("Finalization e2e", () => { if (envType === TestingEnvironment.PRODUCTION) { await signInWithEmailAndPassword(userAuth, users[2].data.email, passwords[2]) await createS3Bucket(userFunctions, bucketName) + await sleep(1000) await uploadFileToS3(bucketName, verificationKeyStoragePath, verificationKeyLocalPath) await uploadFileToS3(bucketName, verifierContractStoragePath, verifierContractLocalPath) } diff --git a/packages/actions/test/unit/finalize.test.ts b/packages/actions/test/unit/finalize.test.ts index 6f6794a9..11e2c899 100644 --- a/packages/actions/test/unit/finalize.test.ts +++ b/packages/actions/test/unit/finalize.test.ts @@ -1,8 +1,8 @@ import chai, { expect } from "chai" import chaiAsPromised from "chai-as-promised" -import fs from "fs" import { getAuth, signInWithEmailAndPassword, signOut } from "firebase/auth" import { randomBytes } from "crypto" +import { cwd } from "process" import { deleteAdminApp, initializeAdminServices, @@ -229,8 +229,12 @@ describe("Finalize", () => { ) const circuitData = fakeCircuitsData.fakeCircuitSmallContributors // Filenames. - const verificationKeyFilename = `${circuitData?.data.prefix}_vkey.json` - const verifierContractFilename = `${circuitData?.data.prefix}_verifier.sol` + const verificationKeyFilename = `${cwd()}/packages/actions/test/data/artifacts/${ + circuitData?.data.prefix + }_vkey.json` + const verifierContractFilename = `${cwd()}/packages/actions/test/data/artifacts/${ + circuitData?.data.prefix + }_verifier.sol` // Get storage paths. const verificationKeyStoragePath = getVerificationKeyStorageFilePath( @@ -242,8 +246,6 @@ describe("Finalize", () => { verifierContractFilename ) - fs.writeFileSync(verificationKeyFilename, JSON.stringify({ test: "test" })) - fs.writeFileSync(verifierContractFilename, "pragma solidity ^0.8.0;") beforeAll(async () => { // need to upload data into the bucket await signInWithEmailAndPassword(userAuth, users[1].data.email, passwords[1]) @@ -334,8 +336,6 @@ describe("Finalize", () => { await deleteObjectFromS3(bucketName, verificationKeyStoragePath) await deleteObjectFromS3(bucketName, verifierContractStoragePath) await deleteBucket(bucketName) - fs.unlinkSync(verificationKeyFilename) - fs.unlinkSync(verifierContractFilename) }) }) } diff --git a/packages/actions/test/unit/verification.test.ts b/packages/actions/test/unit/verification.test.ts index 8e8ff8d2..76a2ebe7 100644 --- a/packages/actions/test/unit/verification.test.ts +++ b/packages/actions/test/unit/verification.test.ts @@ -4,6 +4,7 @@ import { getAuth, signInWithEmailAndPassword } from "firebase/auth" import dotenv from "dotenv" import { cwd } from "process" import fs from "fs" +import { UserDocumentReferenceAndData } from "src/types" import { compareCeremonyArtifacts, createS3Bucket, @@ -17,6 +18,7 @@ import { getPotStorageFilePath, getR1csStorageFilePath, getZkeyStorageFilePath, + verifyCeremony, verifyGROTH16Proof, verifyZKey } from "../../src" @@ -75,10 +77,6 @@ describe("Verification utilities", () => { finalZkeyPath = `${cwd()}/../actions/test/data/artifacts/circuit-small_00001.zkey` verifierExportPath = `${cwd()}/../actions/test/data/artifacts/verifier.sol` vKeyExportPath = `${cwd()}/../actions/test/data/artifacts/vkey.json` - r1csPath = `${cwd()}/../actions/test/data/artifacts/circuit.r1cs` - potPath = `${cwd()}/../actions/test/data/artifacts/powersOfTau28_hez_final_02.ptau` - badzkeyPath = `${cwd()}/../actions/test/data/artifacts/bad_circuit_0000.zkey` - wrongZkeyPath = `${cwd()}/../actions/test/data/artifacts/notcircuit_0000.zkey` } else { wasmPath = `${cwd()}/packages/actions/test/data/artifacts/circuit.wasm` zkeyPath = `${cwd()}/packages/actions/test/data/artifacts/circuit_0000.zkey` @@ -92,13 +90,11 @@ describe("Verification utilities", () => { finalZkeyPath = `${cwd()}/packages/actions/test/data/artifacts/circuit-small_00001.zkey` verifierExportPath = `${cwd()}/packages/actions/test/data/artifacts/verifier.sol` vKeyExportPath = `${cwd()}/packages/actions/test/data/artifacts/vkey.json` - r1csPath = `${cwd()}/packages/actions/test/data/artifacts/circuit.r1cs` - potPath = `${cwd()}/packages/actions/test/data/artifacts/powersOfTau28_hez_final_02.ptau` - badzkeyPath = `${cwd()}/packages/actions/test/data/artifacts/bad_circuit_0000.zkey` - wrongZkeyPath = `${cwd()}/packages/actions/test/data/artifacts/notcircuit_0000.zkey` } - const solidityVersion = "0.8.10" + const verifierTemplatePath = `${cwd()}/node_modules/snarkjs/templates/verifier_groth16.sol.ejs` + + const solidityVersion = "0.8.18" const { ceremonyBucketPostfix } = getStorageConfiguration() @@ -107,13 +103,15 @@ describe("Verification utilities", () => { const { userApp, userFirestore, userFunctions } = initializeUserServices() const userAuth = getAuth(userApp) - const users = [fakeUsersData.fakeUser1] + const users: UserDocumentReferenceAndData[] = [fakeUsersData.fakeUser1] const passwords = generateUserPasswords(users.length) beforeAll(async () => { for (let i = 0; i < users.length; i++) { users[i].uid = await createMockUser(userApp, users[i].data.email, passwords[i], true, adminAuth) } + await sleep(1000) + await signInWithEmailAndPassword(userAuth, users[0].data.email, passwords[0]) }) describe("generateGROTH16Proof", () => { @@ -170,22 +168,12 @@ describe("Verification utilities", () => { describe("exportVerifierContract", () => { if (envType === TestingEnvironment.PRODUCTION) { it("should export the verifier contract", async () => { - const solidityCode = await exportVerifierContract( - solidityVersion, - finalZkeyPath, - `${cwd()}/node_modules/snarkjs/templates/verifier_groth16.sol.ejs` - ) + const solidityCode = await exportVerifierContract(solidityVersion, finalZkeyPath, verifierTemplatePath) expect(solidityCode).to.not.be.undefined }) } it("should fail when the zkey is not found", async () => { - await expect( - exportVerifierContract( - "0.8.0", - "invalid-path", - `${cwd()}/node_modules/snarkjs/templates/verifier_groth16.sol.ejs` - ) - ).to.be.rejected + await expect(exportVerifierContract(solidityVersion, "invalid-path", verifierTemplatePath)).to.be.rejected }) }) describe("exportVkey", () => { @@ -203,11 +191,11 @@ describe("Verification utilities", () => { if (envType === TestingEnvironment.PRODUCTION) { it("should export the verifier contract and the vkey", async () => { await exportVerifierAndVKey( - "0.8.0", + solidityVersion, finalZkeyPath, verifierExportPath, vKeyExportPath, - `${cwd()}/node_modules/snarkjs/templates/verifier_groth16.sol.ejs` + verifierTemplatePath ) expect(fs.existsSync(verifierExportPath)).to.be.true expect(fs.existsSync(vKeyExportPath)).to.be.true @@ -216,11 +204,11 @@ describe("Verification utilities", () => { it("should fail when the zkey is not found", async () => { await expect( exportVerifierAndVKey( - "0.8.0", + solidityVersion, "invalid-path", verifierExportPath, vKeyExportPath, - `${cwd()}/node_modules/snarkjs/templates/verifier_groth16.sol.ejs` + verifierTemplatePath ) ).to.be.rejected }) @@ -300,9 +288,96 @@ describe("Verification utilities", () => { }) }) if (envType === TestingEnvironment.PRODUCTION) { + // this data is shared between other prod tests (download artifacts and verify ceremony) + const ceremony = fakeCeremoniesData.fakeCeremonyOpenedFixed + + // create a circuit object that suits our needs + const circuits = generateFakeCircuit({ + uid: "000000000000000000A3", + data: { + name: "Circuit", + description: "Short description of Circuit", + prefix: "circuit", + sequencePosition: 1, + fixedTimeWindow: 10, + zKeySizeInBytes: 45020, + lastUpdated: Date.now(), + metadata: { + constraints: 65, + curve: "bn-128", + labels: 79, + outputs: 1, + pot: 2, + privateInputs: 0, + publicInputs: 2, + wires: 67 + }, + template: { + commitHash: "295d995802b152a1dc73b5d0690ce3f8ca5d9b23", + paramsConfiguration: ["2"], + source: "https://github.com/0xjei/circom-starter/blob/dev/circuits/exercise/checkAscendingOrder.circom" + }, + waitingQueue: { + completedContributions: 1, + contributors: [fakeUsersData.fakeUser1.uid, fakeUsersData.fakeUser2.uid], + currentContributor: fakeUsersData.fakeUser1.uid, + failedContributions: 0 + }, + files: { + initialZkeyBlake2bHash: + "eea0a468524a984908bff6de1de09867ac5d5b0caed92c3332fd5ec61004f79505a784df9d23f69f33efbfef016ad3138871fa8ad63b6e8124a9d0721b0e9e32", + initialZkeyFilename: "circuit_00000.zkey", + initialZkeyStoragePath: "circuits/circuit/contributions/circuit_00000.zkey", + potBlake2bHash: + "34379653611c22a7647da22893c606f9840b38d1cb6da3368df85c2e0b709cfdb03a8efe91ce621a424a39fe4d5f5451266d91d21203148c2d7d61cf5298d119", + potFilename: "powersOfTau28_hez_final_02.ptau", + potStoragePath: "pot/powersOfTau28_hez_final_02.ptau", + r1csBlake2bHash: + "0739198d5578a4bdaeb2fa2a1043a1d9cac988472f97337a0a60c296052b82d6cecb6ae7ce503ab9864bc86a38cdb583f2d33877c41543cbf19049510bca7472", + r1csFilename: "circuit.r1cs", + r1csStoragePath: "circuits/circuit/circuit.r1cs" + }, + avgTimings: { + contributionComputation: 0, + fullContribution: 0, + verifyCloudFunction: 0 + }, + compiler: { + commitHash: "ed807764a17ce06d8307cd611ab6b917247914f5", + version: "2.0.5" + } + } + }) + + const bucketName = getBucketName(ceremony.data.prefix!, ceremonyBucketPostfix) + + // the r1cs + const r1csStorageFilePath = getR1csStorageFilePath(circuits.data.prefix!, "circuit.r1cs") + // the last zkey + const zkeyStorageFilePath = getZkeyStorageFilePath(circuits.data.prefix!, "circuit_00000.zkey") + // the final zkey + const finalZkeyStorageFilePath = getZkeyStorageFilePath(circuits.data.prefix!, `circuit_final.zkey`) + // the pot + const potStorageFilePath = getPotStorageFilePath("powersOfTau28_hez_final_02.ptau") + + const outputDirectory = `${cwd()}/packages/actions/test/data/artifacts/verification` + + // pre confitions for the tests + beforeAll(async () => { + await createMockCeremony(adminFirestore, ceremony, circuits) + await signInWithEmailAndPassword(userAuth, users[0].data.email, passwords[0]) + await createS3Bucket(userFunctions, bucketName) + await sleep(1000) + // upload all files to S3 + await uploadFileToS3(bucketName, r1csStorageFilePath, r1csPath) + await uploadFileToS3(bucketName, zkeyStorageFilePath, zkeyPath) + await uploadFileToS3(bucketName, finalZkeyStorageFilePath, finalZkeyPath) + await uploadFileToS3(bucketName, potStorageFilePath, potPath) + }) + describe("compareCeremonyArtifacts", () => { - const ceremony = fakeCeremoniesData.fakeCeremonyOpenedDynamic - const bucketName = getBucketName(ceremony.data.prefix!, ceremonyBucketPostfix) + const ceremonyOpened = fakeCeremoniesData.fakeCeremonyOpenedDynamic + const bucketNameOpened = getBucketName(ceremonyOpened.data.prefix!, ceremonyBucketPostfix) const storagePath1 = "zkey1.zkey" const storagePath2 = "zkey2.zkey" const storagePath3 = "wasm.wasm" @@ -313,14 +388,18 @@ describe("Verification utilities", () => { // sign in as coordinator await signInWithEmailAndPassword(userAuth, users[0].data.email, passwords[0]) // create mock ceremony - await createMockCeremony(adminFirestore, ceremony, fakeCircuitsData.fakeCircuitSmallNoContributors) + await createMockCeremony( + adminFirestore, + ceremonyOpened, + fakeCircuitsData.fakeCircuitSmallNoContributors + ) // create ceremony bucket - await createS3Bucket(userFunctions, bucketName) + await createS3Bucket(userFunctions, bucketNameOpened) await sleep(1000) // need to upload files to S3 - await uploadFileToS3(bucketName, storagePath1, zkeyPath) - await uploadFileToS3(bucketName, storagePath2, zkeyPath) - await uploadFileToS3(bucketName, storagePath3, wasmPath) + await uploadFileToS3(bucketNameOpened, storagePath1, zkeyPath) + await uploadFileToS3(bucketNameOpened, storagePath2, zkeyPath) + await uploadFileToS3(bucketNameOpened, storagePath3, wasmPath) }) it("should return true when two artifacts are the same", async () => { expect( @@ -330,8 +409,8 @@ describe("Verification utilities", () => { localPath2, storagePath1, storagePath2, - bucketName, - bucketName, + bucketNameOpened, + bucketNameOpened, true ) ).to.be.true @@ -344,17 +423,17 @@ describe("Verification utilities", () => { localPath3, storagePath1, storagePath3, - bucketName, - bucketName, + bucketNameOpened, + bucketNameOpened, true ) ).to.be.false }) afterAll(async () => { - await deleteObjectFromS3(bucketName, storagePath1) - await deleteObjectFromS3(bucketName, storagePath2) - await deleteObjectFromS3(bucketName, storagePath3) - await deleteBucket(bucketName) + await deleteObjectFromS3(bucketNameOpened, storagePath1) + await deleteObjectFromS3(bucketNameOpened, storagePath2) + await deleteObjectFromS3(bucketNameOpened, storagePath3) + await deleteBucket(bucketNameOpened) if (fs.existsSync(localPath1)) fs.unlinkSync(localPath1) if (fs.existsSync(localPath2)) fs.unlinkSync(localPath2) @@ -362,97 +441,13 @@ describe("Verification utilities", () => { await cleanUpMockCeremony( adminFirestore, - ceremony.uid, + ceremonyOpened.uid, fakeCircuitsData.fakeCircuitSmallNoContributors.uid ) }) }) describe("downloadAllCeremonyArtifacts", () => { - const ceremony = fakeCeremoniesData.fakeCeremonyOpenedFixed - - // create a circuit object that suits our needs - const circuits = generateFakeCircuit({ - uid: "000000000000000000A3", - data: { - name: "Circuit", - description: "Short description of Circuit", - prefix: "circuit", - sequencePosition: 1, - fixedTimeWindow: 10, - zKeySizeInBytes: 45020, - lastUpdated: Date.now(), - metadata: { - constraints: 65, - curve: "bn-128", - labels: 79, - outputs: 1, - pot: 2, - privateInputs: 0, - publicInputs: 2, - wires: 67 - }, - template: { - commitHash: "295d995802b152a1dc73b5d0690ce3f8ca5d9b23", - paramsConfiguration: ["2"], - source: "https://github.com/0xjei/circom-starter/blob/dev/circuits/exercise/checkAscendingOrder.circom" - }, - waitingQueue: { - completedContributions: 1, - contributors: [fakeUsersData.fakeUser1.uid, fakeUsersData.fakeUser2.uid], - currentContributor: fakeUsersData.fakeUser1.uid, - failedContributions: 0 - }, - files: { - initialZkeyBlake2bHash: - "eea0a468524a984908bff6de1de09867ac5d5b0caed92c3332fd5ec61004f79505a784df9d23f69f33efbfef016ad3138871fa8ad63b6e8124a9d0721b0e9e32", - initialZkeyFilename: "circuit_00000.zkey", - initialZkeyStoragePath: "circuits/circuit/contributions/circuit_00000.zkey", - potBlake2bHash: - "34379653611c22a7647da22893c606f9840b38d1cb6da3368df85c2e0b709cfdb03a8efe91ce621a424a39fe4d5f5451266d91d21203148c2d7d61cf5298d119", - potFilename: "powersOfTau28_hez_final_02.ptau", - potStoragePath: "pot/powersOfTau28_hez_final_02.ptau", - r1csBlake2bHash: - "0739198d5578a4bdaeb2fa2a1043a1d9cac988472f97337a0a60c296052b82d6cecb6ae7ce503ab9864bc86a38cdb583f2d33877c41543cbf19049510bca7472", - r1csFilename: "circuit.r1cs", - r1csStoragePath: "circuits/circuit/circuit.r1cs" - }, - avgTimings: { - contributionComputation: 0, - fullContribution: 0, - verifyCloudFunction: 0 - }, - compiler: { - commitHash: "ed807764a17ce06d8307cd611ab6b917247914f5", - version: "2.0.5" - } - } - }) - - const bucketName = getBucketName(ceremony.data.prefix!, ceremonyBucketPostfix) - - // the r1cs - const r1csStorageFilePath = getR1csStorageFilePath(circuits.data.prefix!, "circuit.r1cs") - // the last zkey - const zkeyStorageFilePath = getZkeyStorageFilePath(circuits.data.prefix!, "circuit_00000.zkey") - // the final zkey - const finalZkeyStorageFilePath = getZkeyStorageFilePath(circuits.data.prefix!, `circuit_final.zkey`) - // the pot - const potStorageFilePath = getPotStorageFilePath("powersOfTau28_hez_final_02.ptau") - - const outputDirectory = `${cwd()}/packages/actions/test/data/artifacts/verification` - - beforeAll(async () => { - await createMockCeremony(adminFirestore, ceremony, circuits) - await signInWithEmailAndPassword(userAuth, users[0].data.email, passwords[0]) - await createS3Bucket(userFunctions, bucketName) - await sleep(1000) - // upload all files to S3 - await uploadFileToS3(bucketName, r1csStorageFilePath, r1csPath) - await uploadFileToS3(bucketName, zkeyStorageFilePath, zkeyPath) - await uploadFileToS3(bucketName, finalZkeyStorageFilePath, finalZkeyPath) - await uploadFileToS3(bucketName, potStorageFilePath, potPath) - }) it("should download all artifacts for a ceremony", async () => { await downloadAllCeremonyArtifacts(userFunctions, userFirestore, ceremony.data.prefix!, outputDirectory) }) @@ -462,16 +457,62 @@ describe("Verification utilities", () => { ).to.be.rejectedWith("Ceremony not found. Please review your ceremony prefix and try again.") }) afterAll(async () => { - await cleanUpMockCeremony(adminFirestore, ceremony.uid, circuits.uid) - await deleteObjectFromS3(bucketName, r1csStorageFilePath) - await deleteObjectFromS3(bucketName, zkeyStorageFilePath) - await deleteObjectFromS3(bucketName, finalZkeyStorageFilePath) - await deleteObjectFromS3(bucketName, potStorageFilePath) - await deleteBucket(bucketName) // remove dir with output if (fs.existsSync(outputDirectory)) fs.rmSync(outputDirectory, { recursive: true, force: true }) }) }) + // Verify a ceremony output + describe("verifyCeremony", () => { + it("should return true for a ceremony that was successfully finalized", async () => { + expect( + await verifyCeremony( + userFunctions, + userFirestore, + ceremony.data.prefix!, + outputDirectory, + solidityVersion, + wasmPath, + { + x1: "5", + x2: "10", + x3: "1", + x4: "2" + }, + verifierTemplatePath + ) + ).to.be.true + }) + it("should throw for a ceremony that wasn't successfully finalized", async () => { + await expect( + verifyCeremony( + userFunctions, + userFirestore, + "invalid", + outputDirectory, + solidityVersion, + wasmPath, + { + x1: "5", + x2: "10", + x3: "1", + x4: "2" + }, + verifierTemplatePath + ) + ).to.be.rejected + }) + }) + // clean up + afterAll(async () => { + await cleanUpMockCeremony(adminFirestore, ceremony.uid, circuits.uid) + await deleteObjectFromS3(bucketName, r1csStorageFilePath) + await deleteObjectFromS3(bucketName, zkeyStorageFilePath) + await deleteObjectFromS3(bucketName, finalZkeyStorageFilePath) + await deleteObjectFromS3(bucketName, potStorageFilePath) + await deleteBucket(bucketName) + // remove dir with output + if (fs.existsSync(outputDirectory)) fs.rmSync(outputDirectory, { recursive: true, force: true }) + }) } afterAll(async () => { if (fs.existsSync(verifierExportPath)) {