Open source · Not for profit · Self-hostable

Voting your members can verify.
Ballots nobody can read.

ZK Voting lets any group run its own elections and polls. Zero-knowledge proofs show that only eligible people voted, each one once, and that the published tally matches the ballots. Nobody's choice is ever published.

  • 0choices published
  • 1counted ballot per voter
  • 100%of the tally checkable by anyone
  • $0to voters: no token, no fees

The big picture

Many civic L2s, one shared record on Ethereum

Anyone can run a civic L2 for their own elections. Each one batches votes and settles them to Ethereum L1, so the record outlives any single operator.

Macro view: many independent civic L2s settling to Ethereum L1 Three civic L2 boxes labeled A, B and C, plus a dashed box for unlimited more. Each holds election chips with no limit on how many. Arrows carry vote batches from every L2 down to one Ethereum L1 box, where each batch is keyed by hash of the L2 ID and the election UUID. A two-way arrow notes that results can be rebuilt from either the L2 or L1. Anyone can run one · unlimited L2s Civic L2 A election 1 election 2 + unlimited Civic L2 B election 1 election 2 + unlimited Civic L2 C election 1 election 2 + unlimited ∞ more L2s vote batches, each keyed by hash(L2 ID, election UUID) Ethereum L1 Shared settlement layer and permanent public record L2 data L1 record Results can be rebuilt from either one No single operator has to be trusted to keep them

Macro view

  • Anyone can run a civic L2, and there's no limit on how many exist.
  • Each L2 can hold any number of elections.
  • Every L2 settles its vote batches to Ethereum L1, keyed by hash(L2 ID, election UUID).
  • Results can be rebuilt from the L2's data or from the L1 record.
L2 view: one election team's L2 Voters at the edge vote for free and send ballots to the sequencer. A voter who is not pre-approved is rejected by the prover. The sequencer and prover batch the accepted votes. The operator's minimally funded L1 key pays to submit each batch to Ethereum L1. The L2 ID is bound to the operator key plus a salt, and election UUIDs are unique within the L2. One election team's L2 L2 ID = operator key + salt Voters at the edge · vote free ✓ ✓ ✓ ✓ ? not pre-approved Sequencer orders and batches votes Prover proves each batch ✕ rejected by the prover Election UUIDs are unique within this L2 Operator L1 key minimally funded, pays to submit batch Ethereum L1 batch settled under hash(L2 ID, election UUID)

L2 view: one election team

  • Voters at the edge vote for free.
  • Only pre-approved voters are accepted. The prover rejects everyone else.
  • The sequencer and prover batch the votes.
  • The operator's minimally funded L1 key pays to submit each batch to L1.
  • The L2 ID is bound to the operator key plus a salt, and election UUIDs are unique within the L2.

Why it matters

Most elections still ask voters, candidates and courts to trust a few operators: who is on the roll, who was turned away, whether a ballot was counted, whether the result matches. ZK Voting turns those claims into things anyone can check.

Private by design

The option you pick is never a readable field in a transaction or a database. Receipts are hashes, so they can't show how you voted.

Publicly verifiable

Registrations, disqualification codes, vote proofs and the final tally are public. An independent auditor can rebuild the result without the operator's private database.

One person, one vote

Every ballot carries a proof that the voter is on the frozen roll and not disqualified. A one-time nullifier rejects any second ballot.

Built to resist coercion

Voters can change their mind privately before submitting, and nothing they are given proves their choice to someone pressuring them. Turnout is published hourly, after the hour closes, so a person can't be matched to a live tick.

Due process for disqualification

Barring someone is a public, coded, on-chain change with a stated reason, not a silent database flag.

No vendor lock-in

AGPL-licensed, run at cost, built to be forked and rebranded. Your group owns the node, the roll and the record.

Who it's for

Any body that needs a roll, a ballot and a result people will accept.

Cities, counties and districts

Run civic elections and referendums with nested districts, measures and articles, and a public results page observers can follow.

Unions and member associations

Hold leadership elections and contract ratification votes where members trust the count without trusting the officers.

Cooperatives and boards

Take board votes and member polls, including weighted and ranked-choice ballots, with an auditable outcome report.

Schools and campuses

Run student government and faculty votes with passkey sign-in and a ballot that's accessible on any device.

Communities and clubs

Quick yes/no, single-choice or multiple-choice polls for any group that wants a private, honest show of hands.

Observers and auditors

Follow registrations, disqualifications and the tally on the public explorer, and verify an outcome report offline.

How it works

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

  1. 1

    Register

    Members sign up with a passkey, wallet, email or ID. The roll stores an identity commitment, never an address or document number.

  2. 2

    Freeze the roll

    Officials commit the eligible set and any disqualifications for the election.

  3. 3

    Vote with a proof

    Each ballot proves eligibility and spends a one-time nullifier, so a second ballot is rejected.

  4. 4

    Hourly disclosure

    Vote proofs are published once an hour, after the hour closes.

  5. 5

    Proven tally

    When voting closes, anyone can check that the totals match the accepted ballots.

Run it yourself

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

Day-to-day 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.

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

Honest status

ZK Voting is under active development. Tests use a mock verifier, the first proving path is in progress and the trusted-setup ceremony hasn't happened yet. Don't use it for a binding election today.

Passing a vote never auto-executes anything. Outcomes are verifiable; acting on them stays with the people.

community.zk-voting.org

Talk about how your group could use it

The community forum is where people discuss real use cases, ask questions about running a node, and shape what gets built next.