ZK Anonymous Voting Platform Development by Umut SatırZK Anonymous Voting Platform Development by Umut Satır

ZK Anonymous Voting Platform Development

Umut Satır

Umut Satır

ZK Anonymous Voting

Anonymous on-chain voting for Ethereum (Sepolia) using client-side Zero-Knowledge proofs. Voters prove membership in a registered-voter Merkle tree and cast a vote without revealing their identity. A per-voter nullifier prevents double-voting.

Architecture


ZK Circuit (circuits/)

The circuit (src/main.nr) proves simultaneously:
Voter membership — the voter's Ethereum address is a leaf in the registered-voter Merkle tree (depth 10, Poseidon2 nodes, up to 1 024 voters).
Key ownership — an ECDSA signature over blake2s(election_id) ties the leaf address to the voter's private key.
Nullifier correctnessnullifier = blake2s(sk || election_id) is unique per (voter, election) and cannot be computed without the private key.
Vote range0 <= vote_choice < num_candidates <= 4.
Nullifier note: Full PLUME (ERC-7524) nullifiers require noir-bignum, which is incompatible with nargo 1.0.0-beta.13. The blake2s-based substitute has identical security properties for this use case. A migration path to PLUME is documented in circuits/src/main.nr and circuits/Nargo.toml.

Smart Contracts (contracts/)

Contract Purpose HonkVerifier Auto-generated UltraHonk verifier (from bb write_solidity_verifier) ElectionManager Per-election contract: enforces time window, nullifier dedup, verifier call, tally ElectionFactory Deploys and tracks ElectionManager instances
Public input layout passed to ElectionManager.castVote() (83 bytes32 values):
Index Value 0 merkle_root 1-32 nullifier bytes (one bytes32 per byte) 33 vote_choice 34-65 election_id bytes 66 num_candidates 67-82 UltraHonk pairing points (protocol-internal, supplied by prover)

Frontend (frontend/)

Connect MetaMask, browse active elections, cast a vote
Client-side proof generation via @aztec/bb.js + @noir-lang/noir_js (~10-30 s)
Merkle tree built in-browser using Barretenberg.poseidon2Permutation
Requires COOP/COEP headers for SharedArrayBuffer (configured in vite.config.ts)

Prerequisites

Tool Version Node.js >= 20 nargo 1.0.0-beta.13 bb (Barretenberg) >= 0.67.0

Setup


Circuits


Contracts


Frontend


After deploying contracts, copy contracts/deployments/sepolia.json to frontend/src/assets/deployments.json.

Deployment to Sepolia


CLI Scripts

All scripts run from scripts/ with npx ts-node.

Build Merkle tree


Generate a ZK vote proof (Node.js)


Submit proof to Sepolia


End-to-end test on local Hardhat node


Expected output:

Known Limitations

Limitation Notes PLUME not yet integrated `blake2s(sk Proof generation time ~10-30 s client-side (UltraHonk over secp256k1 ECDSA + depth-10 Merkle). Acceptable for a demo; production would use a proving service. HonkVerifier size 25.5 KB — just over the 24.5 KB EIP-170 limit for Mainnet. Deployable on Sepolia/L2s without restriction. Max 1 024 voters Merkle tree depth is fixed at 10. Increase DEPTH in main.nr and regenerate the verifier to support more. Candidate names off-chain ElectionManager stores only the Merkle root and candidate count. Names are stored in localStorage keyed by electionId. Private key in browser The demo VotePanel prompts for a private key to generate the ECDSA signature locally. The key never leaves the browser, but production would use a hardware-wallet signing flow instead.
Like this project

Posted Jun 26, 2026

Developed anonymous voting platform using zero-knowledge proofs for Ethereum Sepolia. ZK circuits were written in Noir.