Skip to content

Commit

Permalink
refactor: sdk functions for poll and verification
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Jan 23, 2025
1 parent 11e5b8a commit 8841796
Show file tree
Hide file tree
Showing 20 changed files with 978 additions and 29 deletions.
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"maci-core": "^2.5.0",
"maci-crypto": "^2.5.0",
"maci-domainobjs": "^2.5.0",
"maci-sdk": "^0.0.1",
"prompt": "^1.3.0"
},
"devDependencies": {
Expand Down
34 changes: 27 additions & 7 deletions packages/cli/ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import { Command } from "@commander-js/extra-typings";
import { generateTallyCommitments, getPollParams, verify, getPoll } from "maci-sdk";

import fs from "fs";
import path from "path";
Expand All @@ -15,14 +16,12 @@ import {
deploy,
showContracts,
deployPoll,
getPoll,
publish,
setVerifyingKeys,
mergeSignups,
timeTravel,
signup,
isRegisteredUser,
verify,
genProofs,
fundWallet,
proveOnChain,
Expand All @@ -32,7 +31,7 @@ import {
joinPoll,
isJoinedUser,
} from "./commands";
import { TallyData, logError, promptSensitiveValue, readContractAddress } from "./utils";
import { TallyData, banner, logError, logGreen, promptSensitiveValue, readContractAddress, success } from "./utils";

// set the description version and name of the cli tool
const { description, version, name } = JSON.parse(
Expand Down Expand Up @@ -535,20 +534,32 @@ program
.description("Get deployed poll from MACI contract")
.option("-p, --poll <poll>", "the poll id")
.option("-x, --maci-address <maciAddress>", "the MACI contract address")
.option("-q, --quiet <quiet>", "whether to print values to the console", (value) => value === "true", false)
.action(async (cmdObj) => {
try {
const signer = await getSigner();
const network = await signer.provider?.getNetwork();

const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name));

await getPoll({
const details = await getPoll({
pollId: cmdObj.poll,
maciAddress,
signer,
quiet: cmdObj.quiet,
});

logGreen(
true,
success(
[
`ID: ${details.id}`,
`Deploy time: ${new Date(Number(details.deployTime) * 1000).toString()}`,
`End time: ${new Date(Number(details.deployTime) + Number(details.duration) * 1000).toString()}`,
`Number of signups ${details.numSignups}`,
`State tree merged: ${details.isMerged}`,
`Mode: ${details.mode === 0n ? "Quadratic Voting" : "Non-Quadratic Voting"}`,
].join("\n"),
),
);
} catch (error) {
program.error((error as Error).message, { exitCode: 1 });
}
Expand Down Expand Up @@ -582,6 +593,7 @@ program
.option("-r, --rpc-provider <provider>", "the rpc provider URL")
.action(async (cmdObj) => {
try {
banner(cmdObj.quiet);
const signer = await getSigner();
const network = await signer.provider?.getNetwork();

Expand All @@ -595,12 +607,20 @@ program

const maciAddress = tallyData.maci || cmdObj.maciAddress || (await readContractAddress("MACI", network?.name));

const pollParams = await getPollParams({ pollId: cmdObj.pollId, maciContractAddress: maciAddress, signer });
const tallyCommitments = generateTallyCommitments({
tallyData,
voteOptionTreeDepth: pollParams.voteOptionTreeDepth,
});

await verify({
tallyData,
pollId: cmdObj.pollId,
maciAddress,
quiet: cmdObj.quiet,
signer,
tallyCommitments,
numVoteOptions: pollParams.numVoteOptions,
voteOptionTreeDepth: pollParams.voteOptionTreeDepth,
});
} catch (error) {
program.error((error as Error).message, { exitCode: 1 });
Expand Down
20 changes: 20 additions & 0 deletions packages/sdk/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const path = require("path");

module.exports = {
root: true,
extends: ["../../.eslintrc.js"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.resolve(__dirname, "./tsconfig.json"),
sourceType: "module",
typescript: true,
ecmaVersion: 2022,
experimentalDecorators: true,
requireConfigFile: false,
ecmaFeatures: {
classes: true,
impliedStrict: true,
},
warnOnUnsupportedTypeScriptVersion: true,
},
};
6 changes: 6 additions & 0 deletions packages/sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
contractAddresses.json
contractAddresses.old.json
contractAddress.old
contractAddress.txt
localState.json
zkeys
4 changes: 4 additions & 0 deletions packages/sdk/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tests
build/tests
.etherlime-store
.env
12 changes: 12 additions & 0 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# maci-sdk

[![NPM Package][cli-npm-badge]][cli-npm-link]
[![Actions Status][cli-actions-badge]][cli-actions-link]

Please refer to the [documentation for the
CLI](https://maci.pse.dev/docs/developers-references/typescript-code/cli).

[cli-npm-badge]: https://img.shields.io/npm/v/maci-sdk.svg
[cli-actions-badge]: https://github.com/privacy-scaling-explorations/maci/actions/workflows/e2e.yml/badge.svg
[cli-npm-link]: https://www.npmjs.com/package/maci-sdk
[cli-actions-link]: https://github.com/privacy-scaling-explorations/maci/actions?query=workflow%3ACI
77 changes: 77 additions & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "maci-sdk",
"version": "0.0.1",
"description": "MACI's SDK",
"main": "build/ts/index.js",
"exports": {
".": {
"types": "./build/ts/index.d.ts",
"default": "./build/ts/index.js"
},
"./sdk": {
"types": "./build/ts/sdk/index.d.ts",
"default": "./build/ts/sdk/index.js"
}
},
"bin": {
"maci-sdk": "./build/ts/index.js"
},
"files": [
"build",
"CHANGELOG.md",
"README.md"
],
"scripts": {
"watch": "tsc --watch",
"build": "tsc -p tsconfig.build.json",
"postbuild": "cp package.json ./build && mkdir -p ./zkeys",
"types": "tsc -p tsconfig.json --noEmit",
"test": "nyc ts-mocha --exit tests/unit/*.test.ts",
"docs": "typedoc --plugin typedoc-plugin-markdown --options ./typedoc.json"
},
"dependencies": {
"@commander-js/extra-typings": "^12.1.0",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"commander": "^12.1.0",
"dotenv": "^16.4.5",
"ethers": "^6.13.4",
"hardhat": "^2.22.8",
"maci-contracts": "^2.5.0",
"maci-crypto": "^2.5.0"
},
"devDependencies": {
"@types/chai": "^4.3.9",
"@types/chai-as-promised": "^7.1.8",
"@types/mocha": "^10.0.8",
"@types/node": "^22.9.0",
"@types/snarkjs": "^0.7.8",
"chai": "^4.3.10",
"chai-as-promised": "^7.1.2",
"mocha": "^10.7.3",
"nyc": "^17.1.0",
"snarkjs": "^0.7.5",
"ts-mocha": "^10.0.0",
"typescript": "^5.6.3"
},
"nyc": {
"reporter": [
"text",
"lcov"
],
"extensions": [
".ts"
],
"all": true,
"exclude": [
"**/*.js",
"**/*.d.ts",
"hardhat.config.ts",
"tests/**/*.ts",
"ts/index.ts"
],
"branches": ">50%",
"lines": ">50%",
"functions": ">50%",
"statements": ">50%"
}
}
16 changes: 16 additions & 0 deletions packages/sdk/ts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export { getPoll, getPollParams } from "./poll";
export { verify } from "./verify";
export { generateTallyCommitments } from "./tallyCommitments";

export {
linkPoseidonLibraries,
Deployment,
ContractStorage,
EContracts,
EMode,
type IVerifyingKeyStruct,
} from "maci-contracts";

export * from "maci-contracts/typechain-types";

export type { TallyData, VerifyArgs, IGetPollArgs, IGetPollData } from "./utils";
89 changes: 89 additions & 0 deletions packages/sdk/ts/poll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { ZeroAddress } from "ethers";
import {
MACI__factory as MACIFactory,
Poll__factory as PollFactory,
Tally__factory as TallyFactory,
} from "maci-contracts/typechain-types";

import type { IGetPollArgs, IGetPollData, IGetPollParamsArgs, IPollParams } from "./utils/interfaces";

/**
* Get deployed poll from MACI contract
* @param {IGetPollArgs} args - The arguments for the get poll command
* @returns {IGetPollData} poll data
*/
export const getPoll = async ({ maciAddress, signer, provider, pollId }: IGetPollArgs): Promise<IGetPollData> => {
if (!signer && !provider) {
throw new Error("No signer and provider are provided");
}

const maciContract = MACIFactory.connect(maciAddress, signer ?? provider);
const id =
pollId === undefined ? await maciContract.nextPollId().then((nextPollId) => nextPollId - 1n) : BigInt(pollId);

if (id < 0n) {
throw new Error(`Invalid poll id ${id}`);
}

const pollContracts = await maciContract.polls(id);

if (pollContracts.poll === ZeroAddress) {
throw new Error(`MACI contract doesn't have any deployed poll ${id}`);
}

const pollContract = PollFactory.connect(pollContracts.poll, signer ?? provider);

const [[deployTime, duration], mergedStateRoot] = await Promise.all([
pollContract.getDeployTimeAndDuration(),
pollContract.mergedStateRoot(),
]);
const isMerged = mergedStateRoot !== BigInt(0);
const numSignups = await (isMerged ? pollContract.numSignups() : maciContract.numSignUps());

// get the poll mode
const tallyContract = TallyFactory.connect(pollContracts.tally, signer ?? provider);
const mode = await tallyContract.mode();

return {
id,
address: pollContracts.poll,
deployTime,
duration,
numSignups,
isMerged,
mode,
};
};

/**
* Get the parameters for the poll
* @param {IGetPollParamsArgs} args - The arguments for the get poll command
* @returns {IPollParams} poll parameters
*/
export const getPollParams = async ({
pollId,
signer,
maciContractAddress,
}: IGetPollParamsArgs): Promise<IPollParams> => {
// get the contract objects
const maciContract = MACIFactory.connect(maciContractAddress, signer);
const pollContracts = await maciContract.polls(pollId);
const pollContract = PollFactory.connect(pollContracts.poll, signer);

const treeDepths = await pollContract.treeDepths();
const voteOptionTreeDepth = Number(treeDepths.voteOptionTreeDepth);
const numVoteOptions = 5 ** voteOptionTreeDepth;

const messageBatchSize = Number.parseInt((await pollContract.messageBatchSize()).toString(), 10);

const intStateTreeDepth = Number(treeDepths.intStateTreeDepth);
const tallyBatchSize = 5 ** intStateTreeDepth;

return {
messageBatchSize,
numVoteOptions,
tallyBatchSize,
voteOptionTreeDepth,
intStateTreeDepth,
};
};
55 changes: 55 additions & 0 deletions packages/sdk/ts/tallyCommitments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { genTreeCommitment, hash2, hash3, hashLeftRight } from "maci-crypto";

import { IGenerateTallyCommitmentsArgs, ITallyCommitments } from "./utils/interfaces";

/**
* Generate the tally commitments for this current batch of proving
* @param tallyData - The tally data
* @param voteOptionTreeDepth - The vote option tree depth
* @returns The commitments to the Tally data
*/
export const generateTallyCommitments = ({
tallyData,
voteOptionTreeDepth,
}: IGenerateTallyCommitmentsArgs): ITallyCommitments => {
// compute newResultsCommitment
const newResultsCommitment = genTreeCommitment(
tallyData.results.tally.map((x) => BigInt(x)),
BigInt(tallyData.results.salt),
voteOptionTreeDepth,
);

// compute newSpentVoiceCreditsCommitment
const newSpentVoiceCreditsCommitment = hash2([
BigInt(tallyData.totalSpentVoiceCredits.spent),
BigInt(tallyData.totalSpentVoiceCredits.salt),
]);

let newTallyCommitment: bigint;
let newPerVOSpentVoiceCreditsCommitment: bigint | undefined;

if (tallyData.isQuadratic) {
// compute newPerVOSpentVoiceCreditsCommitment
newPerVOSpentVoiceCreditsCommitment = genTreeCommitment(
tallyData.perVOSpentVoiceCredits!.tally.map((x) => BigInt(x)),
BigInt(tallyData.perVOSpentVoiceCredits!.salt),
voteOptionTreeDepth,
);

// compute newTallyCommitment
newTallyCommitment = hash3([
newResultsCommitment,
newSpentVoiceCreditsCommitment,
newPerVOSpentVoiceCreditsCommitment,
]);
} else {
newTallyCommitment = hashLeftRight(newResultsCommitment, newSpentVoiceCreditsCommitment);
}

return {
newTallyCommitment,
newSpentVoiceCreditsCommitment,
newPerVOSpentVoiceCreditsCommitment,
newResultsCommitment,
};
};
Loading

0 comments on commit 8841796

Please sign in to comment.