Introduction
Tornado Cash is a decentralized privacy protocol designed to anonymize cryptocurrency transactions on the Ethereum blockchain. By using advanced cryptographic techniques like zk-SNARKs, it severs the on-chain link between deposit and withdrawal addresses, allowing users to protect their financial privacy. Through its non-custodial and decentralized design,
Tornado Cash enables individuals to conduct private transactions while retaining full control of their funds The goal of this circuit is to allow a prover to prove membership in a group of public keys without revealing which specific key (or secret key) they possess. In this tutorial, we will breakdown the tornado cash circuit to better understand the concept behind the tornado cash platform.
How Tornado cash works
-
Deposit: A user selects a specific amount of cryptocurrency to deposit into Tornado Cash. Upon initiating the deposit, the protocol generates a unique secret, often referred to as a “commitment” or “note.” This secret is a hash that serves as proof of the deposit without revealing the user’s identity. The user is advised to securely store this note, as it will be required for future withdrawals.
-
Mixing: The deposited funds are pooled together with those of other users within Tornado Cash’s smart contract. This aggregation makes it challenging to trace the original source of any specific withdrawal, thereby enhancing privacy.
-
Withdrawal: When the user decides to withdraw their funds, they must provide the unique note generated during the deposit. This note serves as a zero-knowledge proof, allowing the user to demonstrate ownership of the deposited funds without revealing their identity. The user can withdraw the funds to a new address, further obscuring the link between the deposit and withdrawal addresses.
Key Features:
-
Zero-Knowledge Proofs: Tornado Cash employs zk-SNARKs (Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge) to enable users to prove they have made a deposit without revealing any additional information. This cryptographic technique ensures that the withdrawal cannot be linked back to the deposit, maintaining user anonymity.
-
Non-Custodial: Users retain full control over their funds throughout the process. Tornado Cash’s smart contracts manage the mixing process without ever taking custody of the assets, reducing the risk of theft or loss.
Building the Circuit
For installation and project setup, check out Part1 of this section in the series.
Complete Circuit code
you can check out the tornadocash repo here
include "../node_modules/circomlib/circuits/mimcsponge.circom";
// Computes MiMC([left, right])
template HashLeftRight() {
signal input left;
signal input right;
signal output hash;
component hasher = MiMCSponge(2, 1);
hasher.ins[0] <== left;
hasher.ins[1] <== right;
hasher.k <== 0;
hash <== hasher.outs[0];
}
// if s == 0 returns [in[0], in[1]]
// if s == 1 returns [in[1], in[0]]
template DualMux() {
signal input in[2];
signal input s;
signal output out[2];
s * (1 - s) === 0
out[0] <== (in[1] - in[0])*s + in[0];
out[1] <== (in[0] - in[1])*s + in[1];
}
// Verifies that merkle proof is correct for given merkle root and a leaf
// pathIndices input is an array of 0/1 selectors telling whether given pathElement is on the left or right side of merkle path
template MerkleTreeChecker(levels) {
signal input leaf;
signal input root;
signal input pathElements[levels];
signal input pathIndices[levels];
component selectors[levels];
component hashers[levels];
for (var i = 0; i < levels; i++) {
selectors[i] = DualMux();
selectors[i].in[0] <== i == 0 ? leaf : hashers[i - 1].hash;
selectors[i].in[1] <== pathElements[i];
selectors[i].s <== pathIndices[i];
hashers[i] = HashLeftRight();
hashers[i].left <== selectors[i].out[0];
hashers[i].right <== selectors[i].out[1];
}
root === hashers[levels - 1].hash;
}
Code Walkthrough
1. HashLeftRight Template
template HashLeftRight() {
signal input left;
signal input right;
signal output hash;
component hasher = MiMCSponge(2, 1);
hasher.ins[0] <== left;
hasher.ins[1] <== right;
hasher.k <== 0;
hash <== hasher.outs[0];
}
The HashLeftRight
template computes the MiMC sponge hash of two inputs (left
and right
) and produces a single output (hash
).
Concepts:
- MiMC Sponge:
- MiMC is a cryptographic hash function designed for zero-knowledge proofs (ZKPs). It is lightweight and zk-SNARK-friendly.
- Sponge functions process inputs of variable length to produce outputs of fixed size.
- How it Works:
- The
MiMCSponge
component takes two inputs (left
andright
). k
(key) is set to0
since no additional secret keying is required.- The result is stored in
hash
.
Purpose:
This template will hash two nodes of a Merkle tree (a parent node in the Merkle path) to calculate the next level’s hash.
2. DualMux Template
template DualMux() {
signal input in[2];
signal input s;
signal output out[2];
s * (1 - s) === 0
out[0] <== (in[1] - in[0])*s + in[0];
out[1] <== (in[0] - in[1])*s + in[1];
}
The DualMux
template selects one of two values (in[0]
or in[1]
) based on the selector signal (s
) and swaps them accordingly.
Concepts:
- Signal
s
:
s
is a binary signal (either0
or1
).- Constraint:
s * (1 - s) === 0
ensures thats
is always binary.
- Output Computation:
- If
s = 0
:out[0] = in[0]
,out[1] = in[1]
(no swap). - If
s = 1
:out[0] = in[1]
,out[1] = in[0]
(values swapped).
Purpose:
This template determines whether a Merkle proof element is the left or right sibling of the current node. It reorders the inputs to correctly compute the hash for the next level.
3. MerkleTreeChecker Template
template MerkleTreeChecker(levels) {
signal input leaf;
signal input root;
signal input pathElements[levels];
signal input pathIndices[levels];
component selectors[levels];
component hashers[levels];
for (var i = 0; i < levels; i++) {
selectors[i] = DualMux();
selectors[i].in[0] <== i == 0 ? leaf : hashers[i - 1].hash;
selectors[i].in[1] <== pathElements[i];
selectors[i].s <== pathIndices[i];
hashers[i] = HashLeftRight();
hashers[i].left <== selectors[i].out[0];
hashers[i].right <== selectors[i].out[1];
}
root === hashers[levels - 1].hash;
}
This template verifies a Merkle proof for a given leaf
node. The proof consists of pathElements
(siblings along the path) and pathIndices
(indicating left/right positions). The circuit ensures that the computed root matches the provided root
.
- Inputs:
leaf
: The data node to verify.root
: The claimed Merkle root.pathElements
: Array of sibling nodes at each level of the tree.pathIndices
: Array of binary values (0 or 1) indicating whether the sibling is on the left or right.
- Selectors and Hashers:
- For each tree level:
- Use
DualMux
to order the leaf/hash and sibling node correctly based onpathIndices[i]
. - Use
HashLeftRight
to compute the hash for the next level.
- Use
- Root Verification:
- After iterating through all levels, the hash at the last level (
hashers[levels - 1].hash
) is compared to the claimedroot
.
Purpose:
The circuit guarantees that:
- The provided
leaf
and path produce the specifiedroot
. - The constraints are satisfied, ensuring cryptographic integrity.
How it works:
- Initialization:
- Start with the
leaf
as the initial hash. - Traverse up the Merkle tree using
pathElements
andpathIndices
.
- For Each Level:
- Use
DualMux
to select the correct order of sibling nodes. - Hash the current node and sibling to compute the parent hash.
- Final Check:
- Verify that the final computed hash matches the provided
root
.
Conclusion
Congratulations! You’ve completed a hands-on implementation of zero-knowledge proofs. This process may seem complex initially, but as you create more advanced circuits, you’ll see how useful ZKPs can be for privacy-preserving computation.
We encourage you to continue your exploration of topics on zk proofs. If you’re interested in diving deeper, you can follow up on the pathway here Zero-Knowledge Proofs on the Celo Blockchain: A Comprehensive Tutorial Series - Pathways - Celo Academy
Resources
Circom docs
SnarkJS
0xparc Circom Workshop
About the author
I’m Jonathan Iheme, A full stack block-chain Developer from Nigeria. With a great passion for Zero Knowledge Technology.