Private Transactions on Ethereum
Suyash Bagad
Merkle Tree
- Suppose we want to store \(2^{k}\) files in a decentralized and succinct way
\(h = H\big(\)
\(\big)\)
- Here, \(h\) indeed is a succinct representation of the files \(\{f_i\}_{i \in [8]}\)
- The problem is: to check if a file in included in \(h\), you need all \(\{f_i\}_{i \in [8]}\) files
- A better way to achieve this is using Merkle trees!
\(f_1\)
\(f_2\)
\(f_3\)
\(f_4\)
\(f_5\)
\(f_6\)
\(f_7\)
\(f_8\)
Merkle Tree
- Suppose we want to store \(2^{k}\) files in a decentralized and succinct way
\(f_1\)
\(f_2\)
\(f_3\)
\(f_4\)
\(f_5\)
\(f_6\)
\(f_7\)
\(f_8\)
\(H(f_1)\)
\(H(f_2)\)
\(H(f_3)\)
\(H(f_4)\)
\(H(f_5)\)
\(H(f_6)\)
\(H(f_7)\)
\(H(f_8)\)
Merkle Tree
- Suppose we want to store \(2^{k}\) files in a decentralized and succinct way
\(H(f_1)\)
\(H(f_2)\)
\(H(f_3)\)
\(H(f_4)\)
\(H(f_5)\)
\(H(f_6)\)
\(H(f_7)\)
\(H(f_8)\)
\(H'(H(f_1), H(f_2))\)
\(H'(H(f_3), H(f_4))\)
\(H'(H(f_5), H(f_6))\)
\(H'(H(f_7), H(f_8))\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\(h^3_1\)
\(H'(h^1_1, h^1_2)\)
\(H'(h^1_3, h^1_4)\)
\(H'(h^2_1, h^2_2)\)
Merkle Tree
\(H(f_1)\)
\(H(f_2)\)
\(H(f_3)\)
\(H(f_4)\)
\(H(f_5)\)
\(H(f_6)\)
\(H(f_7)\)
\(H(f_8)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\(h^3_1\)
- Indeed, \(h_1^3\) is succinct form of the files. How do we prove inclusion?
Merkle Tree
\(H(f_1)\)
\(H(f_2)\)
\(H(f_3)\)
\(H(f_4)\)
\(H(f_5)\)
\(H(f_6)\)
\(H(f_7)\)
\(H(f_8)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\(h^3_1\)
- Indeed, \(h_1^3\) is succinct form of the files. How do we prove inclusion?
Merkle Tree
\(H(f_1)\)
\(H(f_2)\)
\(H(f_3)\)
\(H(f_4)\)
\(H(f_5)\)
\(H(f_6)\)
\(H(f_7)\)
\(H(f_8)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\(h^3_1\)
- Only \(\left( H(f_6), h^1_4, h_1^2 \right)\) are enough to prove inclusion of \(f_5\)! Sister nodes!
Tornado.Cash
- Brings transaction privacy by breaking the on-chain link between the recipient and destination addresses
- Works in two stages: Deposit and Withdraw
Deposit
- Deposit: \((i)\) Generate \(k, r \leftarrow \mathbb{F}_p\) and compute \(C_1 = H(k, r)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\((ii)\) Send \(N\) ETH to contract \(\mathcal{C}\) which adds \(C_1\) to the Merkle tree
\(h^3_1\)
- Deposit: \((i)\) Generate \(k, r \leftarrow \mathbb{F}_p\) and compute \(C_1 = H(k, r)\)
\(C_1\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\((ii)\) Send \(N\) ETH to contract \(\mathcal{C}\) which adds \(C_1\) to the Merkle tree
\(h^3_1\)
Deposit
- Deposit: \((i)\) Generate \(k, r \leftarrow \mathbb{F}_p\) and compute \(C_1 = H(k, r)\)
\(C_1\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\((ii)\) Send \(N\) ETH to contract \(\mathcal{C}\) which adds \(C_1\) to the Merkle tree
\(h^3_2\)
\(h^3_1\)
Deposit
- Deposit: \((i)\) Generate \(k, r \leftarrow \mathbb{F}_p\) and compute \(C_1 = H(k, r)\)
\(C_1\)
\(C_2\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\((ii)\) Send \(N\) ETH to contract \(\mathcal{C}\) which adds \(C_1\) to the Merkle tree
\(h^3_3\)
\(h^3_1\)
\(h^3_2\)
Deposit
- Deposit: \((i)\) Generate \(k, r \leftarrow \mathbb{F}_p\) and compute \(C_1 = H(k, r)\)
\(C_1\)
\(C_2\)
\(C_3\)
\(C_4\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\((ii)\) Send \(N\) ETH to contract \(\mathcal{C}\) which adds \(C_1\) to the Merkle tree
\(h^3_5\)
\(h^3_1\)
\(h^3_2\)
\(h^3_3\)
\(h^3_4\)
Deposit
Withdraw
- To withdraw a coin \(k,r \in \mathbb{F}_p\) at position \(l \in \mathbb{Z}_{2^{16}-1}\), we do:
\((i)\) Select a withdrawal address \(A\)
\((ii)\) Select a root \(R\) among the stored ones and compute opening \(O(l)\) w.r.t \(R\)
\((iii)\) Compute nullifier hash \(h= H(k)\)
\((iv)\) Compute a proof using Groth16 proof system s.t. :
\((v)\) The contract verifies the proof and uniqueness of the nullifier hash.
If that succeeds, the contract transfers \(N\) ETH to \(A\).
Withdraw
- To withdraw \(C_3\), create a proof \(\pi = \mathcal{P}\left( k_3, r_3, (C_4, h_1^1, h_2^2) \right)\)
- The contract verifies \(\pi\) and checks if nullifier hash is unused.
\(C_1\)
\(C_2\)
\(C_3\)
\(C_4\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(H(0)\)
\(h^1_1\)
\(h^1_2\)
\(h^1_3\)
\(h^1_4\)
\(h^2_1\)
\(h^2_2\)
\(h^3_5\)
Comparison
Anonymity | ||
Non-custodial | ||
Variable amounts | ||
Shielded payments | ||
ZK Rollup | ||
Proof system |
\(^{\dagger}\) Concerns with Tornado.cash: https://lightco.in/2019/08/07/tornado-review/
Groth16
PLONK
\(^{\ddagger}\) Theoretically, ZK Rollups with Groth16 is possible. Loopring uses Groth16 with ZK Rollups.
\(\dagger\)
\(\ddagger\)
Benchmarks
- The CRS in Groth16 is non-universal, each circuit requires a trusted setups
- TurboPLONK competes Groth16 in performance
- UltraPLONK (Q3 2021) is set be 3-4x better than TurboPLONK
Thank you!
Private Transactions on Ethereum
By Suyash Bagad
Private Transactions on Ethereum
Brief overview of projects like Hopper, Hieswap and Tornado.cash.
- 58