diff --git a/.github/workflows/coordinator-build.yml b/.github/workflows/coordinator-build.yml index 35d84c64ae..67bd6bb140 100644 --- a/.github/workflows/coordinator-build.yml +++ b/.github/workflows/coordinator-build.yml @@ -6,12 +6,14 @@ on: pull_request: env: + COORDINATOR_RPC_URL: "http://localhost:8545" COORDINATOR_PUBLIC_KEY_PATH: "./pub.key" COORDINATOR_PRIVATE_KEY_PATH: "./priv.key" - COORDINATOR_TALLY_ZKEY_NAME: "TallyVotes" - COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME: "ProcessMessages" + COORDINATOR_TALLY_ZKEY_NAME: "TallyVotes_10-1-2_test" + COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME: "ProcessMessages_10-2-1-2_test" COORDINATOR_ZKEY_PATH: "./zkeys" - COORDINATOR_RAPIDSNARK_EXE: "" + COORDINATOR_RAPIDSNARK_EXE: "~/rapidsnark/build/prover" + COORDINATOR_MNEMONIC: ${{ secrets.COORDINATOR_MNEMONIC }} concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -33,6 +35,35 @@ jobs: node-version: 20 cache: "pnpm" + # Check for changes in the 'circuit' folder + - name: Get changed files + id: get-changed-files + uses: jitterbit/get-changed-files@v1 + with: + format: "csv" + + - name: Check for changes in 'circuit' folder + id: check_changes + run: | + CHANGED_FILES=${{ steps.get-changed-files.outputs.all }} + if echo "$CHANGED_FILES" | grep -q "\.circom"; then + echo "CHANGED=true" >> $GITHUB_ENV + echo "Circuits have changes." + else + echo "CHANGED=false" >> $GITHUB_ENV + echo "No changes on circuits." + fi + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install --yes \ + build-essential \ + libgmp-dev \ + libsodium-dev \ + nasm \ + nlohmann-json3-dev + - name: Install run: | pnpm install --frozen-lockfile --prefer-offline @@ -41,6 +72,41 @@ jobs: run: | pnpm run build + - name: Run hardhat + run: | + pnpm run hardhat & + sleep 5 + working-directory: coordinator + + - name: Download rapidsnark (1c137) + run: | + mkdir -p ~/rapidsnark/build + wget -qO ~/rapidsnark/build/prover https://maci-devops-zkeys.s3.ap-northeast-2.amazonaws.com/rapidsnark-linux-amd64-1c137 + chmod +x ~/rapidsnark/build/prover + + - name: Download circom Binary v2.1.6 + run: | + wget -qO ${{ github.workspace }}/circom https://github.com/iden3/circom/releases/download/v2.1.6/circom-linux-amd64 + chmod +x ${{ github.workspace }}/circom + sudo mv ${{ github.workspace }}/circom /bin/circom + + - name: Compile Circuits And Generate zkeys + if: ${{ env.CHANGED == 'true' }} + run: | + pnpm build:circuits-c --outPath ../coordinator/zkeys + pnpm setup:zkeys --outPath ../coordinator/zkeys + + - name: Download zkeys + if: ${{ env.CHANGED == 'false' }} + run: | + pnpm download-zkeys:test + working-directory: coordinator + + - name: Generate keypair + run: | + pnpm generate-keypair + working-directory: coordinator + - name: Test run: pnpm run test working-directory: coordinator diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 096d92e06b..13b4bf2852 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -69,8 +69,8 @@ jobs: - name: Compile Circuits And Generate zkeys run: | - pnpm build:circuits-c - pnpm setup:zkeys + pnpm build:circuits-c -- --outPath ../cli/zkeys + pnpm setup:zkeys -- --outPath ../cli/zkeys - name: ${{ matrix.command }} run: pnpm run ${{ matrix.command }} diff --git a/.github/workflows/reusable-e2e.yml b/.github/workflows/reusable-e2e.yml index 3ec01c2996..26b5afedb2 100644 --- a/.github/workflows/reusable-e2e.yml +++ b/.github/workflows/reusable-e2e.yml @@ -82,8 +82,8 @@ jobs: - name: Compile Circuits And Generate zkeys if: ${{ env.CHANGED == 'true' }} run: | - pnpm build:circuits-c - pnpm setup:zkeys + pnpm build:circuits-c -- --outPath ../cli/zkeys + pnpm setup:zkeys -- --outPath ../cli/zkeys - name: Download zkeys if: ${{ env.CHANGED == 'false' }} diff --git a/contracts/scripts/compileSol.ts b/contracts/scripts/compileSol.ts index f3c8e0838f..860841bcd8 100644 --- a/contracts/scripts/compileSol.ts +++ b/contracts/scripts/compileSol.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node import hre from "hardhat"; import fs from "fs"; diff --git a/contracts/tasks/helpers/ProofGenerator.ts b/contracts/tasks/helpers/ProofGenerator.ts index 1e903c7c82..f49768a985 100644 --- a/contracts/tasks/helpers/ProofGenerator.ts +++ b/contracts/tasks/helpers/ProofGenerator.ts @@ -213,7 +213,7 @@ export class ProofGenerator { * @param network - current network * @returns tally proofs */ - async generateTallyProofs(network: Network): Promise { + async generateTallyProofs(network: Network): Promise<{ proofs: Proof[]; tallyData: TallyData }> { performance.mark("tally-proofs-start"); console.log(`Generating proofs of vote tallying...`); @@ -316,7 +316,7 @@ export class ProofGenerator { performance.mark("tally-proofs-end"); performance.measure("Generate tally proofs", "tally-proofs-start", "tally-proofs-end"); - return proofs; + return { proofs, tallyData: tallyFileData }; } /** @@ -359,7 +359,14 @@ export class ProofGenerator { publicInputs: publicSignals, }); - fs.writeFileSync(path.resolve(this.outputDir, outputFile), JSON.stringify(proofs[proofs.length - 1], null, 4)); + if (!fs.existsSync(path.resolve(this.outputDir))) { + await fs.promises.mkdir(path.resolve(this.outputDir)); + } + + await fs.promises.writeFile( + path.resolve(this.outputDir, outputFile), + JSON.stringify(proofs[proofs.length - 1], null, 4), + ); return proofs; } diff --git a/contracts/tasks/runner/prove.ts b/contracts/tasks/runner/prove.ts index 8c7f9e4d47..081e7a2217 100644 --- a/contracts/tasks/runner/prove.ts +++ b/contracts/tasks/runner/prove.ts @@ -184,7 +184,7 @@ task("prove", "Command to generate proof and prove the result of a poll on-chain data.processProofs = await proofGenerator.generateMpProofs(); await prover.proveMessageProcessing(data.processProofs); - data.tallyProofs = await proofGenerator.generateTallyProofs(network); + data.tallyProofs = await proofGenerator.generateTallyProofs(network).then(({ proofs }) => proofs); await prover.proveTally(data.tallyProofs); const endBalance = await signer.provider.getBalance(signer); diff --git a/coordinator/.env.example b/coordinator/.env.example index 4e281320be..fa6ca10910 100644 --- a/coordinator/.env.example +++ b/coordinator/.env.example @@ -7,15 +7,13 @@ LIMIT=10 COORDINATOR_PUBLIC_KEY_PATH="./pub.key" COORDINATOR_PRIVATE_KEY_PATH="./priv.key" -# Make sure you have TallyVotesQv.zkey, TallyVotesQv.wasm -# and TallyVotesNonQv.zkey, TallyVotesNonQv.wasm inside COORDINATOR_ZKEY_PATH folder +# Make sure you have zkeys folder # https://maci.pse.dev/docs/trusted-setup -COORDINATOR_TALLY_ZKEY_NAME=TallyVotes +COORDINATOR_TALLY_ZKEY_NAME=TallyVotes_10-1-2_test -# Make sure you have ProcessMessagesQv.zkey, ProcessMessagesQv.wasm -# and ProcessMessagesNonQv.zkey, ProcessMessagesNonQv.wasm inside COORDINATOR_ZKEY_PATH folder +# Make sure you have zkeys folder # https://maci.pse.dev/docs/trusted-setup -COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME=ProcessMessages +COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME=ProcessMessages_10-2-1-2_test # Rapidsnark executable path COORDINATOR_RAPIDSNARK_EXE= @@ -23,3 +21,9 @@ COORDINATOR_RAPIDSNARK_EXE= # Location of zkey, wasm files COORDINATOR_ZKEY_PATH="./zkeys" +# Coordinator mnemonic phrase +COORDINATOR_MNEMONIC= + +# Coordinator RPC url +COORDINATOR_RPC_URL=http://localhost:8545 + diff --git a/coordinator/.gitignore b/coordinator/.gitignore index 0e11495c45..1751988fa1 100644 --- a/coordinator/.gitignore +++ b/coordinator/.gitignore @@ -1,4 +1,6 @@ *.key *.key zkeys/ +proofs/ +tally.json diff --git a/coordinator/hardhat.config.ts b/coordinator/hardhat.config.ts index 69236cc6d6..09da0ba685 100644 --- a/coordinator/hardhat.config.ts +++ b/coordinator/hardhat.config.ts @@ -1,11 +1,11 @@ import "@nomicfoundation/hardhat-toolbox"; -import { config as envConfig } from "dotenv"; +import dotenv from "dotenv"; import path from "path"; import type { HardhatUserConfig } from "hardhat/config"; -envConfig(); +dotenv.config(); const parentDir = __dirname.includes("build") ? ".." : ""; @@ -13,9 +13,23 @@ const config: HardhatUserConfig = { defaultNetwork: "localhost", networks: { localhost: { - url: process.env.ETH_PROVIDER, - accounts: [process.env.ETH_SK!], + url: process.env.COORDINATOR_RPC_URL, loggingEnabled: false, + accounts: { + mnemonic: process.env.COORDINATOR_MNEMONIC, + path: "m/44'/60'/0'/0", + initialIndex: 0, + count: 20, + }, + }, + hardhat: { + loggingEnabled: false, + accounts: { + mnemonic: process.env.COORDINATOR_MNEMONIC, + path: "m/44'/60'/0'/0", + initialIndex: 0, + count: 20, + }, }, }, paths: { diff --git a/coordinator/package.json b/coordinator/package.json index 12946aa2c9..6de8e988c2 100644 --- a/coordinator/package.json +++ b/coordinator/package.json @@ -9,6 +9,7 @@ "README.md" ], "scripts": { + "hardhat": "hardhat node", "prebuild": "pnpm run generate-keypair", "build": "nest build", "start": "nest start", @@ -17,6 +18,7 @@ "test": "jest", "test:coverage": "jest --coverage", "types": "tsc -p tsconfig.json --noEmit", + "download-zkeys:test": "ts-node ./scripts/downloadZkeys.ts test", "generate-keypair": "ts-node ./scripts/generateKeypair.ts" }, "dependencies": { @@ -34,7 +36,8 @@ "maci-contracts": "^1.2.0", "maci-domainobjs": "^1.2.0", "reflect-metadata": "^0.2.0", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "tar": "^7.1.0" }, "devDependencies": { "@nestjs/cli": "^10.3.2", @@ -52,6 +55,7 @@ "typescript": "^5.4.5" }, "jest": { + "testTimeout": 900000, "moduleFileExtensions": [ "js", "json", @@ -64,11 +68,13 @@ ], "testRegex": ".*\\.test\\.ts$", "transform": { + "^.+\\.js$": "/ts/jest/transform.js", "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s", - "!/ts/main.ts" + "!/ts/main.ts", + "!/hardhat.config.ts" ], "coverageDirectory": "/coverage", "testEnvironment": "node" diff --git a/coordinator/scripts/downloadZkeys.ts b/coordinator/scripts/downloadZkeys.ts new file mode 100644 index 0000000000..187a430d5e --- /dev/null +++ b/coordinator/scripts/downloadZkeys.ts @@ -0,0 +1,44 @@ +import dotenv from "dotenv"; +import * as tar from "tar"; + +import fs from "fs"; +import https from "https"; +import path from "path"; + +dotenv.config({ path: [path.resolve(__dirname, "../.env"), path.resolve(__dirname, "../.env.example")] }); + +const ZKEY_PATH = path.resolve(process.env.COORDINATOR_ZKEY_PATH!); +const ZKEYS_URLS = { + test: "https://maci-develop-fra.s3.eu-central-1.amazonaws.com/v1.3.0/maci_artifacts_10-2-1-2_test.tar.gz", +}; +const ARCHIVE_NAME = path.resolve(ZKEY_PATH, "maci_keys.tar.gz"); + +export async function downloadZkeys(): Promise { + const [type] = process.argv.slice(2).map((arg) => arg.trim() as keyof typeof ZKEYS_URLS); + + if (!Object.keys(ZKEYS_URLS).includes(type)) { + throw new Error(`${type} doesn't exist`); + } + + if (!fs.existsSync(ZKEY_PATH)) { + await fs.promises.mkdir(ZKEY_PATH); + } + + const file = fs.createWriteStream(ARCHIVE_NAME); + + https + .get(ZKEYS_URLS[type], (response) => { + response.pipe(file); + + file + .on("finish", () => { + file.close(); + + tar.x({ f: ARCHIVE_NAME }).then(() => fs.promises.rm(ARCHIVE_NAME)); + }) + .on("error", () => fs.promises.unlink(ARCHIVE_NAME)); + }) + .on("error", () => fs.promises.unlink(ARCHIVE_NAME)); +} + +downloadZkeys(); diff --git a/coordinator/tests/app.test.ts b/coordinator/tests/app.test.ts index 8711401948..e60b5cf397 100644 --- a/coordinator/tests/app.test.ts +++ b/coordinator/tests/app.test.ts @@ -1,16 +1,83 @@ -import { Test, TestingModule } from "@nestjs/testing"; +import { Test } from "@nestjs/testing"; +import hardhat from "hardhat"; +import { + type DeployedContracts, + type PollContracts, + deploy, + deployPoll, + deployVkRegistryContract, + setVerifyingKeys, + signup, + publish, + timeTravel, + mergeMessages, + mergeSignups, +} from "maci-cli"; +import { Keypair } from "maci-domainobjs"; import request from "supertest"; +import fs from "fs"; +import path from "path"; + import type { INestApplication } from "@nestjs/common"; +import type { Signer } from "ethers"; import type { App } from "supertest/types"; import { AppModule } from "../ts/app.module"; +import { CryptoService } from "../ts/crypto/crypto.service"; + +const STATE_TREE_DEPTH = 10; +const INT_STATE_TREE_DEPTH = 1; +const MSG_TREE_DEPTH = 2; +const VOTE_OPTION_TREE_DEPTH = 2; +const MSG_BATCH_DEPTH = 1; describe("AppController (e2e)", () => { + const coordinatorKeypair = new Keypair(); let app: INestApplication; + let signer: Signer; + let maciAddresses: DeployedContracts; + let pollContracts: PollContracts; + + beforeAll(async () => { + [signer] = await hardhat.ethers.getSigners(); + + await deployVkRegistryContract({ signer }); + await setVerifyingKeys({ + quiet: true, + stateTreeDepth: STATE_TREE_DEPTH, + intStateTreeDepth: INT_STATE_TREE_DEPTH, + messageTreeDepth: MSG_TREE_DEPTH, + voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, + messageBatchDepth: MSG_BATCH_DEPTH, + processMessagesZkeyPathNonQv: path.resolve( + __dirname, + "../zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey", + ), + tallyVotesZkeyPathNonQv: path.resolve( + __dirname, + "../zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey", + ), + useQuadraticVoting: false, + signer, + }); + + maciAddresses = await deploy({ stateTreeDepth: 10, signer }); + + pollContracts = await deployPoll({ + pollDuration: 30, + intStateTreeDepth: INT_STATE_TREE_DEPTH, + messageTreeSubDepth: MSG_BATCH_DEPTH, + messageTreeDepth: MSG_TREE_DEPTH, + voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, + coordinatorPubkey: coordinatorKeypair.pubKey.serialize(), + useQuadraticVoting: false, + signer, + }); + }); beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ + const moduleFixture = await Test.createTestingModule({ imports: [AppModule], }).compile(); @@ -18,9 +85,151 @@ describe("AppController (e2e)", () => { await app.init(); }); - test("should get index page properly (/ GET)", () => - request(app.getHttpServer() as App) - .get("/") - .expect(200) - .expect("Hello World!")); + describe("/v1/proof/generate POST", () => { + beforeAll(async () => { + const user = new Keypair(); + + await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }); + await publish({ + pubkey: user.pubKey.serialize(), + stateIndex: 1n, + voteOptionIndex: 0n, + nonce: 1n, + pollId: 0n, + newVoteWeight: 9n, + maciAddress: maciAddresses.maciAddress, + salt: 0n, + privateKey: user.privKey.serialize(), + signer, + }); + }); + + test("should throw an error if poll is not over", async () => { + const publicKey = await fs.promises.readFile(process.env.COORDINATOR_PUBLIC_KEY_PATH!); + const encryptedCoordinatorPrivateKey = CryptoService.getInstance().encrypt( + publicKey, + coordinatorKeypair.privKey.serialize(), + ); + + await request(app.getHttpServer() as App) + .post("/v1/proof/generate") + .send({ + poll: "0", + encryptedCoordinatorPrivateKey, + maciContractAddress: maciAddresses.maciAddress, + tallyContractAddress: pollContracts.tally, + useQuadraticVoting: false, + }) + .expect(400); + }); + + test("should throw an error if messages are not merged", async () => { + await timeTravel({ seconds: 30, signer }); + + const publicKey = await fs.promises.readFile(process.env.COORDINATOR_PUBLIC_KEY_PATH!); + const encryptedCoordinatorPrivateKey = CryptoService.getInstance().encrypt( + publicKey, + coordinatorKeypair.privKey.serialize(), + ); + + await request(app.getHttpServer() as App) + .post("/v1/proof/generate") + .send({ + poll: "0", + encryptedCoordinatorPrivateKey, + maciContractAddress: maciAddresses.maciAddress, + tallyContractAddress: pollContracts.tally, + useQuadraticVoting: false, + }) + .expect(400); + }); + + test("should throw an error if signups are not merged", async () => { + await mergeMessages({ pollId: 0n, signer }); + + const publicKey = await fs.promises.readFile(process.env.COORDINATOR_PUBLIC_KEY_PATH!); + const encryptedCoordinatorPrivateKey = CryptoService.getInstance().encrypt( + publicKey, + coordinatorKeypair.privKey.serialize(), + ); + + await request(app.getHttpServer() as App) + .post("/v1/proof/generate") + .send({ + poll: "0", + encryptedCoordinatorPrivateKey, + maciContractAddress: maciAddresses.maciAddress, + tallyContractAddress: pollContracts.tally, + useQuadraticVoting: false, + }) + .expect(400); + }); + + test("should throw an error if coordinator key decryption is failed", async () => { + await mergeMessages({ pollId: 0n, signer }); + + await request(app.getHttpServer() as App) + .post("/v1/proof/generate") + .send({ + poll: "0", + encryptedCoordinatorPrivateKey: coordinatorKeypair.privKey.serialize(), + maciContractAddress: maciAddresses.maciAddress, + tallyContractAddress: pollContracts.tally, + useQuadraticVoting: false, + }) + .expect(400); + }); + + test("should throw an error if there is no such poll", async () => { + await mergeMessages({ pollId: 0n, signer }); + + await request(app.getHttpServer() as App) + .post("/v1/proof/generate") + .send({ + poll: "9000", + encryptedCoordinatorPrivateKey: coordinatorKeypair.privKey.serialize(), + maciContractAddress: maciAddresses.maciAddress, + tallyContractAddress: pollContracts.tally, + useQuadraticVoting: false, + }) + .expect(400); + }); + + test("should generate proofs properly", async () => { + await mergeSignups({ pollId: 0n, signer }); + + const publicKey = await fs.promises.readFile(process.env.COORDINATOR_PUBLIC_KEY_PATH!); + const encryptedCoordinatorPrivateKey = CryptoService.getInstance().encrypt( + publicKey, + coordinatorKeypair.privKey.serialize(), + ); + + await request(app.getHttpServer() as App) + .post("/v1/proof/generate") + .send({ + poll: "0", + encryptedCoordinatorPrivateKey, + maciContractAddress: maciAddresses.maciAddress, + tallyContractAddress: pollContracts.tally, + useQuadraticVoting: false, + }) + .expect(201); + + const proofData = await Promise.all([ + fs.promises.readFile("./proofs/process_0.json"), + fs.promises.readFile("./proofs/tally_0.json"), + fs.promises.readFile("./tally.json"), + ]) + .then((files) => files.map((item) => JSON.parse(item.toString()) as Record)) + .then((data) => ({ + processProofs: [data[0]], + tallyProofs: [data[1]], + tallyData: [data[2]], + })); + + expect(proofData.processProofs).toHaveLength(1); + expect(proofData.tallyData).toHaveLength(1); + expect(proofData.tallyData).toBeDefined(); + }); + }); }); diff --git a/coordinator/ts/app.controller.test.ts b/coordinator/ts/app.controller.test.ts index 8db8439ce3..c7ba7492e7 100644 --- a/coordinator/ts/app.controller.test.ts +++ b/coordinator/ts/app.controller.test.ts @@ -1,23 +1,69 @@ +import { HttpException, HttpStatus } from "@nestjs/common"; import { Test } from "@nestjs/testing"; +import type { IGenerateArgs, IGenerateData } from "./proof/types"; +import type { TallyData } from "maci-cli"; + import { AppController } from "./app.controller"; -import { AppService } from "./app.service"; +import { ProofGeneratorService } from "./proof/proof.service"; describe("AppController", () => { let appController: AppController; + const defaultProofGeneratorArgs: IGenerateArgs = { + poll: 0n, + maciContractAddress: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + tallyContractAddress: "0x6F1216D1BFe15c98520CA1434FC1d9D57AC95321", + useQuadraticVoting: false, + encryptedCoordinatorPrivateKey: + "siO9W/g7jNVXs9tOUv/pffrcqYdMlgdXw7nSSlqM1q1UvHGSSbhtLJpeT+nJKW7/+xrBTgI0wB866DSkg8Rgr8zD+POUMiKPrGqAO/XhrcmRDL+COURFNDRh9WGeAua6hdiNoufQYvXPl1iWyIYidSDbfmC2wR6F9vVkhg/6KDZyw8Wlr6LUh0RYT+hUHEwwGbz7MeqZJcJQSTpECPF5pnk8NTHL2W/XThaewB4n4HYqjDUbYLmBDLYWsDDMgoPo709a309rTq3uEe0YBgVF8g9aGxucTDhz+/LYYzqaeSxclUwen9Z4BGZjiDSPBZfooOEQEEwIJlViQ2kl1VeOKAmkiWEUVfItivmNbC/PNZchklmfFsGpiu4DT9UU9YVBN2OTcFYHHsslcaqrR7SuesqjluaGjG46oYEmfQlkZ4gXhavdWXw2ant+Tv6HRo4trqjoD1e3jUkN6gJMWomxOeRBTg0czBZlz/IwUtTpBHcKhi3EqGQo8OuQtWww+Ts7ySmeoONuovYUsIAppNuOubfUxvFJoTr2vKbWNAiYetw09kddkjmBe+S8A5PUiFOi262mfc7g5wJwPPP7wpTBY0Fya+2BCPzXqRLMOtNI+1tW3/UQLZYvEY8J0TxmhoAGZaRn8FKaosatRxDZTQS6QUNmKxpmUspkRKzTXN5lznM=", + }; + + const defaultProofGeneratorData: IGenerateData = { + tallyProofs: [], + processProofs: [], + tallyData: {} as TallyData, + }; + + const mockGeneratorService = { + generate: jest.fn(), + }; + beforeEach(async () => { const app = await Test.createTestingModule({ controllers: [AppController], - providers: [AppService], - }).compile(); + }) + .useMocker((token) => { + if (token === ProofGeneratorService) { + mockGeneratorService.generate.mockResolvedValue(defaultProofGeneratorData); + + return mockGeneratorService; + } + + return jest.fn(); + }) + .compile(); appController = app.get(AppController); }); - describe("root", () => { - test('should return "Hello World!"', () => { - expect(appController.getHello()).toBe("Hello World!"); + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("v1/proof", () => { + test("should return generated proof data", async () => { + const data = await appController.generate(defaultProofGeneratorArgs); + expect(data).toStrictEqual(defaultProofGeneratorData); + }); + + test("should throw an error if proof generation if failed", async () => { + const error = new Error("error"); + mockGeneratorService.generate.mockRejectedValue(error); + + await expect(appController.generate(defaultProofGeneratorArgs)).rejects.toThrow( + new HttpException("BadRequest", HttpStatus.BAD_REQUEST, { cause: error.message }), + ); }); }); }); diff --git a/coordinator/ts/app.controller.ts b/coordinator/ts/app.controller.ts index a7fc9f4015..9d2a22d5bd 100644 --- a/coordinator/ts/app.controller.ts +++ b/coordinator/ts/app.controller.ts @@ -1,13 +1,16 @@ -import { Controller, Get } from "@nestjs/common"; +import { Body, Controller, HttpException, HttpStatus, Post } from "@nestjs/common"; -import { AppService } from "./app.service"; +import { ProofGeneratorService } from "./proof/proof.service"; +import { IGenerateArgs, IGenerateData } from "./proof/types"; -@Controller() +@Controller("v1/proof") export class AppController { - constructor(private readonly appService: AppService) {} + constructor(private readonly proofGeneratorService: ProofGeneratorService) {} - @Get() - getHello(): string { - return this.appService.getHello(); + @Post("generate") + async generate(@Body() args: IGenerateArgs): Promise { + return this.proofGeneratorService.generate(args).catch((error: Error) => { + throw new HttpException("BadRequest", HttpStatus.BAD_REQUEST, { cause: error.message }); + }); } } diff --git a/coordinator/ts/app.module.ts b/coordinator/ts/app.module.ts index 1b41584249..14c786903c 100644 --- a/coordinator/ts/app.module.ts +++ b/coordinator/ts/app.module.ts @@ -2,7 +2,7 @@ import { Module } from "@nestjs/common"; import { ThrottlerModule } from "@nestjs/throttler"; import { AppController } from "./app.controller"; -import { AppService } from "./app.service"; +import { ProofGeneratorService } from "./proof/proof.service"; @Module({ imports: [ @@ -14,6 +14,6 @@ import { AppService } from "./app.service"; ]), ], controllers: [AppController], - providers: [AppService], + providers: [ProofGeneratorService], }) export class AppModule {} diff --git a/coordinator/ts/app.service.ts b/coordinator/ts/app.service.ts deleted file mode 100644 index b00a66790d..0000000000 --- a/coordinator/ts/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from "@nestjs/common"; - -@Injectable() -export class AppService { - getHello(): string { - return "Hello World!"; - } -} diff --git a/coordinator/ts/jest/transform.js b/coordinator/ts/jest/transform.js new file mode 100644 index 0000000000..d07dcc1e2a --- /dev/null +++ b/coordinator/ts/jest/transform.js @@ -0,0 +1,9 @@ +/* eslint-disable */ + +module.exports = { + process(sourceText) { + return { + code: sourceText.replace("#!/usr/bin/env node", ""), + }; + }, +}; diff --git a/coordinator/ts/proof/__tests__/proof.service.test.ts b/coordinator/ts/proof/__tests__/proof.service.test.ts index a94625a8bb..ccf139d378 100644 --- a/coordinator/ts/proof/__tests__/proof.service.test.ts +++ b/coordinator/ts/proof/__tests__/proof.service.test.ts @@ -76,7 +76,7 @@ describe("ProofGeneratorService", () => { defaultProofGenerator = { generateMpProofs: jest.fn(() => Promise.resolve([1])), - generateTallyProofs: jest.fn(() => Promise.resolve([1])), + generateTallyProofs: jest.fn(() => Promise.resolve({ proofs: [1], tallyData: {} })), }; defaultCryptoService = { diff --git a/coordinator/ts/proof/proof.service.ts b/coordinator/ts/proof/proof.service.ts index 64aea3a1bf..5e9496f3a5 100644 --- a/coordinator/ts/proof/proof.service.ts +++ b/coordinator/ts/proof/proof.service.ts @@ -81,7 +81,6 @@ export class ProofGeneratorService { const privateKey = await fs.promises.readFile(path.resolve(process.env.COORDINATOR_PRIVATE_KEY_PATH!)); const maciPrivateKey = PrivKey.deserialize(this.cryptoService.decrypt(privateKey, encryptedCoordinatorPrivateKey)); const coordinatorKeypair = new Keypair(maciPrivateKey); - const maciState = await ProofGenerator.prepareState({ maciContract, pollContract, @@ -111,16 +110,17 @@ export class ProofGeneratorService { mp: this.getZkeyFiles(process.env.COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME!, useQuadraticVoting), rapidsnark: process.env.COORDINATOR_RAPIDSNARK_EXE, outputDir: path.resolve("./proofs"), - tallyOutputFile: path.resolve("./tally"), + tallyOutputFile: path.resolve("./tally.json"), useQuadraticVoting, }); const processProofs = await proofGenerator.generateMpProofs(); - const tallyProofs = await proofGenerator.generateTallyProofs(hre.network); + const { proofs: tallyProofs, tallyData } = await proofGenerator.generateTallyProofs(hre.network); return { processProofs, tallyProofs, + tallyData, }; } @@ -133,12 +133,16 @@ export class ProofGeneratorService { */ private getZkeyFiles(name: string, useQuadraticVoting: boolean): { zkey: string; wasm: string; witgen: string } { const root = path.resolve(process.env.COORDINATOR_ZKEY_PATH!); - const mode = useQuadraticVoting ? "Qv" : "NonQv"; + const index = name.indexOf("_"); + const type = name.slice(0, index); + const params = name.slice(index + 1); + const mode = useQuadraticVoting ? "" : "NonQv"; + const filename = `${type}${mode}_${params}`; return { - zkey: path.resolve(root, `${name}${mode}.zkey`), - wasm: path.resolve(root, `${name}${mode}.wasm`), - witgen: path.resolve(root, `${name}${mode}.witgen`), + zkey: path.resolve(root, `${filename}/${filename}.0.zkey`), + wasm: path.resolve(root, `${filename}/${filename}_js/${filename}.wasm`), + witgen: path.resolve(root, `${filename}/${filename}_cpp/${filename}`), }; } } diff --git a/coordinator/ts/proof/types.ts b/coordinator/ts/proof/types.ts index 93537fede4..7c8b50a859 100644 --- a/coordinator/ts/proof/types.ts +++ b/coordinator/ts/proof/types.ts @@ -1,4 +1,5 @@ import type { BigNumberish } from "ethers"; +import type { TallyData } from "maci-cli"; import type { Proof } from "maci-contracts"; /** @@ -59,6 +60,11 @@ export interface IGenerateData { * Tally proofs */ tallyProofs: Proof[]; + + /** + * TallyData + */ + tallyData: TallyData; } /** diff --git a/package.json b/package.json index 4c29e26f92..8f3f9b744d 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "scripts": { "benchmarks": "lerna run benchmarks", "build": "lerna run build --ignore=website", - "build:circuits-c": "lerna run build-test-circuits-c -- --outPath ../cli/zkeys --scope \"maci-circuits\"", - "build:circuits-wasm": "lerna run build-test-circuits-wasm -- --outPath ../cli/zkeys --scope \"maci-circuits\"", - "setup:zkeys": "NODE_OPTIONS=--max-old-space-size=4096 lerna run gen-zkeys -- --outPath ../cli/zkeys --scope \"maci-circuits\"", + "build:circuits-c": "lerna run build-test-circuits-c --scope \"maci-circuits\"", + "build:circuits-wasm": "lerna run build-test-circuits-wasm --scope \"maci-circuits\"", + "setup:zkeys": "NODE_OPTIONS=--max-old-space-size=4096 lerna run gen-zkeys --scope \"maci-circuits\"", "clean": "lerna exec -- rm -rf node_modules build && rm -rf node_modules", "commit": "git cz", "download:test-zkeys-1-2": "lerna run download-zkeys --scope \"maci-integrationtests\"", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5aa1bf1fd3..80b7395e0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -347,6 +347,9 @@ importers: rxjs: specifier: ^7.8.1 version: 7.8.1 + tar: + specifier: ^7.1.0 + version: 7.1.0 devDependencies: '@nestjs/cli': specifier: ^10.3.2 @@ -4174,7 +4177,13 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true + + /@isaacs/fs-minipass@4.0.1: + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + dependencies: + minipass: 7.1.0 + dev: false /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -5669,7 +5678,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@pkgr/core@0.1.1: @@ -8355,6 +8363,11 @@ packages: engines: {node: '>=10'} dev: true + /chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + dev: false + /chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} @@ -11314,7 +11327,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /fork-ts-checker-webpack-plugin@6.5.3(eslint@8.57.0)(typescript@5.4.5)(webpack@5.91.0): resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} @@ -11728,7 +11740,6 @@ packages: minimatch: 9.0.4 minipass: 7.0.4 path-scurry: 1.10.2 - dev: true /glob@5.0.15: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} @@ -13374,7 +13385,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jake@10.8.7: resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} @@ -14428,7 +14438,6 @@ packages: /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} - dev: true /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -15359,7 +15368,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -15450,7 +15458,11 @@ packages: /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} - dev: true + + /minipass@7.1.0: + resolution: {integrity: sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==} + engines: {node: '>=16 || 14 >=14.17'} + dev: false /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -15460,6 +15472,14 @@ packages: yallist: 4.0.0 dev: true + /minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + dependencies: + minipass: 7.1.0 + rimraf: 5.0.5 + dev: false + /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -15471,6 +15491,12 @@ packages: engines: {node: '>=10'} hasBin: true + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + dev: false + /mnemonist@0.38.5: resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} dependencies: @@ -16507,7 +16533,6 @@ packages: dependencies: lru-cache: 10.2.0 minipass: 7.0.4 - dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -18355,6 +18380,14 @@ packages: glob: 9.3.5 dev: true + /rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 10.3.12 + dev: false + /ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} dependencies: @@ -18715,7 +18748,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /sigstore@1.9.0: resolution: {integrity: sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==} @@ -19581,6 +19613,18 @@ packages: yallist: 4.0.0 dev: true + /tar@7.1.0: + resolution: {integrity: sha512-ENhg4W6BmjYxl8GTaE7/h99f0aXiSWv4kikRZ9n2/JRxypZniE84ILZqimAhxxX7Zb8Px6pFdheW3EeHfhnXQQ==} + engines: {node: '>=18'} + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.0 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 + dev: false + /temp-dir@1.0.0: resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} engines: {node: '>=4'} @@ -21072,6 +21116,11 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + dev: false + /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} diff --git a/website/versioned_docs/version-v1.3_alpha/circuits.md b/website/versioned_docs/version-v1.3_alpha/circuits.md index b808da124c..7ca3cf2d17 100644 --- a/website/versioned_docs/version-v1.3_alpha/circuits.md +++ b/website/versioned_docs/version-v1.3_alpha/circuits.md @@ -444,7 +444,7 @@ pnpm circom:build $CIRCUIT_NAME Run from the root directory to save to the `cli/zkeys` folder: ```bash -pnpm setup:zkeys +pnpm setup:zkeys --outPath ../cli/zkeys ``` Run from the circuits folder with `--outPath` to save to a custom folder: diff --git a/website/versioned_docs/version-v1.3_alpha/installation.md b/website/versioned_docs/version-v1.3_alpha/installation.md index d183fc0d30..1688c51ea7 100644 --- a/website/versioned_docs/version-v1.3_alpha/installation.md +++ b/website/versioned_docs/version-v1.3_alpha/installation.md @@ -133,13 +133,13 @@ pnpm test:circuits-c **for the wasm witness generator** ```bash -pnpm build:circuits-wasm +pnpm build:circuits-wasm -- --outPath ../cli/zkeys ``` Finally, generate the `.zkey` files. This may require a lot of memory and time. ```bash -pnpm setup:zkeys +pnpm setup:zkeys --outPath ../cli/zkeys ``` > If on a ARM64 chip, the above will work with the wasm witness only. The errors you will get for the c++ witness are: diff --git a/website/versioned_docs/version-v1.3_alpha/testing.md b/website/versioned_docs/version-v1.3_alpha/testing.md index a3dba3b64b..b4cf2bcce2 100644 --- a/website/versioned_docs/version-v1.3_alpha/testing.md +++ b/website/versioned_docs/version-v1.3_alpha/testing.md @@ -97,13 +97,13 @@ From the root folder, run: **for c++ witness generator** ```bash -pnpm build:circuits-c +pnpm build:circuits-c -- --outPath ../cli/zkeys ``` **for wasm witness generator** ```bash -pnpm build:circuits-wasm +pnpm build:circuits-wasm -- --outPath ../cli/zkeys ``` You should see the following files in `maci/cli/`: @@ -248,7 +248,7 @@ zkeys/SubsidyPerBatch_10-1-2_test/SubsidyPerBatch_10-1-2_test_cpp/SubsidyPerBatc **generate zkeys** ```bash -pnpm setup:zkeys +pnpm setup:zkeys --outPath ../cli/zkeys ``` ### Check the Rapidsnark binary diff --git a/website/versioned_docs/version-v1.3_alpha/troubleshooting.md b/website/versioned_docs/version-v1.3_alpha/troubleshooting.md index 6cd44a3724..b797452fc7 100644 --- a/website/versioned_docs/version-v1.3_alpha/troubleshooting.md +++ b/website/versioned_docs/version-v1.3_alpha/troubleshooting.md @@ -47,7 +47,7 @@ Error: Error executing ./zkeys/ProcessMessages_10-2-1-2_test /tmp/tmp-9904-zG0k8 You can generate the missing `.dat` files using the following command: ```bash -pnpm build:circuits-c +pnpm build:circuits-c -- --outPath ../cli/zkeys ``` ## contracts: `prove` command failure