[Proposal] Contract Deployment

See here for the latest contract creation proposal from Zac.

Just opening up a discussion thread on this topic.


From the hackmd:

Function Tree

Each contract’s function data is stored in a Merkle tree, where each leaf contains a hash of the following data:

  1. Function Signature
  2. bool isPrivate flag
  3. Verification Key Hash

Where the ‘function signature’ in this context would be something like the first 4 bytes of the hash of the abi encoding of the function.

This is a nice leaf, because we get the following:

  • A Noir++ contract can import another contract’s interface, and will be able to compute the function’s function signature using that interface (first 4 bytes of hash of abi encoding).
    • The Kernel Circuit will then be able to validate the existence of that function in this contract*.
  • The function tree can be kept nice and small; e.g. 32 leaves (or however many we think is a reasonable upper bound).

*Just a small note: at the time of deployment, we MUST ensure each function signature is unique, within the contract. Similarly, we must also ensure uniqueness of verification key hashes.


Question: Is the contract tree a sparse merkle tree as well as the public state tree? Or are the contract leaves on the public state tree?


Also, a note on the function signature. Is it a hard requirement that function selectors be 4 bytes? ABI compatibility with Solidity is already broken by using different underlying primitive types and function selector derivation. Because of this, I would advocate for a larger function selector size, ideally no less than 8 bytes. This is useful to reduce the odds of function selector collision within a single contract as well as reducing selector ambiguity in client-side tooling.


I believe the contract tree will not be sparse. It will be a standard merkle tree and will be append-only.

It is declared as such in code here


No hard requirement on function selector size. You make a good point and we will likely choose >4 bytes for this exact reason :+1:


There should come more info in here Trees | Aztec Docs, but the contracts tree is not a sparse tree. As @david-banks says, its an append-only tree to allow us to easily do batch insertions.


There have been some discussions on function selector lengths earlier; the main thoughts were around 8 bytes and longer. I think the main reason that it was not changed already was just that no one made the PR fixing the length everywhere, as it was discussed when everything was in different repos instead of mono.


I have a question: In doc tree, it says:

The contract tree contains information about every function of every contract deployed to the Aztec network. This allows the Kernel Circuits to validate that for every function execution request, the requested function actually belongs to a deployed contract.

How exactly does the Kernel Circuits to validate one function belongs to one deployed contract? IIUC, one circuit(pk/vk) for one function, how does contract work in this situation?


For reference, this kernel logic lives here in the common_contract_logic function which is called from both the initial and inner variations of the private kernel.

There are two relevant trees here:

  • Contract tree: contains one leaf for each contract
  • Function tree: contains one leaf for every function in a specific

When a user executes a private function, they will provide its proof of execution to the private kernel along with the some information about the function and contract they are calling:

  • Function info
    • For leaf computation: function_selector, vk_hash, acir_hash
    • For merkle root computation: leaf_index, sibling_path
    • Note that the function’s raw VK is provided as a private input to the kernel. It is then hashed in-circuit.
  • Contract info
    • Needed for leaf computation: storage_contract_address, portal_contract_address
    • Plus leaf_index, sibling_path

We basically want to ensure the following:

  1. The function’s proof is valid according to the VK provided as input
  2. The function called matches an existing leaf in that contract’s function tree
    • This also confirms that the VK used to verify the function’s proof matches this existing function leaf
  3. The contract called exists in the contracts tree

So how do we do this?
First off, we can verify the function proof using snark recursion and the VK provided as a private input the kernel. We compute a function tree leaf given the function_selector, vk_hash (computed in-circuit from the VK), and acir_hash. We can then use the provided leaf_index and sibling_path to compute the merkle root (function_tree_root) of this contract’s function tree.

Cool so now we have a function_tree_root, but how do we know it’s right? Well next we can compute a contract tree leaf using this function_tree_root along with contract address and portal address. We then use the provided leaf_index and sibling_path to compute the merkle root (contract_tree_root) of the contract tree.

Okay so now we have a contract_tree_root, but how do we know it’s right? Well we don’t… Not at this stage. The private kernel proves the numbered items above with an important exception; someone else needs to prove that this contract tree root matches a real historical root. That is the job of the rollup circuits.

By this point we know that the function and its vk matches a leaf in the contract’s function tree. We know that the function’s execution is valid according to that vk. And we know that the contract along with the root of this function tree match a leaf in some contract tree. We then put off the job of verifying that contract tree root until a later stage.


TLDR: A contract is basically a function tree (including VKs) along with an address that is used to silo storage accesses. We validate that a function belongs to a deployed contract by doing merkle membership proofs for the function in the contract’s function tree, and the contract in the contract tree.


Thanks for detailed reply, it makes much sense! Is acir_hash a commitment of deployed bytecode for function/contract?


That’s right! I’m not sure if it needs to be calculated in-circuit for private functions. We will need to perform some bytecode validation for public functions though since Sequencers and Provers are responsible for executing public function bytecode. @Mike may be able to provide some more insight here.