How Aztec works?

A walkthrough of the circuits in Aztec 2.0

Recursive Proof Verification

  • A Plonk proof \(\pi\) is verified by checking equality of polynomial evaluations

\(\pi = \bigg\{\underbrace{[a]_1, [b]_1, [c]_1, [z]_1, [t_0]_1, [t_1]_1, [t_2]_1, [W_{\mathfrak{z}}]_1, [W_{\mathfrak{z\omega}}]_1}_{\mathbb{G}_1^{2w + 3}}, \ \underbrace{\bar{a}, \bar{b}, \bar{c}, \bar{z}_{\omega}, \bar{s}_{\sigma_1}, \bar{s}_{\sigma_2}}_{\mathbb{F}_p^{2w}} \bigg\}\)

\(W_{\mathfrak{z}}(x) \cdot (x - \mathfrak{z}) = F_1(x) - F_1(\mathfrak{z})\)

\(W_{\mathfrak{z\omega}}(x) \cdot (x - \mathfrak{z}\omega) = F_2(x) - F_2(\mathfrak{z}\omega)\)

\(W_{\mathfrak{z}}(x) \cdot (x - \mathfrak{z}) + u \cdot (W_{\mathfrak{z\omega}}(x) \cdot (x - \mathfrak{z}\omega))= F_1(x) - F_1(\mathfrak{z}) + u \cdot (F_2(x) - F_2(\mathfrak{z}\omega))\)

Recursive Proof Verification

  • A Plonk proof \(\pi\) is verified by checking equality of polynomial evaluations

\(\pi = \bigg\{\underbrace{[a]_1, [b]_1, [c]_1, [z]_1, [t_0]_1, [t_1]_1, [t_2]_1, [W_{\mathfrak{z}}]_1, [W_{\mathfrak{z\omega}}]_1}_{\mathbb{G}_1^{2w + 3}}, \ \underbrace{\bar{a}, \bar{b}, \bar{c}, \bar{z}_{\omega}, \bar{s}_{\sigma_1}, \bar{s}_{\sigma_2}}_{\mathbb{F}_p^{2w}} \bigg\}\)

\(W_{\mathfrak{z}}(x) \cdot (x - \mathfrak{z}) = F_1(x) - F_1(\mathfrak{z})\)

\(W_{\mathfrak{z\omega}}(x) \cdot (x - \mathfrak{z}\omega) = F_2(x) - F_2(\mathfrak{z}\omega)\)

\(W_{\mathfrak{z}}(x) \cdot (x - \mathfrak{z}) + u \cdot (W_{\mathfrak{z\omega}}(x) \cdot (x - \mathfrak{z}\omega))= F_1(x) - F_1(\mathfrak{z}) + u \cdot (F_2(x) - F_2(\mathfrak{z}\omega))\)

\(\underbrace{\left(W_{\mathfrak{z}}(x) + uW_{\mathfrak{z\omega}}(x)\right)}_{P_0} \cdot x = \underbrace{\left(\mathfrak{z}W_{\mathfrak{z}}(x) + u\mathfrak{z}\omega W_{\mathfrak{z\omega}}(x)) + F(x) - E\right)}_{P_1}\)

\(P_0 \cdot x \stackrel{?}{=} P_1\)

Recursive Proof Verification

  • Suppose we have \(n\) Plonk proofs \((\pi_1, \pi_2, \dots, \pi_n)\) with verification equations:

\(P_0^{(i)} \cdot x \stackrel{?}{=} P_1^{(i)} \quad \forall i \in [n]\)

\(\left(P_0^{(1)} + qP_0^{(2)} + \dots +  q^{n-1}P_0^{(n)}\right) \cdot x \stackrel{?}{=} \left(P_1^{(1)} + qP_1^{(2)} \dots + q^{n-1}P_1^{(n)}\right)\)

  • A single pairing is \(\approx 300\) times costlier than a scalar multiplication
  • Using recursive verification, we can verify any number of Plonk proofs using a single pairing
  • We refer to this as rolling up of Plonk proofs
  • The circuit size presents a practical constraint on the number of proofs to be rolled up
  • Failure of the recursive check implies at least one of the \(n\) proofs is wrong

Rollup Proofs

  • A single rollup proof can roll up upto \(M_r = 28\) transaction proofs
  • The rollup circuit verifies the correctness of a rollup proof 
  • Suppose we have \(m_r \le M_r (\le N_r = 32)\) real transaction proofs

