Skip to main content

circuits

Motivation

In Aztec, circuits come from two sources:

  1. Core protocol circuits
  2. User-written circuits (written as Noir Contracts and deployed to the network)

This page focusses on the core protocol circuits. These circuits check that the rules of the protocol are being adhered to.

When a function in an Ethereum smart contract is executed, the EVM performs checks to ensure that Ethereum's transaction rules are being adhered-to correctly. Stuff like:

  • "Does this tx have a valid signature?"
  • "Does this contract address contain deployed code?
  • "Does this function exist in the requested contract?"
  • "Is this function allowed to call this function?"
  • "How much gas has been paid, and how much is left?"
  • "Is this contract allowed to read/update this state variable?"
  • "Perform the state read / state write"
  • "Execute these opcodes"

All of these checks have a computational cost, for which users are charged gas.

Many existing L2s move this logic off-chain, as a way of saving their users gas costs, and as a way of increasing tx throughput.

zk-Rollups, in particular, move these checks off-chain by encoding them in zk-S(N/T)ARK circuits. Rather than paying a committee of Ethereum validators to perform the above kinds of checks, L2 users instead pay a sequencer to execute these checks via the circuit(s) which encode them. The sequencer can then generate a zero-knowledge proof of having executed the circuit(s) correctly, which they can send to a rollup contract on Ethereum. The Ethereum validators then verify this zk-S(N/T)ARK. It often turns out to be much cheaper for users to pay the sequencer to do this, than to execute a smart contract on Ethereum directly.

But there's a problem.

Ethereum (and the EVM) doesn't have a notion of privacy.

  • There is no notion of a private state variable in the EVM.
  • There is no notion of a private function in the EVM.

So users cannot keep private state variables' values private from Ethereum validators, nor from existing (non-private) L2 sequencers. Nor can users keep the details of which function they've executed private from validators or sequencers.

How does Aztec add privacy?

Well, we just encode extra checks in our zk-Rollup's zk-SNARK circuits! These extra checks introduce the notions of private state and private functions, and enforce privacy-preserving constraints on every transaction being sent to the network.

In other words, since neither the EVM nor other rollups have rules for how to preserve privacy, we've written a new rollup which introduces such rules, and we've written circuits to enforce those rules!

What kind of extra rules / checks does a rollup need, to enforce notions of private states and private functions? Stuff like:

  • "Perform state reads and writes using new tree structures which prevent tx linkability" (see trees and notes & nullifiers).
  • "Hide which function was just executed, by wrapping it in a zk-snark"
  • "Hide all functions which were executed as part of this tx's stack trace, by wrapping the whole tx in a zk-snark"

Aztec core protocol circuits

So what kinds of core protocol circuits does Aztec have?

Private Kernel Circuit

This circuit is executed by the user, on their own device. This is to ensure private inputs to the circuit remain private!

  • Verifies a user's signature.
  • Hides the user's address.
  • Verifies an app's proof - i.e. a proof which has been output after the execution of some function in a Noir Contract.
  • Performs private state reads and writes.
  • Exposes (forwards) the following data to the next recursive circuit:
    • new note hashes;
    • new nullifiers;
    • new messages to L1 contracts;
    • private call stacks: hashes representing calls to other private functions;
    • public call stacks: hashes representing calls to other public functions;
    • events;
    • all data accumulated by all previous private kernel circuit recursions of this tx;
  • Hides which private function has been executed, by performing a zero-knowledge proof of membership against the contract tree.
  • Ensures the entire stack trace of private functions (for a particular tx) adheres to function execution rules.
  • Verifies a previous 'Private Kernel Proof', recursively, when verifying transactions which are composed of many private function calls.
  • Optionally can deploy a new private contract.

