← Home
petri-net zk voting groth16 gnark governance solidity

ZK Polls: Voting as a Visible State Machine

What if you could vote anonymously — and anyone could verify the rules were followed? Not by trusting a server, not by reading an audit report, but by looking at a diagram and seeing exactly how the system works?

That's what vote.bitwrap.io does. Create a poll, share a link, collect votes. Each voter proves they're eligible using a zero-knowledge proof — a cryptographic trick that says "I'm on the list" without revealing who on the list. No one sees how you voted. Everyone can see that the rules are airtight.

And the rules? They're a Petri net with four circles and three arrows.

Four States, Three Moves

ZK Poll Petri Net Model

The entire voting protocol fits in a diagram you can hold in your head:

Four states (the circles):

Three transitions (the arrows):

That's it. You can load this model in the editor, drag the pieces around, and simulate vote flows by watching tokens move through the net. The model is the spec.

What the Proof Guarantees

When a voter casts a ballot, their browser generates a zero-knowledge proof. The proof convinces the server (or a blockchain contract) of five things without revealing the voter's identity:

  1. You're registered. Your secret key matches a commitment in the voter registry.
  2. Your nullifier is legit. It's derived from your secret and the poll ID — unique per poll, so you can't be tracked across polls.
  3. You haven't voted yet. The nullifier hasn't been used.
  4. Your choice is valid. It's within the poll's range of options.
  5. Your vote is sealed. The choice is hidden inside a cryptographic commitment that can't be brute-forced.

The proof compiles to about 14,600 constraints and takes 2-5 seconds to generate client-side. The server verifies it in milliseconds.

VoteCast Proof Flow

Secret Ballots, Really

The vote choice never leaves the browser in plaintext. It's wrapped in a commitment — a hash of the choice plus ~248 bits of random entropy. Even though there might only be 3 or 4 options, you can't work backwards from the hash because the entropy makes every commitment unique.

On disk, individual vote records contain only the nullifier and the sealed commitment. The tally file has totals with no voter linkage. Neither the server nor a blockchain ever sees who picked what.

Event Sourcing

Poll state isn't sitting in a database. Every action — create, vote, close — gets appended to a log. The current state is derived by replaying that log through the Petri net runtime. The net processes each event (consuming tokens from input states, producing tokens at output states) and the result is the tallies, the nullifier set, the poll status. No separate counters, no derived tables — just the model executing its own transitions.

This means you can reconstruct any poll's complete state from its event log. Audit by replay.

On-Chain Too

The same Petri net model generates a Solidity contract — same four states, same three transitions, but enforced on-chain with ZK proof verification. The castVote function takes a Groth16 proof, verifies it against the voter registry's Merkle root, records the nullifier, and stores the sealed commitment. All in one transaction.

Download the complete Foundry bundle from vote.bitwrap.io/api/bundle/vote — contract, verifier, tests, and deploy script. All 8 Foundry tests pass. Deploy to any EVM chain and you've got the same poll system running on-chain.

So you get two paths from one model:

How This Compares

Most voting systems separate the rules from the code. Snapshot delegates strategy to off-chain scripts. MACI uses ZK proofs but needs a trusted coordinator to decrypt and tally. Vocdoni runs a whole L2 chain.

The difference here: the Petri net diagram, the ZK circuit, and the Solidity contract are three views of the same four circles and three arrows. There's no gap between the spec and the implementation because they're the same thing.

Try It


Four circles, three arrows, one diagram. Anonymous votes in, verifiable tallies out. That's the whole trick.

×

Follow on Mastodon