diff --git a/apps/subgraph/src/maci.ts b/apps/subgraph/src/maci.ts index ca2473c783..f6ad7128a2 100644 --- a/apps/subgraph/src/maci.ts +++ b/apps/subgraph/src/maci.ts @@ -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); diff --git a/apps/subgraph/templates/subgraph.template.yaml b/apps/subgraph/templates/subgraph.template.yaml index 76b6ac291c..1ea3e50d2a 100644 --- a/apps/subgraph/templates/subgraph.template.yaml +++ b/apps/subgraph/templates/subgraph.template.yaml @@ -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: diff --git a/packages/circuits/circom/anon/pollJoining.circom b/packages/circuits/circom/anon/pollJoining.circom index b66513bac0..581cf0bfd1 100644 --- a/packages/circuits/circom/anon/pollJoining.circom +++ b/packages/circuits/circom/anon/pollJoining.circom @@ -1,64 +1,44 @@ 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 @@ -66,10 +46,6 @@ template PollJoining(stateTreeDepth) { 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; diff --git a/packages/cli/ts/commands/deployPoll.ts b/packages/cli/ts/commands/deployPoll.ts index b69fb1a631..864da53615 100644 --- a/packages/cli/ts/commands/deployPoll.ts +++ b/packages/cli/ts/commands/deployPoll.ts @@ -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 { @@ -11,6 +11,7 @@ import { logGreen, type DeployPollArgs, type PollContracts, + DEFAULT_INITIAL_VOICE_CREDITS, } from "../utils"; /** @@ -27,6 +28,7 @@ export const deployPoll = async ({ maciAddress, vkRegistryAddress, gatekeeperAddress, + voiceCreditProxyAddress, signer, quiet = true, useQuadraticVoting = false, @@ -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"); @@ -115,6 +125,7 @@ export const deployPoll = async ({ vkRegistry, useQuadraticVoting ? EMode.QV : EMode.NON_QV, signupGatekeeperContractAddress, + initialVoiceCreditProxyAddress, { gasLimit: 10000000 }, ); diff --git a/packages/cli/ts/commands/genProofs.ts b/packages/cli/ts/commands/genProofs.ts index e302a10c63..8415577757 100644 --- a/packages/cli/ts/commands/genProofs.ts +++ b/packages/cli/ts/commands/genProofs.ts @@ -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; diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts index a78b52a6dc..e0c7b203a7 100644 --- a/packages/cli/ts/commands/joinPoll.ts +++ b/packages/cli/ts/commands/joinPoll.ts @@ -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; }; /** @@ -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 @@ -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)); @@ -160,11 +136,9 @@ const joiningCircuitInputs = ( privKey: maciPrivKey.asCircuitInputs(), pollPrivKey: pollPrivKey.asCircuitInputs(), pollPubKey: pollPubKey.asCircuitInputs(), - stateLeaf: stateLeafArray, siblings: siblingsArray, indices, nullifier, - credits, stateRoot, actualStateTreeDepth, }; @@ -182,7 +156,6 @@ export const joinPoll = async ({ privateKey, pollPrivKey, stateIndex, - newVoiceCreditBalance, stateFile, pollId, signer, @@ -196,6 +169,7 @@ export const joinPoll = async ({ pollWitgen, pollWasm, sgDataArg, + ivcpDataArg, quiet = true, }: IJoinPollArgs): Promise => { banner(quiet); @@ -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; @@ -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 @@ -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; @@ -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 @@ -322,7 +292,6 @@ export const joinPoll = async ({ stateTreeDepth, userMaciPrivKey, loadedStateIndex!, - loadedCreditBalance!, pollPrivKeyDeserialized, pollPubKey, ) as unknown as CircuitInputs; @@ -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 @@ -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}`)); diff --git a/packages/cli/ts/commands/signup.ts b/packages/cli/ts/commands/signup.ts index f7e3aa1859..7d836e0fb4 100644 --- a/packages/cli/ts/commands/signup.ts +++ b/packages/cli/ts/commands/signup.ts @@ -26,7 +26,7 @@ import type { import { banner } from "../utils/banner"; import { contractExists } from "../utils/contracts"; -import { DEFAULT_IVCP_DATA, DEFAULT_SG_DATA } from "../utils/defaults"; +import { DEFAULT_SG_DATA } from "../utils/defaults"; import { GatekeeperTrait } from "../utils/interfaces"; import { info, logError, logGreen, logYellow, success } from "../utils/theme"; @@ -39,7 +39,6 @@ export const signup = async ({ maciPubKey, maciAddress, sgDataArg, - ivcpDataArg, signer, quiet = true, }: SignupArgs): Promise => { @@ -57,17 +56,12 @@ export const signup = async ({ } const sgData = sgDataArg || DEFAULT_SG_DATA; - const ivcpData = ivcpDataArg || DEFAULT_IVCP_DATA; // we validate that the signup data and voice credit data is valid if (!isBytesLike(sgData)) { logError("invalid signup gateway data"); } - if (!isBytesLike(ivcpData)) { - logError("invalid initial voice credit proxy data"); - } - const maciContract = MACIFactory.connect(maciAddress, signer); let stateIndex = ""; @@ -76,7 +70,7 @@ export const signup = async ({ try { // sign up to the MACI contract - const tx = await maciContract.signUp(userMaciPubKey.asContractParam(), sgData, ivcpData); + const tx = await maciContract.signUp(userMaciPubKey.asContractParam(), sgData); receipt = await tx.wait(); logYellow(quiet, info(`Transaction hash: ${tx.hash}`)); diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index c40a5166e9..cf4cfa7ce3 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -218,6 +218,7 @@ program .option("-i, --state-index ", "the user's state index", BigInt) .requiredOption("-s, --sg-data ", "the signup gateway data") .requiredOption("-esk, --poll-priv-key ", "the user ephemeral private key for the poll") + .option("-i, --ivcp-data ", "the initial voice credit proxy data") .option( "-nv, --new-voice-credit-balance ", "the voice credit balance of the user for the poll", @@ -251,8 +252,7 @@ program maciAddress, privateKey, pollPrivKey: cmdObj.pollPrivKey, - stateIndex: cmdObj.stateIndex || null, - newVoiceCreditBalance: cmdObj.newVoiceCreditBalance || null, + stateIndex: cmdObj.stateIndex || undefined, stateFile: cmdObj.stateFile, pollId: cmdObj.pollId, signer, @@ -267,6 +267,7 @@ program rapidsnark: cmdObj.rapidsnark, pollWitgen: cmdObj.pollWitnessgen, sgDataArg: cmdObj.sgData, + ivcpDataArg: cmdObj.ivcpData, }); } catch (error) { program.error((error as Error).message, { exitCode: 1 }); @@ -456,7 +457,6 @@ program .requiredOption("-p, --pubkey ", "the MACI public key") .option("-x, --maci-address ", "the MACI contract address") .option("-s, --sg-data ", "the signup gateway data") - .option("-i, --ivcp-data ", "the initial voice credit proxy data") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .option("-r, --rpc-provider ", "the rpc provider URL") .action(async (cmdObj) => { @@ -470,7 +470,6 @@ program maciPubKey: cmdObj.pubkey, maciAddress, sgDataArg: cmdObj.sgData, - ivcpDataArg: cmdObj.ivcpData, quiet: cmdObj.quiet, signer, }); diff --git a/packages/cli/ts/utils/interfaces.ts b/packages/cli/ts/utils/interfaces.ts index 68467b9f7a..16a70ebf5c 100644 --- a/packages/cli/ts/utils/interfaces.ts +++ b/packages/cli/ts/utils/interfaces.ts @@ -324,6 +324,11 @@ export interface DeployPollArgs { * The address of the gatekeeper contract */ gatekeeperAddress?: string; + + /** + * The address of the initial voice credit proxy contract + */ + voiceCreditProxyAddress?: string; } /** @@ -374,11 +379,6 @@ export interface IJoinPollArgs { */ privateKey: string; - /** - * User's credit balance for voting within this poll - */ - newVoiceCreditBalance: bigint | null; - /** * The id of the poll */ @@ -387,7 +387,7 @@ export interface IJoinPollArgs { /** * The index of the state leaf */ - stateIndex: bigint | null; + stateIndex: bigint | undefined; /** * Whether to log the output @@ -458,6 +458,11 @@ export interface IJoinPollArgs { * The signup gatekeeper data */ sgDataArg?: string; + + /** + * The initial voice credit proxy data + */ + ivcpDataArg?: string; } /** @@ -945,11 +950,6 @@ export interface SignupArgs { */ sgDataArg?: string; - /** - * The initial voice credit proxy data - */ - ivcpDataArg?: string; - /** * Whether to log the output */ diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index 3575d6a7cf..00bf901b28 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -7,18 +7,18 @@ import { ITallyFactory } from "./interfaces/ITallyFactory.sol"; import { IVerifier } from "./interfaces/IVerifier.sol"; import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; import { ISignUpGatekeeper } from "./interfaces/ISignUpGatekeeper.sol"; -import { InitialVoiceCreditProxy } from "./initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; +import { IInitialVoiceCreditProxy } from "./interfaces/IInitialVoiceCreditProxy.sol"; import { SignUpGatekeeper } from "./gatekeepers/SignUpGatekeeper.sol"; import { IMACI } from "./interfaces/IMACI.sol"; import { Params } from "./utilities/Params.sol"; -import { Utilities } from "./utilities/Utilities.sol"; import { DomainObjs } from "./utilities/DomainObjs.sol"; import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol"; +import { Hasher } from "./crypto/Hasher.sol"; import { InternalLeanIMT, LeanIMTData } from "./trees/LeanIMT.sol"; /// @title MACI - Minimum Anti-Collusion Infrastructure Version 1 /// @notice A contract which allows users to sign up, and deploy new polls -contract MACI is IMACI, DomainObjs, Params, Utilities { +contract MACI is IMACI, DomainObjs, Params, Hasher { /// @notice The state tree depth is fixed. As such it should be as large as feasible /// so that there can be as many users as possible. i.e. 2 ** 23 = 8388608 /// this should also match the parameter of the circom circuits. @@ -31,9 +31,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { uint8 internal constant STATE_TREE_ARITY = 2; - /// @notice The hash of a blank state leaf - uint256 internal constant BLANK_STATE_LEAF_HASH = - uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762); + uint256 internal constant PAD_KEY_HASH = + 6769006970205099520508948723718471724660867171122235270773600567925038008762; /// @notice The roots of the empty ballot trees uint256[5] public emptyBallotRoots; @@ -53,18 +52,13 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @notice Factory contract that deploy a Tally contract ITallyFactory public immutable tallyFactory; - /// @notice The state tree. Represents a mapping between each user's public key - /// and their voice credit balance. + /// @notice The state tree. Stores users' public keys LeanIMTData public leanIMTData; /// @notice Address of the SignUpGatekeeper, a contract which determines whether a /// user may sign up to vote SignUpGatekeeper public immutable signUpGatekeeper; - /// @notice The contract which provides the values of the initial voice credit - /// balance per user - InitialVoiceCreditProxy public immutable initialVoiceCreditProxy; - /// @notice The array of the state tree roots for each sign up /// For the N'th sign up, the state tree root will be stored at the index N uint256[] public stateRootsOnSignUp; @@ -79,11 +73,9 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { // Events event SignUp( uint256 _stateIndex, - uint256 indexed _userPubKeyX, - uint256 indexed _userPubKeyY, - uint256 _voiceCreditBalance, uint256 _timestamp, - uint256 _stateLeaf + uint256 indexed _userPubKeyX, + uint256 indexed _userPubKeyY ); event DeployPoll( uint256 _pollId, @@ -103,7 +95,6 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @param _messageProcessorFactory The MessageProcessorFactory contract /// @param _tallyFactory The TallyFactory contract /// @param _signUpGatekeeper The SignUpGatekeeper contract - /// @param _initialVoiceCreditProxy The InitialVoiceCreditProxy contract /// @param _stateTreeDepth The depth of the state tree /// @param _emptyBallotRoots The roots of the empty ballot trees constructor( @@ -111,19 +102,17 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { IMessageProcessorFactory _messageProcessorFactory, ITallyFactory _tallyFactory, SignUpGatekeeper _signUpGatekeeper, - InitialVoiceCreditProxy _initialVoiceCreditProxy, uint8 _stateTreeDepth, uint256[5] memory _emptyBallotRoots ) payable { // initialize and insert the blank leaf - InternalLeanIMT._insert(leanIMTData, BLANK_STATE_LEAF_HASH); - stateRootsOnSignUp.push(BLANK_STATE_LEAF_HASH); + InternalLeanIMT._insert(leanIMTData, PAD_KEY_HASH); + stateRootsOnSignUp.push(PAD_KEY_HASH); pollFactory = _pollFactory; messageProcessorFactory = _messageProcessorFactory; tallyFactory = _tallyFactory; signUpGatekeeper = _signUpGatekeeper; - initialVoiceCreditProxy = _initialVoiceCreditProxy; stateTreeDepth = _stateTreeDepth; maxSignups = uint256(STATE_TREE_ARITY) ** uint256(_stateTreeDepth); emptyBallotRoots = _emptyBallotRoots; @@ -140,13 +129,9 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// register() function. For instance, the POAPGatekeeper or /// SignUpTokenGatekeeper requires this value to be the ABI-encoded /// token ID. - /// @param _initialVoiceCreditProxyData Data to pass to the - /// InitialVoiceCreditProxy, which allows it to determine how many voice - /// credits this user should have. function signUp( PubKey memory _pubKey, - bytes memory _signUpGatekeeperData, - bytes memory _initialVoiceCreditProxyData + bytes memory _signUpGatekeeperData ) public virtual { // ensure we do not have more signups than what the circuits support if (leanIMTData.size >= maxSignups) revert TooManySignups(); @@ -160,17 +145,14 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { // throw if the user has already registered or if ineligible to do so. signUpGatekeeper.register(msg.sender, _signUpGatekeeperData); - // Get the user's voice credit balance. - uint256 voiceCreditBalance = initialVoiceCreditProxy.getVoiceCredits(msg.sender, _initialVoiceCreditProxyData); - - // Create a state leaf and insert it into the tree. - uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, voiceCreditBalance, block.timestamp)); - uint256 stateRoot = InternalLeanIMT._insert(leanIMTData, stateLeaf); + // Hash the public key and insert it into the tree. + uint256 pubKeyHash = hashLeftRight(_pubKey.x, _pubKey.y); + uint256 stateRoot = InternalLeanIMT._insert(leanIMTData, pubKeyHash); // Store the current state tree root in the array stateRootsOnSignUp.push(stateRoot); - emit SignUp(leanIMTData.size - 1, _pubKey.x, _pubKey.y, voiceCreditBalance, block.timestamp, stateLeaf); + emit SignUp(leanIMTData.size - 1, block.timestamp, _pubKey.x, _pubKey.y); } /// @notice Deploy a new Poll contract. @@ -181,6 +163,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { /// @param _verifier The Verifier Contract /// @param _vkRegistry The VkRegistry Contract /// @param _mode Voting mode + /// @param _gatekeeper The gatekeeper contract + /// @param _initialVoiceCreditProxy The initial voice credit proxy contract function deployPoll( uint256 _duration, TreeDepths memory _treeDepths, @@ -189,7 +173,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { address _verifier, address _vkRegistry, Mode _mode, - address _gatekeeper + address _gatekeeper, + address _initialVoiceCreditProxy ) public virtual { // cache the poll to a local variable so we can increment it uint256 pollId = nextPollId; @@ -211,7 +196,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { maci: IMACI(address(this)), verifier: IVerifier(_verifier), vkRegistry: IVkRegistry(_vkRegistry), - gatekeeper: ISignUpGatekeeper(_gatekeeper) + gatekeeper: ISignUpGatekeeper(_gatekeeper), + initialVoiceCreditProxy: IInitialVoiceCreditProxy(_initialVoiceCreditProxy) }); address p = pollFactory.deploy( diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index 83a700f0cd..23882b0a89 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -271,17 +271,18 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { /// @notice Join the poll for voting /// @param _nullifier Hashed user's private key to check whether user has already voted /// @param _pubKey Poll user's public key - /// @param _newVoiceCreditBalance User's credit balance for voting within this poll /// @param _stateRootIndex Index of the MACI's stateRootOnSignUp when the user signed up /// @param _proof The zk-SNARK proof + /// @param _signUpGatekeeperData Data to pass to the SignUpGatekeeper + /// @param _initialVoiceCreditProxyData Data to pass to the InitialVoiceCreditProxy function joinPoll( uint256 _nullifier, PubKey calldata _pubKey, - uint256 _newVoiceCreditBalance, uint256 _stateRootIndex, uint256[8] calldata _proof, - bytes memory _signUpGatekeeperData - ) public virtual isWithinVotingDeadline { + bytes memory _signUpGatekeeperData, + bytes memory _initialVoiceCreditProxyData + ) external virtual isWithinVotingDeadline { // Whether the user has already joined if (pollNullifier[_nullifier]) { revert UserAlreadyJoined(); @@ -291,31 +292,32 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { pollNullifier[_nullifier] = true; // Verify user's proof - if (!verifyPollProof(_nullifier, _newVoiceCreditBalance, _stateRootIndex, _pubKey, _proof)) { + if (!verifyPollProof(_nullifier, _stateRootIndex, _pubKey, _proof)) { revert InvalidPollProof(); } // Check if the user is eligible to join the poll extContracts.gatekeeper.register(msg.sender, _signUpGatekeeperData); + // Get the user's voice credit balance. + uint256 voiceCreditBalance = extContracts.initialVoiceCreditProxy.getVoiceCredits(msg.sender, _initialVoiceCreditProxyData); + // Store user in the pollStateTree - uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, _newVoiceCreditBalance, block.timestamp)); + uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, voiceCreditBalance, block.timestamp)); InternalLazyIMT._insert(pollStateTree, stateLeaf); uint256 pollStateIndex = pollStateTree.numberOfLeaves - 1; - emit PollJoined(_pubKey.x, _pubKey.y, _newVoiceCreditBalance, block.timestamp, _nullifier, pollStateIndex); + emit PollJoined(_pubKey.x, _pubKey.y, voiceCreditBalance, block.timestamp, _nullifier, pollStateIndex); } /// @notice Verify the proof for Poll /// @param _nullifier Hashed user's private key to check whether user has already voted - /// @param _voiceCreditBalance User's credit balance for voting /// @param _index Index of the MACI's stateRootOnSignUp when the user signed up /// @param _pubKey Poll user's public key /// @param _proof The zk-SNARK proof /// @return isValid Whether the proof is valid function verifyPollProof( uint256 _nullifier, - uint256 _voiceCreditBalance, uint256 _index, PubKey calldata _pubKey, uint256[8] memory _proof @@ -327,30 +329,27 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { ); // Generate the circuit public input - uint256[] memory circuitPublicInputs = getPublicCircuitInputs(_nullifier, _voiceCreditBalance, _index, _pubKey); + uint256[] memory circuitPublicInputs = getPublicCircuitInputs(_nullifier, _index, _pubKey); isValid = extContracts.verifier.verify(_proof, vk, circuitPublicInputs); } /// @notice Get public circuit inputs for poll joining circuit /// @param _nullifier Hashed user's private key to check whether user has already voted - /// @param _voiceCreditBalance User's credit balance for voting /// @param _index Index of the MACI's stateRootOnSignUp when the user signed up /// @param _pubKey Poll user's public key /// @return publicInputs Public circuit inputs function getPublicCircuitInputs( uint256 _nullifier, - uint256 _voiceCreditBalance, uint256 _index, PubKey calldata _pubKey ) public view returns (uint256[] memory publicInputs) { - publicInputs = new uint256[](5); + publicInputs = new uint256[](4); publicInputs[0] = _pubKey.x; publicInputs[1] = _pubKey.y; publicInputs[2] = _nullifier; - publicInputs[3] = _voiceCreditBalance; - publicInputs[4] = extContracts.maci.getStateRootOnIndexedSignUp(_index); + publicInputs[3] = extContracts.maci.getStateRootOnIndexedSignUp(_index); } /// @inheritdoc IPoll diff --git a/packages/contracts/contracts/interfaces/IInitialVoiceCreditProxy.sol b/packages/contracts/contracts/interfaces/IInitialVoiceCreditProxy.sol new file mode 100644 index 0000000000..546e0e9b6f --- /dev/null +++ b/packages/contracts/contracts/interfaces/IInitialVoiceCreditProxy.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +/// @title IInitialVoiceCreditProxy +/// @notice InitialVoiceCreditProxy interface +interface IInitialVoiceCreditProxy { + /// @notice Get the voice credits of a user + /// @param _user User address + /// @param _data Data to get voice credits + function getVoiceCredits(address _user, bytes memory _data) external view returns (uint256); +} diff --git a/packages/contracts/contracts/interfaces/IPoll.sol b/packages/contracts/contracts/interfaces/IPoll.sol index b17491e6ad..2deeb5e9c5 100644 --- a/packages/contracts/contracts/interfaces/IPoll.sol +++ b/packages/contracts/contracts/interfaces/IPoll.sol @@ -11,10 +11,10 @@ interface IPoll { function joinPoll( uint256 _nullifier, DomainObjs.PubKey calldata _pubKey, - uint256 _newVoiceCreditBalance, uint256 _stateRootIndex, uint256[8] calldata _proof, - bytes memory _signUpGatekeeperData + bytes memory _signUpGatekeeperData, + bytes memory _initialVoiceCreditProxyData ) external; /// @notice The number of messages which have been processed and the number of signups diff --git a/packages/contracts/contracts/utilities/Params.sol b/packages/contracts/contracts/utilities/Params.sol index 91d9fae8e0..87d0e10f7f 100644 --- a/packages/contracts/contracts/utilities/Params.sol +++ b/packages/contracts/contracts/utilities/Params.sol @@ -5,6 +5,7 @@ import { IMACI } from "../interfaces/IMACI.sol"; import { IVerifier } from "../interfaces/IVerifier.sol"; import { IVkRegistry } from "../interfaces/IVkRegistry.sol"; import { ISignUpGatekeeper } from "../interfaces/ISignUpGatekeeper.sol"; +import { IInitialVoiceCreditProxy } from "../interfaces/IInitialVoiceCreditProxy.sol"; /// @title Params /// @notice This contracts contains a number of structures @@ -26,5 +27,6 @@ contract Params { IVerifier verifier; IVkRegistry vkRegistry; ISignUpGatekeeper gatekeeper; + IInitialVoiceCreditProxy initialVoiceCreditProxy; } } diff --git a/packages/contracts/tasks/deploy/maci/01-constantInitialVoiceCreditProxy.ts b/packages/contracts/tasks/deploy/poll/01-constantInitialVoiceCreditProxy.ts similarity index 100% rename from packages/contracts/tasks/deploy/maci/01-constantInitialVoiceCreditProxy.ts rename to packages/contracts/tasks/deploy/poll/01-constantInitialVoiceCreditProxy.ts diff --git a/packages/contracts/tasks/deploy/poll/01-gatekeepers.ts b/packages/contracts/tasks/deploy/poll/02-gatekeepers.ts similarity index 100% rename from packages/contracts/tasks/deploy/poll/01-gatekeepers.ts rename to packages/contracts/tasks/deploy/poll/02-gatekeepers.ts diff --git a/packages/contracts/tasks/deploy/poll/02-poll.ts b/packages/contracts/tasks/deploy/poll/03-poll.ts similarity index 95% rename from packages/contracts/tasks/deploy/poll/02-poll.ts rename to packages/contracts/tasks/deploy/poll/03-poll.ts index 98b48ee5b8..23b38b782b 100644 --- a/packages/contracts/tasks/deploy/poll/02-poll.ts +++ b/packages/contracts/tasks/deploy/poll/03-poll.ts @@ -53,6 +53,11 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => deployment.getDeployConfigField(EContracts.Poll, "gatekeeper") || EContracts.FreeForAllGatekeeper; const gatekeeperContractAddress = storage.mustGetAddress(gatekeeper, hre.network.name, `poll-${pollId}`); + const initialVoiceCreditProxyContractAddress = storage.mustGetAddress( + EContracts.ConstantInitialVoiceCreditProxy, + hre.network.name, + `poll-${pollId}`, + ); const tx = await maciContract.deployPoll( pollDuration, @@ -66,6 +71,7 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => vkRegistryContractAddress, mode, gatekeeperContractAddress, + initialVoiceCreditProxyContractAddress, ); const receipt = await tx.wait(); diff --git a/packages/contracts/tasks/helpers/ProofGenerator.ts b/packages/contracts/tasks/helpers/ProofGenerator.ts index 7583a0c550..ac1bd3e313 100644 --- a/packages/contracts/tasks/helpers/ProofGenerator.ts +++ b/packages/contracts/tasks/helpers/ProofGenerator.ts @@ -252,7 +252,7 @@ export class ProofGenerator { console.log(`Generating proofs of vote tallying...`); const { tallyBatchSize } = this.poll.batchSizes; - const numStateLeaves = this.poll.stateLeaves.length; + const numStateLeaves = this.poll.pollStateLeaves.length; let totalTallyBatches = numStateLeaves <= tallyBatchSize ? 1 : Math.floor(numStateLeaves / tallyBatchSize); if (numStateLeaves > tallyBatchSize && numStateLeaves % tallyBatchSize > 0) { totalTallyBatches += 1; diff --git a/packages/contracts/tasks/helpers/constants.ts b/packages/contracts/tasks/helpers/constants.ts index f8bcfec6c2..e0bbbbeac7 100644 --- a/packages/contracts/tasks/helpers/constants.ts +++ b/packages/contracts/tasks/helpers/constants.ts @@ -2,7 +2,6 @@ * Deploy steps */ export enum EDeploySteps { - ConstantInitialVoiceCreditProxy = "full:deploy-constant-initial-voice-credit-proxy", Gatekeepers = "full:deploy-gatekeepers", Verifier = "full:deploy-verifier", Poseidon = "full:deploy-poseidon", @@ -11,6 +10,7 @@ export enum EDeploySteps { TallyFactory = "full:deploy-tally-factory", Maci = "full:deploy-maci", VkRegistry = "full:deploy-vk-registry", + ConstantInitialVoiceCreditProxy = "poll:deploy-constant-initial-voice-credit-proxy", PollGatekeeper = "poll:deploy-gatekeeper", Poll = "poll:deploy-poll", } diff --git a/packages/contracts/ts/genMaciState.ts b/packages/contracts/ts/genMaciState.ts index 4a9e800105..44701d72d6 100644 --- a/packages/contracts/ts/genMaciState.ts +++ b/packages/contracts/ts/genMaciState.ts @@ -76,9 +76,7 @@ export const genMaciStateFromContract = async ( data: { stateIndex: Number(event.args._stateIndex), pubKey: new PubKey([BigInt(event.args._userPubKeyX), BigInt(event.args._userPubKeyY)]), - voiceCreditBalance: Number(event.args._voiceCreditBalance), timestamp: Number(event.args._timestamp), - stateLeaf: BigInt(event.args._stateLeaf), }, }); }); @@ -196,9 +194,9 @@ export const genMaciStateFromContract = async ( sortActions(actions).forEach((action) => { switch (true) { case action.type === "SignUp": { - const { pubKey, voiceCreditBalance, timestamp, stateLeaf } = action.data; + const { pubKey } = action.data; - maciState.signUp(pubKey!, BigInt(voiceCreditBalance!), BigInt(timestamp!), stateLeaf); + maciState.signUp(pubKey!); break; } diff --git a/packages/contracts/ts/genSignUpTree.ts b/packages/contracts/ts/genSignUpTree.ts index 598c47b795..c16b8241d2 100644 --- a/packages/contracts/ts/genSignUpTree.ts +++ b/packages/contracts/ts/genSignUpTree.ts @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle */ import { LeanIMT, LeanIMTHashFunction } from "@zk-kit/lean-imt"; -import { hashLeanIMT } from "maci-crypto"; -import { PubKey, StateLeaf, blankStateLeaf, blankStateLeafHash } from "maci-domainobjs"; +import { hashLeanIMT, hashLeftRight, PAD_KEY_HASH } from "maci-crypto"; +import { PubKey } from "maci-domainobjs"; import { assert } from "console"; @@ -32,8 +32,8 @@ export const genSignUpTree = async ({ const maciContract = MACIFactory.connect(address, provider); const signUpTree = new LeanIMT(hashLeanIMT as LeanIMTHashFunction); - signUpTree.insert(blankStateLeafHash); - const stateLeaves: StateLeaf[] = [blankStateLeaf]; + signUpTree.insert(PAD_KEY_HASH); + const pubKeys: PubKey[] = []; // Fetch event logs in batches (lastBlock inclusive) for (let i = fromBlock; i <= lastBlock; i += blocksPerRequest + 1) { @@ -47,14 +47,11 @@ export const genSignUpTree = async ({ assert(!!event); const pubKeyX = event.args._userPubKeyX; const pubKeyY = event.args._userPubKeyY; - const voiceCreditBalance = event.args._voiceCreditBalance; - const timestamp = event.args._timestamp; const pubKey = new PubKey([pubKeyX, pubKeyY]); - const stateLeaf = new StateLeaf(pubKey, voiceCreditBalance, timestamp); - stateLeaves.push(stateLeaf); - signUpTree.insert(event.args._stateLeaf); + pubKeys.push(pubKey); + signUpTree.insert(hashLeftRight(pubKeyX, pubKeyY)); }); if (sleepAmount) { @@ -64,6 +61,6 @@ export const genSignUpTree = async ({ } return { signUpTree, - stateLeaves, + pubKeys, }; }; diff --git a/packages/contracts/ts/types.ts b/packages/contracts/ts/types.ts index 61435de2f3..24a82bc0dd 100644 --- a/packages/contracts/ts/types.ts +++ b/packages/contracts/ts/types.ts @@ -14,7 +14,7 @@ import type { } from "../typechain-types"; import type { BigNumberish, Provider, Signer, ContractFactory } from "ethers"; import type { CircuitInputs } from "maci-core"; -import type { Message, PubKey, StateLeaf } from "maci-domainobjs"; +import type { Message, PubKey } from "maci-domainobjs"; import type { PublicSignals } from "snarkjs"; /** @@ -237,5 +237,5 @@ export interface IGenSignUpTree { /** * State leaves */ - stateLeaves: StateLeaf[]; + pubKeys: PubKey[]; } diff --git a/packages/core/ts/MaciState.ts b/packages/core/ts/MaciState.ts index 506c51fbb1..c070dbba98 100644 --- a/packages/core/ts/MaciState.ts +++ b/packages/core/ts/MaciState.ts @@ -1,5 +1,5 @@ -import { hash4, IncrementalQuinTree } from "maci-crypto"; -import { type PubKey, type Keypair, StateLeaf, blankStateLeaf } from "maci-domainobjs"; +import { IncrementalQuinTree } from "maci-crypto"; +import { PubKey, type Keypair, padKey } from "maci-domainobjs"; import type { IJsonMaciState, IJsonPoll, IMaciState, TreeDepths } from "./utils/types"; @@ -13,8 +13,8 @@ export class MaciState implements IMaciState { // a MaciState can hold multiple polls polls: Map = new Map(); - // the leaves of the state tree - stateLeaves: StateLeaf[] = []; + // the public keys of the users + pubKeys: PubKey[] = []; // how deep the state tree is stateTreeDepth: number; @@ -37,29 +37,22 @@ export class MaciState implements IMaciState { this.stateTreeDepth = stateTreeDepth; // we put a blank state leaf to prevent a DoS attack - this.stateLeaves.push(blankStateLeaf); + this.pubKeys.push(padKey); // we need to increase the number of signups by one given // that we already added the blank leaf this.numSignUps += 1; } /** - * Sign up a user with the given public key, initial voice credit balance, and timestamp. + * Sign up a user with the given public key. * @param pubKey - The public key of the user. - * @param initialVoiceCreditBalance - The initial voice credit balance of the user. - * @param timestamp - The timestamp of the sign-up. - * @param stateLeaf - The hash state leaf. * @returns The index of the newly signed-up user in the state tree. */ - signUp(pubKey: PubKey, initialVoiceCreditBalance: bigint, timestamp: bigint, stateLeaf?: bigint): number { + signUp(pubKey: PubKey): number { this.numSignUps += 1; - const stateLeafObj = new StateLeaf(pubKey, initialVoiceCreditBalance, timestamp); - const pubKeyAsArray = pubKey.asArray(); - const stateLeafHash = - stateLeaf || hash4([pubKeyAsArray[0], pubKeyAsArray[1], initialVoiceCreditBalance, timestamp]); - this.stateTree?.insert(stateLeafHash); - return this.stateLeaves.push(stateLeafObj.copy()) - 1; + this.stateTree?.insert(pubKey.hash()); + return this.pubKeys.push(pubKey) - 1; } /** @@ -105,7 +98,7 @@ export class MaciState implements IMaciState { copy = (): MaciState => { const copied = new MaciState(this.stateTreeDepth); - copied.stateLeaves = this.stateLeaves.map((x: StateLeaf) => x.copy()); + copied.pubKeys = this.pubKeys.map((x: PubKey) => x.copy()); copied.polls = new Map(Array.from(this.polls, ([key, value]) => [key, value.copy()])); @@ -121,7 +114,7 @@ export class MaciState implements IMaciState { const result = this.stateTreeDepth === m.stateTreeDepth && this.polls.size === m.polls.size && - this.stateLeaves.length === m.stateLeaves.length; + this.pubKeys.length === m.pubKeys.length; if (!result) { return false; @@ -132,8 +125,8 @@ export class MaciState implements IMaciState { return false; } } - for (let i = 0; i < this.stateLeaves.length; i += 1) { - if (!this.stateLeaves[i].equals(m.stateLeaves[i])) { + for (let i = 0; i < this.pubKeys.length; i += 1) { + if (!this.pubKeys[i].equals(m.pubKeys[i])) { return false; } } @@ -149,7 +142,7 @@ export class MaciState implements IMaciState { return { stateTreeDepth: this.stateTreeDepth, polls: Array.from(this.polls.values()).map((poll) => poll.toJSON()), - stateLeaves: this.stateLeaves.map((leaf) => leaf.toJSON()), + pubKeys: this.pubKeys.map((pubKey) => pubKey.toJSON()), pollBeingProcessed: Boolean(this.pollBeingProcessed), currentPollBeingProcessed: this.currentPollBeingProcessed ? this.currentPollBeingProcessed.toString() : "", numSignUps: this.numSignUps, @@ -165,7 +158,7 @@ export class MaciState implements IMaciState { const maciState = new MaciState(json.stateTreeDepth); // assign the json values to the new instance - maciState.stateLeaves = json.stateLeaves.map((leaf) => StateLeaf.fromJSON(leaf)); + maciState.pubKeys = json.pubKeys.map((pubKey) => PubKey.fromJSON(pubKey)); maciState.pollBeingProcessed = json.pollBeingProcessed; maciState.currentPollBeingProcessed = BigInt(json.currentPollBeingProcessed); maciState.numSignUps = json.numSignUps; diff --git a/packages/core/ts/Poll.ts b/packages/core/ts/Poll.ts index 1511410425..3c75bd6b2e 100644 --- a/packages/core/ts/Poll.ts +++ b/packages/core/ts/Poll.ts @@ -25,6 +25,7 @@ import { type IMessageContractParams, type IJsonPCommand, blankStateLeafHash, + padKey, } from "maci-domainobjs"; import assert from "assert"; @@ -81,7 +82,7 @@ export class Poll implements IPoll { stateCopied = false; - stateLeaves: StateLeaf[] = [blankStateLeaf]; + pubKeys: PubKey[] = [padKey]; stateTree?: LeanIMT; @@ -215,13 +216,13 @@ export class Poll implements IPoll { // start by setting the number of signups this.setNumSignups(numSignups); // copy up to numSignups state leaves - this.stateLeaves = this.maciStateRef.stateLeaves.slice(0, Number(this.numSignups)).map((x) => x.copy()); + this.pubKeys = this.maciStateRef.pubKeys.slice(0, Number(this.numSignups)).map((x) => x.copy()); // ensure we have the correct actual state tree depth value this.actualStateTreeDepth = Math.max(1, Math.ceil(Math.log2(Number(this.numSignups)))); this.stateTree = new LeanIMT(hashLeanIMT as LeanIMTHashFunction); // add all leaves - this.stateLeaves.forEach((stateLeaf) => { + this.pubKeys.forEach((stateLeaf) => { this.stateTree?.insert(stateLeaf.hash()); }); @@ -243,7 +244,7 @@ export class Poll implements IPoll { this.ballotTree.insert(this.emptyBallotHash); // we fill the ballotTree with empty ballots hashes to match the number of signups in the tree - while (this.ballots.length < this.stateLeaves.length) { + while (this.ballots.length < this.pubKeys.length) { this.ballotTree.insert(this.emptyBallotHash); this.ballots.push(this.emptyBallot); } @@ -428,7 +429,6 @@ export class Poll implements IPoll { * Create circuit input for pollJoining * @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 @@ -436,19 +436,9 @@ export class Poll implements IPoll { joiningCircuitInputs = ({ maciPrivKey, stateLeafIndex, - credits, pollPrivKey, pollPubKey, - }: IJoiningCircuitArgs): IPollJoiningCircuitInputs => { - // Get the state leaf on the index position - const stateLeaf = this.stateLeaves[Number(stateLeafIndex)]; - const { pubKey, voiceCreditBalance, timestamp } = stateLeaf; - const pubKeyX = pubKey.asArray()[0]; - const pubKeyY = pubKey.asArray()[1]; - const stateLeafArray = [pubKeyX, pubKeyY, voiceCreditBalance, timestamp]; - - assert(credits <= voiceCreditBalance, "Credits must be lower than signed up credits"); - + }: IJoiningCircuitArgs): IPollJoiningCircuitInputs => { // calculate the path elements for the state tree given the original state tree const { siblings, index } = this.stateTree!.generateProof(Number(stateLeafIndex)); const siblingsLength = siblings.length; @@ -483,11 +473,9 @@ export class Poll implements IPoll { privKey: maciPrivKey.asCircuitInputs(), pollPrivKey: pollPrivKey.asCircuitInputs(), pollPubKey: pollPubKey.asCircuitInputs(), - stateLeaf: stateLeafArray, siblings: siblingsArray, indices, nullifier, - credits, stateRoot, actualStateTreeDepth, }; @@ -1303,7 +1291,7 @@ export class Poll implements IPoll { this.maciStateRef, ); - copied.stateLeaves = this.stateLeaves.map((x) => x.copy()); + copied.pubKeys = this.pubKeys.map((x) => x.copy()); copied.pollStateLeaves = this.pollStateLeaves.map((x) => x.copy()); copied.messages = this.messages.map((x) => x.copy()); copied.commands = this.commands.map((x) => x.copy()); @@ -1400,7 +1388,7 @@ export class Poll implements IPoll { ballots: this.ballots.map((ballot) => ballot.toJSON()), encPubKeys: this.encPubKeys.map((encPubKey) => encPubKey.serialize()), currentMessageBatchIndex: this.currentMessageBatchIndex, - stateLeaves: this.stateLeaves.map((leaf) => leaf.toJSON()), + pubKeys: this.pubKeys.map((leaf) => leaf.toJSON()), pollStateLeaves: this.pollStateLeaves.map((leaf) => leaf.toJSON()), results: this.tallyResult.map((result) => result.toString()), numBatchesProcessed: this.numBatchesProcessed, diff --git a/packages/core/ts/utils/types.ts b/packages/core/ts/utils/types.ts index d1b9a79708..d7f569f782 100644 --- a/packages/core/ts/utils/types.ts +++ b/packages/core/ts/utils/types.ts @@ -5,6 +5,7 @@ import type { Ballot, IJsonBallot, IJsonPCommand, + IJsonPublicKey, IJsonStateLeaf, Keypair, Message, @@ -95,7 +96,7 @@ export interface IJsonPoll { ballots: IJsonBallot[]; encPubKeys: string[]; currentMessageBatchIndex: number; - stateLeaves: IJsonStateLeaf[]; + pubKeys: IJsonPublicKey[]; pollStateLeaves: IJsonStateLeaf[]; results: string[]; numBatchesProcessed: number; @@ -110,7 +111,7 @@ export interface IJsonPoll { export interface IJsonMaciState { stateTreeDepth: number; polls: IJsonPoll[]; - stateLeaves: IJsonStateLeaf[]; + pubKeys: IJsonPublicKey[]; pollBeingProcessed: boolean; currentPollBeingProcessed: string; numSignUps: number; @@ -138,7 +139,6 @@ export interface IProcessMessagesOutput { export interface IJoiningCircuitArgs { maciPrivKey: PrivKey; stateLeafIndex: bigint; - credits: bigint; pollPrivKey: PrivKey; pollPubKey: PubKey; } @@ -153,7 +153,6 @@ export interface IPollJoiningCircuitInputs { siblings: string[][]; indices: string[]; nullifier: string; - credits: string; stateRoot: string; actualStateTreeDepth: string; } diff --git a/packages/crypto/ts/constants.ts b/packages/crypto/ts/constants.ts index ce10528f2f..a0609ea14c 100644 --- a/packages/crypto/ts/constants.ts +++ b/packages/crypto/ts/constants.ts @@ -10,3 +10,5 @@ export const SNARK_FIELD_SIZE = r; export const NOTHING_UP_MY_SLEEVE = BigInt(keccak256(toUtf8Bytes("Maci"))) % SNARK_FIELD_SIZE; assert(NOTHING_UP_MY_SLEEVE === BigInt("8370432830353022751713833565135785980866757267633941821328460903436894336785")); + +export const PAD_KEY_HASH = BigInt("6769006970205099520508948723718471724660867171122235270773600567925038008762"); diff --git a/packages/crypto/ts/index.ts b/packages/crypto/ts/index.ts index bfaf8e003c..5d95af0320 100644 --- a/packages/crypto/ts/index.ts +++ b/packages/crypto/ts/index.ts @@ -4,7 +4,7 @@ export { IncrementalQuinTree } from "./quinTree"; export { bigInt2Buffer, stringifyBigInts, unstringifyBigInts, deepCopyBigIntArray } from "./bigIntUtils"; -export { NOTHING_UP_MY_SLEEVE, SNARK_FIELD_SIZE } from "./constants"; +export { NOTHING_UP_MY_SLEEVE, SNARK_FIELD_SIZE, PAD_KEY_HASH} from "./constants"; export { genPrivKey, diff --git a/packages/domainobjs/ts/constants.ts b/packages/domainobjs/ts/constants.ts index aae4709cb1..bc727d7b03 100644 --- a/packages/domainobjs/ts/constants.ts +++ b/packages/domainobjs/ts/constants.ts @@ -1,4 +1,6 @@ +import { PubKey } from "./publicKey"; import { StateLeaf } from "./stateLeaf"; export const blankStateLeaf = StateLeaf.genBlankLeaf(); export const blankStateLeafHash = blankStateLeaf.hash(); +export const padKey = PubKey.genPadKey(); diff --git a/packages/domainobjs/ts/index.ts b/packages/domainobjs/ts/index.ts index ad8081b858..6aabb2e6dc 100644 --- a/packages/domainobjs/ts/index.ts +++ b/packages/domainobjs/ts/index.ts @@ -10,7 +10,7 @@ export { Keypair } from "./keyPair"; export { StateLeaf } from "./stateLeaf"; -export { blankStateLeaf, blankStateLeafHash } from "./constants"; +export { blankStateLeaf, blankStateLeafHash, padKey } from "./constants"; export type { Proof, diff --git a/packages/domainobjs/ts/publicKey.ts b/packages/domainobjs/ts/publicKey.ts index 905107975f..4f5e5f3958 100644 --- a/packages/domainobjs/ts/publicKey.ts +++ b/packages/domainobjs/ts/publicKey.ts @@ -135,4 +135,23 @@ export class PubKey { static fromJSON(json: IJsonPublicKey): PubKey { return PubKey.deserialize(json.pubKey); } + + /** + * Generate a default pad key + * @returns a default pad key + */ + static genPadKey(): PubKey { + // This public key is the first Pedersen base + // point from iden3's circomlib implementation of the Pedersen hash. + // Since it is generated using a hash-to-curve function, we are + // confident that no-one knows the private key associated with this + // public key. See: + // https://github.com/iden3/circomlib/blob/d5ed1c3ce4ca137a6b3ca48bec4ac12c1b38957a/src/pedersen_printbases.js + // Its hash should equal + // 6769006970205099520508948723718471724660867171122235270773600567925038008762. + return new PubKey([ + BigInt("10457101036533406547632367118273992217979173478358440826365724437999023779287"), + BigInt("19824078218392094440610104313265183977899662750282163392862422243483260492317"), + ]); + } } diff --git a/packages/integrationTests/ts/__tests__/integration.test.ts b/packages/integrationTests/ts/__tests__/integration.test.ts index c3666f40f5..1f6d6050b3 100644 --- a/packages/integrationTests/ts/__tests__/integration.test.ts +++ b/packages/integrationTests/ts/__tests__/integration.test.ts @@ -34,7 +34,6 @@ import { VOTE_OPTION_TREE_DEPTH, duration, initialVoiceCredits, - ivcpData, maxMessages, } from "./utils/constants"; import { ITestSuite } from "./utils/interfaces"; @@ -168,7 +167,6 @@ describe("Integration tests", function test() { maciPubKey: user.keypair.pubKey.serialize(), maciAddress: contracts.maciAddress, sgDataArg: SG_DATA, - ivcpDataArg: ivcpData, signer, }).then((result) => result.stateIndex), ); @@ -191,12 +189,11 @@ describe("Integration tests", function test() { ), rapidsnark: `${homedir()}/rapidsnark/build/prover`, signer, - newVoiceCreditBalance: BigInt(initialVoiceCredits), quiet: true, }); // signup on local maci state - maciState.signUp(user.keypair.pubKey, BigInt(initialVoiceCredits), BigInt(timestamp)); + maciState.signUp(user.keypair.pubKey); // join the poll on local const inputNullifier = BigInt(user.keypair.privKey.asCircuitInputs()); diff --git a/packages/integrationTests/ts/__tests__/maci-keys.test.ts b/packages/integrationTests/ts/__tests__/maci-keys.test.ts index 081553e52f..97d59a8b97 100644 --- a/packages/integrationTests/ts/__tests__/maci-keys.test.ts +++ b/packages/integrationTests/ts/__tests__/maci-keys.test.ts @@ -72,7 +72,7 @@ describe("integration tests private/public/keypair", () => { before(async () => { signer = await getDefaultSigner(); - const { maci, verifier, vkRegistry, gatekeeper } = await deployTestContracts( + const { maci, verifier, vkRegistry, gatekeeper, initialVoiceCreditProxy } = await deployTestContracts( initialVoiceCredits, STATE_TREE_DEPTH, signer, @@ -92,6 +92,7 @@ describe("integration tests private/public/keypair", () => { vkRegistry, EMode.NON_QV, gatekeeper, + initialVoiceCreditProxy, ); // we know it's the first poll so id is 0 diff --git a/packages/integrationTests/ts/__tests__/utils/interfaces.ts b/packages/integrationTests/ts/__tests__/utils/interfaces.ts index 647a0b6ee5..abaf203580 100644 --- a/packages/integrationTests/ts/__tests__/utils/interfaces.ts +++ b/packages/integrationTests/ts/__tests__/utils/interfaces.ts @@ -1,4 +1,5 @@ import { MACI, Verifier, VkRegistry, FreeForAllGatekeeper } from "maci-contracts"; +import { ConstantInitialVoiceCreditProxy } from "maci-contracts/typechain-types"; /** * A util interface that represents a vote object @@ -48,4 +49,5 @@ export interface IDeployedTestContracts { verifier: Verifier; vkRegistry: VkRegistry; gatekeeper: FreeForAllGatekeeper; + initialVoiceCreditProxy: ConstantInitialVoiceCreditProxy; } diff --git a/packages/integrationTests/ts/__tests__/utils/utils.ts b/packages/integrationTests/ts/__tests__/utils/utils.ts index 5b59cb923b..33d0d1f307 100644 --- a/packages/integrationTests/ts/__tests__/utils/utils.ts +++ b/packages/integrationTests/ts/__tests__/utils/utils.ts @@ -203,5 +203,6 @@ export const deployTestContracts = async ( verifier: mockVerifierContract as Verifier, vkRegistry: vkRegistryContract, gatekeeper: gatekeeperContract, + initialVoiceCreditProxy: constantInitialVoiceCreditProxyContract, }; };