Skip to content

Commit

Permalink
feat: separate gen proofs and submit tasks (#1888)
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 authored Nov 4, 2024
1 parent 822f1ff commit 6007edf
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 42 deletions.
1 change: 1 addition & 0 deletions packages/contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ docs
.env
deploy-config.json
deployed-contracts.json
proofs
1 change: 1 addition & 0 deletions packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "./tasks/runner/deployFull";
import "./tasks/runner/deployPoll";
import "./tasks/runner/merge";
import "./tasks/runner/prove";
import "./tasks/runner/submitOnChain";
import "./tasks/runner/verifyFull";

dotenv.config();
Expand Down
16 changes: 15 additions & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"verify": "hardhat verify-full",
"merge": "hardhat merge",
"prove": "hardhat prove",
"submitOnChain": "hardhat submitOnChain",
"benchmark": "hardhat benchmark",
"deploy:localhost": "pnpm run deploy",
"deploy:sepolia": "pnpm run deploy --network sepolia",
Expand Down Expand Up @@ -124,12 +125,25 @@
"prove:gnosis-chiado": "pnpm run prove --network gnosis_chiado",
"prove:polygon": "pnpm run prove --network polygon",
"prove:polygon-amoy": "pnpm run prove --network polygon_amoy",
"submitOnChain:localhost": "pnpm run submitOnChain --network localhost",
"submitOnChain:sepolia": "pnpm run submitOnChain --network sepolia",
"submitOnChain:optimism-sepolia": "pnpm run submitOnChain --network optimism_sepolia",
"submitOnChain:optimism": "pnpm run submitOnChain --network optimism",
"submitOnChain:scroll": "pnpm run submitOnChain --network scroll",
"submitOnChain:scroll-sepolia": "pnpm run submitOnChain --network scroll_sepolia",
"submitOnChain:arbitrum": "pnpm run submitOnChain --network arbitrum",
"submitOnChain:arbitrum-sepolia": "pnpm run submitOnChain --network arbitrum_sepolia",
"submitOnChain:base": "pnpm run submitOnChain --network base",
"submitOnChain:base-sepolia": "pnpm run submitOnChain --network base_sepolia",
"submitOnChain:gnosis": "pnpm run submitOnChain --network gnosis",
"submitOnChain:gnosis-chiado": "pnpm run submitOnChain --network gnosis_chiado",
"submitOnChain:polygon": "pnpm run submitOnChain --network polygon",
"submitOnChain:polygon-amoy": "pnpm run submitOnChain --network polygon_amoy",
"verify:sepolia": "pnpm run verify --network sepolia",
"verify:optimism-sepolia": "pnpm run verify --network optimism_sepolia",
"verify:optimism": "pnpm run verify --network optimism",
"verify:scroll": "pnpm run verify --network scroll",
"verify:scroll-sepolia": "pnpm run verify --network scroll_sepolia",
"verify:arbitrum": "pnpm run verify --network arbitrum",
"verify:arbitrum-sepolia": "pnpm run verify --network arbitrum_sepolia",
"verify:base": "pnpm run verify --network base",
"verify:base-sepolia": "pnpm run verify --network base_sepolia",
Expand Down
24 changes: 20 additions & 4 deletions packages/contracts/tasks/helpers/Prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class Prover {
*
* @param proofs tally proofs
*/
async proveTally(proofs: Proof[], tallyData: TallyData): Promise<void> {
async proveTally(proofs: Proof[]): Promise<void> {
const [treeDepths, numSignUpsAndMessages, tallyBatchNumber, mode, stateTreeDepth] = await Promise.all([
this.pollContract.treeDepths(),
this.pollContract.numSignUpsAndMessages(),
Expand Down Expand Up @@ -310,11 +310,25 @@ export class Prover {
if (tallyBatchNum === totalTallyBatches) {
console.log("All vote tallying proofs have been submitted.");
}
}

/**
* Submit tally results on chain
*
* @param tallyData - tally data
* @param recipients - number of recipients
*/
async submitResults(tallyData: TallyData, recipients?: number): Promise<void> {
console.log("Submitting results...");

const tallyResults = tallyData.results.tally.map((t) => BigInt(t));
const tallyResultProofs = tallyData.results.tally.map((_, index) =>
genTreeProof(index, tallyResults, Number(treeDepths.voteOptionTreeDepth)),
);

const [treeDepths] = await Promise.all([this.pollContract.treeDepths()]);

const resultLength = recipients ?? tallyResults.length;
const tallyResultProofs = tallyResults
.slice(0, resultLength)
.map((_, index) => genTreeProof(index, tallyResults, Number(treeDepths.voteOptionTreeDepth)));

await this.tallyContract
.addTallyResults({
Expand All @@ -329,6 +343,8 @@ export class Prover {
perVOSpentVoiceCreditsHash: tallyData.perVOSpentVoiceCredits?.commitment ?? 0n,
})
.then((tx) => tx.wait());

console.log("Results have been submitted.");
}

/**
Expand Down
20 changes: 20 additions & 0 deletions packages/contracts/tasks/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ export interface IProveParams {
transactionHash?: string;
}

/**
* Interface that represents task submitOnChain params
*/
export interface ISubmitOnChainParams {
/**
* The poll id
*/
poll: BigNumberish;

/**
* The directory where proofs are stored
*/
outputDir: string;

/**
* The file to store the tally proof
*/
tallyFile: string;
}

/**
* Interface that represents prove generator params
*/
Expand Down
45 changes: 8 additions & 37 deletions packages/contracts/tasks/runner/prove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@ import { Keypair, PrivKey } from "maci-domainobjs";

import fs from "fs";

import type { Proof } from "../../ts/types";
import type { VkRegistry, Verifier, MACI, Poll, AccQueue, MessageProcessor, Tally } from "../../typechain-types";
import type { MACI, Poll, AccQueue } from "../../typechain-types";

import { ContractStorage } from "../helpers/ContractStorage";
import { Deployment } from "../helpers/Deployment";
import { ProofGenerator } from "../helpers/ProofGenerator";
import { Prover } from "../helpers/Prover";
import { EContracts, type IProveParams } from "../helpers/types";

/**
* Prove hardhat task for generating off-chain proofs and sending them on-chain
*/
task("prove", "Command to generate proof and prove the result of a poll on-chain")
task("prove", "Command to generate proofs")
.addParam("poll", "The poll id", undefined, types.string)
.addParam("outputDir", "Output directory for proofs", undefined, types.string)
.addParam("coordinatorPrivateKey", "Coordinator maci private key", undefined, types.string)
Expand Down Expand Up @@ -71,8 +69,6 @@ task("prove", "Command to generate proof and prove the result of a poll on-chain

const maciContractAddress = storage.mustGetAddress(EContracts.MACI, network.name);
const maciContract = await deployment.getContract<MACI>({ name: EContracts.MACI, address: maciContractAddress });
const vkRegistryContract = await deployment.getContract<VkRegistry>({ name: EContracts.VkRegistry });
const verifierContract = await deployment.getContract<Verifier>({ name: EContracts.Verifier });

const pollContracts = await maciContract.polls(poll);
const pollContract = await deployment.getContract<Poll>({ name: EContracts.Poll, address: pollContracts.poll });
Expand Down Expand Up @@ -127,16 +123,6 @@ task("prove", "Command to generate proof and prove the result of a poll on-chain
throw new Error(`Poll ${poll} not found`);
}

const mpContract = await deployment.getContract<MessageProcessor>({
name: EContracts.MessageProcessor,
address: pollContracts.messageProcessor,
});

// get the tally contract based on the useQuadraticVoting flag
const tallyContract = await deployment.getContract<Tally>({
name: EContracts.Tally,
address: pollContracts.tally,
});
const useQuadraticVoting =
deployment.getDeployConfigField<boolean | null>(EContracts.Poll, "useQuadraticVoting") ?? false;
const mode = useQuadraticVoting ? "qv" : "nonQv";
Expand Down Expand Up @@ -176,31 +162,16 @@ task("prove", "Command to generate proof and prove the result of a poll on-chain
useQuadraticVoting,
});

const data = {
processProofs: [] as Proof[],
tallyProofs: [] as Proof[],
};

const prover = new Prover({
maciContract,
messageAqContract,
mpContract,
pollContract,
vkRegistryContract,
verifierContract,
tallyContract,
});

data.processProofs = await proofGenerator.generateMpProofs();
await prover.proveMessageProcessing(data.processProofs);

const { proofs: tallyProofs, tallyData } = await proofGenerator.generateTallyProofs(network);
data.tallyProofs = tallyProofs;
await prover.proveTally(data.tallyProofs, tallyData);
await proofGenerator.generateMpProofs();
await proofGenerator.generateTallyProofs(network);

const endBalance = await signer.provider.getBalance(signer);

console.log("End balance: ", Number(endBalance / 10n ** 12n) / 1e6);
console.log("Prove expenses: ", Number((startBalance - endBalance) / 10n ** 12n) / 1e6);

console.log(
"Please make sure that you do not delete the proofs from the proof directory until they are all submitted on-chain.\nRegenerating proofs will result in overwriting the existing proofs and commitments which will be different due to the use of random salts.",
);
},
);
132 changes: 132 additions & 0 deletions packages/contracts/tasks/runner/submitOnChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-console */
import { task, types } from "hardhat/config";

import fs from "fs";

import type { Proof } from "../../ts/types";
import type { VkRegistry, Verifier, MACI, Poll, AccQueue, MessageProcessor, Tally } from "../../typechain-types";

import { ContractStorage } from "../helpers/ContractStorage";
import { Deployment } from "../helpers/Deployment";
import { Prover } from "../helpers/Prover";
import { EContracts, TallyData, type ISubmitOnChainParams } from "../helpers/types";

/**
* Prove hardhat task for submitting proofs on-chain as well as uploading tally results
*/
task("submitOnChain", "Command to prove the result of a poll on-chain")
.addParam("poll", "The poll id", undefined, types.string)
.addParam("outputDir", "Output directory for proofs", undefined, types.string)
.addParam("tallyFile", "The file to store the tally proof", undefined, types.string)
.setAction(async ({ outputDir, poll, tallyFile }: ISubmitOnChainParams, hre) => {
const deployment = Deployment.getInstance();
deployment.setHre(hre);
const storage = ContractStorage.getInstance();
// if we do not have the output directory just create it
const isOutputDirExists = fs.existsSync(outputDir);

if (!isOutputDirExists) {
// Create the directory
throw new Error(
`Output directory ${outputDir} does not exist. You must provide a valid directory containing the poll zk-SNARK proofs.`,
);
}

const signer = await deployment.getDeployer();
const { network } = hre;

const startBalance = await signer.provider.getBalance(signer);

console.log("Start balance: ", Number(startBalance / 10n ** 12n) / 1e6);

const maciContractAddress = storage.mustGetAddress(EContracts.MACI, network.name);
const maciContract = await deployment.getContract<MACI>({ name: EContracts.MACI, address: maciContractAddress });
const vkRegistryContract = await deployment.getContract<VkRegistry>({ name: EContracts.VkRegistry });
const verifierContract = await deployment.getContract<Verifier>({ name: EContracts.Verifier });

const pollContracts = await maciContract.polls(poll);
const pollContract = await deployment.getContract<Poll>({ name: EContracts.Poll, address: pollContracts.poll });

const [, messageAqContractAddress] = await pollContract.extContracts();
const messageAqContract = await deployment.getContract<AccQueue>({
name: EContracts.AccQueue,
address: messageAqContractAddress,
});
const isStateAqMerged = await pollContract.stateMerged();

// Check that the state and message trees have been merged for at least the first poll
if (!isStateAqMerged && poll.toString() === "0") {
throw new Error("The state tree has not been merged yet. Please use the mergeSignups subcommand to do so.");
}

const messageTreeDepth = await pollContract.treeDepths().then((depths) => Number(depths[2]));

// check that the main root is set
const mainRoot = await messageAqContract.getMainRoot(messageTreeDepth.toString());

if (mainRoot.toString() === "0") {
throw new Error("The message tree has not been merged yet. Please use the mergeMessages subcommand to do so.");
}

const mpContract = await deployment.getContract<MessageProcessor>({
name: EContracts.MessageProcessor,
address: pollContracts.messageProcessor,
});

// get the tally contract based on the useQuadraticVoting flag
const tallyContract = await deployment.getContract<Tally>({
name: EContracts.Tally,
address: pollContracts.tally,
});

const data = {
processProofs: [] as Proof[],
tallyProofs: [] as Proof[],
};

// read the proofs from the output directory
const files = fs.readdirSync(outputDir);

// Read process proofs
const processProofFiles = files.filter((f) => f.startsWith("process_") && f.endsWith(".json"));
await Promise.all(
processProofFiles.sort().map(async (file) => {
const proofData = JSON.parse(await fs.promises.readFile(`${outputDir}/${file}`, "utf8")) as Proof;
data.processProofs.push(proofData);
}),
);

// Read tally proofs
const tallyProofFiles = files.filter((f) => f.startsWith("tally_") && f.endsWith(".json"));
await Promise.all(
tallyProofFiles.sort().map(async (file) => {
const proofData = JSON.parse(await fs.promises.readFile(`${outputDir}/${file}`, "utf8")) as Proof;
data.tallyProofs.push(proofData);
}),
);

const prover = new Prover({
maciContract,
messageAqContract,
mpContract,
pollContract,
vkRegistryContract,
verifierContract,
tallyContract,
});

await prover.proveMessageProcessing(data.processProofs);

// read tally data
const tallyData = JSON.parse(await fs.promises.readFile(tallyFile, "utf8")) as unknown as TallyData;

await prover.proveTally(data.tallyProofs);

await prover.submitResults(tallyData);

const endBalance = await signer.provider.getBalance(signer);

console.log("End balance: ", Number(endBalance / 10n ** 12n) / 1e6);
console.log("Prove expenses: ", Number((startBalance - endBalance) / 10n ** 12n) / 1e6);
});
4 changes: 4 additions & 0 deletions packages/contracts/testScriptLocalhost.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ pnpm run prove:localhost --poll 0 \
--coordinator-private-key "macisk.1751146b59d32e3c0d7426de411218172428263f93b2fc4d981c036047a4d8c0" \
--tally-file ../cli/tally.json \
--output-dir ../cli/proofs/

pnpm run submitOnChain:localhost --poll 0 \
--tally-file ../cli/tally.json \
--output-dir ../cli/proofs/

0 comments on commit 6007edf

Please sign in to comment.