Building Blocks of Aztec

Aztec Study Club 6

16th March 2022

A

Z

T

E

C

A

Z

T

E

C

nonymous

ero-knowledge

ransactions

fficient

ommunication

with

  • What do we want to achieve?
    • Transaction details (sender, receiver, amount) must not be public
    • Nothing more than the validity of the transaction must be revealed
    • Transaction creation must not be computation intensive
    • Cost per transaction must be low
  • What tools do we need?
    • Encryption
    • Hashing
    • Data storage (to keep track of transactions)
    • Zero-knowledge proofs
    • Digital signatures

An Aztec Transaction

12
\textsf{zk}
\textsf{alice}
5
\textsf{zk}
\textsf{alice}
16
\textsf{zk}
\textsf{bob}
1
\textsf{zk}
\textsf{alice}
  • How do we achieve our goal?
    • Encrypt the note data: sender, receiver, amounts and asset type
    • Compress the usernames and note data (for efficient storage)
    • Track the compressed notes to avoid double spending
    • Use Plonk to generate a zero-knowledge proof
    • Publish the proof with only necessary data

Encryption

16
\textsf{zk}
\textsf{bob}
  • The new note created must be accessible by receiver
0\textsf{x}8ae\dots f0
\mathbb{ENC}
\textsf{alice} \leftrightarrow \textsf{bob}
\mathbb{DEC}
\textsf{bob} \leftrightarrow \textsf{alice}
16
\textsf{zk}
\textsf{bob}

Sent to Bob offline

Encryption key

Decryption key

  • The encrypted notes are not stored on chain
  • After decrypting the note, how can bob spend it?
    • The decrypted note must match the on-chain compressed note data 
    • How is the compressed data stored?

Recap: Hash Functions

\(\texttt{Input}\)

\(\texttt{Output}\)

