How gnark circuits prove that a state transition is valid — without revealing the state itself.
When a Petri net fires a transition, the marking (token counts for every place) changes. In a multiplayer game or a blockchain, you want to prove that the transition was legal — that the input places had enough tokens and the output places received the right amount — without revealing the full game state to everyone.
This is exactly what zero-knowledge proofs (ZK proofs) do: they let a prover convince a verifier that a statement is true, without revealing why it's true.
Every ZK proof in this system proves one statement:
"I know a valid Petri net marking that, when transition T fires, produces the claimed next state."
More precisely, the proof demonstrates:
The verifier sees only the two state roots and which transition fired. The actual token counts (the full game state) remain hidden.
A ZK proof is generated by an arithmetic circuit — a program built from additions and multiplications over a finite field. We use gnark, a Go library for writing these circuits.
The circuit for a Petri net transition has five steps:
Compute MiMC(pre[0], pre[1], ..., pre[N]) and assert it equals the public pre-state root. This binds the private marking to the public commitment.
Same hash, same check. Now both states are committed.
From the transition ID, look up the Petri net topology: which places lose tokens (inputs) and which gain tokens (outputs). Build a delta vector: delta[p] = outputs[p] - inputs[p].
For every place, check that post[p] == pre[p] + delta[p]. This ensures the marking changed exactly as the topology dictates.
For every input place of the transition, check that pre[p] >= 1. This is done by decomposing pre[p] - 1 into bits — if the value were negative, it would wrap to a huge field element and the bit decomposition would fail.
In gnark code, the core constraint looks like this:
The hash function MiMC (Minimal Multiplicative Complexity) is designed for ZK circuits. Unlike SHA-256 which needs thousands of boolean operations, MiMC is defined over the same finite field the circuit uses, making it extremely cheap in constraints.
Each state root is a MiMC hash of all token counts in the marking:
The verifier only sees the state root. To find the actual token counts, an attacker would need to invert the hash — computationally infeasible.
The ZK Tic-Tac-Toe demo uses two circuits, both encoding the full 33-place Petri net:
Proves a move is legal. The prover shows they know a valid marking where the transition fires and produces the next state. Used for every move.
Proves a player has won. The win condition is already in the Petri net topology (win-detection transitions). The circuit just checks that the win_x or win_o place has a token.
Because the Petri net topology is the game rules, the ZK circuit doesn't need to know anything about tic-tac-toe specifically. It proves valid Petri net state transitions — the game logic lives in the net structure.
Key insight: The same circuit structure works for any Petri net. Change the topology constants and you get ZK proofs for a different game, workflow, or token standard — without writing a new circuit.
The arcnet project uses the same approach for blockchain token transfers. An ERC-20 transfer is just a Petri net transition:
balances[from] is an input place (tokens consumed)balances[to] is an output place (tokens produced)balances[from] >= amount is the enabledness checkThe gnark circuit proves the transfer is valid using a Merkle tree of account balances, so the full state doesn't need to live on-chain. Only state roots are published — the bridge contract verifies the proof and updates the root.
Groth16 is the proving system used. It requires a one-time trusted setup per circuit but produces the smallest proofs (~128 bytes) and fastest verification. gnark handles the setup, proving, and Solidity export.
Play the ZK Tic-Tac-Toe demo to see proofs generated in real time. Each move fires a Petri net transition, generates a gnark proof, and displays the proof structure. You can inspect the state roots, verify the chain of proofs, and export Solidity calldata for on-chain verification.
Play ZK Tic-Tac-Toe →