Tornado Cash: A Whitepaper for the First Non-Custodial ETH Mixer
Three and a half years after the DAO hack made Ethereum users painfully aware of how transparent a public ledger can be, a small team of three developers has put forward a protocol that tries to do something almost heretical for the space: deliberately break the on-chain link between a deposit and a withdrawal. The whitepaper behind it landed quietly in December 2019, and the system it describes is, in early 2020, the most fully-formed attempt to bring strong, opt-in privacy to Ethereum at the smart-contract layer.
The project is Tornado Cash. The document is Tornado Cash Privacy Solution, Version 1.4, authored by Alexey Pertsev, Roman Semenov, and Roman Storm. It is short, technically dense, and, for a paper that is really a protocol spec, surprisingly readable. In roughly two pages of substantive content, it lays out a fixed-denomination mixer whose anonymity set grows with every deposit, where the only cryptographic bridge between the depositor's address and the recipient's fresh wallet is a Groth16 zero-knowledge proof on a BN254 elliptic curve.
What makes the document worth a deep read is not the goal. Privacy on a public chain has been a talking point for years. What makes it worth a deep read is how cleanly the authors picked a small set of primitives (a Pedersen commitment, a 20-level Merkle tree, a nullifier, and a Groth16 SNARK) and assembled them into something that actually works on mainnet. The contracts are deployed. The code is open. The team has run a trusted setup, and the system is beginning to attract real volume from users who value what it offers.
Briefing Registry
- Title: Tornado Cash Privacy Solution, Version 1.4
- Authors: Alexey Pertsev, Roman Semenov, Roman Storm
- Publication date: December 17, 2019 (PDF metadata)
- Source / Venue: Tornado Cash project whitepaper, hosted at berkeley-defi.github.io and via the official repository
- Companion implementation: github.com/tornadocash/tornado-core (v1.0 tagged December 2019)
- Cryptographic primitives: Pedersen hash, MiMC hash, BN254 elliptic curve, Groth16 zk-SNARK
The Transparency Trap That Blockchains Built
Ethereum is, by design, a public spreadsheet. Every transaction is a row visible to every node, every block explorer, and every analytics firm. For most of the asset's history, this was treated as a feature: traceability deters theft, makes exchanges auditable, and lets tax authorities follow the money. But the same property has a darker edge. A user who pays a salary from a known wallet reveals the recipient. A trader who funds a fresh strategy wallet from a CEX withdrawal immediately hands the CEX a free read on their on-chain life. A donor who sends a public grant from a politically sensitive address cannot pretend it came from somewhere else.
Existing workarounds fall into two camps. Custodial mixers (services that take your coins, mix them with everyone else's, and hand you back coins from a different source) work, but require you to trust the operator with your funds and your privacy. CoinJoin-style constructions on Bitcoin decentralize the operator role but do not hide amounts and depend on a coordinator. The Tornado Cash authors wanted something stricter: a smart contract, on Ethereum, that neither knows nor stores any user identity, and yet still gives the depositor a credible claim that no one can link the deposit address to the withdrawal address.
A Commitment, a Tree, and a Proof
The core idea is the kind of construction that looks obvious only after you have seen it. A user generates two random numbers locally: a nullifier k and a secret r. They hash them together with a Pedersen commitment scheme to produce a value C = H(k, r), and they send that commitment to the contract along with a fixed deposit (0.1, 1, 10, or 100 ETH). The contract does not store k or r. It records C as a leaf in a 20-level Merkle tree, and updates the Merkle root.
Some time later, minutes or days or weeks, the user (or anyone holding their private note) submits a withdrawal transaction. This transaction reveals nothing about the original deposit address. It includes a Groth16 proof, the Merkle root, and a public value h = H(k). The proof asserts, in zero knowledge, that the prover knows a leaf index l and a nullifier-secret pair (k, r) such that h = H(k) and that a valid Merkle path from leaf l reaches the provided root. The contract checks the proof, checks that h has not been used before, marks h as spent, and releases the funds to a recipient address of the prover's choice.
The trick, and the whole reason the paper exists, is that every assertion about the deposit (its amount, its leaf position, its nullifier, its secret) is folded into a single zero-knowledge proof. The verifier learns only that some previously committed deposit is being redeemed, not which one. The anonymity set for any withdrawal is therefore the set of every deposit that has ever happened in that denomination, multiplied by the number of honest, well-spaced withdrawals.
How the Pieces Snap Together

The diagram walks the full path from the user's local entropy to a clean, unlinkable payout. Three regions map to the three actors in the protocol: the depositor's machine, the on-chain pool contract, and the recipient's environment.
On the depositor side, the user generates k and r and computes the commitment. These two values never leave the user's device. They are the private note the user must store. Lose it and the deposit is unrecoverable, because the contract has no way to identify which leaf is yours.
In the contract, the commitment is inserted as the leftmost available zero leaf in a 20-level Merkle tree built from a MiMC-friendly hash. The tree's height supports up to 2^20 leaves, which is more than a million deposits per denomination. The contract stores a small history of recent Merkle roots (the paper says 100, the deployed code uses 30) so users can submit a proof against a slightly stale root without race conditions. A nullifier set tracks every h that has been spent, and the same set is checked on every withdrawal.
On the withdrawal side, the user (or, in the relayer variant, a third party that fronts the gas) submits (R, h, π) and a fresh recipient address. The on-chain Verifier.sol is a Groth16 verifier compiled from the same circuit as the off-chain prover. If the proof checks, the funds go to the recipient and h is added to the spent set. No gas ever has to be paid by the depositor's original wallet. That is the whole point. Paying from the original wallet would re-link the two sides and undo the privacy.
What the Paper Claims, and What It Actually Proves
The whitepaper is short, so its claims are correspondingly few. The principal claim is functional rather than performance-oriented: that the contract, as deployed, lets a user deposit and later withdraw funds in a way that no observer can cryptographically link. The paper does not quantify an anonymity set, does not benchmark proof generation time, and does not claim unlinkability against an adversary that has compromised either the user's note storage or a majority of relayers.
What the paper does establish, by construction, is more limited and more useful. The link between deposit and withdrawal is replaced by a Groth16 proof, and Groth16's soundness holds under standard cryptographic assumptions on the BN254 curve. Double-spending is prevented by the nullifier construction, which is a straightforward commitment-binding argument: if a user tries to spend the same k twice, the second h = H(k) collides with the first and the contract rejects the withdrawal. The contract is non-custodial in the cryptographic sense. The keys to the funds are either the SNARK proof (when withdrawing) or the secret note (when recovering the deposit), and neither is ever held by any single party.
What the paper deliberately does not claim is also worth noting. It does not claim that Tornado Cash is safe against user error (reusing an address, reusing a note, broadcasting IP-level metadata during withdrawal). It does not claim that the fixed-denomination design prevents all forms of analysis; large deposits at off-hours are obviously more traceable than small ones in busy pools. And it does not claim that the team's trusted setup for the circuit was generated with anything more than a single provider's honesty, which is a real caveat for a system that depends on it.
Why a Mixer on a Public Ledger Matters at All
The case for Tornado Cash is not that everyone needs a mixer. It is that a public ledger without a privacy primitive is a public ledger with a single shape of user: the user who has nothing to hide because they have already decided to be visible. The construction here makes the opposite case quietly. A donor to a controversial cause, a corporate treasury testing a new counterparty, a researcher being paid for a bug bounty that they would rather not see tied to their public GitHub: these are normal uses of a financial network. On a fully transparent chain, they are all forced into one of two bad options, sloppy privacy hygiene or nothing at all.
The protocol also matters for what it does not do. There is no off-chain operator. There is no whitelist. The contract has no admin key and no upgrade path of the kind that has burned users in other Ethereum projects. That is unusual, and it is the reason the document is being read carefully by people who care about protocol ossification as much as protocol novelty. The paper argues, in effect, that the strongest privacy property a smart contract can have is the property of being unchangeable.
Anonymity Sets, Relayer Trust, and What Comes Next
The most important caveat is also the most boring one. Anonymity in this design is a function of how many people have used the pool recently, in the same denomination, with at least one well-timed withdrawal. A 1 ETH deposit withdrawn from a 10-deposit pool, with all the other deposits made minutes before, is a different privacy proposition than a 1 ETH deposit withdrawn from a pool of several hundred honest deposits spanning weeks. The protocol does its job. The user has to do theirs.
The relayer introduces a second caveat. To break the link between the deposit address and the gas-paying wallet, the user can hand the withdrawal transaction to a relayer, which posts it on-chain and takes a small fee. The relayer cannot redirect funds or alter the recipient, but it can refuse to relay, and a single trusted relayer used repeatedly can become a metadata point. The paper gestures at this by recommending that users pick independent relayers per withdrawal.
What is open is whether the construction generalizes. The denomination-fixed design is a deliberate sacrifice. It is what makes the anonymity set large enough to be meaningful. The same paper suggests in passing that future work could use a broader range of amounts, or could support ERC-20 tokens, or could compose with optimistic rollups to lower the per-withdrawal cost. None of that is in the document. What is in the document is enough to ship, and shipping a usable, non-custodial privacy primitive on a six-year-old chain is, on its own, a real result.
Sources
- Pertsev, A., Semenov, R., Storm, R. (2019). Tornado Cash Privacy Solution, Version 1.4. Available at https://berkeley-defi.github.io/assets/material/Tornado%20Cash%20Whitepaper.pdf
- Tornado Cash source code and v1.0 release: https://github.com/tornadocash/tornado-core/releases/tag/1.0
- Tornado Cash team announcement of the trusted setup ceremony (community MPC concluded in May 2020): https://tornado-cash.medium.com/the-biggest-trusted-setup-ceremony-in-the-world-3c6ab9c8fffa
- ABDK Consulting, Tornado Cash Cryptographic Review: https://web.archive.org/web/20220509033247/https://tornado.cash/Tornado_cryptographic_review.pdf
- Groth16 zk-SNARK paper: Jens Groth, On the Size of Pairing-based Non-interactive Arguments (EUROCRYPT 2016), https://eprint.iacr.org/2016/260