Skip to content

Commit

Permalink
feat: voice credits per poll
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Dec 12, 2024
1 parent 06d7ab4 commit 3e2ac79
Show file tree
Hide file tree
Showing 34 changed files with 187 additions and 234 deletions.
2 changes: 1 addition & 1 deletion apps/subgraph/src/maci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function handleDeployPoll(event: DeployPollEvent): void {

export function handleSignUp(event: SignUpEvent): void {
const user = createOrLoadUser(event.params._userPubKeyX, event.params._userPubKeyY, event);
createOrLoadAccount(event.params._stateIndex, event, user.id, event.params._voiceCreditBalance);
createOrLoadAccount(event.params._stateIndex, event, user.id);

const maci = createOrLoadMACI(event);
maci.numSignUps = maci.numSignUps.plus(ONE_BIG_INT);
Expand Down
2 changes: 1 addition & 1 deletion apps/subgraph/templates/subgraph.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ dataSources:
eventHandlers:
- event: DeployPoll(uint256,indexed uint256,indexed uint256,uint8)
handler: handleDeployPoll
- event: SignUp(uint256,indexed uint256,indexed uint256,uint256,uint256,uint256)
- event: SignUp(uint256,uint256,indexed uint256,indexed uint256)
handler: handleSignUp
file: ./src/maci.ts
templates:
Expand Down
36 changes: 6 additions & 30 deletions packages/circuits/circom/anon/pollJoining.circom
Original file line number Diff line number Diff line change
@@ -1,75 +1,51 @@
pragma circom 2.0.0;

// circomlib import
include "./mux1.circom";
// zk-kit imports
include "./safe-comparators.circom";
// local imports
include "../utils/hashers.circom";
include "../utils/privToPubKey.circom";
include "../trees/incrementalMerkleTree.circom";

template PollJoining(stateTreeDepth) {
// Constants defining the structure and size of state.
var STATE_LEAF_LENGTH = 4;
// Constants defining the tree structure
var STATE_TREE_ARITY = 2;

// Public key IDs.
var STATE_LEAF_PUB_X_IDX = 0;
var STATE_LEAF_PUB_Y_IDX = 1;
// Voice Credit balance id
var STATE_LEAF_VOICE_CREDIT_BALANCE_IDX = 2;
var N_BITS = 252;

// User's private key
signal input privKey;
// Poll's private key
signal input pollPrivKey;
// Poll's public key
signal input pollPubKey[2];
// The state leaf and related path elements.
signal input stateLeaf[STATE_LEAF_LENGTH];
// Siblings
signal input siblings[stateTreeDepth][STATE_TREE_ARITY - 1];
// Indices
signal input indices[stateTreeDepth];
// User's hashed private key
signal input nullifier;
// User's credits for poll joining (might be <= oldCredits)
signal input credits;
// MACI State tree root which proves the user is signed up
signal input stateRoot;
// The actual tree depth (might be <= stateTreeDepth) Used in BinaryMerkleRoot
signal input actualStateTreeDepth;

var computedNullifier = PoseidonHasher(1)([privKey]);
nullifier === computedNullifier;

// User private to public key
var derivedPubKey[2] = PrivToPubKey()(privKey);
derivedPubKey[0] === stateLeaf[STATE_LEAF_PUB_X_IDX];
derivedPubKey[1] === stateLeaf[STATE_LEAF_PUB_Y_IDX];
// Poll private to public key
// Hash the public key
var pubKeyHash = PoseidonHasher(2)([derivedPubKey[0], derivedPubKey[1]]);

// Poll private to public key to verify the correct one is used to join the poll (public input)
var derivedPollPubKey[2] = PrivToPubKey()(pollPrivKey);
derivedPollPubKey[0] === pollPubKey[0];
derivedPollPubKey[1] === pollPubKey[1];

// Inclusion proof
var stateLeafHash = PoseidonHasher(4)(stateLeaf);
var stateLeafQip = BinaryMerkleRoot(stateTreeDepth)(
stateLeafHash,
pubKeyHash,
actualStateTreeDepth,
indices,
siblings
);

stateLeafQip === stateRoot;

// Check credits
var isCreditsValid = SafeLessEqThan(N_BITS)([credits, stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX]]);
isCreditsValid === 1;

// Check nullifier
var hashedPrivKey = PoseidonHasher(1)([privKey]);
hashedPrivKey === nullifier;
Expand Down
13 changes: 12 additions & 1 deletion packages/cli/ts/commands/deployPoll.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MACI__factory as MACIFactory, EMode, deployFreeForAllSignUpGatekeeper } from "maci-contracts";
import { MACI__factory as MACIFactory, EMode, deployFreeForAllSignUpGatekeeper, deployConstantInitialVoiceCreditProxy } from "maci-contracts";
import { PubKey } from "maci-domainobjs";

import {
Expand All @@ -11,6 +11,7 @@ import {
logGreen,
type DeployPollArgs,
type PollContracts,
DEFAULT_INITIAL_VOICE_CREDITS,
} from "../utils";

/**
Expand All @@ -27,6 +28,7 @@ export const deployPoll = async ({
maciAddress,
vkRegistryAddress,
gatekeeperAddress,
voiceCreditProxyAddress,
signer,
quiet = true,
useQuadraticVoting = false,
Expand Down Expand Up @@ -57,11 +59,19 @@ export const deployPoll = async ({
let signupGatekeeperContractAddress =
gatekeeperAddress || (await readContractAddress(`SignUpGatekeeper-${pollId.toString()}`, network?.name));



if (!signupGatekeeperContractAddress) {
const contract = await deployFreeForAllSignUpGatekeeper(signer, true);
signupGatekeeperContractAddress = await contract.getAddress();
}

let initialVoiceCreditProxyAddress = voiceCreditProxyAddress || (await readContractAddress("VoiceCreditProxy", network?.name));
if (!initialVoiceCreditProxyAddress) {
const contract = await deployConstantInitialVoiceCreditProxy(DEFAULT_INITIAL_VOICE_CREDITS, signer, true);
initialVoiceCreditProxyAddress = await contract.getAddress();
}

// required arg -> poll duration
if (pollDuration <= 0) {
logError("Duration cannot be <= 0");
Expand Down Expand Up @@ -115,6 +125,7 @@ export const deployPoll = async ({
vkRegistry,
useQuadraticVoting ? EMode.QV : EMode.NON_QV,
signupGatekeeperContractAddress,
initialVoiceCreditProxyAddress,
{ gasLimit: 10000000 },
);

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/ts/commands/genProofs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ export const genProofs = async ({
const tallyStartTime = Date.now();

const { tallyBatchSize } = poll.batchSizes;
const numStateLeaves = poll.stateLeaves.length;
const numStateLeaves = poll.pubKeys.length;
let totalTallyBatches = numStateLeaves <= tallyBatchSize ? 1 : Math.floor(numStateLeaves / tallyBatchSize);
if (numStateLeaves > tallyBatchSize && numStateLeaves % tallyBatchSize > 0) {
totalTallyBatches += 1;
Expand Down
78 changes: 24 additions & 54 deletions packages/cli/ts/commands/joinPoll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,39 @@ import { formatProofForVerifierContract, genSignUpTree, IGenSignUpTree } from "m
import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts/typechain-types";
import { CircuitInputs, IJsonMaciState, MaciState, IPollJoiningCircuitInputs } from "maci-core";
import { poseidon, stringifyBigInts } from "maci-crypto";
import { IVkObjectParams, Keypair, PrivKey, PubKey, StateLeaf } from "maci-domainobjs";
import { IVkObjectParams, Keypair, PrivKey, PubKey } from "maci-domainobjs";

import fs from "fs";

import type { IJoinPollArgs, IJoinedUserArgs, IParsePollJoinEventsArgs, IJoinPollData } from "../utils";

import { contractExists, logError, logYellow, info, logGreen, success, BLOCKS_STEP, DEFAULT_SG_DATA } from "../utils";
import { contractExists, logError, logYellow, info, logGreen, success, BLOCKS_STEP, DEFAULT_SG_DATA, DEFAULT_IVCP_DATA } from "../utils";
import { banner } from "../utils/banner";

/**
* Get state index and credit balance
* either from command line or
* from maci state leaves or from sign up leaves
* @param stateIndex State index from the command
* @param newVoiceCreditBalance Credit balance from the command
* @param stateLeaves State leaves from maci state or sign up tree
* @param pubKeys Public keys from maci state or sign up tree
* @param userMaciPubKey Public key of the maci user
* @returns State index and credit balance
* @param stateIndex State index from the command
* @returns State index
*/
const getStateIndexAndCreditBalance = (
stateIndex: bigint | null,
newVoiceCreditBalance: bigint | null,
stateLeaves: StateLeaf[],
const getStateIndex = (
pubKeys: PubKey[],
userMaciPubKey: PubKey,
): [bigint | null, bigint | null] => {
let loadedStateIndex = stateIndex;
let loadedCreditBalance = newVoiceCreditBalance;

stateIndex?: bigint,
): bigint | undefined => {
if (!stateIndex) {
const index = stateLeaves.findIndex((leaf) => leaf.pubKey.equals(userMaciPubKey));
const index = pubKeys.findIndex((key) => key.equals(userMaciPubKey));
if (index > 0) {
loadedStateIndex = BigInt(index);
return BigInt(index);
} else {
logError("State leaf not found");
}
}

if (!newVoiceCreditBalance) {
const balance = stateLeaves[Number(loadedStateIndex!)].voiceCreditBalance;
if (balance) {
loadedCreditBalance = balance;
} else {
logError("Voice credit balance not found");
}
}

return [loadedStateIndex, loadedCreditBalance];
return stateIndex;
};

/**
Expand Down Expand Up @@ -98,7 +84,6 @@ const generateAndVerifyProof = async (
* @param stateTreeDepth Maci state tree depth
* @param maciPrivKey User's private key for signing up
* @param stateLeafIndex Index where the user is stored in the state leaves
* @param credits Credits for voting
* @param pollPrivKey Poll's private key for the poll joining
* @param pollPubKey Poll's public key for the poll joining
* @returns stringified circuit inputs
Expand All @@ -108,20 +93,11 @@ const joiningCircuitInputs = (
stateTreeDepth: bigint,
maciPrivKey: PrivKey,
stateLeafIndex: bigint,
credits: bigint,
pollPrivKey: PrivKey,
pollPubKey: PubKey,
): IPollJoiningCircuitInputs => {
// Get the state leaf on the index position
const { signUpTree: stateTree, stateLeaves } = signUpData;
const stateLeaf = stateLeaves[Number(stateLeafIndex)];
const { pubKey, voiceCreditBalance, timestamp } = stateLeaf;
const [pubKeyX, pubKeyY] = pubKey.asArray();
const stateLeafArray = [pubKeyX, pubKeyY, voiceCreditBalance, timestamp];

if (credits > voiceCreditBalance) {
logError("Credits must be lower than signed up credits");
}
const { signUpTree: stateTree } = signUpData;

// calculate the path elements for the state tree given the original state tree
const { siblings, index } = stateTree.generateProof(Number(stateLeafIndex));
Expand Down Expand Up @@ -160,11 +136,9 @@ const joiningCircuitInputs = (
privKey: maciPrivKey.asCircuitInputs(),
pollPrivKey: pollPrivKey.asCircuitInputs(),
pollPubKey: pollPubKey.asCircuitInputs(),
stateLeaf: stateLeafArray,
siblings: siblingsArray,
indices,
nullifier,
credits,
stateRoot,
actualStateTreeDepth,
};
Expand All @@ -182,7 +156,6 @@ export const joinPoll = async ({
privateKey,
pollPrivKey,
stateIndex,
newVoiceCreditBalance,
stateFile,
pollId,
signer,
Expand All @@ -196,6 +169,7 @@ export const joinPoll = async ({
pollWitgen,
pollWasm,
sgDataArg,
ivcpDataArg,
quiet = true,
}: IJoinPollArgs): Promise<IJoinPollData> => {
banner(quiet);
Expand Down Expand Up @@ -230,8 +204,7 @@ export const joinPoll = async ({

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

let loadedStateIndex: bigint | null;
let loadedCreditBalance: bigint | null;
let loadedStateIndex: bigint | undefined;
let maciState: MaciState | undefined;
let signUpData: IGenSignUpTree | undefined;
let currentStateRootIndex: number;
Expand All @@ -251,11 +224,10 @@ export const joinPoll = async ({
throw new Error("User the given nullifier has already joined");
}

[loadedStateIndex, loadedCreditBalance] = getStateIndexAndCreditBalance(
stateIndex,
newVoiceCreditBalance,
maciState!.stateLeaves,
loadedStateIndex = getStateIndex(
maciState!.pubKeys,
userMaciPubKey,
stateIndex,
);

// check < 1 cause index zero is a blank state leaf
Expand All @@ -265,12 +237,11 @@ export const joinPoll = async ({

currentStateRootIndex = poll.maciStateRef.numSignUps - 1;

poll.updatePoll(BigInt(maciState!.stateLeaves.length));
poll.updatePoll(BigInt(maciState!.pubKeys.length));

circuitInputs = poll.joiningCircuitInputs({
maciPrivKey: userMaciPrivKey,
stateLeafIndex: loadedStateIndex!,
credits: loadedCreditBalance!,
pollPrivKey: pollPrivKeyDeserialized,
pollPubKey,
}) as unknown as CircuitInputs;
Expand Down Expand Up @@ -305,11 +276,10 @@ export const joinPoll = async ({

currentStateRootIndex = Number(numSignups) - 1;

[loadedStateIndex, loadedCreditBalance] = getStateIndexAndCreditBalance(
stateIndex,
newVoiceCreditBalance,
signUpData.stateLeaves,
loadedStateIndex = getStateIndex(
maciState!.pubKeys,
userMaciPubKey,
stateIndex,
);

// check < 1 cause index zero is a blank state leaf
Expand All @@ -322,7 +292,6 @@ export const joinPoll = async ({
stateTreeDepth,
userMaciPrivKey,
loadedStateIndex!,
loadedCreditBalance!,
pollPrivKeyDeserialized,
pollPubKey,
) as unknown as CircuitInputs;
Expand All @@ -334,6 +303,7 @@ export const joinPoll = async ({
let receipt: ContractTransactionReceipt | null = null;

const sgData = sgDataArg || DEFAULT_SG_DATA;
const ivcpData = ivcpDataArg || DEFAULT_IVCP_DATA;

try {
// generate the proof for this batch
Expand All @@ -351,10 +321,10 @@ export const joinPoll = async ({
const tx = await pollContract.joinPoll(
nullifier,
pollPubKey.asContractParam(),
loadedCreditBalance!,
currentStateRootIndex,
proof,
sgData,
ivcpData,
);
receipt = await tx.wait();
logYellow(quiet, info(`Transaction hash: ${receipt!.hash}`));
Expand Down
Loading

0 comments on commit 3e2ac79

Please sign in to comment.