Tornado Cash is a privacy tool that breaks the on-chain link between crypto transfer senders and recipients by having users deposit fixed amounts into smart contracts, then withdraw using zero knowledge proofs that verify ownership without revealing which deposit was made; this works through Pedersen commitments (which hide values while proving knowledge of them) and incremental Merkle trees that mix deposits, with the anonymity set size determining privacy effectiveness.
Tornado Cash Explained: Zero-Knowledge Proofs & Privacy
Added:Tornado Cashache is a privacy tool that's designed to break the onchain link between the sender and the recipient of crypto transfers. In this video, we will be going through what tornado cashache is and how it works so you can understand how zero knowledge proof applications actually work. Just a quick disclaimer to say that this video is purely for educational purposes. It is meant as a resource for readers to be able to understand cryptographic techniques like Merkel trees, zero knowledge proofs, and commitments and how they can all be used together in conjunction. It is not meant as a resource to build similar tools, but instead an educational resource for developers to understand how zero knowledge proof protocols actually work.
Let's go into the problem that Tornado Cache is solving. So, imagine this.
You've been grinding away on Cipher Updraft. You've become a badass solidity smart contract engineer and you decide to test your knowledge by entering a hackathon. You build the coolest project that the judges have ever seen and they award you the highest prize of 10 ETH and they send these funds to your wallet. After the hackathon, you're bombarded with messages saying things like, "Hey, I saw online that you won a big prize. Congratulations for winning 10 ETH." And this makes you realize that everyone can track who you are. They can see that the organizers sent you 10 ETH to your address and therefore they can associate your address with you. They know how much crypto you hold, whether you sell or transfer your winnings, and you feel this makes you vulnerable to being targeted by attackers. Hopefully, you've never pasted your private key into AENV. So, you're probably going to be fine, but nonetheless, it makes you feel uneasy. So, what's the solution?
How do you break this chain of ownership? This is one example where privacy tools like Tornado Cash can come in useful. Blockchains are inherently transparent, which creates a fundamental paradox. This transparency also creates a problem. Since all transactions are permanently recorded, when I send my funds to Patrick, everyone can see who the sender and the receiver are and the fact that the funds were sent. addresses can then be linked to real identities through correlation techniques. For example, when someone wins a hackathon and Tornado aims to solve this problem while maintaining the benefits of blockchains by, of course, breaking the sender receiver connection. Tornado achieves this by allowing users to deposit fixed amounts of cryptocurrency into a smart contract and then later withdraw that deposit using a zero knowledge proof that verifies their ownership without revealing which deposit they actually made. Let's go through how this works from a very high level before we delve into the nitty-gritty details.
The first phase is the deposit phase.
Users deposit a fixed amount of cryptocurrency into the tornado cash smart contract. Why this needs to be a fixed amount will be explained shortly.
And a note is generated containing their secret deposit information. And this note is stored locally on their machine.
When the user deposits into the smart contract, it is now indistinguishable from all of the other users deposits.
And given sufficient depositors, it makes it difficult to trace an individual deposit to an individual user.
The third part is withdrawals. Using a zero knowledge proof of the secret deposit information in the note, users can withdraw the funds to a different address than the one that they used to deposit. And since their specific deposit wasn't linked to any address and instead linked to the secret deposit information by providing a proof of knowledge of this secret information, the connection between the depositor and the withdrawer is broken.
Tornado cash requires users to deposit in a fixed amount such as 0.1 E, 1 E, 10 E, etc. And each of these amounts is denoted the denomination and there is a separate smart contract for each of these denominations. So in one smart contract which mixes the funds from multiple users, there will be a single denomination i.e. users will only be able to deposit one ETH for instance. To understand why, let's imagine that there isn't this denomination requirement. If variable amounts were allowed, we would be able to link deposits and withdrawals. If Alice deposited 1.2 34567 ETH and later withdrawed exactly 1.2 34567 ETH, despite the zero knowledge protections, these transactions would be able to be linked by their unique amount. So in the smart contract, it enforces that the amount that of ETH that can be sent when depositing must be this fixed denomination amount and each of these denominations acts through a separate smart contract instance with its own pool, its own Merkel tree and therefore anonymity set and therefore all transactions in the pool are identical in value.
Let's talk about this anonymity set for a second. the effectiveness of the privacy protection increases with the number of depositors and this number of depositors is referred to as the anonymity set. So with one deposit into the protocol there is no privacy. Of course we would know the person who made the deposit is the person that withdrew it. With 10 deposits the withdrawal could be any one of the 10 depositors.
With a thousand depositors you have a 0.1% chance of being linked. Therefore, we need lots of depositors in order for the protocol to be privacy preserving.
Tornado Cache has two types of smart contracts that enable users to mix their funds. They have an ETH implementation and an ERC20 implementation. And one contract must be deployed for every denomination and also for different ERC20 tokens. For the rest of this video, we are going to focus on the ETH implementation, but they work pretty much the same because they both inherit from the same base contract, which is called Tornado.
Now we understand how the deposits are actually mixed together in the smart contract. Let's go through the process of doing a deposit and a withdrawal to understand how the different cryptographic techniques are used.
Tornado cashache has two primary external functions deposit and withdraw.
Let's first go through the depositing phase now in detail.
They deposit into cash by sending a fixed amount equal to the denomination when they call the deposit function. And this function takes one parameter called a commitment. This commitment is comprised of that secret deposit information. But what actually is a commitment? A commitment is a cryptographic technique that allows a user to commit to a chosen value or values while keeping it hidden with the ability to reveal the value later if they so choose. To commit to a value means to create a cryptographic output that locks the chosen value without revealing what it is. similar to sending your choice in a sealed envelope. And commitment schemes must satisfy two properties. They must be binding, which means that the committer cannot change the values that they committed to at a later date. And secondly, they must be hiding. So the commitment must reveal nothing about the values that were being committed to. It's like creating an opaque tamperproof container for your information. Once you put your data into this container, you cannot change what is inside the box and others can't see into the box or open the box and see what those original values were. Torn tornado cashe in particular uses a pedison commitment scheme which provides something called information theoretic hiding which means that even with unlimited computational power you wouldn't be able to find the undenying values and computational binding which means that as long as the computational hardness assumptions hold which in this case is the discrete logarithm problem they are secure and pedome commitment schemes work using elliptic curve points and they are calculated using this equation where g and h are generated points on the elliptic curve and the oper operations are performed using elliptic curve, scaler, multiplication, and addition. How they actually work is outside of the scope of this article, but all you need to understand here is that the commitment combines some secret value, which in this equation is value, with some random blinding factor, which is called randomness in this equation.
And in tornado cache, this value is actually called the secret. And this randomness is called the nullifier. And the nullifier serves for a couple of purposes which we will go into shortly.
To create this commitment to be able to call deposit, we need to create two random bytes values. The nullifier and the secret and they form the secret deposit information that cannot be revealed, cannot be stored on chain and knowledge of would break the privacy assumptions. So these two values are then hashed together using a pedison commitment scheme. Side note here, if you're keen on watching Cipher and Updraft, where we will teach you how to build zero knowledge applications, we actually build a very similar tool to Tornado Cache, of course, for educational purposes. And instead of using a Pettison commitment, we use a Poseidon commitment. And the reason for this is that it is a newer commitment scheme that is more efficient for zero knowledge briefs. So head on over to Cipher Updraft, which I'll leave a link down below if you're keen on watching that course. this commitment information. So the secret and the nullifier are then stored in a secure note in the format that you can see here. So an example of such a note looks like this and then this is securely stored and can be used to later withdraw the deposit. This is a non-custodial design which means that there's no recovery mechanism if this note is lost.
It is the user's responsibility to save this note securely. Users can then call the deposit function using this generated commitment and send the fixed denomination of ETH. Inside the deposit function, if you look at the logic, you can see that it will first check if the commitment has already been submitted.
If it has not, it will add it to this commitments mapping which maps a byte 32 object which is a commitment to a boolean to say whether the commitment has already been used and this prevents the commitment being resubmitted where multiple commitments are present in the protocol with the same bytes 32 value.
This underscoreinsert function adds the commitment to an onchain merkel tree.
Now you might be thinking here surely since we add the commitment to the commitments mapping we don't need to add it to a merkel tree. But the issue is that when we are withdrawing, we do not provide the commitment because otherwise we could associate a withdrawing address to a depositing address. So instead we store it in a Merkel tree and this enables us to prove commitment inclusion into the protocol through a zero knowledge proof which we'll see later.
Now Merkel trees are usually stored offchain and only the root is submitted to the smart contract onchain. But here we need to have a Merkel tree stored onchain and be able to be updated whenever someone makes a deposit. So a special type of Merkel tree is used and that is an incremental Merkel tree. If you don't understand how incremental Merkel trees work or what they are, then I encourage you to go watch this video which I'm linking on this little flag here and I will also leave it in the link down below because we're not going to be going through how incremental Merkel trees actually work in this video. So this insert function will insert the commitment into the onchain Merkel tree in underscore process deposit. We will then also check that the amount of ETH that was sent with the transaction was equal to the denomination. You should also note here that the contract never sees the nullifier or the secret in their raw form. It only ever sees the commitment which preserves the privacy of the system. At the end of the deposit function, you'll see that it emits a deposit event and these events are used by tornado cache to obtain the history of commitments and this is needed to generate the Merkel tree offchain when users create zero knowledge proofs when they are withdrawing. In tornado cache in the Merkel tree, the hash function that is used is the mimic sponge hash function. In the updraft version of our mixer protocol, we actually use again a Poseidon hash function. And this is because it's a newer updated hash function that is much more efficient for generating efficient zero knowledge proofs.
Once the user has made their deposit, they can later withdraw by calling the withdraw function. And when they withdraw, they need to provide two things. They need to provide their zero knowledge proof which they would generate offchain using the circuit. And we'll go through this shortly. And the other thing they need to provide is the public inputs that they used to generate their proof so that we can verify this proof on chain.
Let's go through the zero knowledge proof and the proof generation so we can understand how this fits into the wider picture. So when the user wants to make a withdrawal they need to generate a zero knowledge proof. And this is usually done in the tornado cache front end or it's done using their CLI tool.
And what will happen is it will run some JavaScript that will compile the circuit which is essentially just some code which dictates the rules that the provided inputs need to follow and it will generate a witness. This witness can then be used to generate a zero knowledge proof. So this is all done offchain and it will generate this proof which is a bytes value. So ox blah blah blah blah blah. To generate this zero knowledge proof, the user either via the CLI or via the front end needs to provide their secure note. And this secure note contains the private inputs for the circuit.
We can see that there are private and public inputs. The private inputs are the nullifier, the secret, some path elements and path indices. The nullifier and the secret are taken from the secure note. the path elements and the path indices form the Merkel proof. So in the JavaScript code that generates the proof, what will happen is it will take all of the events, all of the deposit events and then form a Merkel tree and it will recalculate this entire Merkel tree using all of the commitments from the events. Once it has formed the Merkel tree, we can then query this Merkel tree to get the Merkel proof, the intermediate nodes required to reconstruct the tree for a specific commitment, the commitment that we want to withdraw. These node values that form the Merkel proof are this path elements array in the circuit. Path indices denotes whether the path elements need to be on the left or the right of the hash function whether their indices are even or odd. So the first two private inputs are provided by the user in the secure note and the second two are generated using JavaScript when we reconstruct the Merkel tree. We then have 1 2 3 4 5 six public inputs. the root that we are saying our Merkel proof will reconstruct to. So this is the route that we will check against the nullifier hash which we'll be exploring shortly but it is literally just the hash of the nullifier and this is what will actually be recorded on chain.
recipient, the address receiving the funds, and then a relayer and fee, which we're not going to be explaining in this video, but it is just so that you can use a relayer to pay for the transaction fees rather than having to have funds in the recipient address to pay for the transaction fees. In this proof, we number one calculate the commitment. We then reconstruct the Merkel tree again using this commitment and the Merkel proof with the indices. And we check that this calculated route matches the provided route. We also check that the nullifier matches the nullifier hash that our private input matches our public input. We have some dummy calculations for the recipient, the fee, the relayer and the refund. And this is to prevent front running which we will explore very shortly. And we need to have these calculations to prevent the optimizer from removing these constraints. So the user well the CLI the JavaScript will create a proof using the circumjs library to construct the proof from the inputs and then the user can call the withdraw function using this proof and their public inputs. So going back to our withdraw function first we have a check on the fee which is just used if you are using a relayer.
We check whether this withdrawal has already occurred whether this note has already been spent. The commitment associated with the nullifier has already been withdrawn. And here we add the nullifier hash to a nullifier hashes mapping rather than the nullifier. And this is just for privacy reasons. We don't want to be revealing the nullifier. And then we check that the public input the root is a known route in the onchain Merkel tree. And this is known root function will check whether the root is in one of the 30 most recent roots. And it does this by iterating through a mapping of the 30 most recent roots. And the reason we don't just store one route, the most recent updated route, is that if someone generates a proof offchain, and then in the time between them generating the proof and then submitting the withdrawal, people make deposits, then this check will fail. And we go through exactly how this works, how this is known root function works, and why we can't just store the most recent up-to-date route in the smart contract in the updraft course where we build a mixer protocol. Then we use an onchain verifier smart contract to verify the proof. And this verifier smart contract is generated automatically using cirom from the compiled circuit bite code. So the code that was used to generate the proof the bytes is also used to generate a verifier smart contract. So it is circuit specific. And here we're checking that the proof satisfies the constraints and the public inputs that we have provided to this function call match the public inputs used inside the proof. If either the proof doesn't pass the constraints or the public inputs do not match the public inputs which we used to generate the proof, this verification will fail. If it passes, we add the nullifier hash to the nullifier hashes array to say that this commitment has now been spent. The note has been spent. The reason that we need to record this nullifier hashes array is so that we can't withdraw the note more than once. And this prevents double spending.
If we didn't record the nullifier hash on chain, then we would be able to withdraw the note multiple times. So by recording the nullifier hash on chain, we prevent double spending. And then in process withdraw, we do some fun stuff for the relayer, we send the relayer the fees if we're using one, and we send the funds to the recipient.
So why did we have to include the address and the other relayer information like the fee etc in the circuit? And the answer for this is to prevent against frontunning. Front running is a blockchain vulnerability in which a mempool observer could see the pending withdrawal with some valid proof that they've generated offchain and then resubmit the transaction to be included before theirs but to send the funds to a different address. So a different recipient address. So to mitigate against this, we include the recipient in the circuit itself. To tie the proof to the recipient, so it can't be used to send the funds to a different address because we know that when we verify the proof, the verification checks that the public inputs provided to the function call matches public inputs used to generate the proof. So if a different recipient address was used by the attacker, it wouldn't match what was used in the circuit and therefore the verification would fail. So the proof is now generated to be specific for a particular recipient and changing the recipient would invalidate the proof. An attacker cannot change out the recipient address without having to regenerate the proof which would require knowing the secret and the nullifier which they don't know.
Let's just very quickly touch on the relayer system. Relayers allow for gasless withdrawals. So users can generate a proof with a specific relayer address and fee and then the relayer would submit the transaction and pay the gas fees. When the withdrawal executes, the relayer would then get paid the fee and the remaining funds would go to the recipient. And this creates a trustless system where the relayer cannot steal the funds because the proof is bound to a specific recipient and the users are not required to hold funds in their wallet. So that is it. That is exactly how tornado cash works. It allows you to deposit into the protocol. The funds are mixed up with other users deposits and then you can withdraw with a different address, breaking the chain between your depositing and withdrawing address.
Thank you so much for watching. Again, if you would like to have more information on how similar protocols work and how zero knowledge proofs can be used in practice to build privacy applications, then please watch Cipher Updraft, which I have linked down below, where we go through how to use Noir from scratch. I have also left down below all the resources that were used. So, big thank you to all of those people. Thank you, and I will see you soon in the next one or on Cipher Updraft. Goodbye.
Up Next

Zero Knowledge Proofs with zkSNARKs, Circom, and Snarkjs
@cryptonaukri2360
2.8K views•2022-07-09

Torrent File Format & Bencoding: A Technical Deep Dive
@AsliEngineering
12.5K views•2022-08-08

Blockchain Bridges & Cross-Chain Messaging Protocols Explained
@ciaranightingale
997 views•2025-02-09

Understanding Ethereum: A Comprehensive Beginner's Overview
@99Bitcoins
3.1M views•2018-06-26
Related Study Plans & Knowledge Roadmaps
Structured learning paths in Blockchain & Crypto







































