Anonymous on-chain voting using Zero Knowledge proofs. Voters prove membership in a Merkle tree without revealing their identity. Double-voting is prevented via nullifiers.
- Each eligible voter has a secret whose hash is a leaf in a Merkle tree
- To vote, the voter generates a ZK proof that they know a secret in the tree — without revealing which leaf
- A nullifier derived from the secret is recorded on-chain to prevent voting twice
- The proof is verified by a Solidity contract generated by Barretenberg
| Tool | Version | Install |
|---|---|---|
| Nargo (Noir) | 1.0.0-beta.20 | See below |
Barretenberg (bb) |
4.2.0 | See below |
| Node.js | 18+ | nodejs.org |
| Remix IDE | — | remix.ethereum.org |
curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
noirup --version 1.0.0-beta.20Verify:
nargo --versioncurl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/bbup/install | bash
bbup --version 4.2.0If bbup fails, install manually:
curl -L -o /tmp/bb.tar.gz https://github.com/AztecProtocol/aztec-packages/releases/download/v4.2.0/barretenberg-amd64-linux.tar.gz
mkdir -p ~/.bb
tar -xzf /tmp/bb.tar.gz -C ~/.bb/
chmod +x ~/.bb/bbVerify:
bb --versionnargo compileEdit Prover.toml with your inputs, then:
nargo executemkdir -p ./target/vk
bb write_vk -b ./target/zkvote.json -o ./target/vk -t evmbb prove -b ./target/zkvote.json -w ./target/zkvote.gz -k ./target/vk/vk -o ./target/proof -t evmpython3 -c "
with open('./target/proof/proof', 'rb') as f:
data = f.read()
print('0x' + data.hex())
"bb write_solidity_verifier -k ./target/vk/vk -o ./target/HonkVerifier.sol- Open remix.ethereum.org
- Upload
HonkVerifier.solandZKVote.sol - Compile both with Solidity
^0.8.20 - Deploy
HonkVerifierfirst — copy its address - Deploy
ZKVotewith:_verifier: HonkVerifier address_merkleRoot: your Merkle root_startTime: voting start (unix timestamp)_endTime: voting end (unix timestamp)
- Call
castVotewith your proof hex, nullifier, and vote (0or1)
secret = "42" # your private secret
vote = "1" # 0 = against, 1 = for
index = "0" # leaf index in the Merkle tree
path = ["1", "2", "3"] # sibling hashes along the Merkle path
merkle_root = "0x..." # public Merkle root
nullifier = "0x..." # public nullifier (pedersen_hash([secret]))To get the correct merkle_root and nullifier for your inputs:
nargo test --show-outputzkvote/
├── src/
│ └── main.nr # Noir circuit
├── Nargo.toml # Noir project config
├── Prover.toml # Witness inputs
├── ZKVote.sol # Voting contract
└── HonkVerifier.sol # Auto-generated ZK verifier
MIT