This repository contains code for workshop on ZK rollups for the SPACE 2022 conference. We illustrate a toy, but an end to end zk rollup system, using a local ganache blockchain. The workshop illustrates:
- Writing circuits for zero knowledge proofs using circom.
- Generating and verifying proofs for above circuits.
- Expressing the state update of rollup chain as a circuit.
- Verifying the proof of state update in a smart contract.
Relevant Links:
- Node Installation
- Circuit Compiler (circom)
- Zero Knowledge Proofs (snarkjs)
- Local Blockchain for Testing (Ganache)
- Managing Smart Contracts (truffle)
Also see for more information:
- Install circom and snarkjs by following the links above.
- Run
npm install
in the project directory to install required javascript modules. - Fetch the
powers-of-tau
file and save it aspot21_final.ptau
- Create the directory
mkdir zk-keys
to store keys for zero knowledge circuits. - Run
./generate-zk-keys.sh
to generate the keys. This may take a while and will require occasional random input from the user. - Correct the solidity version of the generated
verifier.sol
file under thezk-keys
directory, and either copy it to pathcontracts/verifier50.sol
or create a soft-link.
- We first create a blockchain instance in Ganache. Provide for 32 accounts with initial balance as 100.
- Set network id to 5777 and port to 8545. Set the gas limit per transaction to be high.
- Add truffle-config.js to the Ganache blockchain.
- For this, we will use the client script merkletree.js to fetch the account data for 32 accounts from the blockchain
and create initial merkle tree:
node merkletree.js initialize
- Note the value of initial root returned by the above script and configure the value in the contract Democoin.sol
under the
contracts
directory. - Deploy the contracts using
truffle migrate
- Copy the address of the deployed contract Democoin from Ganache, and configure the same in the client script blockchainclient.js
- The initial state of the L2 chain is committed as part of deploying the Democoin smart contract.
- Now, we execute
node merkletree.js commit50
which creates transaction data for block update under transactions/txCommitBlock.json - Now, we execute
node blockchainclient.js commit
to update the L2 block. This will create a transaction on ganache blockchain. This transactions updates the state of L2 chain maintained by the contract Democoin. - Now we execute
node blockchainclient.js verify 1
which submits a transaction, attesting correctness of the L2 block number 1. The transaction payload contains zero-knowledge proof of update created using data under transactions/txInput.json
-
Public Inputs: old-merkle-root, new-merkle-root,
$(sender_i, recv_i, v_i)_{i\in [B]}$ . -
Private Inputs:
- Intermediate roots for each transaction.
- Authentication Paths for leaf for sender_i for each i.
- Authentication Paths for updated leaf for sender_i
- Authentication paths for receiver leaf before and after update.
- Valid signature on each transaction data.
-
Verifying a single transaction
Let
$(i, j, v)$ be the public part of transaction denoting transfer of amount$v$ from account$i$ to account$j$ . Let$rt$ and$rt'$ denote merkle root of accounts tree before and after applying the transaction. The prover shows knowledge of following witness to prove correctness of update:- Initial Sender leaf:
$(addr_i, pub_i, bal_i, nonce_i)$ and$path_i$ consisting of partner nodes for the sender leaf. - Inital Receiver leaf:
$(addr_j, pub_j, bal_j, nonce_j)$ and path$path_j$ consisting of partner nodes for the receiver leaf. - Updated Sender leaf:
$(addr_i, pub_i, bal_i', nonce_i')$ and intermediate root$irt$ computed from updated sender leaf and$path_i$ . We also check$v\leq bal_i$ ,$bal_i'=bal_i-v$ and$nonce_i'=nonce_i+1$ . - Updated Receiver leaf:
$(addr_j, pub_j, bal_j', nonce_j)$ and root$irt'$ computed from updated receiver leaf and$path_j$ . We check that$irt'==root'$ . - Signature check: Signature
$\sigma_i$ which is valid with respect to public key$pub_i$ on message$(i, j, v, nonce_i)$ .
- Initial Sender leaf:
-
Verifying multiple transactions
We sequentially repeat the circuit for verifying single transaction, providing intermediate merkle roots after applying each transaction as part of the witness.
-
Circuit Complexity
For a batch size of
$B$ transactions, the circuit complexity is roughly$B.C_{one}$ where$C_{one}$ denotes the circuit complexity of verifying one transaction. We decompose$C_{one}$ as:- 4 subcircuits
$C_{merkle}$ for checking computation of merkle root given leaf and partner nodes. Assuming$d$ to be the depth of the trees, this results in$4d$ copies of the circuit computing the hash, i.e.$C_{merkle}\approx 4d.C_{hash}$ . - 1 subcircuit for checking EDDSA signature
$\sigma_i$ on message$(i,j,nonce_i)$ . - 1 comparison.
The overall circuit complexity is roughy
$B(4d\cdot C_{hash} + C_{sig} + C_{comp})$ . For$B=50$ ,$d=5$ ,$C_{hash}=1000$ ,$C_{sig}=1000$ and$C_{comp}=100$ , the expression works out to approximately$1$ million.
- 4 subcircuits