Skip to content

Commit

Permalink
refactor: vote sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Jan 30, 2025
1 parent 76fa5b4 commit d1a9019
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 66 deletions.
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",
"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

0 comments on commit d1a9019

Please sign in to comment.