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

refactor: vote sdk #2089

Merged
merged 1 commit into from
Jan 30, 2025
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
2 changes: 1 addition & 1 deletion apps/relayer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
"helmet": "^8.0.0",
"lodash": "^4.17.21",
"maci-cli": "workspace:^2.5.0",
"maci-contracts": "workspace:^2.5.0",
"maci-sdk": "workspace:^0.0.1",
"maci-domainobjs": "workspace:^2.5.0",
"maci-contracts": "workspace:^2.5.0",
ctrlc03 marked this conversation as resolved.
Show resolved Hide resolved
"mongoose": "^8.9.5",
"multiformats": "^13.3.1",
"mustache": "^4.2.0",
Expand Down
2 changes: 1 addition & 1 deletion apps/relayer/tests/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import hardhat from "hardhat";
import { deploy, deployPoll, deployVkRegistryContract, joinPoll, setVerifyingKeys, signup } from "maci-cli";
import { genMaciStateFromContract } from "maci-contracts";
import { Keypair } from "maci-domainobjs";
import { genMaciStateFromContract } from "maci-sdk";

import {
INT_STATE_TREE_DEPTH,
Expand Down
2 changes: 1 addition & 1 deletion apps/relayer/tests/messageBatches.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { jest } from "@jest/globals";
import { HttpStatus, ValidationPipe, type INestApplication } from "@nestjs/common";
import { Test } from "@nestjs/testing";
import { formatProofForVerifierContract, genProofSnarkjs } from "maci-contracts";
import { Keypair } from "maci-domainobjs";
import { formatProofForVerifierContract, genProofSnarkjs } from "maci-sdk";
import request from "supertest";

import type { App } from "supertest/types";
Expand Down
3 changes: 1 addition & 2 deletions apps/relayer/tests/messages.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { jest } from "@jest/globals";
import { HttpStatus, ValidationPipe, type INestApplication } from "@nestjs/common";
import { Test } from "@nestjs/testing";
import { formatProofForVerifierContract } from "maci-contracts";
import { Keypair } from "maci-domainobjs";
import { genProofSnarkjs } from "maci-sdk";
import { formatProofForVerifierContract, genProofSnarkjs } from "maci-sdk";
import request from "supertest";

import type { App } from "supertest/types";
Expand Down
2 changes: 1 addition & 1 deletion apps/relayer/ts/message/__tests__/message.guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { HttpException, type ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import dotenv from "dotenv";
import { ZeroAddress } from "ethers";
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts/typechain-types";
import { Keypair } from "maci-domainobjs";
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-sdk";

import { MessageGuard, PUBLIC_METADATA_KEY, Public } from "../message.guard";

Expand Down
2 changes: 1 addition & 1 deletion apps/relayer/ts/message/message.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import flatMap from "lodash/flatMap";
import flatten from "lodash/flatten";
import map from "lodash/map";
import values from "lodash/values";
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts/typechain-types";
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-sdk";

import type { Request as Req } from "express";

Expand Down
74 changes: 18 additions & 56 deletions packages/cli/ts/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PrivKey,
PubKey,
} from "maci-domainobjs";
import { generateVote, getCoordinatorPubKey } from "maci-sdk";

import type { IPublishBatchArgs, IPublishBatchData, PublishArgs } from "../utils/interfaces";

Expand Down Expand Up @@ -41,7 +42,7 @@ export const publish = async ({
logError("invalid MACI public key");
}
// deserialize
const pollPubKey = PubKey.deserialize(pubkey);
const votePubKey = PubKey.deserialize(pubkey);

if (!(await contractExists(signer.provider!, maciAddress))) {
logError("MACI contract does not exist");
Expand All @@ -51,31 +52,7 @@ export const publish = async ({
logError("Invalid MACI private key");
}

const pollPrivKey = PrivKey.deserialize(privateKey);

// validate args
if (voteOptionIndex < 0) {
logError("invalid vote option index");
}

// check < 1 cause index zero is a blank state leaf
if (stateIndex < 1) {
logError("invalid state index");
}

if (nonce < 0) {
logError("invalid nonce");
}

if (salt && !validateSalt(salt)) {
logError("Invalid salt");
}

const userSalt = salt ? BigInt(salt) : genRandomSalt();

if (pollId < 0) {
logError("Invalid poll id");
}
const privKey = PrivKey.deserialize(privateKey);

const maciContract = MACIFactory.connect(maciAddress, signer);
const pollContracts = await maciContract.getPoll(pollId);
Expand All @@ -86,54 +63,39 @@ export const publish = async ({

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

const maxVoteOptions = Number(await pollContract.maxVoteOptions());
const coordinatorPubKeyResult = await pollContract.coordinatorPubKey();
const coordinatorPubKey = await getCoordinatorPubKey(pollContracts.poll, signer);
const maxVoteOptions = await pollContract.maxVoteOptions();

// validate the vote options index against the max leaf index on-chain
if (maxVoteOptions < voteOptionIndex) {
logError("Invalid vote option index");
}

const coordinatorPubKey = new PubKey([
BigInt(coordinatorPubKeyResult.x.toString()),
BigInt(coordinatorPubKeyResult.y.toString()),
]);

const encKeypair = new Keypair();

// create the command object
const command: PCommand = new PCommand(
stateIndex,
pollPubKey,
const { message, ephemeralKeypair } = generateVote({
pollId,
voteOptionIndex,
newVoteWeight,
salt,
nonce,
BigInt(pollId),
userSalt,
);

// sign the command with the poll private key
const signature = command.sign(pollPrivKey);
// encrypt the command using a shared key between the user and the coordinator
const message = command.encrypt(signature, Keypair.genEcdhSharedKey(encKeypair.privKey, coordinatorPubKey));
privateKey: privKey,
stateIndex,
voteWeight: newVoteWeight,
coordinatorPubKey,
maxVoteOption: maxVoteOptions,
newPubKey: votePubKey,
});

try {
// submit the message onchain as well as the encryption public key
const tx = await pollContract.publishMessage(message.asContractParam(), encKeypair.pubKey.asContractParam());
const tx = await pollContract.publishMessage(message.asContractParam(), ephemeralKeypair.pubKey.asContractParam());
const receipt = await tx.wait();

if (receipt?.status !== 1) {
logError("Transaction failed");
}

logYellow(quiet, info(`Transaction hash: ${receipt!.hash}`));
logGreen(quiet, info(`Ephemeral private key: ${encKeypair.privKey.serialize()}`));
logGreen(quiet, info(`Ephemeral private key: ${ephemeralKeypair.privKey.serialize()}`));
} catch (error) {
logError((error as Error).message);
}

// we want the user to have the ephemeral private key
return encKeypair.privKey.serialize();
return ephemeralKeypair.privKey.serialize();
};

/**
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { isUserRegistered, isJoinedUser, signup } from "./user";
export { getAllOnChainVks, compareVks, extractAllVks } from "./verifyingKeys";
export { isArm } from "./utils";
export { genSignUpTree } from "./trees";
export { generateVote, getCoordinatorPubKey } from "./votes";

export {
EMode,
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/ts/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type {
IProcessMessagesInputs,
ISnarkJSVerificationKey,
ITallyVotesInputs,
IVote,
} from "./types";
export { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "./verifiers";
export { BLOCKS_STEP } from "./constants";
Expand Down
68 changes: 66 additions & 2 deletions packages/sdk/ts/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MACI, Poll } from "maci-contracts/typechain-types";
import { PubKey } from "maci-domainobjs";
import { PubKey, PrivKey } from "maci-domainobjs";

import type { LeanIMT } from "@zk-kit/lean-imt";
import type { Provider, Signer } from "ethers";
import type { EMode } from "maci-contracts";
import type { IVkContractParams, VerifyingKey } from "maci-domainobjs";
import type { IVkContractParams, Keypair, Message, VerifyingKey } from "maci-domainobjs";

/**
* A circuit inputs for the circom circuit
Expand Down Expand Up @@ -748,3 +748,67 @@ export interface IGenSignUpTree {
*/
pubKeys: PubKey[];
}

/**
* Interface for the arguments for the generateVote function
*/
export interface IGenerateVoteArgs {
/**
* The poll id
*/
pollId: bigint;
/**
* The index of the vote option
*/
voteOptionIndex: bigint;
/**
* The salt for the vote
*/
salt?: bigint;
/**
* The nonce for the vote
*/
nonce: bigint;
/**
* The private key for the vote
*/
privateKey: PrivKey;
/**
* The state index for the vote
*/
stateIndex: bigint;
/**
* The weight of the vote
*/
voteWeight: bigint;
/**
* The coordinator public key
*/
coordinatorPubKey: PubKey;
/**
* The largest vote option index
*/
maxVoteOption: bigint;
/**
* Ephemeral keypair
*/
ephemeralKeypair?: Keypair;
/**
* New key in case of key change message
*/
newPubKey?: PubKey;
}

/**
* Interface for the vote object
*/
export interface IVote {
/**
* The message to be sent to the contract
*/
message: Message;
/**
* The ephemeral keypair used to generate the shared key for encrypting the message
*/
ephemeralKeypair: Keypair;
}
22 changes: 22 additions & 0 deletions packages/sdk/ts/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { SNARK_FIELD_SIZE } from "maci-crypto";

import fs from "fs";
import os from "os";

/**
Expand All @@ -6,6 +9,18 @@ import os from "os";
*/
export const isArm = (): boolean => os.arch().includes("arm");

/**
* Remove a file
* @param filepath - the path to the file
*/
export const unlinkFile = async (filepath: string): Promise<void> => {
const isFileExists = fs.existsSync(filepath);

if (isFileExists) {
await fs.promises.unlink(filepath);
}
};

/**
* Pause the thread for n milliseconds
* @param ms - the amount of time to sleep in milliseconds
Expand All @@ -15,3 +30,10 @@ export const sleep = async (ms: number): Promise<void> => {
setTimeout(resolve, ms);
});
};

/**
* Run both format check and size check on a salt value
* @param salt the salt to validate
* @returns whether it is valid or not
*/
export const validateSalt = (salt: bigint): boolean => salt < SNARK_FIELD_SIZE;
2 changes: 1 addition & 1 deletion packages/sdk/ts/verifyingKeys.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { VkRegistry__factory as VkRegistryFactory, extractVk } from "maci-contracts";
import { IVkContractParams, VerifyingKey } from "maci-domainobjs";

import type { GetAllVksArgs, IExtractAllVksArgs, IMaciVks, IMaciVerifyingKeys } from "./utils";
import type { GetAllVksArgs, IExtractAllVksArgs, IMaciVks, IMaciVerifyingKeys } from "./utils/types";

/**
* Get all the verifying keys from the contract
Expand Down
Loading
Loading