All explainers

2026-05-25

How zero-knowledge proofs work (without the math)

A zero-knowledge proof lets you prove a statement is true without revealing why it's true.

The canonical example: imagine a colorblind friend holding two identical-looking balls — one red, one green. You can prove to them that the balls really are different colors, without ever telling them which is which. You do this by having them swap (or not swap) the balls behind their back, then telling them whether they swapped. After enough rounds, they're convinced you can tell red from green, but they still don't know which ball is which color.

That's the whole idea. SolMask uses zero-knowledge proofs for one specific statement:

"I know a secret that corresponds to one of the deposits in this shielded pool, and I've authorized a withdrawal of N tokens to wallet X."

The proof says nothing about which deposit. The chain sees a valid withdrawal but cannot match it to any specific deposit.

What goes into the proof

When you deposited, SolMask computed a commitment:

commitment = Poseidon(secret, nullifier_secret, amount, unlock_slot)

Only the commitment lands on-chain. The four inputs — your secret, the nullifier, the amount, and the time you've agreed to wait — never leave your browser. They aren't saved to a file you have to guard: your wallet re-derives them on demand by signing a fixed message, and the deposit publishes a wallet-encrypted recovery blob on-chain so the same wallet can rediscover the note anywhere.

When you withdraw, your browser generates a Groth16 proof that, in math terms:

  1. The commitment is one of the leaves in the pool's Merkle tree (without saying which leaf).
  2. The nullifier you're revealing now is the hash of the nullifier secret you committed to.
  3. Enough time has passed (current_slot >= unlock_slot).
  4. The amount you're withdrawing is consistent with the commitment's value.
  5. Any change is hashed into a fresh commitment you've added to the tree.

The proof is ~200 bytes. The Solana program verifies it in a few milliseconds.

Why nullifiers?

Each deposit comes with a unique secret. When you withdraw, you reveal a hash of that secret — the nullifier. The chain records every nullifier seen so far; if you try to withdraw the same deposit twice, the second attempt reveals the same nullifier and is rejected.

Crucially, the nullifier is generated from your private secret — so only you can produce it for your deposit, but the chain can verify it without learning the secret itself.

Why Poseidon?

Standard hashes like SHA-256 are wildly expensive to express as zero-knowledge proofs (think: hundreds of thousands of constraints). Poseidon is a hash designed to be cheap inside ZK circuits — about 5× fewer constraints than Keccak for the same security level. The trade-off: it's slower than SHA-256 if you computed it on a CPU directly, which doesn't matter for our use case.

Why Groth16?

Groth16 is the most production-tested proving system in ZK. Its proofs are tiny (3 elliptic-curve points, ~200 bytes) and verify in constant time. The catch: it needs a one-time trusted setup ceremony to generate public parameters. SolMask's ceremony is documented in our trusted setup notes.

What you don't need to trust

You don't need to trust SolMask's operators. The verifier code is on-chain and public. The circuit is public and reproducible. The relayer can refuse to broadcast your withdraw, but it cannot steal your funds — the proof binds the recipient address, so any attempt to redirect would invalidate it.

What you do need to trust

The trusted setup. If anyone who participated in the ceremony retained the secret "toxic waste", they could forge proofs and drain the pool. SolMask's ceremony uses a public Bitcoin-block beacon as a randomness anchor; a public multi-party ceremony is planned as the protocol matures. See Trusted setup.

Launch the app

How zero-knowledge proofs work (without the math) · SolMask