\(\texttt{SHA3 }(\text{Keccak})\)

  • Hash functions have three properties
    • Fast to compute: \(y = H(x)\)
    • Collision resistance: \(H(x) = H(x') {\ \;\not\!\!\!\implies \ } x \neq x'\)
    • Pre-image resistance: \(y = H(x) {\ \;\not\!\!\!\implies \ } x\)
  • A modern GPU can compute \(\approx 29\times 10^7\) hashes per second
  • We must not use fast hash functions for password hashing

\(\text{SHA-}3(\hspace{2cm}) = \Big(\hspace{4.8cm}\Big)\)

\(\texttt{b27ensk=wh}\)

\(\texttt{b654e400924d2d43b0b49b6beb52cd96}\)

\(\texttt{c983e26536eb455f80e2ab7fe07827a8}\)

\(\texttt{2bd0650eae8e3e9bda13c067f08da778}\)

\(\texttt{9624f52e63757ce0db5da6940c0c74e1}\)

\(\texttt{tbsowsn293nsj}\)

\(\texttt{089f29913f16c3cea73116b3445d2244}\)

\(\texttt{97fea922c4c501f1cd965cfd921c1a4d}\)

\(\texttt{92528721816}\)

  • So why do we need hash functions in Aztec?
    • Compact representation of variable-length data

Recap: Hash Functions

Storage

12
\textsf{zk}
\textsf{alice}
5
\textsf{zk}
\textsf{alice}
16
\textsf{zk}
\textsf{bob}
1
\textsf{zk}
\textsf{alice}

Storage

12
\textsf{zk}
\textsf{alice}
5
\textsf{zk}
\textsf{alice}
16
\textsf{zk}
\textsf{bob}
1
\textsf{zk}
\textsf{alice}
H\Bigg[\hspace{1.9cm}\Bigg]
H\Bigg[\hspace{1.9cm}\Bigg]
H\Bigg[\hspace{1.9cm}\Bigg]
H\Bigg[\hspace{1.9cm}\Bigg]
= h_{\textsf{old}, 1}
= h_{\textsf{old}, 2}
= h_{\textsf{new}, 1}
= h_{\textsf{new}, 2}
  • Compressed note data (256 bits) is stored in a Merkle tree
  • We need to ensure the following:
    • Old notes \((h_{\textsf{old}, 1}, h_{\textsf{old}, 2})\) are valid notes
    • Old notes aren't already spent
    • Nullify the old notes so that nobody can spend them again
    • Add new notes \((h_{\textsf{new}, 1}, h_{\textsf{new}, 2})\) to the tree
  • If encryption also provides a fixed-length compressed output, why do we need hash function?
    • Its because hash functions are collision resistant!
    • For a note \(N\) and encryption key \(\textsf{ek}\):

Question

\(\mathbb{ENC}_{\textsf{ek}}(N) = \texttt{ciphertext}\)

\(\longleftarrow\mathbb{ENC}_{\textsf{ek'}}(N')\)

  • Some notation before we proceed:
    • Compressed note data is called as note commitments
    • \(\mathbb{D}\): Data tree stores note commitments
    • \(\mathbb{N}\): Nullifier tree to store spent notes

Merkle Trees

  • Note commitments are stored in \(\mathbb{D}\) while spent note nullifiers in \(\mathbb{N}\)
  • 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 Trees

  • Note commitments are stored in \(\mathbb{D}\) while spent note nullifiers in \(\mathbb{N}\)
  • 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 Trees

\(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 Trees

  • Indeed, \(h_1^3\) is succinct form of the files. How do we prove inclusion?
  • Only \(\left( H(f_6), h^1_4, h_1^2 \right)\) are enough to prove inclusion of \(f_5\)! Sister nodes!

\(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\)

Merkle Trees

  • For root \(h_1^{3}\), index \(\text{idx} = 5\), the Merkle proof is \(\left\{H(f_6), h_4^1, h_1^2\right\}\).

\(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\)

Data Tree

  • \(\mathbb{D}\) is size \(2^{32}\) Merkle tree which supports batch updates
  • Contains commitments to all account and value notes ever created in Aztec  
  • Suppose we wish to add \(\mathcal{A}_1, \mathcal{A}_2, \mathcal{V}_1, \mathcal{V}_2\) to \(\mathbb{D}\)

Data Tree

  • Old data root: \(D_{\text{old}},\)

\(\mathfrak{C}(\mathcal{A}_1)\)

\(\mathfrak{C}(\mathcal{A}_2)\)

\(\mathfrak{C}(\mathcal{V}_1)\)

\(\mathfrak{C}(\mathcal{V}_2)\)

\(D\)

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

Data Tree

  • Old data root: \(D_{\text{old}},\)
  • With subtree root \(S\) and the partial proof \(\{h_1, h_2\}\), we can verify:

\(\mathfrak{C}(\mathcal{A}_1)\)

\(\mathfrak{C}(\mathcal{A}_2)\)

\(\mathfrak{C}(\mathcal{V}_1)\)

\(\mathfrak{C}(\mathcal{V}_2)\)

\(D\)

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

\(S\)

\(h_1\)

\(h_2\)

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

Nullifier Tree

  • \(\mathbb{N}\) is size \(2^{256}\) sparse Merkle tree which supports non-membership proofs
  • To prove that a note is unspent, we need to give a non-membership proof
  • Suppose we have \(16\) leaf values \(\{A, B, \dots, P\}\) s.t. \(\text{idx}(A) = 1\) and so on
  • To prove \(J \notin \mathbb{T}\), a membership proof \((10, \phi, \pi_{\text{merkle}})\) suffices!

\(A\)

\(F\)

\(N\)

\(\phi\)

Nullifier Tree

  • \(\mathbb{N}\) is size \(2^{256}\) sparse Merkle tree which supports non-membership proofs
  • To prove that a note is unspent, we need to give a non-membership proof
  • A nullifier of a note is a computed as

\(A\)

\(F\)

\(N\)

\(\phi\)

\(\mathfrak{N}(\mathcal{V}) = H_B\left(H_P\left( \ \mathfrak{C}(\mathcal{V})_x, \ \text{idx}, \ \text{nk} \ \right)\right) \in \mathbb{F}_q, \ q \approx 2^{254}\)

Summary

  • We tried to understand the how following tools are used in Aztec
    • Hashing
    • Encryption
    • Merkle trees (data storage)
  • In the next part, we will cover:
    • High-level idea of a zero-knowledge proof
    • Intuitive explanation of Plonk
    • How proof aggregation saves cost per transaction
  • Read more about our merkle trees here 

Aztec in a Nutshell

By Suyash Bagad

Aztec in a Nutshell

High-level summary of how Aztec works.

  • 78