\(\big(\underbrace{\pi_1, \pi_2, \dots, \pi_{m_r}}_{`\text{real' inner proofs}}, \ \underbrace{\pi_{m_r+1}, \dots, \pi_{M_r}}_{\text{inner padding proofs}}, \ \underbrace{\pi_{M_r+1}, \dots, \pi_{N_r}}_{\text{outer padding proofs}} \big) \in |\pi_{\text{plonk}}|^{N_r}\)

  • Public inputs to the rollup circuit consist of
    • Rollup proof data: \(9 + n_a\) elements in \(\mathbb{F}_p^{9+n_a}\)
    • Inner proof data: \(12 \times N_r\) elements in \(\mathbb{F}_p^{12 \times N_r}\)
    • Recursive proof data: \(P_0, P_1 \in \mathbb{G}_1\)

\(\pi_i : \left([a]_1, [b]_1, \dots, \bar{s}_{\sigma_1}\right), \left(\text{id}, v_{\text{pi}}, v_{\text{po}}, a, \mathfrak{C}_1, \mathfrak{C}_2, \mathfrak{N}_1, \mathfrak{N}_1, \mathcal{A}^{in}, \mathcal{A}^{out}, D_{\pi}, f \right)\)

Rollup Circuit

  • For every inner proof \(\pi_i\), rollup circuit processes the following:
    • Check if the proof type is valid, i.e. join-split or account
    • Update recursive elements \(P_0 \mathrel{{+}{=}} u_iP_0^{(i)}, P_1 \mathrel{{+}{=}}  u_iP_1^{(i)} \)
    • Ensure public input is \(\vec{0}\) for padding proofs
    • Insert output note commitments to the data tree \(\mathbb{D} \leftarrow \mathfrak{C}^{(i)}_1, \mathfrak{C}^{(i)}_2\)
    • Insert input note nullifiers to the nullifier tree \(\mathbb{N} \leftarrow \mathfrak{N}^{(i)}_1, \mathfrak{N}^{(i)}_2\)
    • Check validity of the data root \(D_{\pi}^{(i)} \in \mathbb{R}\) at a given index
    • Accumulate transaction fees \(f_a \mathrel{{+}{=}} f^{(i)}, \ a \in \{0,1,2,3\}\)

State Updates

  • Rollup circuit checks if the trees were updated correctly
  • Subtree insertion is used for updates to the data tree \(\mathbb{D}\)
  • Old data root \(D_{\text{old}},\)

State Updates

  • Rollup circuit checks if the trees were updated correctly
  • Subtree insertion is used for updates to the data tree \(\mathbb{D}\)
  • Old data root \(D_{\text{old}},\)

\(\mathfrak{C}^{(1)}_1\)

 \(\mathfrak{C}^{(1)}_2\) 

 \(\mathfrak{C}^{(2)}_1\) 

 \(\mathfrak{C}^{(2)}_2\) 

new data root \(D_{\text{new}}\)

State Updates

  • With subtree root \(S\) and the hash path \(\text{HP}_{\text{data}} = \{h_1, h_2\}\), we can verify:

\(\mathfrak{C}^{(1)}_1\)

 \(\mathfrak{C}^{(1)}_2\) 

 \(\mathfrak{C}^{(2)}_1\) 

 \(\mathfrak{C}^{(2)}_2\) 

\(D\)

\(S\)

\(h_1\)

\(h_2\)

\(D_{\text{new}} \stackrel{?}{=} H\left(h_2, H(S, h_1)\right)\)

  • Also, verify if new leaves were indeed empty: \(D_{\text{old}} \stackrel{?}{=} H\left(h_2, H(S_{\text{empty}}, h_1)\right)\)

State Updates

  • Private inputs to rollup circuit for data tree: \(D_{\text{old}}, D_{\text{new}}, \text{HP}_{\text{data}} \)
  • Nullifier tree does not support batch updates, we need multiple updates
  • To insert \(\mathfrak{N}^{(i)}_1,\) first check if the corresponding leaf is empty.

\(\phi\)

\(N_{\text{old}}^{(i)} \stackrel{?}{=} H\left(0, \mathfrak{N}_1^{(i)}, \ \text{HP}_{\text{null}}^{(i)}\right)\)

State Updates

  • Private inputs to rollup circuit for data tree: \(D_{\text{old}}, D_{\text{new}}, \text{HP}_{\text{data}} \)
  • Nullifier tree does not support batch updates, we need multiple updates
  • To insert \(\mathfrak{N}^{(i)}_1,\) first check if the corresponding leaf is empty.

\(\mathfrak{N}^{(i)}_1\)

Then,

\(N_{\text{new}}^{(i)} \stackrel{?}{=} H\left(1, \mathfrak{N}^{(i)}_1, \ \text{HP}_{\text{null}}^{(i)}\right)\)

State Updates

  • Private inputs to rollup circuit for data tree: \(D_{\text{old}}, D_{\text{new}}, \text{HP}_{\text{data}} \)
  • Private inputs to the rollup circuit for nullifier tree: \(\left\{N_{\text{old}}^{(i)}, N_{\text{new}}^{(i)}, \text{HP}_{\text{null}}^{(i)}\right\}_{i \in [m_r]}\)
  • The root tree keeps track of the data tree roots

\(D^{(i)}_{\pi}\)

\(R \stackrel{?}{=} H\left(D^{(i)}_{\pi}, \text{idx}_{\text{root}}, \ \text{HP}_{\text{root}}^{(i)}\right) \ \forall i \in [m_r]\)

State Updates

  • Private inputs to rollup circuit for data tree: \(D_{\text{old}}, D_{\text{new}}, \text{HP}_{\text{data}} \)
  • Private inputs to the rollup circuit for nullifier tree: \(\left\{N_{\text{old}}^{(i)}, N_{\text{new}}^{(i)}, \text{HP}_{\text{null}}^{(i)}\right\}_{i \in [m_r]}\)
  • Private inputs to the rollup circuit for root tree: \(R, \ \left\{\text{idx}_{\text{root}}^{(i)}, \text{HP}^{(i)}_{\text{root}}\right\}_{i \in [m_r]}\)
  • The rollup proof data (i.e. public inputs) becomes:

\(\left\{ \text{id}, N_r, D_{\text{old}}, D_{\text{new}}, N_{\text{old}}, N_{\text{new}}, R, f_0, f_1, f_2, f_3 \right\} \)

Root Rollup Proof

  • A root rollup proof rolls up multiple rollup proofs
  • An example of the structure of a root rollup proof:
    • For a tx rollup, we have: \(M_r = 7, \ N_r := 2^{\lceil \text{log}_2(M_r) \rceil} = 8\)
    • Actual number of tx proofs in each tx rollup: \(m_{r,1} = 7, m_{r,2}=3, m_{r,3}=5\)
    • In a root rollup, no of tx rollups: \(M = 3\), rollup size \(N := 2^{\lceil \text{log}_2 (M \cdot N_r)\rceil}\)

\(\Pi_{r,1}\)

\(\pi_{1} \ \ \pi_2 \ \ \pi_3 \ \ \pi_4 \ \ \pi_5 \ \ \pi_6 \ \ \pi_7 \ \ \pi_8\)

\(\Pi_{r,2}\)

\(\pi_{1} \ \ \pi_2 \ \ \pi_3 \ \ \pi_4 \ \ \pi_5 \ \ \pi_6 \ \ \pi_7 \ \ \pi_8\)

\(\Pi_{r,4}\)

\(\pi_{1} \ \ \pi_2 \ \ \pi_3 \ \ \pi_4 \ \ \pi_5 \ \ \pi_6 \ \ \pi_7 \ \ \pi_8\)

\(\Pi_{\text{root}}\)

\(\Pi_{r,3}\)

\(\pi_{1} \ \ \pi_2 \ \ \pi_3 \ \ \pi_4 \ \ \pi_5 \ \ \pi_6 \ \ \pi_7 \ \ \pi_8\)

Root Rollup Circuit

  • The root rollup circuit validates multiple tx rollup proofs
  • For each tx rollup proof \(\Pi_{i}\), we check:
    • Update recursive elements \(P_0 \mathrel{{+}{=}} u_iP_0^{(i)}, P_1 \mathrel{{+}{=}}  u_iP_1^{(i)} \)
    • Ensure public inputs are \(\vec{0}\) for padding tx rollups
    • Check if the rollup id is consistent, i.e. all tx rollups must have same id 
    • Check if the data tree start index is correct: \(\text{idx}_{\text{start}, i} \stackrel{?}{=} \text{idx}_{\text{start}, 0} + (2N_r + i)\)
    • Ensure that the tree roots are consistent before trees are updated

\(D^{(i)}_{\text{old}} = D_{\text{old}}, \ N^{(i)}_{\text{old}} = N_{\text{old}}, \ R^{(i)}_{\text{old}} = R_{\text{old}}\)

  • The data and nullifier tree roots are updated to be:

\(D_{\text{new}} = D^{(i)}_{\text{old}}, \ N_{\text{new}} = N_{\text{old}}\big|_{i = \text{last real tx rollup}}\)

  • Finally, check root tree: \(R_{\text{old}} \stackrel{?}{=} H(0, \text{id}+1, \text{HP}_{\text{root}}), \ R_{\text{new}} \stackrel{?}{=} H(D_{\text{new}}, \text{id}+1, \text{HP}_{\text{root}})\)

Summary

  • In this part, we covered:
    • How multiple plonk proofs are aggregated for faster verification
    • How the rollup and root rollup circuits work
    • How does a rollup provider perform state updates
  • In future, we might cover: 
    • How lookup tables work in practice
    • How range constraints work
    • How Shplonk commitment scheme works

Aztec Circuits II

By Suyash Bagad

Aztec Circuits II

A very short presentation explaining how circuits in Aztec are structured.

  • 117