NOTE: This repository is now in read-only mode. Please use the circuits folder in https://github.com/AztecProtocol/aztec3-packages/ instead.
The aztec3-circuits
repository contains circuits and related C++ code (cpp/
) for Aztec3 along with Typescript wrappers (ts/
).
- Circuits project board
- [DO NOT EDIT] Diagram with pseudocode for circuits
- This diagram's contents are likely more up-to-date than code and than the other links below
- Kernel circuits
- Rollup circuits
- Outdated specs
- Explanation of Indexed Merkle Tree (for nullifiers)
Clone the repo and build the C++:
git clone [email protected]:AztecProtocol/aztec3-circuits.git
cd aztec3-circuits
git submodule update --init --recursive
cd cpp
./bootstrap.sh
Here is an example of rapidly rebuilding and running all tests in src/aztec3/circuits/abis/tests
:
cmake --preset clang15
cmake --build --preset clang15 --target aztec3_circuits_abis_tests
(cd build && ./bin/aztec3_circuits_abis_tests)
You can also limit it to run only specific test cases using a gtest filter:
(cd build && ./bin/aztec3_circuits_abis_tests --gtest_filter=*hash_tx_request*)
Here's a list of the tests currently available (conveniently combined with the command to build, then execute them, for easy copy-pasta):
-
aztec3_circuits_abis_tests
cmake --build --preset clang15 --target aztec3_circuits_abis_tests && (cd build && ./bin/aztec3_circuits_abis_tests --gtest_filter=*)
-
aztec3_circuits_apps_tests
cmake --build --preset clang15 --target aztec3_circuits_apps_tests && (cd build && ./bin/aztec3_circuits_apps_tests --gtest_filter=*)
-
aztec3_circuits_kernel_tests
cmake --build --preset clang15 --target aztec3_circuits_kernel_tests && (cd build && ./bin/aztec3_circuits_kernel_tests --gtest_filter=*)
-
aztec3_circuits_recursion_tests
cmake --build --preset clang15 --target aztec3_circuits_recursion_tests && (cd build && ./bin/aztec3_circuits_recursion_tests --gtest_filter=*)
-
aztec3_circuits_rollup_tests
cmake --build --preset clang15 --target aztec3_circuits_rollup_tests && (cd build && ./bin/aztec3_circuits_rollup_tests --gtest_filter=*)
This repository submodules barretenberg
as a C++ library containing proving systems and utilities at cpp/barretenberg/
.
The core Aztec 3 C++ code lives in cpp/src/aztec3/
, and is split into the following subdirectories/files:
constants.hpp
: top-level constants relevant to Aztec 3circuits
: circuits and their types and interfacesapps
: infrastructure and early prototypes for application circuits (more here)abis
: types, interfaces, and cbinds for representing/constructing outputs of application circuits that will be fed into kernel circuits (more here)kernel
: kernel circuits, their interfaces, and their testsrollup
: rollup circuits, their interfaces, and thier testsrecursion
: types and examples for aggregation of recursive proof objectsmock
: mock circuits
oracle
: used to fetch external information (like private data notes) and inject them as inputs into the circuit "Composer" during execution of circuit logic (more here)dbs
: database infrastructure (e.g. PrivateStateDb)
All typescript code was moved from here into aztec3-packages/yarn-project/circuits.js
.
The private kernel circuit validates that a particular private function was correctly executed by the user. Therefore, the private kernel circuit is going to be run on the user's device. A private function execution can involve calls to other private functions from the same contract or private functions from other contracts. Each call to another private function needs to be proven that the execution was correct. Therefore, each nested call to another private function will have its own circuit execution proof, and that proof must then be validated by the private kernel circuit. The proof generated by the private kernel circuit will be submitted to the transaction pool, from where rollup providers will include those private kernel proofs in their L2 blocks.
The private kernel circuit in Aztec 3 is implemented in circuit/kernel/private
directory. The input and output interface of the private kernel circuit is written in circuits/abis/private_kernel
.
See pseudocode in this diagram and the slides here for a deeper dive into the private kernel circuit.
The rollup providers (or sequencers - nomenclature is yet to be decided) pull up transactions from the pool to be rolled up. Each of these transactions is essentially a proof generated by the private kernel circuit including its public inputs. To accumulate several such transactions in a single L2 block, the rollup provider needs to aggregate the private kernel proofs. The base rollup circuit is meant to aggregate these private kernel proofs. In simple words, the rollup circuit validates that the private kernel circuit was correctly executed.
In our design, we allow the rollup providers to only aggregate two private kernel proofs at once. This would mean that if a rollup provider wishes to roll-up 1024 transactions in one L2 block, for example, he would need
:::info At a very high-level, the rollup circuits need to perform the following checks:
- Aggregate the inner proofs (most expensive part taking up appx 75% circuit size)
- Merkle membership checks (second most expensive part taking up appx 15% circuit size)
- Public input hashing using SHA-256 (third most expensive part, appx 10%, can blow up if you need to hash tons of public inputs)
We have a limit on the first point: you can aggregate a maximum of two proofs per rollup circuit. We still need to decide if we wish to put any limits on the second step. Mike has an idea of splitting up the Merkle membership checks across multiple rollup circuits so that all of the Merkle membership computation doesn't need to be performed by a single circuit. Similarly, we need to ensure that a rollup circuit doesn't need to hash huge amounts of data in one stage. :::
:::warning Note: For the first milestone, we do not include public function execution in our circuit design. :::
See pseudocode in this diagram for a deep dive into the Base Rollup Circuit's functionality.
The base rollup proofs further need to be validated if they were executed correctly. The merge rollup circuit is supposed to verify that the base rollup proofs are correct. In principle, the design of the merge rollup circuit would be very similar to the base rollup circuit except the change in the ABIs. As with the base rollup circuit, every merge rollup circuit validates two base rollup proofs.
Furthermore, we can use the same merge rollup circuit to verify two merge rollup proofs to further compress the proof validation. This leads to a formation of a tree-like structure to create an L2 block.
See pseudocode in this diagram for a deep dive into the Merge Rollup Circuit's functionality.
:::warning Note: We might not need the merge rollup circuit for the offsite but its anyway going to be very similar to the base rollup circuit. :::
The root rollup circuit is the final circuit execution layer before the proof is sent to L1 for on-chain verification. The root rollup circuit verifies that the final merge rollup circuit was correctly executed. The proof from the root rollup circuit is verified by the rollup contract on Ethereum. So effectively, verifying that one proof on-chain gives a final green flag to whatever number of transactions that were included in that particular L2 block.
It is interesting to note that the root rollup circuit takes one proof and outputs one proof. The reason we do this is to switch the types of the proof: i.e. ultra-plonk/honk to standard-plonk. This is because standard-plonk proofs are cheaper to verify on-chain (in terms of gas costs).
See pseudocode in this diagram for a deep dive into the Root Rollup Circuit's functionality.
The private kernel circuit is recursive in that it will perform validation of a single function call, and then recursively verify its previous iteration along with the next function call.
Below is a list of the private kernel circuit's high-level responsibilities:
- For the first iteration:
- [M1.1] Validate the signature of a signed tx object
- Validate the function in that tx object matches the one currently being processed
- For all subsequent iterations:
- Pop an item off of the kernel's dynamic callstack
- Validate that this function matches the one currently being processed
- Verify a 'previous' kernel circuit (mock for first iteration, always mocked for [M1.1])
- Verify the proof of execution for the function (or constructor [M1.1]) currently being processed
- [M1.1] If this is a contract deployment, check the contract deployment logic
- [After M1.1] Includes checks for private circuit execution logic
- [M1.1] Generate
contract_address
and its nullifier (inserted in later circuit) - [M1.1] Generate
new_contract_data
which is the contract tree leaf preimage - Copy the current function's callstack into kernel's dynamic callstack
- Validate the function data against
function_tree_root
- Includes a membership check of the function leaf
- Validate the contract data against
contract_tree_root
- Includes a membership check of the contract leaf
- Perform membership checks for commitments accessed by this function
- Collect new commitments, nullifiers, contracts, and messages to L1
- Add recursion byproducts to
aggregation_object
- TODO: L1 messages
- Section in progress...