Skip to main content

One post tagged with "development"

View All Tags

Getting Started with MACI

· 8 min read
Crisgarner
MACI team contributor

Hey folks, we are excited to start a series of tutorials on how to get started with all things MACI, this guide focuses on installing MACI and how to deploy the contracts to a testnet.

Understanding Roles

MACI protocol has two main roles, User (Voter) and Coordinator. A simplified version would be to say that the coordinator is in charge of deploying the MACI smart contracts, inititalting the polls, allying the final results of a vote, and finalizing polls by publishing the final results on-chain. Usually the contract deployer is the coordinator, but this can be a separate role.

Requirements

Let's install the requirements first, you are going to need:

  • Node.js: use a JS toolchain manager like nvm or volta to install Node.js. We recommend using Node 20 or above.
  • pnpm: Fast, disk space efficient package manager.

Installation

Now you can run the following commands to install MACI locally:

git clone https://github.com/privacy-scaling-explorations/maci.git && \
cd maci && \
git checkout v2.5.0 && \
pnpm i && \
pnpm run build
note

Unless you are doing a contribution to MACI, we suggest you use the latest released version. You can verify the versions here.

Download the .zkey files

MACI has two main zk-SNARK circuits. Each circuit is parameterised. There should be one .zkey file for each circuit and set of parameters.

Unless you wish to generate a fresh set of .zkey files, you should obtain them from someone who has performed a multi-party trusted setup for said circuits. For more details on which artifacts have undergone a trusted setup, please refer to the Trusted Setup page.

important

Note the locations of the .zkey files as the CLI requires them as command-line flags.

Download test artifacts

For quickly testing MACI or trying it on a testnet we suggest using the test artifacts, using the latest dev updates you can do it by running:

pnpm download-zkeys:test

Download ceremony artifacts

For production you need to use the ceremony artifacts which have undergone a trusted setup, you can download it with the command:

pnpm download-zkeys:ceremony
info

Currently, the ceremony artifacts work with MACI version up to 2.x

Deploy Contracts

Before deploying the contracts we need to do some final configurations to our repository.

Set the environment variables

Head to the packages/contracts folder and copy the .env.example file.

cd packages/contracts && \
cp .env.example .env

Make sure to include a mnemonic and RPC url (make sure to replace NETWORK with the network you want to use).

MNEMONIC = "your ethereum secret key";
NETWORK_RPC_URL = "the eth provider url";
NETWORK_ETHERSCAN_API_KEY = "etherscan api key";

Generate Coordinator Keys

In order to encrypt and decrypt the messages, we need to generate MACI key pair, treat it just as your ethereum private keys, store it on a safe place as you won't be able to finish a round if you lose access, or a bad actor could decrypt the results and publish them online.

Before deploying a Poll, make sure you have set the coordinator public MACI key to which you own the private key. You can generate a new key pair using maci-cli by running the following commands:

cd packages/cli && \
node build/ts/index.js genMaciKeyPair

Set the configuration file

Head back to the contracts folder and copy the config example and update the fields as necessary:

cd ../contracts && \
cp deploy-config-example.json deploy-config.json

ConstantInitialVoiceCreditProxy

Defines how many credits will get each voter.

PropertyDescription
deployDefines if the contract is needs to be deployed.
amountDefines how many vote credits will get each participant.
important

The current deployed contract has 99 as the voice credits amount.

Gatekeeper

MACI uses a "gatekeeper" contract to configure and enforce the eligibility criteria of voters who can participate in MACI polls. In other words, it is a way to allowlist signups to the system. Operators of MACI can use the gatekeeper contract to configure signup eligibility and to protect against sybil attacks in their polls. Please refer to the gatekeepers section for more information on the supported Gatekeepers.

PropertyDescription
deployDefines if the contract is going to be deployed.
important

For testing we suggest using the FreeForAllGatekeeper.

MACI

PropertyDescription
stateTreeDepthDefines how many users the system supports.
gatekeeperDefines which gatekeeper will use.

VkRegistry

Verifying key registry used for generating proofs.

PropertyDescription
stateTreeDepthDefines how many users the system supports.
intStateTreeDepthDefines how many ballots can be processed per batch when tallying the results.
messageTreeDepthDefines how many messages (votes) the system supports.
voteOptionTreeDepthDefines how many vote options the system supports.
messageBatchDepthDefines how many messages in a batch can the circuit process.
zkeysDefines the path to the zkey files for QV and Non QV keys.
important

