Protocol

ZK proofs & circuits

Every private action in Cowl is a zero-knowledge proof: math that convinces the chain a state change is valid without revealing what changed. This is what makes a shielded balance both private and verifiable.

What a proof asserts

  • You own the notes you are spending.
  • Inputs equal outputs — no value is created or destroyed.
  • Each spent note is nullified exactly once, so nothing double-spends.
  • The trade or transfer follows the pool’s rules.

None of the amounts, assets, or addresses appear on-chain — only the proof and the resulting commitments and nullifiers.

Commitments & nullifiers

PrimitiveWhat the chain sees
CommitmentA hash that hides a note’s amount, asset, and owner
NullifierA one-time marker that a note was spent — unlinkable to the note
Merkle rootA snapshot proving your note is in the set, without saying which one

The circuits

Two circuits carry the whole protocol, written in Noir and compiled to Solidity verifiers that live on chain.

CircuitWhat it proves
shieldA deposit of a stated public amount becomes exactly one valid note
spendA join-split: two input notes are yours and unspent, two output notes are well formed, and value is conserved across them — covering private send, unshield, merge and trade with one circuit

Notes are hashed with Poseidon2 over BN254 and proven with UltraHonk. The commitment tree is fixed-depth, and every leaf insertion is proven inside the circuit rather than recomputed by the contract — the pool holds a root and never hashes a thing. Verification on chain is a constant-cost check whatever the proof is about.

Proving is always yours

Every proof is built on your own machine: in a web worker in the app, in-process in the CLI. There is no remote prover and no proving service, because handing a witness to somebody else is handing them everything the proof exists to hide. A transfer proof takes around 2.3 seconds and a shield proof under one.

A relayer only submits the finished proof and pays gas. It never sees a witness.

Proven load-bearing

The failure mode that empties a pool is not a circuit that rejects a good proof — it is one that accepts a bad one. So the circuits are tested against adversarial witnesses, not just valid ones, and every constraint is checked for whether it actually holds anything up: 17 deleted one at a time, 17 caught. Public inputs are pinned against what the contract reads, on all 14 of spend and 6 of shield.

Open by design
Circuits and verifiers are open source, and every check run against them is published with its findings on the audits page— including the commands to reproduce them. Privacy that can’t be reviewed isn’t privacy you should trust.