From e99a882fcc69041a34ecd7febafe46d661b76094 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 30 Jan 2024 15:46:43 -0300 Subject: [PATCH] docs: Update contract deployment section in YP (#4290) Update contract deployment section in YP --------- Co-authored-by: iAmMichaelConnor --- cspell.json | 7 + .../diversified-and-stealth.md | 4 - .../docs/addresses-and-keys/specification.md | 47 +++- .../docs/contract-deployment/classes.md | 223 +++++++++++++++--- .../docs/contract-deployment/instances.md | 177 ++++++++++---- 5 files changed, 363 insertions(+), 95 deletions(-) diff --git a/cspell.json b/cspell.json index fe0e7cd0497..c9298ca6640 100644 --- a/cspell.json +++ b/cspell.json @@ -118,6 +118,9 @@ "memdown", "memfs", "Merkle", + "merkleization", + "merkleized", + "merkleizing", "messagebox", "mimc", "mktemp", @@ -140,6 +143,7 @@ "noirup", "nullifer", "offchain", + "onchain", "otterscan", "outdir", "overlayfs", @@ -148,6 +152,7 @@ "parallelizable", "Pedersen", "permissionless", + "permissionlessly", "persistable", "pids", "pkgs", @@ -156,6 +161,7 @@ "pnat", "Pokeable", "preauthenticated", + "precompute", "preimage", "preimages", "prestat", @@ -216,6 +222,7 @@ "typecheck", "typegen", "typeparam", + "undeployed", "unexclude", "unexcluded", "unprefixed", diff --git a/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md b/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md index 7e2e849243b..3da4713d275 100644 --- a/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md +++ b/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md @@ -30,12 +30,8 @@ contract DiversifiedAccount return address_preimage.deployer_address ``` - - Given the contract does not require initialization since it has no constructor, it can be used by its owner without being actually deployed, which reduces the setup cost. - - ## Discarded Approaches An alternative approach was to introduce a new type of call, a diversified call, that would allow the caller to impersonate any address they can derive from their own, for an enshrined derivation mechanism. Account contracts could use this opcode, as opposed to a regular call, to issue calls on behalf on their diversified and stealth addresses. However, this approach failed to account for calls made back to the account contracts, in particular authwit checks. It also required protocol changes, introducing a new type of call which could be difficult to reason about, and increased attack surface. The only benefit over the approach chosen is that it would require one less extra function call to hop from the user's main account contract to the diversified or stealth one. diff --git a/yellow-paper/docs/addresses-and-keys/specification.md b/yellow-paper/docs/addresses-and-keys/specification.md index 64d3c10468a..d04e3f2aa76 100644 --- a/yellow-paper/docs/addresses-and-keys/specification.md +++ b/yellow-paper/docs/addresses-and-keys/specification.md @@ -334,15 +334,44 @@ An address is computed as the hash of the following fields: | Field | Type | Description | |----------|----------|----------| -| version | u8 | Version identifier. Initially one, bumped for any changes to the contract instance struct. | -| deployer_address | AztecAddress | Address of the deployer for this instance. | -| salt | Field | User-generated pseudorandom value for uniqueness. | -| contract_class_id | Field | Identifier of the contract class for this instance. | -| contract_args_hash | Field | Hash of the arguments to the constructor. | -| portal_contract_address | EthereumAddress | Optional address of the L1 portal contract. | -| public_keys | PublicKeys | Optional struct of public keys used for encryption and nullifying by this contract. | - -The `PublicKeys` struct can vary depending on the format of keys used by the address, but it is suggested it includes the master keys defined above: $\Npkm$, $\Tpkm$, $\Ivpkm$, $\Ovpkm$. +| `salt` | `Field` | User-generated pseudorandom value for uniqueness. | +| `deployer` | `AztecAddress` | Optional address of the deployer of the contract. | +| `contract_class_id` | `Field` | Identifier of the contract class for this instance. | +| `initialization_hash` | `Field` | Hash of the selector and arguments to the constructor. | +| `portal_contract_address` | `EthereumAddress` | Address of the L1 portal contract, zero if none. | +| `public_keys_hash` | `Field` | Hash of the struct of public keys used for encryption and nullifying by this contract, zero if no public keys. | + +Storing these fields in the address preimage allows any part of the protocol to check them by recomputing the hash and verifying that the address matches. Examples of these checks are: +- Sending an encrypted note to an undeployed account, which requires the sender app to check the recipient's public key given their address. This scenario also requires the recipient to share with the sender their public key and rest of preimage. +- Having the kernel circuit verify that the code executed at a given address matches the one from the class. +- Asserting that the initialization hash matches the function call in the contract constructor. +- Checking the portal contract address when sending a cross-chain message. + +:::warning +We may remove the `portal_contract_address` as a first-class citizen. +::: + +The hashing scheme for the address should then ensure that checks that are more frequent can be done cheaply, and that data shared out of band is kept manageable. We define the hash to be computed as follows: + +``` +salted_initialization_hash = pedersen([salt, initialization_hash, deployer as Field, portal_contract_address as Field], GENERATOR__SALTED_INITIALIZATION_HASH) +partial_address = pedersen([contract_class_id, salted_initialization_hash], GENERATOR__CONTRACT_PARTIAL_ADDRESS_V1) +address = pedersen([public_keys_hash, partial_address], GENERATOR__CONTRACT_ADDRESS_V1) +``` + +The `public_keys` array can vary depending on the format of keys used by the address, but it is suggested it includes the master keys defined above: $\Npkm$, $\Tpkm$, $\Ivpkm$, $\Ovpkm$. A suggested hashing is: +``` +public_keys_hash = pedersen([ + nullifier_pubkey.x, nullifier_pubkey.y, + tagging_pubkey.x, tagging_pubkey.y, + incoming_view_pubkey.x, incoming_view_pubkey.y, + outgoing_view_pubkey.x, outgoing_view_pubkey.y +], GENERATOR__PUBLIC_KEYS) +``` + +This recommended hash format is compatible with the [encryption precompiles](./precompiles.md#encryption-and-tagging-precompiles) initially defined in the protocol and advertised in the canonical [registry](../private-message-delivery/registry.md) for private message delivery. An address that chooses to use a different format for its keys will not be compatible with apps that rely on the registry for note encryption. Nevertheless, new precompiles introduced in future versions of the protocol could use different public keys formats. + + ## Derive siloed keys diff --git a/yellow-paper/docs/contract-deployment/classes.md b/yellow-paper/docs/contract-deployment/classes.md index 33f14c27100..7069d497479 100644 --- a/yellow-paper/docs/contract-deployment/classes.md +++ b/yellow-paper/docs/contract-deployment/classes.md @@ -1,10 +1,10 @@ # Contract classes -A contract class is a collection of related unconstrained, private, and public functions. Contract classes don't have state, they just define code. +A contract class is a collection of state variable declarations, and related unconstrained, private, and public functions. Contract classes don't have any initialized state, they just define code. A contract class cannot be called; only a contract instance can be called. ## Rationale -Contract classes simplify the process of reusing code by enshrining implementations as a first-class citizen at the protocol. Given multiple contract instances that rely on the same class, the class needs to be declared only once, reducing the deployment cost for all contract instances. Classes also simplify the process of upgradeability. Classes decouple state from code, making it easier for an instance to switch to different code while retaining its state. +Contract classes simplify the process of reusing code by enshrining implementations as a first-class citizen at the protocol. Given multiple [contract instances](./instances.md) that rely on the same class, the class needs to be declared only once, reducing the deployment cost for all contract instances. Classes also simplify the process of upgradeability; classes decouple state from code, making it easier for an instance to switch to different code while retaining its state. :::info Read the following discussions for additional context: @@ -21,56 +21,207 @@ The structure of a contract class is defined as: | Field | Type | Description | |----------|----------|----------| -| version | u8 | Version identifier. Initially one, bumped for any changes to the contract class struct. | -| registerer_address | AztecAddress | Address of the canonical contract used for registering this class. | -| artifact_hash | Field | Hash of the entire contract artifact, including compiler information, proving backend, and all generated ACIR and Brillig. The specification of this hash is left to the compiler and not enforced by the protocol. | -| constructor_function | PrivateFunction | PublicFunction | Constructor for instances of this class. | -| private_functions | PrivateFunction[] | List of private functions. | -| public_functions | PublicFunction[] | List of public functions. | -| unconstrained_functions | UnconstrainedFunction[] | List of unconstrained functions. | +| `version` | `u8` | Version identifier. Initially one, bumped for any changes to the contract class struct. | +| `artifact_hash` | `Field` | Hash of the contract artifact. The specification of this hash is not enforced by the protocol. Should include commitments to unconstrained code and compilation metadata. Intended to be used by clients to verify that an off-chain fetched artifact matches a registered class. | +| `private_functions` | [`PrivateFunction[]`](#private-function) | List of individual private functions, constructors included. | +| `packed_public_bytecode` | `Field[]` | [Packed bytecode representation](../public-vm/bytecode-validation-circuit.md#packed-bytecode-representation) of the AVM bytecode for all public functions in this contract. | - - +Note that individual public functions are not first-class citizens in the protocol, so the contract entire public function bytecode is stored in the class, unlike private or unconstrained functions which are differentiated individual circuits recognized by the protocol. + +As for unconstrained functions, these are not used standalone within the protocol. They are either inlined within private functions, or called from a PXE as _getters_ for a contract. Calling from a private function to an unconstrained one in a different contract is forbidden, since the caller would have no guarantee of the code run by the callee. Considering this, unconstrained functions are not part of a contract class at the protocol level. + +### Class Identifier + +Also known as `contract_class_id`, the Class Identifier is both a unique identifier and a commitment to the struct contents. It is computed as: + +``` +private_function_leaves = private_functions.map(fn => pedersen([fn.function_selector as Field, fn.vk_hash], GENERATOR__FUNCTION_LEAF)) +private_functions_root = merkleize(private_function_leaves) +public_bytecode_commitment = calculate_commitment(packed_public_bytecode) +contract_class_id = pedersen([artifact_hash, private_functions_root, public_bytecode_commitment], GENERATOR__CLASS_IDENTIFIER_V1) +``` + +Private Functions are hashed into Function Leaves before being merkleized into a tree of height `FUNCTION_TREE_HEIGHT=5`. Empty leaves have value `0`. A poseidon hash is used. The AVM public bytecode commitment is calculated as [defined in the Public VM section](../public-vm/bytecode-validation-circuit.md#committed-representation). ### Private Function +The structure of each private function within the protocol is the following: + | Field | Type | Description | |----------|----------|----------| -| function_selector | u32 | Selector of the function. Calculated as the hash of the method name and arguments. | -| vk_hash | Field | Hash of the verification key associated to this private function. | -| salt | Field | Optional value for salting the bytecode of a function. | -| bytecode_hash | Field | Hash of the compiled function artifact, including all generated ACIR and Brillig. | -| optional_bytecode | Buffer | Optional bytecode for the function. Private function bytecode can be kept private if desired and only broadcasted to participants of the contract. | +| `function_selector` | `u32` | Selector of the function. Calculated as the hash of the method name and parameters. The specification of this is not enforced by the protocol. | +| `vk_hash` | `Field` | Hash of the verification key associated to this private function. | -### Public and Unconstrained Function +Note the lack of visibility modifiers. Internal functions are specified as a macro, and the check is handled at the application circuit level by verifying that the `context.msg_sender` equals the contract current address. - -| Field | Type | Description | -|----------|----------|----------| -| function_selector | u32 | Selector of the function. Calculated as the hash of the method name and arguments. | -| bytecode_hash | Field | Hash of the compiled function artifact, including all generated Brillig code. | -| bytecode | Buffer | Full bytecode for the function. Must hash to the `artifact_hash`. | +Also note the lack of commitment to the function compilation artifact. Even though a commitment to a function is required so that the PXE can verify the execution of correct unconstrained Brillig code embedded within private functions, this is handled entirely out of protocol. As such, PXEs are expected to verify it against the `artifact_hash` in the containing contract class. - +### Artifact Hash -### Class Identifier +Even though not enforced by the protocol, it is suggested for the `artifact_hash` to follow this general structure, in order to be compatible with the definition of the [`broadcast` function below](#broadcast). + +``` +private_functions_artifact_leaves = artifact.private_functions.map(fn => + sha256(fn.selector, fn.metadata_hash, sha256(fn.private_bytecode)) +) +private_functions_artifact_tree_root = merkleize(private_functions_artifact_leaves) + +unconstrained_functions_artifact_leaves = artifact.unconstrained_functions.map(fn => + sha256(fn.selector, fn.metadata_hash, sha256(fn.unconstrained_bytecode)) +) +unconstrained_functions_artifact_tree_root = merkleize(unconstrained_functions_artifact_leaves) + +artifact_hash = sha256( + private_functions_artifact_tree_root, + unconstrained_functions_artifact_tree_root, + artifact_metadata, +) +``` + +For the artifact hash merkleization and hashing is done using sha256, since it is computed and verified outside of circuits and does not need to be SNARK friendly. Fields are left-padded with zeros to 256 bits before being hashed. Function leaves are sorted in ascending order before being merkleized, according to their function selectors. Note that a tree with dynamic height is built instead of having a tree with a fixed height, since the merkleization is done out of a circuit. + +Bytecode for private functions is a mix of ACIR and Brillig, whereas unconstrained function bytecode is Brillig exclusively, as described on the [bytecode section](../bytecode/index.md). + +The metadata hash for each function is suggested to be computed as the sha256 of all JSON-serialized fields in the function struct of the compilation artifact, except for bytecode and debug symbols. The metadata is JSON-serialized using no spaces, and sorting ascending all keys in objects before serializing them. + +``` +function_metadata = omit(function, "bytecode", "debug_symbols") +function_metadata_hash = sha256(json_serialize(function_metadata)) +``` + +The artifact metadata stores all data that is not contained within the contract functions and is not debug specific. This includes the compiler version identifier, events interface, and name. Metadata is JSON-serialized in the same fashion as the function metadata. + +``` +artifact_metadata = omit(artifact, "functions", "file_map") +artifact_metadata_hash = sha256(json_serialize(artifact_metadata)) +``` + +### Versioning + +A contract class has an implicit `version` field that identifies the schema of the struct. This allows to change the shape of a contract class in future upgrades to the protocol to include new fields or change existing ones, while preserving the structure for existing classes. Supporting new types of contract classes would require introducing new kernel circuits, and a transaction proof may require switching between different kernel circuits depending on the version of the contract class used for each function call. + +Note that the version field is not directly used when computing the contract class id, but is implicit in the generator index. Bumping the version of a contract class struct would involve using a different generator index for computing its id. + +## Canonical Contract Class Registerer + +A contract class is registered by calling a private `register` function in a canonical `ContractClassRegisterer` contract, which will emit a Registration Nullifier. The Registration Nullifier is defined as the `contract_class_id` itself of the class being registered. Note that the Private Kernel circuit will [silo](../circuits/private-kernel-tail.md#siloing-values) this value with the contract address of the `ContractClassRegisterer`, effectively storing the hash of the `contract_class_id` and `ContractClassRegisterer` address in the nullifier tree. As such, proving that a given contract class has been registered requires checking existence of this siloed nullifier. + +The rationale for the Registerer contract is to guarantee that the public bytecode for a contract class is publicly available. This is a requirement for publicly [deploying a contract instance](./instances.md#publicly_deployed), which ultimately prevents a sequencer from executing a public function for which other nodes in the network may not have the code. + +### Register Function + +The `register` function receives the artifact hash, private functions tree root, and packed public bytecode of a `ContractClass` struct as [defined above](#structure), and performs the following steps: + +- Assert that `packed_public_bytecode` is valid according to the definition in the [Public VM section](../public-vm/bytecode-validation-circuit.md#packed-bytecode-representation). +- Computes the `contract_class_id` as [defined above](#class-identifier). +- Emits the resulting `contract_class_id` as a nullifier to prevent the same class from being registered again. +- Emits an unencrypted event `ContractClassRegistered` with the contents of the contract class. + +In pseudocode: + +``` +function register( + artifact_hash: Field, + private_functions_root: Field, + packed_public_bytecode: Field[], +) + assert is_valid_packed_public_bytecode(packed_public_bytecode) + + version = 1 + bytecode_commitment = calculate_commitment(packed_public_bytecode) + contract_class_id = pedersen([version, artifact_hash, private_functions_root, bytecode_commitment], GENERATOR__CLASS_IDENTIFIER) + + emit_nullifier contract_class_id + emit_unencrypted_event ContractClassRegistered(contract_class_id, version, artifact_hash, private_functions_root, packed_public_bytecode) +``` + +Upon seeing a `ContractClassRegistered` event in a mined transaction, nodes are expected to store the contract class, so they can retrieve it when executing a public function for that class. Note that a class may be used for deploying a contract within the same transaction in which it is registered. -The class identifier is computed by merkleizing the lists of private, public, and unconstrained functions separately, replacing the functions lists in the contract class struct with their respective tree roots, and then hashing the resulting struct. +Note that emitting the `contract_class_id` as a nullifier (the `contract_class_id_nullifier`), instead of as an entry in the note hashes tree, allows nodes to prove non-existence of a class. This is needed so a sequencer can provably revert a transaction that includes a call to an unregistered class. -## Registration +### Genesis -A contract class is registered by calling a private `register` function in a canonical `ClassRegisterer` contract. The `register` function receives a `ContractClass` struct as defined above, except for the `registerer_address`, and performs the following checks: +The `ContractClassRegisterer` will need to exist from the genesis of the Aztec Network, otherwise nothing will ever be publicly deployable to the network. The Class Nullifier for the `ContractClassRegisterer` contract will be pre-inserted into the genesis nullifier tree at leaf index `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_CLASS_REGISTERER_CLASS_ID_NULLIFIER=1`. The canonical instance will be deployed at `CONTRACT_CLASS_REGISTERER_ADDRESS=0x10000`, and its Deployment Nullifier will be inserted at `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_CLASS_REGISTERER_DEPLOYMENT_NULLIFIER=2`. -- `version` is 1 for the initial release -- `bytecode` for each function hashes to the `bytecode_hash` + -The `register` function then: + -- Emits the `ContractClass` struct as unencrypted events. -- Computes the class identifier as the hash of the `ContractClass` object. -- Emits the computed class identifier as a nullifier. +### Broadcast -Upon seeing a new contract class registration event in a mined transaction, nodes are expected to store the contract class, so they can retrieve it when executing a public function for that class. +The `ContractClassRegisterer` has an additional private `broadcast` functions that can be used for broadcasting on-chain the bytecode, both ACIR and Brillig, for private functions and unconstrained in the contract. Any user can freely call this function. Given that ACIR and Brillig [do not have a circuit-friendly commitment](../bytecode/index.md), it is left up to nodes to perform this check. -Note that emitting the class identifier as a nullifier, instead of as an entry in the note hashes tree, allows public functions to prove non-existence of a class, which is required to support public contract instance deployments. +Broadcasted contract artifacts that do not match with their corresponding `artifact_hash`, or that reference a `contract_class_id` that has not been broadcasted, can be safely discarded. + +``` +function broadcast_all_private_functions( + contract_class_id: Field, + artifact_metadata: Field, + unconstrained_functions_artifact_tree_root: Field, + functions: { selector: Field, metadata: Field, vk_hash: Field, bytecode: Field[] }[], +) + emit_unencrypted_event ClassPrivateFunctionsBroadcasted( + contract_class_id, + artifact_metadata, + unconstrained_functions_artifact_tree_root, + functions, + ) +``` + +``` +function broadcast_all_unconstrained_functions( + contract_class_id: Field, + artifact_metadata: Field, + private_functions_artifact_tree_root: Field, + functions:{ selector: Field, metadata: Field, bytecode: Field[] }[], +) + emit_unencrypted_event ClassUnconstrainedFunctionsBroadcasted( + contract_class_id, + artifact_metadata, + unconstrained_functions_artifact_tree_root, + functions, + ) +``` + + + +The broadcast functions are split between private and unconstrained to allow for private bytecode to be broadcasted, which is valuable for composability purposes, without having to also include unconstrained functions, which could be costly to do due to data broadcasting costs. Additionally, note that each broadcast function must include enough information to reconstruct the `artifact_hash` from the Contract Class, so nodes can verify it against the one previously registered. + +The `ContractClassRegisterer` contract also allows broadcasting individual functions, in case not every function needs to be put on-chain. This requires providing a Merkle membership proof for the function within its tree, that nodes can validate. + +``` +function broadcast_private_function( + contract_class_id: Field, + artifact_metadata: Field, + unconstrained_functions_artifact_tree_root: Field, + function_leaf_sibling_path: Field, + function: { selector: Field, metadata: Field, vk_hash: Field, bytecode: Field[] }, +) + emit_unencrypted_event ClassPrivateFunctionBroadcasted( + contract_class_id, + artifact_metadata, + unconstrained_functions_artifact_tree_root, + function_leaf_sibling_path, + function, + ) +``` + +``` +function broadcast_unconstrained_function( + contract_class_id: Field, + artifact_metadata: Field, + private_functions_artifact_tree_root: Field, + function_leaf_sibling_path: Field, + function: { selector: Field, metadata: Field, bytecode: Field[] }[], +) + emit_unencrypted_event ClassUnconstrainedFunctionBroadcasted( + contract_class_id, + artifact_metadata, + unconstrained_functions_artifact_tree_root, + function_leaf_sibling_path: Field, + function, + ) +``` + +It is strongly recommended for developers registering new classes to broadcast the code for `compute_hash_and_nullifier`, so any private message recipients have the code available to process their incoming notes. However, the `ContractClassRegisterer` contract does not enforce this during registration, since it is difficult to check the multiple signatures for `compute_hash_and_nullifier` as they may evolve over time to account for new note sizes. diff --git a/yellow-paper/docs/contract-deployment/instances.md b/yellow-paper/docs/contract-deployment/instances.md index 6bf602068be..9d5e333824d 100644 --- a/yellow-paper/docs/contract-deployment/instances.md +++ b/yellow-paper/docs/contract-deployment/instances.md @@ -1,90 +1,175 @@ # Contract instances -A contract instance is a concrete deployment of a [contract class](./classes.md). A contract instance has state, both private and public, as well as an address that acts as identifier, and can be called into. A contract instance always references a contract class, that dictates what code it executes when called. +A contract instance is a concrete deployment of a [contract class](./classes.md). A contract instance always references a contract class, which dictates what code it executes when called. A contract instance has state (both private and public), as well as an address that acts as its identifier. A contract instance can be called into. ## Requirements -- Users must be able to precompute the address of a given deployment. This allows users to precompute their account contract addresses and receive funds before interacting with the chain, and also allows counterfactual deployments. -- An address must be linked to its deployer address. This allows simple diversified and stealth account contracts. Related, a precomputed deployment may or may not be restricted to be executed by a given address. +- Users must be able to precompute the address of a given contract instance. This allows users to precompute their account contract addresses and receive funds before interacting with the chain, and also allows counterfactual deployments. +- An address must be linkable to its deployer address. This allows simple diversified and stealth account contracts. Related, a precomputed deployment may or may not be restricted to be executed by a given address. - A user calling into an address must be able to prove that it has not been deployed. This allows the executor to prove that a given call in a transaction is unsatisfiable and revert accordingly. - A user should be able to privately call into a contract without publicly deploying it. This allows private applications to deploy contracts without leaking information about their usage. -## Structure +## `ContractInstance` structure The structure of a contract instance is defined as: | Field | Type | Description | |----------|----------|----------| -| version | u8 | Version identifier. Initially one, bumped for any changes to the contract instance struct. | -| deployer_address | AztecAddress | Address of the canonical deployer contract that creates this instance. | -| salt | Field | User-generated pseudorandom value for uniqueness. | -| contract_class_id | Field | Identifier of the contract class for this instance. | -| contract_args_hash | Field | Hash of the arguments to the constructor. | -| portal_contract_address | EthereumAddress | Optional address of the L1 portal contract. | -| public_keys_hash | Field | Optional hash of the struct of public keys used for encryption and nullifying by this contract. | +| `version` | `u8` | Version identifier. Initially one, bumped for any changes to the contract instance struct. | +| `salt` | `Field` | User-generated pseudorandom value for uniqueness. | +| `deployer` | `AztecAddress` | Optional address of the deployer of the contract. | +| `contract_class_id` | `Field` | Identifier of the contract class for this instance. | +| `initialization_hash` | `Field` | Hash of the selector and arguments to the constructor. | +| `portal_contract_address` | `EthereumAddress` | Optional address of the L1 portal contract. | +| `public_keys_hash` | `Field` | Optional hash of the struct of public keys used for encryption and nullifying by this contract. | - + -## Address +### Versioning -The address of the contract instance is computed as the hash of all elements in the structure above, as defined in the addresses and keys section. This computation is deterministic, which allows any user to precompute the expected deployment address of their contract, including account contracts. +Contract instances have a `version` field that identifies the schema of the instance, allowing for changes to the struct in future versions of the protocol, same as the contract class [version](./classes.md#versioning). -## Statuses +### Address -A contract instance at a given address can be in any of the following statuses: +The address of the contract instance is computed as the hash of the elements in the structure above, as defined in [the addresses and keys section](../addresses-and-keys/specification.md#address). This computation is deterministic, which allows any user to precompute the expected deployment address of their contract, including account contracts. -- **Undeployed**: The instance has not yet been deployed. A user who knows the preimage of the address can issue a private call into the contract, as long as it does not require initialization. Public function calls to this address will fail. -- **Privately deployed**: The instance constructor has been executed, but its class identifier has not been broadcasted. A user who knows the preimage of the address can issue a private call into the contract. Public function calls to this address will fail. Private deployments are signalled by emitting an initialization nullifier when the constructor runs. -- **Publicly deployed**: The instance constructor has been executed, and the address preimage has been broadcasted. All function calls to the address, private or public, are valid. Public deployments are signalled by emitting a public deployment nullifier. +### Deployer - +The `deployer` address of a contract instance is used to restrict who can initialize the contract (ie call its constructor) and who can publicly deploy it. Note that neither of these checks are enforced by the protocol: the initialization is checked by the constructor itself, and the deployment by the `ContractInstanceDeployer` (described below). Furthermore, a contract class may choose to not enforce this restriction by removing the check from the constructor. -## Constructors +The `deployer` address can be set to zero to signal that anyone can initialize or publicly deploy an instance. -Contract constructors are not enshrined in the protocol, but handled at the application circuit level. A contract must satisfy the following requirements: +## Initialization -- The constructor must be invoked exactly once -- The constructor must be invoked with the arguments in the address preimage -- Functions that depend on contract initialization cannot be invoked until the constructor is run +A contract instance at a given address can be either Initialized or not. An address by default is not initialized, and it is considered to be Initialized once it emits an Initialization Nullifier, meaning it can only be initialized once. -These checks can be embedded in the application circuit itself. The constructor emits a standardized initialization nullifier when it is invoked, which prevents it from being called more than once. The constructor code must also check that the arguments hash it received matches the ones in the address preimage, supplied via an oracle call. All other functions in the contract must include a merkle membership proof for the nullifier, to prevent them from being called before the constructor is invoked. Note that a contract may choose to allow some functions to be called before initialization. +### Uninitialized -The checks listed above should not be manually implemented by a contract developer, but rather included as part of the Aztec macros for Noir. +The instance has not yet been initialized, meaning its constructor has not been called. This is the default state for any given address. A user who knows the preimage of the address can still issue a private call into a function in the contract, as long as that function does not assert that the contract has been initialized by checking the Initialization Nullifier. -Constructors may be either private or public functions. Contracts with private constructors can be privately or publicly deployed, contracts with public constructors can only be publicly deployed. +All public function calls to an Uninitialized address _must_ fail, since the Contract Class for it is not known to the network. If the Class is not known to the network, then an Aztec Node, whether it is the elected sequencer or a full node following the chain, may not be able to execute the bytecode for a public function call, which is undesirable. The failing of public function calls to Uninitialized addresses is enforced by having the Public Kernel Circuit check that the Deployment Nullifier for the instance has been emitted. -Removing constructors from the protocol itself simplifies the kernel circuit. Separating initialization from public deployment also allows to implement private deployments, since a private deployment is equivalent to just invoking the constructor function at a given address. +This state allows using a contract privately before it has been initialized or deployed, which is used in [diversified and stealth accounts](../addresses-and-keys/diversified-and-stealth.md). + +### Initialized + +An instance is Initialized when a constructor for the instance has been invoked, and the constructor has emitted the instance's Initialization Nullifier. All private functions that require the contract to be initialized by checking the existence of the Initialization Nullifier can now be called by any user who knows the address preimage. + +The Initialization Nullifier is defined as the contract address itself. Note that the nullifier later gets [siloed by the Private Kernel Circuit](../circuits/private-kernel-tail.md#siloing-values) before it gets broadcasted in a transaction. + +In this state, public functions must still fail, for the same reason as for Uninitialized instances. This state then allows using a contract privately before it has been publicly deployed, which is useful for working on private contracts between a small set of parties. + +:::warning +It may be the case that it is not possible to read a nullifier in the same transaction that it was emitted due to protocol limitations. That would lead to a contract not being callable in the same transaction as it is initialized. To work around this, we can emit an Initialization Commitment along with the Initialization Nullifier, which _can_ be read in the same transaction as it is emitted. If needed, the Initialization Commitment is defined exactly as the Initialization Nullifier. +::: + +### Constructors + +Contract constructors are not enshrined in the protocol, but handled at the application circuit level. Constructors are methods used for initializing a contract, either private or public, and a contract may have more than a single constructor. A contract must ensure the following requirements are met: + +- A contract may be initialized at most once +- A contract must be initialized using the method and arguments defined in its address preimage +- A contract must be initialized by its `deployer` (if it's non-zero) +- All functions that depend on contract initialization cannot be invoked until the contract is initialized + +These checks are embedded in the application circuits themselves. The constructor emits an Initialization Nullifier when it is invoked, which prevents it from being called more than once. The constructor code must also check that its own selector and the arguments for the call match the ones in the address preimage, which are supplied via an oracle call. + +All non-constructor functions in the contract should require a merkle membership proof for the Initialization Nullifier, to prevent them from being called before the constructor is invoked. Nevertheless, a contract may choose to allow some functions to be called before initialization, such as in the case of [Diversified and Stealth account contracts](../addresses-and-keys/diversified-and-stealth.md). + +Removing constructors from the protocol itself simplifies the kernel circuit, and decoupling Initialization from Public Deployments allows users to keep contract instances private if they wish to do so. ## Public Deployment -A new contract instance can be _publicly deployed_ by calling a `deploy` function in a canonical `Deployer` contract. This function receives the arguments for a `ContractInstance` struct as described above, except for the `deployer_address` which is the deployer's own address, and executes the following actions: +A Contract Instance is considered to be Publicly Deployed when it has been broadcasted to the network via a canonical `ContractInstanceDeployer` contract, which also emits a Deployment Nullifier associated to the deployed instance. A contract needs to be Publicly Deployed for any of its public functions to be called. Note that this last restriction makes Public Deployment a protocol-level concern, whereas Initialization is an application-level concern. + +The Deployment Nullifier is defined as the address of the contract being deployed. Note that it later gets [siloed](../circuits/private-kernel-tail.md#siloing-values) using the `ContractInstanceDeployer` address by the Kernel Circuit, so this nullifier is effectively the hash of the deployed contract address and the `ContractInstanceDeployer` address. + +Only in this state public function calls are valid. The Public Kernel Circuit validates that the Deployment Nullifier has been emitted by the `ContractInstanceDeployer` as part of its checks. Note that this requires hardcoding the address of an application-level contract in a protocol circuit. + +### Canonical Contract Instance Deployer + +A new contract instance can be _Publicly Deployed_ by calling a `deploy` function in a canonical `ContractInstanceDeployer` contract. This function receives the arguments for a `ContractInstance` struct as described [above](#contractinstance-structure): + +- Validates the referenced `contract_class_id` exists. This can be done via either a call to the `ClassRegisterer` contract, or by directly reading the corresponding nullifier. +- Set `deployer` to zero or `msg_sender` depending on whether the `universal_deploy` flag is set. +- Computes the resulting `new_contract_address`. +- Emits the resulting address as the Deployment Nullifier to signal the public deployment, so callers can prove that the contract has or has not been publicly deployed. +- Emits an unencrypted event `ContractInstanceDeployed` with the address preimage. + +The pseudocode for the process described above is the following: -- Validates the referenced `contract_class_id` exists. -- Mixes in the `msg_sender` with the user-provided `salt` by hashing them together, ensuring that the deployment is unique for the requester. -- Computes the resulting contract address. -- Emits the resulting address as a nullifier to signal the public deployment, so callers can prove that the contract has or has not been publicly deployed. -- Emits an unencrypted event with the address preimage, excluding the `deployer_address` which is already part of the log. -- Either proves the corresponding class identifier has no constructor, or calls the constructor with the preimage of the supplied `contract_args_hash`, or proves that the constructor nullifier has already been emitted so a previously privately-deployed contract can be publicly deployed. +``` +function deploy ( + salt: Field, + contract_class_id: Field, + initialization_hash: Field, + portal_contract_address: Field, + public_keys_hash: Field, + universal_deploy?: boolean, +) + assert nullifier_exists silo(contract_class_id, ContractClassRegisterer) + assert is_valid_eth_address(portal_contract_address) + + deployer = if universal_deploy then zero else msg_sender + version = 1 + address = compute_address(version, salt, deployer, contract_class_id, initialization_hash, portal_contract_address, public_keys_hash) -Upon seeing a new contract deployed event from the canonical deployer contract, nodes are expected to store the address and preimage, to verify executed code during public code execution as described in the next section. + emit_nullifier(address) -The `Deployer` contract provides two implementations of the `deploy` function: a private and a public one. Contracts with a private constructor are expected to use the former, and contracts with public constructors expected to use the latter. Contracts with no constructors or that have already been privately-deployed can use either. + emit_unencrypted_event ContractInstanceDeployed(address, version, salt, contract_class_id, initialization_hash, portal_contract_address, public_keys_hash) +``` -Additionally, the `Deployer` contract provides two `universal_deploy` functions, a private and a public one, with the same arguments as the `deploy` one, that just forwards the call to the `deploy` contract. This makes `msg_sender` in the `deploy` contract to be the `Deployer` contract itself, and allows for universal deployments that are semantically decoupled from their deployer, and can be permissionlessly invoked by any user who knows the address preimage. +Upon seeing a `ContractInstanceDeployed` event from the canonical `ContractInstanceDeployer` contract, nodes are expected to store the address and preimage, so they can verify executed code during public code execution as described in the next section. + +The `ContractInstanceDeployer` contract provides two implementations of the `deploy` function: a private and a public one. Contracts with a private constructor are expected to use the former, and contracts with public constructors expected to use the latter. Contracts that have already been privately Initialized can use either. + +### Genesis + +The `ContractInstanceDeployer` will need to exist from the genesis of the Aztec Network, otherwise nothing will ever be deployable to the network. The Class Nullifier for the `ContractInstanceDeployer` contract will be pre-inserted into the genesis nullifier tree at leaf index `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_INSTANCE_DEPLOYER_CLASS_ID_NULLIFIER=3`. The canonical instance will be deployed at `CONTRACT_INSTANCE_DEPLOYER_ADDRESS=0x10001`, and its Deployment Nullifier will be inserted at `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_INSTANCE_DEPLOYER_DEPLOYMENT_NULLIFIER=4`. + + + + ## Verification of Executed Code -The kernel circuit, both private and public, is responsible for verifying that the code loaded for a given function execution matches the expected one. This requires the following checks: +The Kernel Circuit, both private and public, is responsible for verifying that the code loaded for a given function execution matches the expected one. This requires the following checks: + +- The `contract_class_id` of the address called is the expected one, verified by hashing the address preimage that includes the `contract_class_id`. +- The [function selector](./classes.md#private-function) being executed is part of the `contract_class_id`, verified via a Merkle membership proof of the selector in the functions tree of the Contract Class. + +Specific to private functions: +- The hash of the `verification_key` matches the `vk_hash` defined in the corresponding [Private Function](./classes.md#private-function) for the Contract Class. + +Specific to public functions: +- The bytecode loaded by the [AVM](../public-vm/avm.md) for the contract matches the `bytecode_commitment` in the contract class, verified using the [bytecode validation circuit](../public-vm/bytecode-validation-circuit.md). +- The contract Deployment Nullifier has been emitted, or prove that it hasn't, in which case the transaction is expected to revert. This check is done via a merkle (non-)membership proof of the Deployment Nullifier. Note that a public function should be callable in the same transaction in which its contract Deployment Nullifier was emitted. + +Note that, since constructors are handled at the application level, the kernel circuit is not required to check the Initialization Nullifier before executing code. -- The contract class identifier of the address called is the expected one, verified by hashing the address preimage that includes the class id. -- The function identifier being executed is part of the class id, verified via a merkle membership proof. -- The function code executed matches the commitment in the function identifier, verified via a merkle membership proof and a bytecode commitment proof. +### Verifying Brillig in Private Functions -Note that, since constructors are handled at the application level, the kernel circuit is not required to check that a constructor has been run before executing code. +Private functions may have unconstrained code, inlined as Brillig bytecode. While unconstrained code, as it name implies, is not constrained within the protocol, a user PXE still needs a mechanism to verify that the code it has been delivered off-chain for a given function is correct. -The public kernel circuit, additionally, needs to check that the corresponding contract instance has been publicly deployed, or prove that it hasn't. This is done via a merkle (non-)membership proof of the public deployment nullifier. +This verification is done via the [contract class `artifact_hash`](./classes.md#structure), which contains a commitment to all bytecode in the contract. The PXE should receive the entire contract artifact, or at least the relevant sections to execute along with the commitments for the others to reconstruct the original `artifact_hash`, and verify that the resulting `artifact_hash` matches the one declared on-chain for the class of the contract being run. ## Discarded Approaches -Earlier versions of the protocol relied on a dedicated contract tree, which required dedicated kernel code to process deployments, which had to be enshrined as new outputs from the application circuits. By abstracting contract deployment and storing deployments as nullifiers, the interface between the application and kernel circuits is simplified, and the kernel circuit has fewer responsibilities. +### Contracts Tree + +Earlier versions of the protocol relied on a dedicated contract tree, which required dedicated kernel code to process deployments, which had to be enshrined as new outputs from the application circuits. By abstracting contract deployment and storing deployments as nullifiers, the interface between the application and kernel circuits is simplified, and the kernel circuit has far fewer responsibilities. Furthermore, multiple contract deployments within a single transaction are now possible. + +### Requiring initialization for Public Deployment + +An earlier version of this draft required contracts to be Initialized in order to be Publicly Deployed. While this was useful for removing the initialization check in public functions, it caused a mix of concerns where the `ContractInstanceDeployer` needed to read a nullifier emitted from another contract. It also coupled the `ContractInstanceDeployer` to the convention decided for Initialization Nullifiers, and forced every contract to have a constructor in order to be publicly deployed even if they didn't need one. Furthermore, it required public constructors to be called via the `ContractInstanceDeployer` only. + +Fully separating Initialization and Public Deployment leads to a cleaner `ContractInstanceDeployer`, and allows more flexibility to applications in handling their own initialization. The main downsides are that this opens the door for a contract to be simultaneously Publicly Deployed and Uninitialized, which is a state that does not seem to map to a valid use case. And it requires public functions to check the Initialization Nullifier on every call, which in the current approach is not needed as the presence of the Deployment Nullifier checked by the Public Kernel is enough of a guarantee that the contract was initialized. + +### Execute Initialization during Public Deployment only + +While it is appealing to allow a user to privately create a new contract instance and not reveal it to the world, we have not yet validated this use case. We could simplify deployment by relying on a single nullifier to track Initialization, and couple it with Public Deployment. Private functions can check initialization via the Deployment Nullifier emitted by the `ContractInstanceDeployer`. + +This approach requires that constructors are only invoked as part of Public Deployment, so constructors would require an additional check for `msg_sender` being the canonical `ContractInstanceDeployer`. Furthermore, to ensure that an instance constructor is properly run, the `ContractInstanceDeployer` would need to know the selector for the instance constructor, which now needs to be part of the Contract Class, re-enshrining it into the protocol. Last, being able to keep agreements (contracts) private among their parties is commonplace in the traditional world, so there is a compelling argument for keeping this requirement. + +Alternatively, we could remove constructor abstraction altogether, and have the Private Kernel Circuit check for the Deployment Nullifier, much like the Public Kernel Circuit does. However, this hurts Diversified and Stealth account contracts, which now require an explicit deployment and cannot be used directly. \ No newline at end of file