The recommended values for test keys are: 10-1-2-2-1. For ceremony keys: 14-5-9-3-2.

Poll

PropertyDescription
pollDurationDefines how long is going to be the poll in seconds.
coordinatorPubkeyDefines the coordinator public MACI key.
useQuadraticVotingDefines if the poll uses quadratic voting or not.

Deploy MACI Contracts

To deploy the MACI contracts to a specific network you can append :network to the deployment commands, e.g. pnpm deploy:sepolia - please refer to the available networks on the package.json scripts section.

pnpm deploy:NETWORK

There are already some deployed contracts that could be reused, copy the default-deployed-contracts.json file if you need them to avoid deploying redundant contracts and save your gas fee.

cp default-deployed-contracts.json deployed-contracts.json

Delete any contract you want to redeploy and then run the following command to save gas:

pnpm deploy:NETWORK --incremental

Deploy Poll

To deploy your first Poll you can run the following command:

pnpm deploy-poll:NETWORK
important

Making another poll doesn't require deploying MACI contracts again, you can deploy another poll running the pnpm deploy-poll:NETWORK command and then use the updated poll-id.

Poll Finalization

As a coordinator, first you need to merge signups and messages (votes). Messages are stored in a queue so when the poll is over, the coordinator needs to create the tree from the queue (AccQueue). This optimization is needed to reduce gas cost for voters. Then coordinator generates proofs for the message processing, and tally calculations. This allows to publish the poll results on-chain and then everyone can verify the results when the poll is over. You run a merge with:

pnpm merge:[network] --poll [poll-id]

Then you need to generate the proofs for the message processing, and tally calculations, we do it with the following command:

pnpm run prove:[network] --poll [poll-id] \
--coordinator-private-key [coordinator-maci-private-key] \
--tally-file ../proofs/tally.json \
--output-dir ../proofs/proofs/ \
--start-block [block-number] \
--blocks-per-batch [number-of-blocks]
important

You can improve the time of the proving by including more blocks per batch, by default you can start with 500.

Submit On-chain

Now it's time to submit the poll results on-chai and then everyone can verify the results:

pnpm submitOnChain:[network] --poll [poll-id] \
--output-dir proofs/ \
--tally-file proofs/tally.json

Tally

After proofs are generated, and results tallied, the results (Tally) is written to a file. This file contains the result of a Poll. Let's take a look at one:

{
"maci": "0xd54b47F8e6A1b97F3A84f63c867286272b273b7C",
"pollId": "0",
"network": "localhost",
"chainId": "31337",
"isQuadratic": true,
"tallyAddress": "0xD4fbAF1dFe100d07f8Ef73d8c92e93d0Bcf7b45D",
"newTallyCommitment": "0x2f55cc85f7f141098ba791a9f6a646f8773b9bb4f5852ccc33b5a28e7b0756e5",
"results": {
"tally": [
"9",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
],
"salt": "0x2e9cd240b86cf456fa4deced8e7420c45e3c16941d2dcec308f8b6d48264dda3",
"commitment": "0x296eac2a7289974f23497bebd39e86599d0b7032796fb84dcc1f6bbda38262ca"
},
"totalSpentVoiceCredits": {
"spent": "81",
"salt": "0x24f57b75c227987727c13d1e83409d70478b42bdc12a4a4df8129c72fbaf5aaf",
"commitment": "0xb4ebe68b0da828c0b978ddee86ba934b8e215499ac766491f236ad85fd606de"
},
"perVOSpentVoiceCredits": {
"tally": [
"81",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
],
"salt": "0x2590434ea2d600f7bd2396ba7fa454ad4c975c29424ee481561d9786538a5e48",
"commitment": "0x54ec996599886da21c4b07c25d1de544292a8b7c38b79726995c869c9e95db"
}
}

We see that there is an array named results, this contains the aggregated votes for each option, where each option is represented by an index in the array. In this case above, the first option (index 0) received a total of 9 votes, where every other option did not receive any votes.

The totalSpentVoiceCredits object contains the total amount of voice credits spent in the poll. This is the sum of all voice credits spent by all voters, and in quadratic voting, is the sum of the squares of all votes.

The perVOSpentVoiceCredits will contain the amount of voice credits spent per vote option. In this case, the first option received 81 voice credits, and every other option received 0 voice credits. This is because there was only one valid vote casted, with a weight of 9. Given the quadratic voting formula, the total amount of voice credits spent is 81.


That should be all for this tutorial, feel free to join our discord for any questions or recommendations, and feel free to read our documentation for more in depth resources.