This example project shows how to create a simple zk voting circuit in Noir with a corresponding Solidity contract to track eligible voters, proposals and votes.
This example was last tested with Noir version 0.28.0. You can install it with noirup using
noirup -v 0.28.0
This is the model used for creating the circuit and the zkVote contract to manage private voting.
- Create a set of voters. A merkle root is stored in the zkVote Solidity contract that voters will use to verify membership against. In this example, there are 4 accounts in the set of voters. The private keys are 0, 1, 2, 3 and the secret value to create the commitment is 9.
Private Key | Commitment = pedersen(private key, secret) |
---|---|
1 | 0x03542cb720369f19a74fd05b4edfbedb27a78514ad3283f1b3270a1656cced8e |
2 | 0x1efa9d6bb4dfdf86063cc77efdec90eb9262079230f1898049efad264835b6c8 |
3 | 0x24013340c052ebf847e0d7081f84e6a8e92f54e2e1726a1e559ac46a8f242007 |
4 | 0x04fd3da9756f25c72ca8990437b7f7b58e7ca48bfc21e65e7978320db8b1e5c5 |
This gives intermediate hashes of 0x046394ae1ebbf494f2cd2c2d37171099510d099489c9accef59f90512d5f0477
(pedersen(commitment0, commitment1)
) and 0x2a653551d87767c545a2a11b29f0581a392b4e177a87c8e3eb425c51a26a8c77
(pedersen(commitment2, commitment3)
) and a root hash of 0x215597bacd9c7e977dfc170f320074155de974be494579d2586e5b268fa3b629
.
- Users will input their information into the circuit and generate a proof (see example inputs in Prover.toml and run
nargo prove
to generate the proof.)- Public inputs and outputs are printed in Verifier.toml.
- The proof is saved to
./proofs/foundry_voting.proof
.
- The generated proof + the contents of Verifier.toml are sent in a transaction to the
castVote
function in the zkVote contract. The function verifies that the sender is authorized to vote on the proposal, that they haven't already voted and tallies their vote.
You can run the Noir tests (also defined in main.nr) with nargo test
. To print test output, use nargo test --show-output
.
See the test file here. Run tests with forge test
.
- Run
nargo compile
to compile the circuit. - Run
nargo prove
to generate the proof (with the inputs in Prover.toml). - Run
nargo codegen-verifier
to generate the solidity verifier contract. - Run
yarn test
to run the Foundry test the Solidity verifier contract at./test/zkVote.t.sol
.
If you change the circuit at ./circuits/src/main.nr
you will need to recompile (nargo compile
) the circuit and regenerate the Solidity verifier (saved to ./circuits/contract/plonk_vk.sol
).
The merkle tree will need to be recalculated whenever there are users added to the set or if there are any changes to the voters secrets (secrets are the input to the merkle membership commitment, so changing a key changes the corresponding leaf in the merkle tree, which changes the root). See test_valid_build_merkle_tree
for an example calculation.
Run nargo test --show-output
in ./circuits
to print the example merkle tree.
Thanks to the folks at zkCamp modifying the original example and adding tests. You can see their repo here.