Note: This is the only core protocol circuit which actually needs to be "zk" (zero knowledge)!!! That's because this is the only core protocol circuit which handles private data, and hence the only circuit for which proofs must not leak any information about witnesses! (The private data being handled includes: details of the Noir Contract function which has been executed; the address of the user who executed the function; the intelligible inputs and outputs of that function). This is a really interesting point. Most so-called "zk-Rollups" do not make use of this "zero knowledge" property. Their snarks are "snarks"; with no need for zero-knowledge, because they don't seek privacy; they only seek the 'succinct' computation-compression properties of snarks. Aztec's "zk-Rollup" actually makes use of "zero knowledge" snarks. That's why we sometimes call it a "zk-zk-Rollup", or "actual zk-Rollup".

Public Kernel Circuit

This circuit is executed by the Sequencer, since only the Sequencer knows the current state of the public data tree at any time. The Sequencer might choose to delegate proof generation to the Prover pool.

  • Exposes (forwards) the following data to the next recursive circuit:
    • all data accumulated by all previous private kernel circuit recursions of this tx;
    • all data accumulated by all previous public kernel circuit recursions of this tx;
    • new public state read requests;
    • new public state transition requests;
    • new messages to L1 contracts;
    • new messages to private L2 functions;
    • public call stacks: hashes representing calls to other public functions;
    • events;
  • Verifies a previous 'Private/Public Kernel Proof', recursively, when verifying transactions which are composed of many function calls.
  • Ensures the entire stack trace of public functions (for a particular tx) adheres to function execution rules.

Rollup Circuits

The primary purpose of the Rollup Circuits is to 'squish' all of the many thousands of transactions in a rollup into a single SNARK, which can then be efficiently (cheaply) verified on Ethereum.

These circuits are executed by the Sequencer, since their primary role is to order transactions. The Sequencer might choose to delegate proof generation to the Prover pool.

The way we 'squish' all this data is in a 'binary tree of proofs' topology.

Example: If there were 16 txs in a rollup, we'd arrange the 16 kernel proofs into 8 pairs and merge each pair into a single proof (using zk-snark recursion techniques), resulting in 8 output proofs. We'd then arrange those 8 proofs into pairs and again merge each pair into a single proof, resulting in 4 output proofs. And so on until we'd be left with a single proof, which represents the correctness of the original 16 txs.

This 'binary tree of proofs' topology allows proof generation to be greatly parallelised across prover instances. Each layer of the tree can be computed in parallel. Or alternatively, subtrees can be coordinated to be computed in parallel.

Note: 'binary tree of proofs' is actually an oversimlification. The Rollup Circuits are designed so that a Sequencer can actually deviate from a neat, symmetrical tree, for the purposes of efficiency, and instead sometimes create wonky trees.

Some of the Rollup Circuits also do some protocol checks and computations, for efficiency reasons. We might rearrange which circuit does what computation, as we discover opportunities for efficiency.

Base Rollup Circuit

The Base Rollup Circuit takes two kernel circuit proofs (from two distinct transactions) and compresses them into a single snark.

For both transactions, it:

  • Performs public state read membership checks.
  • Updates the public data tree in line with the requested state transitions.
  • Checks that the nullifiers haven't previously been inserted into the indexed nullifier tree.
  • Batch-inserts new nullifiers into the nullifier tree.
  • Batch-inserts new commitments into the private data tree
  • Batch-inserts any new contract deployments into the contract tree.
  • Hashes all the new nullifiers, commitments, public state transitions, and new contract deployments, to prevent exponential growth in public inputs with each later layer of recurtion.
  • Verifies the input kernel proof.

Merge Rollup Circuit

The Merge Rollup Circuit takes two Base or Merge circuit proofs and compresses them into a single snark.

It:

  • Hashes the two input hashes into a single output hash.
  • Verifies the two input base or merge proofs.

Root Rollup Circuit

The Root Rollup Circuit takes two Base or Merge circuit proofs and compresses them into a single snark.

It:

  • Hashes the two input hashes into a single output hash.
  • Verifies the two input base or merge proofs.

Squisher Circuits

We haven't fully specced these out, as Honk and Goblin Plonk schemes are still being improved! But we'll need some extra circuit(s) to squish a Honk proof (as produced by the Root Rollup Circuit) into a Standard Plonk or Fflonk proof, for cheap verification on Ethereum.