Not for profit · Open source (AGPL-3.0-or-later)

Elections you can verify.
Ballots that stay private.

ZK Voting is a self-hostable voting and polling system. Zero-knowledge proofs let anyone check that only eligible people voted, each at most once, and that the published tally matches the accepted ballots. Nobody's choice is published.

# run your own node
cp .env.example .env
docker compose up -d --build
curl -fsS http://127.0.0.1:3000/health

What ZK Voting is

Democratic processes still ask voters, candidates and courts to trust a small set of operators: who is on the roll, who was turned away, whether a ballot was counted, and whether the result matches the ballots. ZK Voting makes those claims checkable.

It has two tightly coupled parts: a Node application where voters, election officials and observers do their work, and Solidity smart contracts that register voters, open ballots, accept votes with zero-knowledge proofs and enforce disqualification on Ethereum.

  • No token. No paid tier. No vendor lock-in.
  • Built to be forked and rebranded by a city, union, school, cooperative or other body.
  • Outcomes are verifiable on chain. Passing a vote does not auto-execute anything.
  • Run at cost: hosting, gas sponsorship, paper notices.

How it works

Zero-knowledge proofs let the chain check facts about a voter without putting the ballot in the clear.

  1. 1

    Register

    A person registers for a jurisdiction. Their Merkle leaf is an identity commitment, never their wallet address.

  2. 2

    Freeze the roll

    Officials commit the eligible set and the disqualification set for the election. Disqualifications are public, coded, on-chain state changes.

  3. 3

    Vote with a proof

    The ballot carries a proof that the voter is eligible and not disqualified. A one-time nullifier means a second ballot is rejected.

  4. 4

    Hourly disclosure

    Public dashboards publish vote proofs once an hour, after the hour closes, so a person leaving a polling place can't be matched to a live tick.

  5. 5

    Proven tally

    After the poll closes, anyone can check that the published totals match the accepted ballots.

Features

Eligibility proofs

A vote is only valid if the prover is in the current eligible set for that election.

Disqualification with due process

Officials can bar a registered person for coded, jurisdiction-defined reasons, and the circuits reject their vote.

One counted ballot

Each nullifier is accepted once. Voters can change their mind privately on the form before they submit.

Hidden choice

The selected option is never a plaintext field anyone can read from a transaction.

Many ballot types

Yes/no, single choice, multiple choice, ranked choice (IRV), measures and articles.

Flexible sign-in

Passkeys, Ethereum wallet, email, smart card or civic ID, with email MFA and a vote PIN at ballot submit.

Public explorer and results

A block explorer and results pages let observers follow registrations, disqualifications and the tally.

Independent auditor

An offline auditor reconstructs an election from published artifacts without the node's private database.

Accessible and scriptable

WCAG 2.2 AA checks on the voter and admin pages, plus a documented bot API with OpenAPI and llms.txt.

Self-host it

A jurisdiction runs its own node with Docker Compose, Coolify, Dokploy or CapRover, optionally private behind a Tailscale sidecar. Every node serves its self-hosting guides at /docs.

Cheap, high-volume civic activity lands on the ZK Voting L2, a purpose-built civic sequencer and prover that settles to Ethereum. Final tallies can be mirrored on Ethereum mainnet.

Project status

Under active development. Unit tests use a mock verifier, the first proving path is in progress and the trusted-setup ceremony is not done yet. Don't use it for a binding election today.

See it running on the demo →

Open source, not for profit

ZK Voting is licensed AGPL-3.0-or-later. Nobody is meant to make money from it; voter data is not a product.