Commitments, Merkle trees and nullifiers
The hash-based building blocks behind private allowlists, airdrops and ZK Compression, and why they hold up against quantum attacks.
- A commitment is a sealed envelope: hash(secret, salt) hides the secret, and you can't open it to anything else later.
- A Merkle tree turns thousands of commitments into one 32-byte root. A path of sibling hashes proves one leaf is in it.
- Put the path inside a zero-knowledge proof and you prove 'one of these members' without saying which.
- A nullifier, hash(secret, scope), is the same every time you claim, so a second claim shows up while the two claims stay unlinkable across scopes.
- All of this rests on hashes, so it survives quantum computers. What's exposed is the proof system wrapped around it.
A commitment is a sealed envelope
Hash a secret together with a random salt and publish the result. The output reveals nothing about the secret (hiding), and later you can't claim you committed to something else, because that would mean finding a hash collision (binding).
Inside circuits the hash of choice is Poseidon. It is built from field arithmetic, so it costs a few hundred constraints where SHA-256 costs tens of thousands. Solana has a Poseidon syscall, sol_poseidon, with the circom parameters on BN254. It costs 61·n² + 542 compute units for n inputs, so a program can recompute the same hashes a circuit did.
Merkle trees: one root for thousands of members
Pair the commitments up and hash each pair, then pair the results, and keep going until one hash is left: the root. A tree of depth 12 holds 4,096 leaves, and proving one is inside takes 12 sibling hashes, 384 bytes in the clear. A program only has to store the 32-byte root.
A bare Merkle path gives you away, though: it says exactly which leaf you are. Zero-knowledge fixes that. The path goes in as a private input, and the circuit only outputs the root it rebuilds.
Proving membership without saying which
The allowlist circuit in this site's lab checks four things privately:
- Your commitment is Poseidon(secret).
- Hashing it up your path gives the public root.
- Your nullifier is Poseidon(secret, scope).
- The recipient is bound to the proof.
The verifier sees four numbers (root, scope, recipient, nullifier) and nothing else. In circom that comes to 3,370 constraints, and the proof is still 256 bytes.
Nullifiers stop double claims
If proofs reveal nothing, what stops someone from claiming twice? The nullifier. It's a hash of your secret and the claim's scope, so it's the same every time you claim this drop. The program keeps a list of used nullifiers, typically one small account per nullifier, and refuses repeats. Because the scope is part of the hash, your nullifier for one drop can't be linked to your nullifier for another.
The recipient matters too. A valid proof sitting in the mempool could be copied by someone else and sent with their own address. Making the recipient a public input, constrained inside the circuit, means changing it breaks the proof. The lab lets you try exactly that.
ZK Compression is the same trick at scale
Light Protocol's ZK Compression stores accounts as leaves in state trees of height 26. A raw path in such a tree would be 832 bytes. Instead, one 128-byte Groth16 proof shows that up to eight accounts are in the tree. Address trees use the opposite kind of proof, non-inclusion, to show a new address hasn't been taken.
The trees and hashes would survive a quantum computer. The 128-byte proof wouldn't: Chapter 4 explains the difference.
Sources
- Poseidon Grassi et al.: Poseidon, a new hash function for zero-knowledge proof systemsThe SNARK-friendly hash used by circomlib, Light Protocol and Solana's syscall.
- solana-poseidon solana_poseidon crate sourceBn254X5 parameters, circom-compatible, up to 12 inputs.
- Agave cost table agave program-runtime/src/execution_budget.rsCompute-unit prices for alt_bn128, compression and Poseidon syscalls.
- Semaphore Semaphore protocol documentationThe commitment + Merkle + nullifier pattern the allowlist demo follows.
- ZK Compression docs Merkle trees and validity proofs128-byte proofs of state-tree inclusion and address non-inclusion.
checked 2026-10-16 against the documents above
Outside this chapter
- Sparse and indexed Merkle trees (used for non-inclusion)
- Vector commitments and KZG
- Rate-limiting nullifiers and other variants