See CODING_STANDARD.md** before contributing!
The aztec3-circuits
circuits folder contains circuits and related C++ code (cpp/
) for Aztec3 along with Typescript wrappers (ts/
).
- cmake >= 3.24
- Ninja (used by the presets as the default generator)
- clang16
- clang-format
- wasm-opt (part of the Binaryen toolkit)
- wasmtime for running tests in a wasm environment
- 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-packages.git
cd circuits
git submodule update --init --recursive
cd cpp
./bootstrap.sh
Here is an example of rapidly rebuilding and running all tests for x86_64
:
./bootstrap.sh
./scripts/run_tests_local x86_64 glob
WARNING: the
x86_64
(andwasm
used below) as well as the keywordglob
MUST BE LOWERCASE!
Here is an example of rapidly rebuilding and running only the abis tests for wasm
:
./bootstrap.sh aztec3_circuits_abis_tests
./scripts/run_tests_local wasm aztec3_circuits_abis_tests
Note: to run wasm tests you must first follow the instructions here to install
wasmtime
.
You can choose which tests will run via a gtest filter. This one below runs only tests that omit the string '.circuit':
./scripts/run_tests_local wasm aztec3_circuits_abis_tests -*.circuit*
Here's a list of the tests currently available (conveniently combined with the command to build, then execute them on x86_64
, for easy copy-pasta):
-
aztec3_circuits_abis_tests
./bootstrap.sh aztec3_circuits_abis_tests && ./scripts/run_tests_local x86_64 aztec3_circuits_abis_tests
-
aztec3_circuits_apps_tests
./bootstrap.sh aztec3_circuits_apps_tests && ./scripts/run_tests_local x86_64 aztec3_circuits_apps_tests
-
aztec3_circuits_kernel_tests
./bootstrap.sh aztec3_circuits_kernel_tests && ./scripts/run_tests_local x86_64 aztec3_circuits_kernel_tests
-
aztec3_circuits_recursion_tests
./bootstrap.sh aztec3_circuits_recursion_tests && ./scripts/run_tests_local x86_64 aztec3_circuits_recursion_tests
-
aztec3_circuits_rollup_tests
./bootstrap.sh aztec3_circuits_rollup_tests && ./scripts/run_tests_local x86_64 aztec3_circuits_rollup_tests
You can also run tests in docker. This is useful for replicating CI failures that you can't replicate with your standard local environment.
To build and run all tests in an x86_64
docker image:
./bootstrap.sh
./scripts/build_run_tests_docker_local 1 x86_64 glob
You can choose wasm
instead of x86_64
. You can also specify individual test executables instead of glob
and can use gtest filters exactly as described for run_tests_local
.
At this time, it is common to run wasm tests with the filter
-*.circuit*
as there are circuit issues in wasm.
The
build_run_tests_docker_local
script builds the chosen docker image (x86_64
orwasm
) and then launches a container from that image to run therun_tests_local
script (used above).
You can generate coverage reports for your tests. To build and run coverage on all tests:
./bootstrap.sh
./scripts/run_coverage
Producing coverage reports is computationally intensive
You can select a specific test suite to run coverage on by supplying it as an argument to the run_coverage
. For example, to only compile and produce
./bootstrap.sh
./scripts/run_coverage aztec3_circuits_abis_tests
Toggles
Running with the CLEAN
environment variable set will delete the existing build-coverage
folder.
Running with the CLEAR_COV
environment variable will delete any existing lcov.info
file.
Once a report has been generated, you can view them within the build-coverage
folder in html format. If you ran coverage with any tests in mind, the report will exist in a folder prefixed with its name, otherwise they can be found in one labelled all_tests.
It may be useful to view coverage information from within vscode. The ./scripts/run_coverage
will produce an lcov.info
file that should automatically be picked up by the coverage-gutters
vscode extension.
WARNING: to debug in WASM (to use the
-g
option towasmtime
) you will unfortunately need to revert towasmtime
version1.0.0
until this bug is fixed. To install that version, remove the~/.wasmtime
directory and runcurl https://wasmtime.dev/install.sh -sSf | bash /dev/stdin --version v1.0.0
- Make sure you have the recommended plugins installed
- Open the command pallete (
Ctrl+Shift+P
)Cmd+Shift+P
on Macs
- Type and select "Extensions: Show Recommended Extensions"
- Install any plugins shown not already installed
- Open the command pallete (
- Configure CMake for whichever preset you'd like to use
- Open the command pallete (
Ctrl+Shift+P
) - Type and select "CMake: Select Configure Preset"
- Choose a debug preset such as:
- "Debugging build with Clang-16"
- "Debugging build for WASM"
- Redo this step later to switch between Clang-16/native and wasm
- Open the command pallete (
- Go to the "Run and Debug" panel
- Button (usually on left) that looks like a play button with a bug
- Or
Ctrl+Shift+D
- Select the proper launch option at the top of the "Run and Debug" panel
- "Launch native"
- "Launch in WASM"
- Select the test executable to debug
- Open the command pallete (
Ctrl+Shift+P
) - Type and select "CMake: Set Debug Target"
- Select executable to debug like
aztec3_circuits_abis_tests
- Open the command pallete (
- Check output for progress
- [OPTIONAL] change
gtest_filter
args to filter specific test cases- In
circuits.code-workspace
'slaunch->configurations-><native or wasm>
- Don't commit these changes
- In
- [OPTIONAL] set breakpoints in C++ files
Note: redo steps 3-5 to switch between debugging Clang-16/native test executables and WASM test executables
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 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 public kernel circuit performs many of the functions of the private kernel circuit but for public functions. Public functions differ from public functions in that they they are exeucted by the sequencer and can modify the public state tree. Like private functions, public functions can include calls to other public functions and these calls are validated within the circuit.
The public kernel circuit in Aztec 3 is implemented in circuit/kernel/public
directory. The input and output interface of the private kernel circuit is written in circuits/abis/public_kernel
.
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...