Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(contracts): add tally results #1844

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/tests/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export const mergeSignupsArgs: Omit<MergeSignupsArgs, "signer"> = {

export const proveOnChainArgs: Omit<ProveOnChainArgs, "signer"> = {
pollId: 0n,
tallyFile: testTallyFilePath,
proofDir: testProofsDirPath,
};

Expand Down
25 changes: 23 additions & 2 deletions packages/cli/ts/commands/proveOnChain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-await-in-loop */
import { type BigNumberish } from "ethers";
import { type IVerifyingKeyStruct, formatProofForVerifierContract } from "maci-contracts";
import { type IVerifyingKeyStruct, TallyData, formatProofForVerifierContract } from "maci-contracts";
import {
MACI__factory as MACIFactory,
AccQueue__factory as AccQueueFactory,
Expand All @@ -11,7 +11,7 @@ import {
Verifier__factory as VerifierFactory,
} from "maci-contracts/typechain-types";
import { MESSAGE_TREE_ARITY, STATE_TREE_ARITY } from "maci-core";
import { G1Point, G2Point } from "maci-crypto";
import { G1Point, G2Point, genTreeProof } from "maci-crypto";
import { VerifyingKey } from "maci-domainobjs";

import fs from "fs";
Expand Down Expand Up @@ -42,6 +42,7 @@ export const proveOnChain = async ({
proofDir,
maciAddress,
signer,
tallyFile,
quiet = true,
}: ProveOnChainArgs): Promise<void> => {
banner(quiet);
Expand Down Expand Up @@ -368,4 +369,24 @@ export const proveOnChain = async ({
if (tallyBatchNum === totalTallyBatches) {
logGreen(quiet, success("All vote tallying proofs have been submitted."));
}

if (tallyFile) {
const tallyData = await fs.promises.readFile(tallyFile).then((res) => JSON.parse(res.toString()) as TallyData);

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

await tallyContract
.addTallyResults(
tallyData.results.tally.map((_, index) => index),
tallyResults,
tallyResultProofs,
tallyData.results.salt,
tallyData.totalSpentVoiceCredits.commitment,
tallyData.perVOSpentVoiceCredits?.commitment ?? 0n,
)
.then((tx) => tx.wait());
}
};
5 changes: 5 additions & 0 deletions packages/cli/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,10 @@ program
.command("proveOnChain")
.description("prove the results of a poll on chain")
.requiredOption("-o, --poll-id <pollId>", "the poll id", BigInt)
.option(
"-t, --tally-file <tallyFile>",
"the tally file with results, per vote option spent credits, spent voice credits total",
)
.option("-q, --quiet <quiet>", "whether to print values to the console", (value) => value === "true", false)
.option("-r, --rpc-provider <provider>", "the rpc provider URL")
.option("-x, --maci-address <maciAddress>", "the MACI contract address")
Expand All @@ -645,6 +649,7 @@ program

await proveOnChain({
pollId: cmdObj.pollId,
tallyFile: cmdObj.tallyFile,
proofDir: cmdObj.proofDir,
maciAddress: cmdObj.maciAddress,
quiet: cmdObj.quiet,
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/ts/utils/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,11 @@ export interface ProveOnChainArgs {
*/
signer: Signer;

/**
* The tally file with results, per vote option spent credits, spent voice credits total
*/
tallyFile?: string;

/**
* The address of the MACI contract
*/
Expand Down
86 changes: 86 additions & 0 deletions packages/contracts/contracts/Tally.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa
IMessageProcessor public immutable messageProcessor;
Mode public immutable mode;

// The tally results
mapping(uint256 => uint256) public tallyResults;

// The total tally results number
uint256 public totalTallyResults;

/// @notice custom errors
error ProcessingNotComplete();
error InvalidTallyVotesProof();
Expand All @@ -60,6 +66,7 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa
error BatchStartIndexTooLarge();
error TallyBatchSizeTooLarge();
error NotSupported();
error VotesNotTallied();

/// @notice Create a new Tally contract
/// @param _verifier The Verifier contract
Expand Down Expand Up @@ -344,4 +351,83 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs, ITa
isValid = hash2(tally) == tallyCommitment;
}
}

/**
* @notice Add and verify tally results by batch.
* @param _voteOptionIndices Vote option index.
* @param _tallyResults The results of vote tally for the recipients.
* @param _tallyResultProofs Proofs of correctness of the vote tally results.
* @param _tallyResultSalt the respective salt in the results object in the tally.json
* @param _spentVoiceCreditsHashes hashLeftRight(number of spent voice credits, spent salt)
* @param _perVOSpentVoiceCreditsHashes hashLeftRight(merkle root of the no spent voice credits per vote option, perVOSpentVoiceCredits salt)
*/
function addTallyResults(
uint256[] calldata _voteOptionIndices,
Dismissed Show dismissed Hide dismissed
uint256[] calldata _tallyResults,
Dismissed Show dismissed Hide dismissed
uint256[][][] calldata _tallyResultProofs,
Dismissed Show dismissed Hide dismissed
uint256 _tallyResultSalt,
Dismissed Show dismissed Hide dismissed
uint256 _spentVoiceCreditsHashes,
Dismissed Show dismissed Hide dismissed
uint256 _perVOSpentVoiceCreditsHashes
Dismissed Show dismissed Hide dismissed
) public virtual onlyOwner {
if (!isTallied()) {
revert VotesNotTallied();
}

(, , , uint8 voteOptionTreeDepth) = poll.treeDepths();
uint256 voteOptionsLength = _voteOptionIndices.length;

for (uint256 i = 0; i < voteOptionsLength; ) {
addTallyResult(
_voteOptionIndices[i],
_tallyResults[i],
_tallyResultProofs[i],
_tallyResultSalt,
_spentVoiceCreditsHashes,
_perVOSpentVoiceCreditsHashes,
voteOptionTreeDepth
);

unchecked {
i++;
}
}

totalTallyResults += voteOptionsLength;
}
Dismissed Show dismissed Hide dismissed

/**
* @dev Add and verify tally votes and calculate sum of tally squares for alpha calculation.
* @param _voteOptionIndex Vote option index.
* @param _tallyResult The results of vote tally for the recipients.
* @param _tallyResultProof Proofs of correctness of the vote tally results.
* @param _tallyResultSalt the respective salt in the results object in the tally.json
* @param _spentVoiceCreditsHash hashLeftRight(number of spent voice credits, spent salt)
* @param _perVOSpentVoiceCreditsHash hashLeftRight(merkle root of the no spent voice credits per vote option, perVOSpentVoiceCredits salt)
* @param _voteOptionTreeDepth vote option tree depth
*/
function addTallyResult(
uint256 _voteOptionIndex,
Dismissed Show dismissed Hide dismissed
uint256 _tallyResult,
Dismissed Show dismissed Hide dismissed
uint256[][] calldata _tallyResultProof,
Dismissed Show dismissed Hide dismissed
uint256 _tallyResultSalt,
Fixed Show fixed Hide fixed
Dismissed Show dismissed Hide dismissed
uint256 _spentVoiceCreditsHash,
Fixed Show fixed Hide fixed
Dismissed Show dismissed Hide dismissed
uint256 _perVOSpentVoiceCreditsHash,
Dismissed Show dismissed Hide dismissed
uint8 _voteOptionTreeDepth
Dismissed Show dismissed Hide dismissed
) internal virtual {
bool isValid = verifyTallyResult(
_voteOptionIndex,
_tallyResult,
_tallyResultProof,
_tallyResultSalt,
_voteOptionTreeDepth,
_spentVoiceCreditsHash,
_perVOSpentVoiceCreditsHash
);

if (!isValid) {
revert InvalidTallyVotesProof();
}

tallyResults[_voteOptionIndex] = _tallyResult;
}
}
22 changes: 19 additions & 3 deletions packages/contracts/tasks/helpers/Prover.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console, no-await-in-loop */
import { STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "maci-core";
import { G1Point, G2Point } from "maci-crypto";
import { G1Point, G2Point, genTreeProof } from "maci-crypto";
import { VerifyingKey } from "maci-domainobjs";

import type { IVerifyingKeyStruct, Proof } from "../../ts/types";
Expand All @@ -9,7 +9,7 @@ import type { BigNumberish } from "ethers";

import { asHex, formatProofForVerifierContract } from "../../ts/utils";

import { IProverParams } from "./types";
import { IProverParams, TallyData } from "./types";

/**
* Prover class is designed to prove message processing and tally proofs on-chain.
Expand Down Expand Up @@ -219,7 +219,7 @@ export class Prover {
*
* @param proofs tally proofs
*/
async proveTally(proofs: Proof[]): Promise<void> {
async proveTally(proofs: Proof[], tallyData: TallyData): Promise<void> {
const [treeDepths, numSignUpsAndMessages, tallyBatchNumber, mode, stateTreeDepth] = await Promise.all([
this.pollContract.treeDepths(),
this.pollContract.numSignUpsAndMessages(),
Expand Down Expand Up @@ -310,6 +310,22 @@ export class Prover {
if (tallyBatchNum === totalTallyBatches) {
console.log("All vote tallying proofs have been submitted.");
}

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

await this.tallyContract
.addTallyResults(
tallyData.results.tally.map((_, index) => index),
tallyResults,
tallyResultProofs,
tallyData.results.salt,
tallyData.totalSpentVoiceCredits.commitment,
tallyData.perVOSpentVoiceCredits?.commitment ?? 0n,
)
.then((tx) => tx.wait());
}

/**
Expand Down
5 changes: 3 additions & 2 deletions packages/contracts/tasks/runner/prove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,9 @@ 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).then(({ proofs }) => proofs);
await prover.proveTally(data.tallyProofs);
const { proofs: tallyProofs, tallyData } = await proofGenerator.generateTallyProofs(network);
data.tallyProofs = tallyProofs;
await prover.proveTally(data.tallyProofs, tallyData);

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

Expand Down
Loading
Loading