diff --git a/yellow-paper/docs/circuits/high-level-topology.md b/yellow-paper/docs/circuits/high-level-topology.md new file mode 100644 index 00000000000..e296d4bfa09 --- /dev/null +++ b/yellow-paper/docs/circuits/high-level-topology.md @@ -0,0 +1,136 @@ +# High Level Topology + +:::info Disclaimer +This is a draft. These requirements need to be considered by the wider team, and might change significantly before a mainnet release. +::: + +## Overview + +A transaction begins with a call to a private function, which may invoke nested calls to other private and public functions. The entire set of private function calls is executed in a secure environment, and their proofs are validated and aggregated by private kernel circuits. Meanwhile, any public function calls triggered from private functions will be enqueued. The proofs for these calls, along with those from the nested public function calls, are generated and processed through public kernel circuits in any entity possessing the correct contexts. + +Once all functions in a transaction are executed, the accumulated data is outputted from a tail circuit. These values are then inserted or updated to the trees within the base rollup circuit. The merge rollup circuit facilitates the merging of two rollup proofs. Repeating this merging process enables the inclusion of more transactions in a block. Finally, the root rollup circuit produces the final proof, which is subsequently submitted and validated onchain. + +To illustrate, consider a transaction involving the following functions, where circles depict private functions, and squares denote public functions: + +```mermaid +flowchart LR + f0([f0]) --> f1([f1]) + f0 --> f2([f2]) + f0 --> f3([f3]) + f1 -.-> F0 + F0 --> F1 + F0 --> F2 + F2 --> F3 + f3 --> f4([f4]) + f3 -.-> F4 + f3 --> f5([f5]) +``` + +This transaction contains 6 private functions (f0 to f5) and 5 public functions (F0 to F4), with `f0` being the entrypoint. The entire transaction is processed as follows: + +```mermaid +flowchart TB + subgraph Transaction A + subgraph Private Functions + f0([f0]) + f1([f1]) + f2([f2]) + f3([f3]) + f4([f4]) + f5([f5]) + end + subgraph Public Functions + F0 + F1 + F2 + F3 + F4 + end + end + subgraph Transaction C + init2(...) + tail2(Tail Private Kernel) + init2 -.-> tail2 + end + subgraph Transaction B + init1(...) + tail1(Tail Private Kernel) + init1 -.-> tail1 + end + subgraph Public Kernel + INIT0(Initial Public Kernel) + INNER0(Inner Public Kernel) + INNER1(Inner Public Kernel) + INNER2(Inner Public Kernel) + INNER3(Inner Public Kernel) + TAIL0(Tail Public Kernel) + INIT0 --> INNER0 + INNER0 --> INNER1 + INNER1 --> INNER2 + INNER2 --> INNER3 + INNER3 --> TAIL0 + end + subgraph Private Kernel + init0(Initial Private Kernel) + inner0(Inner Private Kernel) + inner1(Inner Private Kernel) + inner2(Inner Private Kernel) + reset0(Reset Private Kernel) + inner3(Inner Private Kernel) + inner4(Inner Private Kernel) + reset1(Reset Private Kernel) + tail0(Tail Private Kernel) + init0 --> inner0 + inner0 --> inner1 + inner1 --> inner2 + inner2 --> reset0 + reset0 --> inner3 + inner3 --> inner4 + inner4 --> reset1 + reset1 --> tail0 + end + f0 --> init0 + f1 --> inner0 + f2 --> inner1 + f3 --> inner2 + f4 --> inner3 + f5 --> inner4 + F0 --> INIT0 + F1 --> INNER0 + F2 --> INNER1 + F3 --> INNER2 + F4 --> INNER3 + subgraph Rollup + BR0(Base Rollup) + BR1(Base Rollup) + BR2(Base Rollup) + BR3(Base Rollup) + MR0(Merge Rollup) + MR1(Merge Rollup) + MR2(Merge Rollup) + MR3(Merge Rollup) + ROOT(Root Rollup) + end + tail0 --> INIT0 + TAIL0 --> BR0 + tail1 --> BR1 + tail2 --> BR2 + BR0 --> MR0 + BR1 --> MR0 + BR2 --> MR1 + BR3 --> MR1 + MR0 --> MR2 + MR1 --> MR2 + MR2 --> ROOT + MR3 --> ROOT +``` + +A few things to note: + +- A transaction always starts with an [initial private kernel circuit](./private-kernel-initial.md). +- An [inner private kernel circuit](./private-kernel-inner.md) won't be required if there is only one private function in a transaction. +- A [reset private kernel circuit](./private-kernel-reset.md) can be executed between two private kernel circuits to "reset" transient data. The reset process can be repeated as needed. +- Public functions are "enqueued" when invoked from a private function. Public kernel circuits will be executed after the completion of all private kernel iterations. +- A [base rollup circuit](../rollup-circuits/base_rollup.md) can accept either a [tail public kernel circuit](./public-kernel-tail.md), or a [tail private kernel circuit](./private-kernel-tail.md) in cases where no public functions are present in the transaction. +- A [merge rollup circuit](../rollup-circuits/merge_rollup.md) can merge two base rollup circuits or two merge rollup circuits. +- The final step is the execution of the [root rollup circuit](../rollup-circuits/root_rollup.md), which combines two base rollup circuits or two merge rollup circuits. diff --git a/yellow-paper/docs/circuits/private-function.md b/yellow-paper/docs/circuits/private-function.md index d55191d5590..3ae07a63742 100644 --- a/yellow-paper/docs/circuits/private-function.md +++ b/yellow-paper/docs/circuits/private-function.md @@ -6,9 +6,9 @@ This is a draft. These requirements need to be considered by the wider team, and ## Requirements -A private function circuit is a custom circuit tailored to the needs of a specific application. This circuit should be designed to handle private data processing while generating public inputs that safeguard the application and account's intentions without compromising sensitive information. +Private function circuits represent smart contract functions that modify the Aztec private state trees. They serve as untrusted, third-party code that is executed as part of evaluating an Aztec transaction. -The logic of this circuit is flexible, yet its public inputs must adhere to a specific format. +The logic of each private function circuit is tailored to the needs of a particular application or scenario, yet its public inputs must adhere to a specific format. This circuit should be designed to handle private data processing while generating public inputs that safeguard the application and account's intentions without compromising sensitive information. ## Private Inputs @@ -18,28 +18,69 @@ The private inputs of a private function circuit are customizable. The public inputs of a private function circuit will be incorporated into the private inputs of a private kernel circuit. Private kernel circuits leverage these public inputs, coupled with proof data and verification key from a private function circuit, to prove the correct execution of a private function. -It must adhere to the following format: - -| Field | Type | Description | -| ---------------------------------- | -------------------------- | ---------------------------------------------------------------------- | -| _call_context_ | _CallContext_ | Context of the call corresponding to this function execution. | -| _args_hash_ | _field_ | Hash of the function arguments. | -| _return_values_ | [_field_; C] | Return values of this function call. | -| _read_requests_ | [_ReadRequest_; C] | Requests to read a note in the note hash tree. | -| _note_hash_contexts_ | [_NoteHashContext_; C] | New note hashes created in this function call. | -| _nullifier_contexts_ | [_NullifierContext_; C] | New nullifiers created in this function call. | -| _l2_to_l1_msg_contexts_ | [_L2L1MessageContext; C] | New L2 to L1 messages created in this function call. | -| _new_contract_contexts_ | [_ContractDataContext_; C] | Data of contracts deployed in this function call. | -| _encrypted_logs_hash_ | [_field_; N] | Hash of the encrypted logs emitted in this function call. | -| _unencrypted_logs_hash_ | [_field_; N] | Hash of the unencrypted logs emitted in this function call. | -| _encrypted_log_preimages_length_ | [_field_; N] | Length of the encrypted log preimages emitted in this function call. | -| _unencrypted_log_preimages_length_ | [_field_; N] | Length of the unencrypted log preimages emitted in this function call. | -| _private_call_stack_hashes_ | [_field_; C] | Hashes of the private function calls initiated by this function. | -| _public_call_stack_hashes_ | [_field_; C] | Hashes of the public function calls initiated by this function. | -| _block_header_ | _BlockHeader_ | Information about the trees used for the transaction. | -| _chain_id_ | _field_ | Chain ID of the transaction. | -| _version_ | _field_ | Version of the transaction. | +The following format defines the ABI that is used by the private kernel circuit when processing private function public inputs: + +| Field | Type | Description | +| ---------------------------------- | ------------------------------------ | ---------------------------------------------------------------------- | +| _call_context_ | _[CallContext](#callcontext)_ | Context of the call corresponding to this function execution. | +| _args_hash_ | _field_ | Hash of the function arguments. | +| _return_values_ | [_field_; _C_] | Return values of this function call. | +| _read_requests_ | [_[ReadRequest](#readrequest)_; _C_] | Requests to read notes in the note hash tree. | +| _note_hashes_ | [_[NoteHash](#notehash)_; _C_] | New note hashes created in this function call. | +| _nullifiers_ | [_[Nullifier](#nullifier)_; _C_] | New nullifiers created in this function call. | +| _l2_to_l1_messages_ | [_field_; _C_] | New L2 to L1 messages created in this function call. | +| _encrypted_logs_hash_ | _field_ | Hash of the encrypted logs emitted in this function call. | +| _unencrypted_logs_hash_ | _field_ | Hash of the unencrypted logs emitted in this function call. | +| _encrypted_log_preimages_length_ | _field_ | Length of the encrypted log preimages emitted in this function call. | +| _unencrypted_log_preimages_length_ | _field_ | Length of the unencrypted log preimages emitted in this function call. | +| _private_call_stack_item_hashes_ | [_field_; _C_] | Hashes of the private function calls initiated by this function. | +| _public_call_stack_item_hashes_ | [_field_; _C_] | Hashes of the public function calls initiated by this function. | +| _block_header_ | _[BlockHeader](#blockheader)_ | Information about the trees used for the transaction. | +| _chain_id_ | _field_ | Chain ID of the transaction. | +| _version_ | _field_ | Version of the transaction. | > The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. -> The above **N**s represent the number of _field_ of a hash. Its value depends on the hash function chosen by the protocol. +## Types + +#### _CallContext_ + +| Field | Type | Description | +| -------------------------- | -------------- | ----------------------------------------------------------------------- | +| _msg_sender_ | _AztecAddress_ | Address of the caller contract. | +| _storage_contract_address_ | _AztecAddress_ | Address of the contract against which all state changes will be stored. | +| _portal_contract_address_ | _AztecAddress_ | Address of the portal contract to the storage contract. | +| _is_delegate_call_ | _bool_ | A flag indicating whether the call is a delegate call. | +| _is_static_call_ | _bool_ | A flag indicating whether the call is a static call. | + +#### _ReadRequest_ + +| Field | Type | Description | +| ----------- | ------- | -------------------------------------- | +| _note_hash_ | _field_ | Hash of the note to be read. | +| _counter_ | _field_ | Counter at which the request was made. | + +#### _NoteHash_ + +| Field | Type | Description | +| --------- | ------- | ------------------------------------------- | +| _value_ | _field_ | Hash of the note. | +| _counter_ | _field_ | Counter at which the note hash was created. | + +#### _Nullifier_ + +| Field | Type | Description | +| --------- | ------- | ------------------------------------------- | +| _value_ | _field_ | Value of the nullifier. | +| _counter_ | _field_ | Counter at which the nullifier was created. | + +#### _BlockHeader_ + +| Field | Type | Description | +| ----------------------------- | ------- | ----------------------------------------------------------------------------------------------- | +| _note_hash_tree_root_ | _field_ | Root of the note hash tree. | +| _nullifier_tree_root_ | _field_ | Root of the nullifier tree. | +| _l1_to_l2_messages_tree_root_ | _field_ | Root of the l1-to-l2 messages tree. | +| _public_data_tree_root_ | _field_ | Root of the public data tree. | +| _archive_tree_root_ | _field_ | Root of the state roots tree archived at the block prior to when the transaction was assembled. | +| _global_variables_hash_ | _field_ | Hash of the previous global variables. | diff --git a/yellow-paper/docs/circuits/private-kernel-initial.md b/yellow-paper/docs/circuits/private-kernel-initial.md index 71eeec5cb33..dc3231a2e25 100644 --- a/yellow-paper/docs/circuits/private-kernel-initial.md +++ b/yellow-paper/docs/circuits/private-kernel-initial.md @@ -12,247 +12,354 @@ In the **initial** kernel iteration, the process involves taking a transaction r #### Validating the correspondence of function call with caller's intent. -This entails ensuring that the following data from the private call aligns with the specifications in the transaction request: +This entails ensuring that the following from the _[private_call](#privatecall)_ aligns with the specifications in the _[transaction_request](#transactionrequest)_: -- Contract address. -- [Function data](#function_data). -- Function arguments. +- _contract_address_ +- _function_data_ +- _args_hash_: Hash of the function arguments. -> Although it's not enforced in the protocol, it is customary to provide a signature signed over the transaction request and verify it in the first function call. This practice guarantees that only the party possessing the key(s) can authorize a transaction with the exact transaction request. +> Although it's not enforced in the protocol, it is customary to provide a signature signed over the transaction request and verify it in the first function call. This practice guarantees that only the party possessing the key(s) can authorize a transaction with the exact transaction request on behalf of an account. #### Verifying the legitimacy of the function as the entrypoint. -- It must be a private function. -- It must not be an internal function. +For the _[function_data](#functiondata)_ in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem)_, the circuit verifies that: + +- It must be a private function: + - _`function_data.function_type == private`_ +- It must not be an internal function: + - _`function_data.is_internal == false`_ #### Ensuring the function call is the first call. -- It must not be a delegate call. -- It must not be a static call. +For the _[call_context](./private-function.md#callcontext)_ in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_, the circuit checks that: + +- It must not be a delegate call: + - _`call_context.is_delegate_call == false`_ +- It must not be a static call: + - _`call_context.is_static_call == false`_ #### Ensuring transaction uniqueness. -- It must emit the hash of the transaction request as the **first** nullifier. +It must emit the hash of the _[transaction_request](#transactionrequest)_ as the **first** nullifier. + +The hash is computed as: + +_`hash(origin, function_data.hash(), args_hash, tx_context.hash())`_ + +Where _function_data.hash()_ and _tx_context.hash()_ are the hashes of the serialized field elements. This nullifier serves multiple purposes: - Identifying a transaction. -- Preventing the signature of a transaction request from being reused in another transaction. -- Generating values that should be maintained within the transaction's scope. For example, it is utilized to compute the nonces for all the note hashes in a transaction. +- Non-malleability. Preventing the signature of a transaction request from being reused in another transaction. +- Generating values that should be maintained within the transaction's scope. For example, it is utilized to [compute the nonces](./private-kernel-tail.md#siloing-values) for all the note hashes in a transaction. > Note that the final transaction data is not deterministic for a given transaction request. The production of new notes, the destruction of notes, and various other values are likely to change based on the time and conditions when a transaction is being composed. However, the intricacies of implementation should not be a concern for the entity initiating the transaction. ### Processing Private Function Call -#### Ensuring the contract instance being called is deployed. +#### Ensuring the function being called exists in the contract. -It proves that the nullifier representing the contract exists in the contract tree. +With the following data provided from _[private_inputs](#private-inputs).[private_call](#privatecall)_: -This nullifier is the contract address siloed with the address of a precompiled deployment contract. +- _contract_address_ in _private_call.[call_stack_item](#privatecallstackitem)_. +- _contract_data_ +- _contract_class_data_ +- _function_data_ in _private_call.[call_stack_item](#privatecallstackitem)_. -#### Ensuring the function being called exists in the contract. +This circuit validates the existence of the function in the contract through the following checks: + +1. Verify that the _contract_address_ can be derived from the _contract_data_: -The contract address contains the contract class ID, which is a hash of the root of its function tree and additional values. This circuit leverages these characteristics to establish the validity of the function's association with the contract address. + Refer to the details [here](../contract-deployment/instances.md#address) for the process of computing the address for a contract instance. -Each leaf of the function tree is a hash representing a function. The preimage includes: +2. Verify that the _contract_data.contract_class_id_ can be derived from the given _contract_class_data_: -- Function data. -- Hash of the verification key. -- Hash of the function bytecode. + Refer to the details [here](../contract-deployment/classes.md#class-identifier) for the process of computing the _contract_class_id_. -To ensure the function's existence, the circuit executes the following steps: +3. Verify that _contract_class_data.private_functions_ includes the function being called: -1. Computes the hash of the verification key. -2. Calculates the function leaf: `hash(...function_data, vk_hash, bytecode_hash)` -3. Derives the function tree root with the leaf and the specified sibling path. -4. Computes the contract class ID using the function tree root and additional information. -5. Generates the contract address using the contract class ID and other relevant details. -6. Validates that the contract address matches the address specified in the private call data. + 1. Compute the hash of the verification key: + - _`vk_hash = hash(private_call.vk)`_ + 2. Compute the function leaf: + - _`hash(function_data.selector, vk_hash, private_call.bytecode_hash)`_ + 3. Perform a membership check on the function leaf, where: + - The index and sibling path are provided through _function_leaf_membership_witness_ within _[private_call](#privatecall)_. + - The root is _contract_class_data.private_functions_. #### Verifying the private function proof. -It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs of the private function circuit. +It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs of the [private function circuit](./private-function.md). #### Verifying the public inputs of the private function circuit. -It ensures the private function circuit's intention by checking the following: +It ensures the private function circuit's intention by checking the following in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_: -- The contract address for each non-empty item in the following arrays must equal the current contract address: - - Note hash contexts. - - Nullifier contexts. - - L2-to-L1 message contexts. - - Read requests. -- The portal contract address for each non-empty L2-to-L1 message context must equal the current portal contract address. -- If the new contract contexts array is not empty, the contract address must equal the precompiled deployment contract address. -- The historical data must match the one in the constant data. +- The _block_header_ must match the one in the _[constant_data](#constantdata)_. -> Ensuring the alignment of the contract addresses is crucial, as it is later used to silo the value and to establish associations with values within the same contract. - -#### Verifying the call requests. +#### Verifying the counters. -For both private and public call requests initiated in the current function call, it ensures that for each request at index _i_: +It verifies that each value listed below is associated with a legitimate counter. -- Its hash equals the value at index _i_ within the call request hashes array in private function circuit's public inputs. -- Its caller context is either empty or aligns with the call context of the current function call, including: - - _msg_sender_ - - Storage contract address. +1. For the _[call_stack_item](#privatecallstackitem)_: -> It is important to note that the caller context in a call request may be empty for standard calls. This precaution is crucial to prevent information leakage, particularly as revealing the _msg_sender_ to the public could pose security risks when calling a public function. + - The _counter_start_ must be 0. + - This check can be skipped for [inner private kernel circuit](./private-kernel-inner.md#verifying-the-counters). + - The _counter_end_ must be greater than the _counter_start_. -#### Verifying the counters. +2. For items in each ordered array in _[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_: -It verifies that each relevant value is associated with a legitimate counter. + - The _counter_ of the first item must be greater than the _counter_start_ of the _call_stack_item_. + - The _counter_ of each subsequent item must be greater than the _counter_ of the previous item. + - The _counter_ of the last item must be less than the _counter_end_ of the _call_stack_item_. -1. For the current call: + The ordered arrays include: - - The _counter_start_ must be 0. - - The _counter_end_ must be greater than the _counter_start_. + - _note_hashes_ + - _nullifiers_ + - _read_requests_ -2. For private call requests: +3. For the last _N_ non-empty items in the _private_call_requests_ in the _[transient_accumulated_data](#transientaccumulateddata)_: - The _counter_end_ of each request must be greater than its _counter_start_. - - The _counter_start_ of the first request must be greater than the _counter_start_ of the current call. - - The _counter_start_ of the second and subsequent requests must be greater than the _counter_end_ of the previous request. - - The _counter_end_ of the last request must be less than the _counter_end_ of the current call. + - The _counter_end_ of the first request must be less than the _counter_end_ of the _call_stack_item_. + - The _counter_end_ of the second and subsequent requests must be less than the _counter_start_ of the previous request. + - The _counter_start_ of the last request must be greater than the _counter_start_ of the _call_stack_item_. -3. For items in each ordered array created in the current call: + > _N_ is the number of non-zero hashes in the _private_call_stack_item_hashes_ in _[private_inputs](#private-inputs).[private_call](#privatecall).[public_inputs](./private-function.md#public-inputs)_. - - The counter of the first item much be greater than the _counter_start_ of the current call. - - The counter of each subsequent item much be greater than the counter of the previous item. - - The counter of the last item much be less than the _counter_end_ of the current call. +4. For the last _N_ non-empty items in the _public_call_requests_ in the _[transient_accumulated_data](#transientaccumulateddata)_: - The ordered arrays include: + - The _counter_start_ of the first request must be greater than the _counter_start_ of the _call_stack_item_. + - The _counter_start_ of each subsequent request must be greater than the _counter_start_ of the previous item. + - The _counter_start_ of the last item must be less than the _counter_end_ of the _call_stack_item_. + + > _N_ is the number of non-zero hashes in the _public_call_stack_item_hashes_ in _[private_inputs](#private-inputs).[private_call](#privatecall).[public_inputs](./private-function.md#public-inputs)_. - - Note hash contexts. - - Nullifier contexts. - - New contract contexts. - - Read requests. - - Public call requests. + > Note that the _counter_end_ of public call request is unknown at this point. Both counters will be [recalibrated](./public-kernel-initial.md#recalibrating-counters) in the initial public kernel circuit following the simulation of all public function calls. - > Note that _counter_start_ is used in the above steps for public call requests to ensure their correct ordering. At this point, the _counter_end_ of public call request is unknown. Both counters will be [recalibrated](./private-kernel-tail.md#recalibrating-counters) in the tail circuit following the simulation of all public function calls. +> Note that, for the initial private kernel circuit, all values within the _[transient_accumulated_data](#transientaccumulateddata)_ originate from the _[private_call](#privatecall)_. However, this process of counter verification is also applicable to the [inner private kernel circuit](./private-kernel-inner.md#verifying-the-counters), where the _transient_accumulated_data_ comprises values from previous iterations and the current function call. Therefor, only the last _N_ items need to be checked in the above operations. ### Validating Public Inputs #### Verifying the accumulated data. -It verifies that the following values align with those in the private call data: - -- Log hashes. -- Log lengths. +It verifies that all the values in the _[accumulated_data](#accumulateddata)_ align with the corresponding values in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_. #### Verifying the transient accumulated data. -1. It ensures that the following arrays match those in the private call data: +Within the _[public_inputs](#public-inputs)_, the _[transient_accumulated_data](#transientaccumulateddata)_ encapsulates values reflecting the operations conducted by the _private_call_. + +This circuit verifies that the values in _[private_inputs](#private-inputs).[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_ (_private_function_public_inputs_) are aggregated into the _public_inputs_ correctly: - - Note hash contexts. - - Nullifier contexts. - - L2-to-L1 message contexts. - - New contract contexts. - - Read requests. - - Public call requests. +1. Ensure that the specified values in the following arrays match those in the corresponding arrays in the _private_function_public_inputs_: -2. It checks that the following aligns with the array in the private call data, with items arranged in **reverse** order: + - _note_hash_contexts_ + - _value_, _counter_ + - _nullifier_contexts_ + - _value_, _counter_ + - _l2_to_l1_message_contexts_ + - _value_ + - _read_request_contexts_ + - _note_hash_, _counter_ + - _public_call_requests_ + - _hash_ - - Private call requests. +2. Check that the hashes in the _private_call_requests_ align with the values in the _private_call_stack_item_hashes_ in the _private_function_public_inputs_, but in **reverse** order. - > It's important that the call requests are arranged in reverse order to ensure they are executed in chronological order. This becomes particularly crucial when calling a contract deployed earlier within the same transaction. + > It's important that the call requests are arranged in reverse order to ensure they are executed in chronological order. -3. For the note hash contexts, it also verifies that each is associated with a nullifier counter, which is provided as a hint via the private inputs. The nullifier counter can be: +3. For each non-empty call request in both _private_call_requests_ and _public_call_requests_: + + - The _caller_contract_address_ equals the _contract_address_ in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem)_. + - The _caller_context_ is either empty or aligns with the values in the _call_context_ within _private_function_public_inputs_. + + > The caller context in a call request may be empty for standard calls. This precaution is crucial to prevent information leakage, particularly as revealing the _msg_sender_ of this private function when calling a public function could pose security risks. + +4. For each non-empty item in the following arrays, its _contract_address_ must equal the _storage_contract_address_ defined in _private_function_public_inputs.call_context_: + + - _note_hash_contexts_ + - _nullifier_contexts_ + - _l2_to_l1_message_contexts_ + - _read_request_contexts_ + + > Ensuring the alignment of the contract addresses is crucial, as it is later used to [silo the values](./private-kernel-tail.md#siloing-values) and to establish associations with values within the same contract. + +5. For each non-empty item in _l2_to_l1_message_contexts_, its _portal_contract_address_ must equal the _portal_contract_address_ defined in _private_function_public_inputs.call_context_. + +6. For each _note_hash_ in the _note_hash_contexts_, verify that it is associated with a _nullifier_counter_. The value of the _nullifier_counter_ can be: - Zero: if the note is not nullified in the same transaction. - - Greater than zero: if the note is nullified in the same transaction. - - This value must be greater than the counter of the note hash. + - Greater than _note_hash.counter_: if the note is nullified in the same transaction. - > Nullifier counters are used in the [reset private kernel circuit](./private-kernel-reset.md#verifying-read-requests) to ensure a read happens **before** a transient note is nullified. + > Nullifier counters are used in the [reset private kernel circuit](./private-kernel-reset.md#read-request-reset-private-kernel-circuit) to ensure a read happens **before** a transient note is nullified. > Zero can be used to indicate a non-existing transient nullifier, as this value can never serve as the counter of a nullifier. It corresponds to the _counter_start_ of the first function call. +> Note that the verification process outlined above is also applicable to the inner private kernel circuit. However, given that the _transient_accumulated_data_ for the inner private kernel circuit comprises both values from previous iterations and the _private_call_, the above process specifically targets the values stemming from the _private_call_. The inner kernel circuit performs an [extra check](./private-kernel-inner.md#verifying-the-transient-accumulated-data) to ensure that the _transient_accumulated_data_ also contains values from the previous iterations. + #### Verifying the constant data. It verifies that: -- The transaction context matches the one in the transaction request. - -> The historical data must align with the data used in the private function circuit, as verified [earlier](#verifying-the-public-inputs-of-the-private-function-circuit). +- The _tx_context_ in the _[constant_data](#constantdata)_ matches the _tx_context_ in the _[transaction_request](#transactionrequest)_. +- The _block_header_ must align with the one used in the private function circuit, as verified [earlier](#verifying-the-public-inputs-of-the-private-function-circuit). ## Private Inputs -### Transaction Request +### _TransactionRequest_ -A transaction request represents the caller's intent. It contains: +Data that represents the caller's intent. -- Sender's address. -- Function data: +| Field | Type | Description | +| --------------- | ------------------------------------------- | -------------------------------------------- | +| _origin_ | _AztecAddress_ | The Aztec address of the transaction sender. | +| _function_data_ | _[FunctionData](#functiondata)_ | Data of the function being called. | +| _args_hash_ | _field_ | Hash of the function arguments. | +| _tx_context_ | _[TransactionContext](#transactioncontext)_ | Information about the transaction. | - - Function selector. - - Function type (private/public/unconstrained). - - A flag indicating whether the function is an internal function. +### _PrivateCall_ -- Hash of the function arguments. -- Transaction context - - A flag indicating whether it is a fee paying transaction. - - A flag indicating whether it is a fee rebate transaction. - - Chain ID. - - Version of the transaction. +Data that holds details about the current private function call. -### Private Call Data +| Field | Type | Description | +| ---------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------- | +| _call_stack_item_ | _[PrivateCallStackItem](#privatecallstackitem)_ | Information about the current private function call. | +| _proof_ | _Proof_ | Proof of the private function circuit. | +| _vk_ | _VerificationKey_ | Verification key of the private function circuit. | +| _bytecode_hash_ | _field_ | Hash of the function bytecode. | +| _contract_data_ | _[ContractInstance](../contract-deployment/instances.md#structure)_ | Data of the contract instance being called. | +| _contract_class_data_ | _[ContractClassData](#contractclassdata)_ | Data of the contract class. | +| _function_leaf_membership_witness_ | _[MembershipWitness](#membershipwitness)_ | Membership witness for the function being called. | -The private call data holds details about the current private function call: +## Public Inputs -- Contract address. -- Function data. -- Private call requests. -- Public call requests. -- Private function circuit public inputs. -- Proof of the private function circuit. -- Verification key of the private function circuit. -- Hash of the function bytecode. +### _ConstantData_ -### Hints +Data that remains the same throughout the entire transaction. + +| Field | Type | Description | +| -------------- | -------------------------------------------------- | ------------------------------------------------------------- | +| _block_header_ | _[BlockHeader](./private-function.md#blockheader)_ | Roots of the trees at the time the transaction was assembled. | +| _tx_context_ | _[TransactionContext](#transactioncontext)_ | Context of the transaction. | -Data that aids in the verifications carried out in this circuit or later iterations: +### _AccumulatedData_ + +| Field | Type | Description | +| ---------------------------------- | ------- | ---------------------------------------------------- | +| _encrypted_logs_hash_ | _field_ | Hash of the accumulated encrypted logs. | +| _unencrypted_logs_hash_ | _field_ | Hash of the accumulated unencrypted logs. | +| _encrypted_log_preimages_length_ | _field_ | Length of the accumulated encrypted log preimages. | +| _unencrypted_log_preimages_length_ | _field_ | Length of the accumulated unencrypted log preimages. | -- Membership witness for the function leaf. -- Membership witness for the contract leaf. -- Transient note nullifier counters. +### _TransientAccumulatedData_ + +| Field | Type | Description | +| --------------------------- | ------------------------------------------------------ | ------------------------------------------------------ | +| _note_hash_contexts_ | [_[NoteHashContext](#notehashcontext)_; _C_] | Note hashes with extra data aiding verification. | +| _nullifier_contexts_ | [_[NullifierContext](#nullifiercontext)_; _C_] | Nullifiers with extra data aiding verification. | +| _l2_to_l1_message_contexts_ | [_[L2toL1MessageContext](#l2tol1messagecontext)_; _C_] | L2-to-l1 messages with extra data aiding verification. | +| _read_request_contexts_ | [_[ReadRequestContext](#readrequestcontext)_; _C_] | Requests to read notes in the note hash tree. | +| _private_call_requests_ | [_[CallRequest](#callrequest)_; _C_] | Requests to call private functions. | +| _public_call_requests_ | [_[CallRequest](#callrequest)_; _C_] | Requests to call publics functions. | -## Public Inputs +> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. + +## Types + +#### _FunctionData_ + +| Field | Type | Description | +| ------------------- | ---------------------------------- | --------------------------------------------------- | +| _function_selector_ | _u32_ | Selector of the function being called. | +| _function_type_ | private \| public \| unconstrained | Type of the function being called. | +| _is_internal_ | _bool_ | A flag indicating whether the function is internal. | + +#### _ContractClassData_ + +| Field | Type | Description | +| ------------------------- | -------------- | ------------------------------------------------------------------ | +| _version_ | _u8_ | Version identifier. | +| _registerer_address_ | _AztecAddress_ | Address of the canonical contract used for registering this class. | +| _artifact_hash_ | _field_ | Hash of the contract artifact. | +| _private_functions_ | _field_ | Merkle root of the private function tree. | +| _public_functions_ | _field_ | Merkle root of the public function tree. | +| _unconstrained_functions_ | _field_ | Merkle root of the unconstrained function tree. | + +#### _TransactionContext_ + +| Field | Type | Description | +| ---------- | ------------------------------------ | ---------------------------- | +| _tx_type_ | standard \| fee_paying \| fee_rebate | Type of the transaction. | +| _chain_id_ | _field_ | Chain ID of the transaction. | +| _version_ | _field_ | Version of the transaction. | + +#### _PrivateCallStackItem_ -The structure of this public inputs aligns with that of the [inner private kernel circuit](./private-kernel-inner.md) and the [reset private kernel circuit](./private-kernel-reset.md). +| Field | Type | Description | +| ------------------ | -------------------------------------------------------------------- | --------------------------------------------------------- | +| _contract_address_ | _AztecAddress_ | Address of the contract on which the function is invoked. | +| _function_data_ | _[FunctionData](#functiondata)_ | Data of the function being called. | +| _public_inputs_ | _[PrivateFunctionPublicInputs](./private-function.md#public-inputs)_ | Public inputs of the private function circuit. | +| _counter_start_ | _field_ | Counter at which the function call was initiated. | +| _counter_end_ | _field_ | Counter at which the function call ended. | -### Constant Data +#### _CallRequest_ -These are constants that remain the same throughout the entire transaction: +| Field | Type | Description | +| ----------------- | --------------------------------- | --------------------------------------------- | +| _hash_ | _field_ | Hash of the call stack item. | +| _caller_contract_ | _AztecAddress_ | Address of the contract calling the function. | +| _caller_context_ | _[CallerContext](#callercontext)_ | Context of the contract calling the function. | +| _counter_start_ | _field_ | Counter at which the call was initiated. | +| _counter_end_ | _field_ | Counter at which the call ended. | -- Historical data - representing the states of the block at which the transaction is constructed, including: - - Hash of the global variables. - - Roots of the trees: - - Note hash tree. - - Nullifier tree. - - Contract tree. - - L1-to-l2 message tree. - - Public data tree. -- Transaction context - - A flag indicating whether it is a fee paying transaction. - - A flag indicating whether it is a fee rebate transaction. - - Chain ID. - - Version of the transaction. +#### _CallerContext_ -### Accumulated Data +| Field | Type | Description | +| ------------------ | -------------- | ------------------------------------------------ | +| _msg_sender_ | _AztecAddress_ | Address of the caller contract. | +| _storage_contract_ | _AztecAddress_ | Storage contract address of the caller contract. | -It contains the result from the current function call: +#### _NoteHashContext_ -- Log hashes. -- Log lengths. +| Field | Type | Description | +| ------------------- | -------------- | -------------------------------------------------------- | +| _value_ | _field_ | Hash of the note. | +| _contract_address_ | _AztecAddress_ | Address of the contract the note was created. | +| _counter_ | _field_ | Counter at which the note hash was created. | +| _nullifier_counter_ | _field_ | Counter at which the nullifier for the note was created. | -### Transient Accumulated Data +#### _NullifierContext_ -It includes transient data accumulated during the execution of the transaction up to this point: +| Field | Type | Description | +| --------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------- | +| _value_ | _field_ | Value of the nullifier. | +| _contract_address_ | _AztecAddress_ | Address of the contract the nullifier was created. | +| _counter_ | _field_ | Counter at which the nullifier was created. | +| _nullified_note_hash_ | _field_ | The hash of the transient note the nullifier is created for. 0 if the nullifier does not associated with a transient note. | + +#### _L2toL1MessageContext_ + +| Field | Type | Description | +| ------------------------- | -------------- | ------------------------------------------------ | +| _value_ | _field_ | L2-to-l2 message. | +| _contract_address_ | _AztecAddress_ | Address of the contract the message was created. | +| _portal_contract_address_ | _AztecAddress_ | Address of the portal contract to the contract. | + +#### _ReadRequestContext_ + +| Field | Type | Description | +| ------------------ | -------------- | --------------------------------------------- | +| _note_hash_ | _field_ | Hash of the note to be read. | +| _contract_address_ | _AztecAddress_ | Address of the contract the request was made. | +| _counter_ | _field_ | Counter at which the request was made. | + +#### _MembershipWitness_ + +| Field | Type | Description | +| -------------- | ------------ | ------------------------------------- | +| _leaf_index_ | _field_ | Index of the leaf in the tree. | +| _sibling_path_ | [_field_; H] | Sibling path to the leaf in the tree. | -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. -- New contract contexts. -- Read requests. -- Private call requests. -- Public call requests. +> **H** represents the height of the tree. diff --git a/yellow-paper/docs/circuits/private-kernel-inner.md b/yellow-paper/docs/circuits/private-kernel-inner.md index 4b0f709d11c..4df1178f1cb 100644 --- a/yellow-paper/docs/circuits/private-kernel-inner.md +++ b/yellow-paper/docs/circuits/private-kernel-inner.md @@ -12,11 +12,11 @@ Each **inner** kernel iteration processes a private function call and the result #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs. +It verifies that the previous iteration was executed successfully with the provided proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. The preceding proof can be: -- [Initial private kernel proof](./private-kernel-initial.md) +- [Initial private kernel proof](./private-kernel-initial.md). - Inner private kernel proof. - [Reset private kernel proof](./private-kernel-reset.md). @@ -24,143 +24,83 @@ The previous proof and the proof for the current function call are verified usin ### Processing Private Function Call -#### Ensuring the contract instance being called is deployed. - -It proves that either of the following conditions is true: - -1. The nullifier representing the contract exists in the contract tree. - - - Specifically, this nullifier is the contract address siloed with the address of a precompiled deployment contract. - -2. The contract is listed in the new contract context within the public inputs. - - - The index of this contract in the new contracts array is supplied as a hint through private inputs. - - The counter of the new contract context must be less than the _counter_start_ of the current call. - #### Ensuring the function being called exists in the contract. -The contract address contains the contract class ID, which is a hash of the root of its function tree and additional values. This circuit leverages these characteristics to establish the validity of the function's association with the contract address. +This section follows the same [process](./private-kernel-initial.md#ensuring-the-function-being-called-exists-in-the-contract) as outlined in the initial private kernel circuit. -Each leaf of the function tree is a hash representing a function. The preimage includes: - -- Function data. -- Hash of the verification key. -- Hash of the function bytecode. +#### Ensuring the function is legitimate: -To ensure the function's existence, the circuit executes the following steps: +For the _[function_data](./private-kernel-initial.md#functiondata)_ in _[private_call](#privatecall).[call_stack_item](./private-kernel-initial.md#privatecallstackitem)_, this circuit verifies that: -1. Computes the hash of the verification key. -2. Calculates the function leaf: `hash(...function_data, vk_hash, bytecode_hash)` -3. Derives the function tree root with the leaf and the specified sibling path. -4. Computes the contract class ID using the function tree root and additional information. -5. Generates the contract address using the contract class ID and other relevant details. -6. Validates that the contract address matches the address specified in the private call data. +- It must be a private function: + - _`function_data.function_type == private`_ -#### Ensuring the function is legitimate: +#### Ensuring the current call matches the call request. -- It must be a private function. +The top item in the _private_call_requests_ of the _[previous_kernel](#previouskernel)_ must pertain to the current function call. -#### Ensuring the current call matches the call request. +This circuit will: -The top item in the previous iteration's private call requests must pertain to the current function call. +1. Pop the request from the stack: -This circuit will pop the request from the stack, comparing the hash with that of the current function call. + - _`call_request = previous_kernel.public_inputs.transient_accumulated_data.private_call_requests.pop()`_ -The preimage of the hash contains: +2. Compare the hash with that of the current function call: -- Contract address. -- Function data. -- Private function circuit's public inputs. + - _`call_request.hash == private_call.call_stack_item.hash()`_ + - The hash of the _call_stack_item_ is computed as: + - _`hash(contract_address, function_data.hash(), public_inputs.hash(), counter_start, counter_end)`_ + - Where _function_data.hash()_ and _public_inputs.hash()_ are the hashes of the serialized field elements. #### Ensuring this function is called with the correct context. -1. If it is a standard call: +For the _call_context_ in the [public_inputs](./private-function.md#public-inputs) of the _[private_call](#privatecall).[call_stack_item](./private-kernel-initial.md#privatecallstackitem)_ and the _call_request_ popped in the [previous step](#ensuring-the-current-call-matches-the-call-request), this circuit checks that: - - The storage contract address of the current iteration must be the same as its contract address. - - The _msg_sender_ of the current iteration must be the same as the caller's contract address. +1. If it is a standard call (`call_context.is_delegate_call == false`): -2. If it is a delegate call: + - The _msg_sender_ of the current iteration must be the same as the caller's _contract_address_: + - _`call_context.msg_sender == call_request.caller_contract_address`_ + - The _storage_contract_address_ of the current iteration must be the same as its _contract_address_: + - _`call_context.storage_contract_address == call_stack_item.contract_address`_ - - The caller context in the call request must not be empty. Specifically, the following values of the caller should not be zeros: - - _msg_sender_. - - Storage contract address. - - The _msg_sender_ of the current iteration must equal the caller's _msg_sender_. - - The storage contract address of the current iteration must equal the caller's storage contract address. - - The storage contract address of the current iteration must NOT equal the contract address. +2. If it is a delegate call (`call_context.is_delegate_call == true`): -3. If it is an internal call: + - The _caller_context_ in the _call_request_ must not be empty. Specifically, the following values of the caller must not be zeros: + - _msg_sender_ + - _storage_contract_address_ + - The _msg_sender_ of the current iteration must equal the caller's _msg_sender_: + - _`call_context.msg_sender == caller_context.msg_sender`_ + - The _storage_contract_address_ of the current iteration must equal the caller's _storage_contract_address_: + - _`call_context.storage_contract_address == caller_context.storage_contract_address`_ + - The _storage_contract_address_ of the current iteration must not equal the _contract_address_: + - _`call_context.storage_contract_address != call_stack_item.contract_address`_ - - The _msg_sender_ of the current iteration must equal the storage contract address. +3. If it is an internal call (`call_stack_item.function_data.is_internal == true`): + + - The _msg_sender_ of the current iteration must equal the _storage_contract_address_: + - _`call_context.msg_sender == call_context.storage_contract_address`_ #### Verifying the private function proof. -It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs of the private function circuit. +It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs, sourced from _[private_inputs](#private-inputs).[private_call](#privatecall)_. -This circuit verifies this proof and [the proof for the previous function call](#verifying-the-previous-kernel-proof) using recursion, and generates a single proof. This consolidation of multiple proofs into one is what allows the private kernel circuits to gradually merge private function proofs into a single proof of execution that represents the entire private section of a transaction. +This circuit verifies this proof and [the proof of the previous kernel iteration](#verifying-the-previous-kernel-proof) using recursion, and generates a single proof. This consolidation of multiple proofs into one is what allows the private kernel circuits to gradually merge private function proofs into a single proof of execution that represents the entire private section of a transaction. #### Verifying the public inputs of the private function circuit. -It ensures the private function circuit's intention by checking the following: - -- The contract address for each non-empty item in the following arrays must equal the storage contract address of the current call: - - Note hash contexts. - - Nullifier contexts. - - L2-to-L1 message contexts. - - Read requests. -- The portal contract address for each non-empty L2-to-L1 message must equal the portal contract address of the current call. -- If the new contract contexts array is not empty, the contract address must equal the precompiled deployment contract address. -- The historical data must match the one in the constant data. - -> Ensuring the alignment of the contract addresses is crucial, as it is later used to silo the value and to establish associations with values within the same contract. - -If it is a static call, it must ensure that the function does not induce any state changes by verifying that the following arrays are empty: - -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. - -#### Verifying the call requests. +It ensures the private function circuit's intention by checking the following in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_: -For both private and public call requests initiated in the current function call, it ensures that for each request at index _i_: - -- Its hash equals the value at index _i_ within the call request hashes array in private function circuit's public inputs. -- Its caller context is either empty or aligns with the call context of the current function call, including: - - _msg_sender_ - - Storage contract address. - -> It is important to note that the caller context in a call request may be empty for standard calls. This precaution is crucial to prevent information leakage, particularly as revealing the _msg_sender_ to the public could pose security risks when calling a public function. +- The _block_header_ must match the one in the _[constant_data](./private-kernel-initial.md#constantdata)_. +- If it is a static call (_`public_inputs.call_context.is_static_call == true`_), it ensures that the function does not induce any state changes by verifying that the following arrays are empty: + - _note_hashes_ + - _nullifiers_ + - _l2_to_l1_messages_ #### Verifying the counters. -It verifies that each relevant value is associated with a legitimate counter. - -1. For the current call: - - - The _counter_end_ of the current call must be greater than its _counter_start_. - - Both counters must match the ones defined in the top item in the previous iteration's private call requests. - -2. For private call requests in the private call data: +This section follows the same [process](./private-kernel-initial.md#verifying-the-counters) as outlined in the initial private kernel circuit. - - The _counter_end_ of each request must be greater than its _counter_start_. - - The _counter_start_ of the first request must be greater than the _counter_start_ of the current call. - - The _counter_start_ of the second and subsequent requests must be greater than the _counter_end_ of the previous request. - - The _counter_end_ of the last request must be less than the _counter_end_ of the current call. - -3. For items in each ordered array in the private call data: - - - The counter of the first item much be greater than the _counter_start_ of the current call. - - The counter of each subsequent item much be greater than the counter of the previous item. - - The counter of the last item much be less than the _counter_end_ of the current call. - - The ordered arrays include: - - - Note hash contexts. - - Nullifier contexts. - - New contract contexts. - - Read requests. - - Public call requests. - - > Note that _counter_start_ is used in the above steps for public call requests to ensure their correct ordering. At this point, the _counter_end_ of public call request is unknown. Both counters will be [recalibrated](./private-kernel-tail.md#recalibrating-counters) in the tail circuit following the simulation of all public function calls. +Additionally, it verifies that for the _[call_stack_item](#privatecallstackitem)_, the _counter_start_ and _counter_end_ must match those in the _call_request_ [popped](#ensuring-the-current-call-matches-the-call-request) from the _private_call_requests_ in a previous step. ### Validating Public Inputs @@ -168,115 +108,51 @@ It verifies that each relevant value is associated with a legitimate counter. It checks that the hashes and the lengths for both encrypted and unencrypted logs are accumulated as follows: -- New log hash = `hash(prev_hash, cur_hash)` - - If either hash is zero, the new hash will be `prev_hash | cur_hash` -- New log length = `prev_length + cur_length` +- `new_hash = hash(prev_hash, cur_hash)` + - If either hash is zero, the new hash will be `prev_hash | cur_hash`. +- `new_length = prev_length + cur_length` -#### Verifying the transient accumulated data. +Where: -1. It verifies that the following values match the result of combining the values in the previous iteration's public inputs with those in the private call data: +- _new_hash_ and _new_length_ are the values in _[public_inputs](#public-inputs).[accumulated_data](./private-kernel-initial.md#accumulateddata)_. +- _prev_hash_ and _prev_length_ are the values in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[accumulated_data](./private-kernel-initial.md#accumulateddata)_. +- _cur_hash_ and _cur_length_ are the values in _[private_inputs](#private-inputs).[private_call](#privatecall).[call_stack_item](./private-kernel-initial.md#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_. - - Note hash contexts. - - Nullifier contexts. - - L2-to-L1 message contexts. - - New contract contexts. - - Read requests. - - Public call requests. - -2. For the newly added note hashes from private function circuits' public inputs, this circuit also checks that each is associated with a nullifier counter, provided as a hint via the private inputs. The nullifier counter can be: +#### Verifying the transient accumulated data. - - Zero: if the note is not nullified in the same transaction. - - Greater than zero: if the note is nullified in the same transaction. - - This value must be greater than the counter of the note hash. +The _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in this circuit's _[public_inputs](#public-inputs)_ includes values from both the previous iterations and the _[private_call](#privatecall)_. - > Nullifier counters are used in the [reset private kernel circuit](./private-kernel-reset.md) to ensure a read happens **before** a transient note is nullified. +For each array in the _transient_accumulated_data_, this circuit verifies that: - > Zero can be used to indicate a non-existing transient nullifier, as this value can never serve as the counter of a nullifier. It corresponds to the _counter_start_ of the first function call. +1. It is populated with the values from the previous iterations, specifically: -3. It verifies that the private call requests include: + - _`public_inputs.transient_accumulated_data.ARRAY[0..N] == private_inputs.previous_kernel.public_inputs.transient_accumulated_data.ARRAY[0..N]`_ - - All requests from the previous iteration's public inputs excluding the top one. - - All requests present in the private call data, appended to the above in **reverse** order. + > It's important to note that the top item in the _private_call_requests_ from the _previous_kernel_ won't be included, as it has been removed in a [previous step](#ensuring-the-current-call-matches-the-call-request). - > Ensuring the chronological execution of call requests is vital, requiring them to be arranged in reverse order. This becomes particularly crucial when calling a contract deployed earlier within the same transaction. +2. As for the subsequent items appended after the values from the previous iterations, they constitute the values from the _private_call_, and each must undergo the same [verification](./private-kernel-initial.md#verifying-the-transient-accumulated-data) as outlined in the initial private kernel circuit. #### Verifying the constant data. -It verifies that the constant data matches the one in the previous iteration's public inputs. +It verifies that the _[constant_data](./private-kernel-initial.md#constantdata)_ in the _[public_inputs](#public-inputs)_ matches the _constant_data_ in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs)_. ## Private Inputs -### Previous Kernel - -The data of the previous kernel iteration: - -- Proof of the kernel circuit. It must be one of the following: - - [Initial private kernel circuit](./private-kernel-initial.md). - - Inner private kernel circuit. - - [Reset private kernel circuit](./private-kernel-reset.md). -- Public inputs of the proof. -- Verification key of the kernel circuit. -- Membership witness for the verification key. - -### Private Call Data +### _PreviousKernel_ -The private call data holds details about the current private function call: +Data of the previous kernel iteration. -- Contract address. -- Function data. -- Private call requests. -- Public call requests. -- Private function circuit public inputs. -- Proof of the private function circuit. -- Verification key of the private function circuit. -- Hash of the function bytecode. +| Field | Type | Description | +| -------------------- | ------------------------------------------------------------------------------- | -------------------------------------------- | +| _public_inputs_ | _[InitialPrivateKernelPublicInputs](./private-kernel-initial.md#public-inputs)_ | Public inputs of the proof. | +| _proof_ | _Proof_ | Proof of the kernel circuit. | +| _vk_ | _VerificationKey_ | Verification key of the kernel circuit. | +| _membership_witness_ | _[MembershipWitness](./private-kernel-initial.md#membershipwitness)_ | Membership witness for the verification key. | -### Hints +### _PrivateCall_ -Data that aids in the verifications carried out in this circuit or later iterations: - -- Index of the new contract. -- Membership witness for the function leaf. -- Membership witness for the contract leaf. -- Transient note nullifier counters. +The format aligns with the _[PrivateCall](./private-kernel-initial.md#privatecall)_ of the initial private kernel circuit. ## Public Inputs -The structure of this public inputs aligns with that of the [initial private kernel circuit](./private-kernel-initial.md) and the [reset private kernel circuit](./private-kernel-reset.md). - -### Constant Data - -These are constants that remain the same throughout the entire transaction: - -- Historical data - representing the states of the block at which the transaction is constructed, including: - - Hash of the global variables. - - Roots of the trees: - - Note hash tree. - - Nullifier tree. - - Contract tree. - - L1-to-l2 message tree. - - Public data tree. -- Transaction context - - A flag indicating whether it is a fee paying transaction. - - A flag indicating whether it is a fee rebate transaction. - - Chain ID. - - Version of the transaction. - -### Accumulated Data - -It contains data accumulated during the execution of the transaction up to this point: - -- Log hashes. -- Log lengths. - -### Transient Accumulated Data - -It includes transient data accumulated during the execution of the transaction up to this point: - -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. -- New contract contexts. -- Read requests. -- Private call requests. -- Public call requests. +The format aligns with the _[Public Inputs](./private-kernel-initial.md#public-inputs)_ of the initial private kernel circuit. diff --git a/yellow-paper/docs/circuits/private-kernel-reset.md b/yellow-paper/docs/circuits/private-kernel-reset.md index 1f4547caa64..e3b168a2e04 100644 --- a/yellow-paper/docs/circuits/private-kernel-reset.md +++ b/yellow-paper/docs/circuits/private-kernel-reset.md @@ -6,155 +6,183 @@ This is a draft. These requirements need to be considered by the wider team, and ## Requirements -The **reset** circuit is designed to abstain from processing individual private function calls. Instead, it injects the outcomes of an initial or inner private kernel circuit, scrutinizes the public inputs, and resets the verified data within its scope. This circuit can be executed either preceding the tail circuit or as a means to "reset" public inputs, allowing data to accumulate seamlessly in subsequent iterations. +A **reset** circuit is designed to abstain from processing individual private function calls. Instead, it injects the outcomes of an initial, inner, or another reset private kernel circuit, scrutinizes the public inputs, and clears the verifiable data within its scope. A reset circuit can be executed either preceding the tail private kernel circuit, or as a means to "reset" public inputs at any point between two private kernels, allowing data to accumulate seamlessly in subsequent iterations. -The incorporation of this circuit not only enhances the modularity and repeatability of the "reset" process but also diminishes the overall workload. Rather than conducting resource-intensive computations such as membership checks in each iteration, these tasks are only performed as necessary within the reset circuits. +There are 2 variations of reset circuits: -### Verification of the Previous Iteration +- [Read Request Reset Private Kernel Circuit](#read-request-reset-private-kernel-circuit). +- [Transient Note Reset Private Kernel Circuit](#transient-note-reset-private-kernel-circuit). -#### Verifying the previous kernel proof. +The incorporation of these circuits not only enhances the modularity and repeatability of the "reset" process but also diminishes the overall workload. Rather than conducting resource-intensive computations such as membership checks in each iteration, these tasks are only performed as necessary within the reset circuits. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs. +### Read Request Reset Private Kernel Circuit. -The preceding proof can be: +This reset circuit conducts verification on some or all accumulated read requests and subsequently removes them from the _read_request_contexts_ in the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ within the _public_inputs_ of the [_previous_kernel_](#previouskernel). -- [Initial private kernel proof](./private-kernel-initial.md). -- [Inner private kernel proof](./private-kernel-inner.md). +A read request can pertain to one of two note types: -### Verifying and Resetting Data +- A settled note: generated in a prior successful transaction and included in the note hash tree. +- A pending note: created in the current transaction, not yet part of the note hash tree. -#### Verifying read requests. +1. To clear read requests for settled notes, the circuit performs membership checks for the targeted read requests using the [hints](#hints-for-read-request-reset-private-kernel-circuit) provided via _private_inputs_. -A read request can pertain to one of two note types: + For each _persistent_read_index_ at index _i_ in _persistent_read_indices_: -- A persistent note: generated in a prior successful transaction and included in the note hash tree. -- A transient note: created in the current transaction, not yet part of the note hash tree. + 1. If the _persistent_read_index_ equals the length of the _read_request_contexts_ array, there is no read request to be verified. Skip the rest. + 2. Locate the _read_request_: _`read_request_contexts[persistent_read_index]`_ + 3. Perform a membership check on the note being read. Where: + - The leaf corresponds to the hash of the note: _`read_request.note_hash`_ + - The index and sibling path are in: _`read_request_membership_witnesses[i]`_. + - The root is the _note_hash_tree_root_ in the _[block_header](./private-function.md#blockheader)_ within _[public_inputs](#public-inputs).[constant_data](./private-kernel-initial.md#constantdata)_. -For each non-empty read request in the previous kernel's public inputs, it can be cleared if it meets either of the following conditions: + > Following the above process, at most _N_ read requests will be cleared, where _N_ is the length of the _persistent_read_indices_ array. It's worth noting that there can be multiple versions of this reset circuit, each with a different value of _N_. -1. When reading a persistent note, it requires a valid membership check, where: +2. To clear read requests for pending notes, the circuit ensures that the notes were created before the corresponding read operation, utilizing the [hints](#hints-for-read-request-reset-private-kernel-circuit) provided via _private_inputs_ - - The leaf corresponds to the note hash being read. - - The sibling path is provided as a hint. - - The root matches the note hash tree root in the historical data. + For each _transient_read_index_ at index _i_ in _transient_read_indices_: -2. When reading a transient note, it must have been created before the read operation: + 1. If the _transient_read_index_ equals the length of the _read_request_contexts_ array, there is no read request to be verified. Skip the rest. + 2. Locate the _read_request_: _`read_request_contexts[transient_read_index]`_ + 3. Locate the _note_hash_ being read: _`note_hash_contexts[pending_note_indices[i]]`_ + 4. Verify the following: + - _`read_request.note_hash == note_hash.value`_ + - _`read_request.contract_address == note_hash.contract_address`_ + - _`read_request.counter > note_hash.counter`_ + - _`(read_request.counter < note_hash.nullifier_counter) | (note_hash.nullifier_counter == 0)`_ - - Locates the note hash within the note hash contexts. - - Its index in the note hash contexts is provided as a hint through private inputs. - - The note hash must equal the note hash of the read request. - - The contract address of the note hash must equal the contract address of the read request. - - The counter of the note hash must be less than the counter of the read request. - - The nullifier counter of the note hash must be zero or a value greater than the counter of the read request. + > Given that a reset circuit can execute between two private kernel circuits, there's a possibility that a note is created in a nested execution and hasn't been added to the _public_inputs_. In such cases, the read request cannot be verified in the current reset circuit and must be processed in another reset circuit after the note has been included in the _public_inputs_. -For reading a transient note created in a yet-to-be-processed nested execution: +3. This circuit then ensures that the read requests that haven't been verified should remain in the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ within its _public_inputs_. -- The index provided as a hint will be the length of the note hash contexts array, indicating the transient note hasn't been added yet. -- The read request must be propagated to the public inputs. + For each _read_request_ at index _i_ in the _read_request_contexts_ within the _private_inputs_, find its _status_ at _`read_request_statuses[i]`_: -> Given that a reset circuit can execute between two inner circuits, there's a possibility that a transient note is created in a nested execution and hasn't been added to the public inputs. In such cases, the read request cannot be verified in the current reset circuit and must be processed in another reset circuit after the transient note has been included in the public input. + - If _status.state == persistent_, _`i == persistent_read_indices[status.index]`_. + - If _status.state == transient_, _`i == transient_read_indices[status.index]`_. + - If _status.state == nada_, _`read_request == public_inputs.transient_accumulated_data.read_request_contexts[status.index]`_. -#### Squashing transient note hashes and nullifiers. +### Transient Note Reset Private Kernel Circuit. -In the event that a transient note is nullified within the same transaction, both its note hash and nullifier can be expunged from the public inputs. This not only avoids redundant data broadcasting but also frees up space for additional note hashes and nullifiers. +In the event that a pending note is nullified within the same transaction, both its note hash and nullifier can be removed from the public inputs. This not only avoids redundant data being broadcasted, but also frees up space for additional note hashes and nullifiers in the subsequent iterations. -For each nullifier associated with a non-zero nullified note hash: +1. Ensure that each note hash is either propagated to the _public_inputs_ or nullified in the same transaction. -1. Finds its index in the note hash contexts using hints provided through private inputs. Proceeds no further if the index value equals the length of the note hash contexts array. -2. Locates the note hash in the note hash contexts using the identified index. -3. The note hash must equal the nullified note hash associated with the nullifier. -4. The contract address of the note hash must equal the contract address of the nullifier. -5. The nullifier counter of the note hash must equal the counter of the nullifier. - - The nullifier counter is assured to be greater than the counter of the note hash when propagated from the [initial](./private-kernel-initial.md#verifying-the-accumulated-data) or [inner](./private-kernel-inner.md#verifying-the-accumulated-data) private kernel circuits. -6. Sets both the note hash and the nullifier to zero. + Initialize both _notes_kept_ and _notes_removed_ to 0. -> It is imperative to set the note hash and nullifier to zeros before processing the next nullifier. This precaution prevents two nullifiers from nullifying the same note. By clearing the values, the second nullifier will identify its corresponding note hash as having a zero value, and will fail to compare its nullified note hash with a zero hash. + For each _note_hash_ at index _i_ in the _note_hash_contexts_ within the _private_inputs_, find the _transient_nullifier_index_ of its associated nullifer at _`transient_nullifier_indices[i]`_, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): -> Note that an index hint can be set as the length of the note hash array to bypass the above process even when the corresponding nullifier is nullifying a transient note hash already present in the public inputs. The transient note hash must be retained in the public inputs for reading in a yet-to-be-processed nested execution. + - If _`transient_nullifier_index == nullifier_contexts.len()`_: -### Validating Public Inputs + - Verify that the _note_hash_ remains within the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in the _public_inputs_: + - _`note_hash == public_inputs.transient_accumulated_data.note_hash_contexts[notes_kept]`_ + - Increment _notes_kept_ by 1: _`notes_kept += 1`_ -#### Verifying the accumulated data. + - Else, locate the _nullifier_ at _nullifier_contexts[transient_nullifier_index]_: -It ensures that the accumulated data matches the data in the previous iteration's public inputs. + - Verify that the nullifier is associated with the note: + - _`nullifier.contract_address == note_hash.contract_address`_ + - _`nullifier.nullified_note_hash == note_hash.value`_ + - _`nullifier.counter == note_hash.nullifier_counter`_ + - Increment _notes_removed_ by 1: _`notes_removed += 1`_ + - Ensure that an empty _note_hash_ is appended to the end of _note_hash_contexts_ in the _public_inputs_: + - _`public_inputs.transient_accumulated_data.note_hash_contexts[N - notes_removed].is_empty() == true`_ + - Where _N_ is the length of _note_hash_contexts_. -#### Verifying the transient accumulated data. + > Note that the check `nullifier.counter > note_hash.counter` is not necessary as the nullifier counter is assured to be greater than the counter of the note hash when [propagated](./private-kernel-initial.md#verifying-the-transient-accumulated-data) from either the initial or inner private kernel circuits. -The following must equal the result after verification or squashing: +2. Ensure that nullifiers not associated with note hashes removed in the previous step are retained within the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in the _public_inputs_. -- Note hash contexts. -- Nullifier contexts. -- Read requests. + Initialize both _nullifiers_kept_ and _nullifiers_removed_ to 0. -The following must equal the corresponding values in the previous kernel's public inputs: + For each _nullifier_ at index _i_ in the _nullifier_contexts_ within the _private_inputs_, find the index of its corresponding _transient_nullifier_index_ at _`nullifier_index_hints[i]`_, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): -- L2-to-L1 message contexts. -- New contract contexts. -- Private call requests. -- Public call requests. + - If _`nullifier_index_hints[i] == transient_nullifier_indices.len()`_: + - Verify that the _nullifier_ remains within the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in the _public_inputs_: + - _`nullifier == public_inputs.transient_accumulated_data.nullifier_contexts[nullifiers_kept]`_ + - Increment _nullifiers_kept_ by 1: _`nullifiers_kept += 1`_ + - Else, compute _transient_nullifier_index_ as _`transient_nullifier_indices[nullifier_index_hints[i]]`_: + - Verify that: _`transient_nullifier_index == i`_ + - Increment _nullifiers_removed_ by 1: _`nullifiers_removed += 1`_ + - Ensure that an empty _nullifier_ is appended to the end of _nullifier_contexts_ in the _public_inputs_: + - _`public_inputs.transient_accumulated_data.nullifier_contexts[N - nullifiers_removed].is_empty() == true`_ + - Where _N_ is the length of _nullifer_contexts_. -#### Verifying the constant data. + After these steps, ensure that all nullifiers associated with transient note hashes have been identified and removed: -It verifies that the constant data matches the one in the previous iteration's public inputs. + _`nullifiers_removed == notes_removed`_ -## Private Inputs +> Note that this reset process may not necessarily be applied to all transient note hashes and nullifiers at a time. In cases where a note will be read in a yet-to-be-processed nested execution, the transient note hash and its nullifier must be retained in the _public_inputs_. The reset can only occur in a later reset circuit after all associated read requests have been verified and cleared. -### Previous Kernel +### Common Verifications -The data of the previous kernel iteration: +Below are the verifications applicable to all reset circuits: -- Proof of the kernel circuit. It must be one of the following: - - [Initial private kernel circuit](./private-kernel-initial.md). - - [Inner private kernel circuit](./private-kernel-inner.md). -- Public inputs of the proof. -- Verification key of the kernel circuit. -- Membership witness for the verification key. +#### Verifying the previous kernel proof. -### Hints +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. -Data that aids in the verifications carried out in this circuit: +The preceding proof can be: -- Membership witnesses for persistent read requests. -- Indices of note hashes for transient read requests. -- Indices of note hashes for transient nullifiers. +- [Initial private kernel proof](./private-kernel-initial.md). +- [Inner private kernel proof](./private-kernel-inner.md). +- Reset private kernel proof. -## Public Inputs +#### Verifying the accumulated data. -The structure of this public inputs aligns with that of the [initial private kernel circuit](./private-kernel-initial.md) and the [inner private kernel circuit](./private-kernel-inner.md). +It ensures that the _accumulated_data_ in the _[public_inputs](#public-inputs)_ matches the _accumulated_data_ in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs)_. -### Constant Data +#### Verifying the transient accumulated data. + +The following must equal the corresponding arrays in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_: + +- _l2_to_l1_message_contexts_ +- _private_call_requests_ +- _public_call_requests_ + +The following must remain the same for [read request reset private kernel circuit](#read-request-reset-private-kernel-circuit): + +- _note_hash_contexts_ +- _nullifier_contexts_ + +The following must remain the same for [transient note reset private kernel circuit](#transient-note-reset-private-kernel-circuit): + +- _read_request_contexts_ + +#### Verifying the constant data. + +This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. + +## Private Inputs -These are constants that remain the same throughout the entire transaction: +### _PreviousKernel_ -- Historical data - representing the states of the block at which the transaction is constructed, including: - - Hash of the global variables. - - Roots of the trees: - - Note hash tree. - - Nullifier tree. - - Contract tree. - - L1-to-l2 message tree. - - Public data tree. -- Transaction context - - A flag indicating whether it is a fee paying transaction. - - A flag indicating whether it is a fee rebate transaction. - - Chain ID. - - Version of the transaction. +The format aligns with the _[PreviousKernel](./private-kernel-inner.md#previouskernel)_ of the inner private kernel circuit. -### Accumulated Data +### _Hints_ for [Read Request Reset Private Kernel Circuit](#read-request-reset-private-kernel-circuit) -It contains data accumulated during the execution of the transaction up to this point: +| Field | Type | Description | +| ----------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| _transient_read_indices_ | [_field_; _N_] | Indices of the read requests for transient notes. | +| _pending_note_indices_ | [_field_; _N_] | Indices of the note hash contexts for transient reads. | +| _persistent_read_indices_ | [_field_; _M_] | Indices of the read requests for settled notes. | +| _read_request_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _M_] | Membership witnesses for the persistent reads. | +| _read_request_statuses_ | [_[ReadRequestStatus](#readrequeststatus)_; _C_] | Statuses of the read request contexts. _C_ equals the length of _read_request_contexts_. | -- Log hashes. -- Log lengths. +> There can be multiple versions of the read request reset private kernel circuit, each with a different values of _N_ and _M_. -### Transient Accumulated Data +#### _ReadRequestStatus_ -It includes transient data accumulated during the execution of the transaction up to this point: +| Field | Type | Description | +| ------- | ------------------------------- | --------------------------------------- | +| _state_ | persistent \| transient \| nada | State of the read request. | +| _index_ | _field_ | Index of the hint for the read request. | + +### _Hints_ for [Transient Note Reset Private Kernel Circuit](#transient-note-reset-private-kernel-circuit) + +| Field | Type | Description | +| ----------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------- | +| _transient_nullifier_indices_ | [_field_; _C_] | Indices of the nullifiers for transient notes. _C_ equals the length of _note_hash_contexts_. | +| _nullifier_index_hints_ | [_field_; _C_] | Indices of the _transient_nullifier_indices_ for transient nullifiers. _C_ equals the length of _nullifier_contexts_. | + +## Public Inputs -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. -- New contract contexts. -- Read requests. -- Private call requests. -- Public call requests. +The format aligns with the _[Public Inputs](./private-kernel-initial.md#public-inputs)_ of the initial private kernel circuit. diff --git a/yellow-paper/docs/circuits/private-kernel-tail.md b/yellow-paper/docs/circuits/private-kernel-tail.md index b36464a25f3..95c74cf07b7 100644 --- a/yellow-paper/docs/circuits/private-kernel-tail.md +++ b/yellow-paper/docs/circuits/private-kernel-tail.md @@ -12,7 +12,7 @@ The **tail** circuit abstains from processing individual private function calls. #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs. +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. The preceding proof can be: @@ -20,205 +20,184 @@ The preceding proof can be: - [Inner private kernel proof](./private-kernel-inner.md). - [Reset private kernel proof](./private-kernel-reset.md). -An inner iteration may be omitted when there's only a single private function call for the transaction. And a reset iteration can be skipped if there are no read requests and transient nullifiers in the public inputs from the last initial or inner iteration. +An inner iteration may be omitted when there's only a single private function call for the transaction. And a reset iteration can be skipped if there are no read requests and transient notes in the public inputs from the last iteration. #### Ensuring the previous iteration is the last. -The following must be empty to ensure all the private function calls are processed: +It checks the data within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ to ensure that no further private kernel iteration is needed. -- Private call requests. +1. The following must be empty to ensure all the private function calls are processed: -The following must be empty to ensure a comprehensive final reset: + - _private_call_requests_ -- The nullified note hash associated with each nullifier. -- Read requests. +2. The following must be empty to ensure a comprehensive final reset: -> A [reset iteration](./private-kernel-reset.md) should ideally precede this step. Although it doesn't have to be executed immediately before the tail circuit, as long as it effectively clears the specified values. + - _read_requests_ + - The _nullifier_counter_ associated with each note hash in _note_hash_contexts_. + - The _nullified_note_hash_ associated with each nullifier in _nullifier_contexts_. + + > A [reset iteration](./private-kernel-reset.md) should ideally precede this step. Although it doesn't have to be executed immediately before the tail circuit, as long as it effectively clears the specified values. ### Processing Final Outputs #### Siloing values. -1. This circuit must silo the following with each item's contract address: +Siloing a value with the address of the contract generating the value ensures that data produced by a contract is accurately attributed to the correct contract and cannot be misconstrued as data created in a different contract. This circuit guarantees the following siloed values: + +1. Silo _nullifiers_: + + For each _nullifier_ at index _i_ _> 0_ in the _nullifier_contexts_ within _private_inputs_, if _`nullifier.value != 0`_: + + _`nullifier_contexts[i].value = hash(nullifier.contract_address, nullifier.value)`_ + + > This process does not apply to _nullifier_contexts[0]_, which is the [hash of the transaction request](./private-kernel-initial.md#ensuring-transaction-uniqueness) created by the initial private kernel circuit. - - Note hash contexts. - - Nullifier contexts. +2. Silo _note_hashes_: - The siloed value is computed as: `hash(contract_address, value)`. + For each _note_hash_ at index _i_ in the _note_hash_contexts_ within _private_inputs_, if _`note_hash.value != 0`_: - Siloing with a contract address ensures that data produced by a contract is accurately attributed to the correct contract and cannot be misconstrued as data created in a different contract. + _`note_hash_contexts[i].value = hash(nonce, siloed_hash)`_ -2. The circuit then applies nonces to the note hashes: + Where: - - The nonce for a note hash is computed as: `hash(first_nullifier, index)`, where: - - `first_nullifier` is the [hash of the transaction request](./private-kernel-initial.md#ensuring-transaction-uniqueness). - - `index` is the position of the note hash in the note hashes array in the public inputs. + - _`nonce = hash(first_nullifier, index)`_ + - _`first_nullifier = nullifier_contexts[0].value`_. + - _`index = note_hash_indices[i]`_, which is the index of the same note hash within _public_inputs.note_hashes_. Where _note_hash_indices_ is provided as [hints](#hints) via _private_inputs_ and are verified in the [following step](#verifying-ordered-arrays). + - _`siloed_hash = hash(note_hash.contract_address, note_hash.value)`_ - Siloing with a nonce guarantees that each final note hash is a unique value in the note hash tree. + > Siloing with a nonce guarantees that each final note hash is a unique value in the note hash tree. -3. Additionally, this circuit generates the final hashes for L2-L1 messages, calculated as: +3. Verify the _l2_to_l1_messages_ within _[public_inputs](#public-inputs).[accumulated_data](./public-kernel-tail.md#accumulateddata)_: - `hash(contract_address, version_id, portal_contract_address, chain_id, message)` + For each _l2_to_l1_message_ at index _i_ in _l2_to_l1_message_contexts_ within _[private_inputs](#private-inputs).[previous_kernel](./private-kernel-inner.md#previouskernel).[public_inputs](./private-kernel-initial.md#private-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_: - Where _version_id_ and _portal_contract_address_ equal the values defined in the constant data. + - If _l2_to_l1_message.value == 0_: + - Verify that _`l2_to_l1_messages[i] == 0`_ + - Else: + - Verify that _`l2_to_l1_messages[i] == hash(l2_to_l1_message.contract_address, version_id, l2_to_l1_message.portal_contract_address, chain_id, l2_to_l1_message.value)`_ + - Where _version_id_ and _chain_id_ are defined in _[public_inputs](#public-inputs).[constant_data](./private-kernel-initial.md#constantdata).[tx_context](./private-kernel-initial.md#transactioncontext)_. #### Verifying ordered arrays. The initial and inner kernel iterations may produce values in an unordered state due to the serial nature of the kernel, contrasting with the stack-based nature of code execution. -This circuit ensures the correct ordering of the following arrays (_ordered_array_) in public inputs: +This circuit ensures the correct ordering of the following arrays within _[public_inputs](#public-inputs).[accumulated_data](./public-kernel-tail.md#accumulateddata)_: -- Note hashes. -- Nullifiers. -- L2-to-l1 messages. -- Public call requests. -- New contracts (if public call request is empty). -- New contract contexts (if public call request is not empty). +- _note_hashes_ +- _nullifiers_ +- _public_call_requests_ -The corresponding _unordered_arrays_ for the above are sourced either from the transient accumulated data of the previous iteration or from the [siloed results](#siloing-values). +The corresponding unordered array for each of the above is sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_. -A hints array is provided through private inputs for every _unordered_array_. +1. Verify ordered _note_hashes_: -For each hint _hints[i]_ at index _i_, this circuit locates the item at index _i_ in _ordered_array_: + For each _note_hash_ at index _i_ in _note_hashes_, the associated _note_hash_context_ is at _`note_hash_contexts[note_hash_hints[i]]`_. -- If the item is not empty: - - It must correspond to the item at index _hints[i]_ in _unordered_array_. - - For _i_ != 0, the counter must be greater (less for public call requests) than the counter of the item at index _hints[i - 1]_ in _unordered_array_. -- If the item is empty: - - All the subsequent items must be empty in both _ordered_array_ and _unordered_array_. + - If _`note_hash != 0`_, verify that: + - _`note_hash == note_hash_context.value`_ + - _`note_hash_indices[note_hash_hints[i]] == i`_ + - The values in _note_hash_indices_ are provided as [hints](#hints) to [silo note hashes](#siloing-values) and must be verified. + - If _i > 0_, verify that: + - _`note_hash_context.counter > note_hash_contexts[note_hash_hints[i - 1]].counter`_ + - Else: + - All the subsequent values in _note_hashes_ must be 0. + - All the subsequent contexts in _note_hash_contexts_ must have 0 values. -> Note that the public call requests must be arranged in descending order to ensure the calls are executed in chronological order. +2. Verify ordered _nullifiers_: -> Note that while ordering could occur gradually in each kernel iteration, the implementation is much simpler and **typically** more efficient to be done once in the tail circuit. + For each _nullifier_ at index _i_ in _nullifiers_, the associated _nullifier_context_ is at _`nullifier_contexts[nullifier_hints[i]]`_. -#### Recalibrating counters. + - If _`nullifier != 0`_, verify that: + - _`nullifier == nullifier_context.value`_ + - If _i > 0_, verify that: + - _`nullifier_context.counter > nullifier_contexts[nullifier_hints[i - 1]].counter`_ + - Else: + - All the subsequent values in _nullifiers_ must be 0. + - All the subsequent contexts in _nullifier_contexts_ must have 0 values. -1. For public call requests: +3. Verify ordered _public_call_requests_: - The _counter_end_ for a public call request is determined by the overall count of call requests, reads and writes, note hashes and nullifiers within its scope, including those nested within its child function executions. This calculation, performed in advance of executing this circuit, provides the necessary input for the recalibration process. + For each _request_ at index _i_ in _public_call_requests_ within _public_inputs_, the associated _mapped_request_ is at _`unordered_requests[public_call_request_hints[i]]`_, where _unordered_requests_ refers to the _public_call_requests_ within _private_inputs_. - This circuit enables the adjustment of counters, ensuring that subsequent public kernels can be executed with the correct counter range. + - If _`request.hash != 0`_, verify that: + - _`request.hash == mapped_request.hash`_ + - _`request.caller_contract == mapped_request.caller_contract`_ + - _`request.caller_context == mapped_request.caller_context`_ + - If _i > 0_, verify that: + - _`mapped_request.counter < unordered_requests[public_call_request_hints[i - 1]].counter`_ + - Else: + - All the subsequent requests in both _public_call_requests_ and _unordered_requests_ must have 0 hashes. - An array _public_call_counters_ is provided through private inputs. The reassignment process unfolds as follows: + > Note that _public_call_requests_ must be arranged in descending order to ensure the calls are executed in chronological order. - 1. Check that items in _public_call_counters_ are in descending order: - - The _counter_end_ of each item must be greater than its _counter_start_. - - The _counter_end_ of the second and subsequent items must be less than the _counter_start_ of the previous item. - 2. Ensure that the _counter_start_ of the last item in _public_call_counters_ is _1_. - 3. Assign the _counter_start_ and _counter_end_ of the item at index _i_ in _public_call_counters_ to the corresponding item at index _i_ in the [ordered](#verifying-ordered-arrays) public call requests. +> While ordering could occur gradually in each kernel iteration, the implementation is much simpler and **typically** more efficient to be done once in the tail circuit. - > It's crucial for the _counter_start_ of the last item to be _1_, as it's assumed in the [tail public kernel circuit](./public-kernel-tail.md#grouping-update-requests) that no update requests have a counter _1_. +#### Recalibrating counters. - > While the _counter_start_ of public call request is assigned in the private function circuit to preserve the order, it's important to acknowledge that it may be modified in this step. As using _counter_start_ populated from private function circuits maybe leak information. It's recommended to adept the values that mirror the incremented amount on the public side without including any side effects on the private side. +While the _counter_start_ of a _public_call_request_ is initially assigned in the private function circuit to ensure proper ordering within the transaction, it should be modified in this step. As using _counter_start_ values obtained from private function circuits may leak information. -2. For new contract contexts: +The _counter_start_ in the _public_call_requests_ within _public_inputs_ should have been recalibrated. This circuit validates the values through the following checks: - If there's at least one non-empty public call request, the new contract contexts will be carried forward for processing in the public kernels. However, the counters in new contract contexts must be adjusted to reflect the changes to the counters for public call requests in the previous step. +- The _counter_start_ of the non-empty requests are continuous values in descending order: + - _`public_call_requests[i].counter_start == public_call_requests[i + 1].counter_start + 1`_ +- The _counter_start_ of the last non-empty request must be _1_. - For each new contract context in the transient accumulated data: +> It's crucial for the _counter_start_ of the last request to be _1_, as it's assumed in the [tail public kernel circuit](./public-kernel-tail.md#grouping-storage-writes) that no storage writes have a counter _1_. - 1. If its counter is greater than the **old** _counter_start_ of the public call request at index _0_, update it to be the **new** _counter_end_ of the public call request and skip the remaining steps. - 2. Find the public call request at index _i_ where the **old** _counter_start_ is greater than the counter. - 3. Check that the **old** _counter_start_ of the public call request at index _i + 1_ is less than the counter. - 4. Update its counter to be the **new** _counter_start_ of the public call request at index _i_. +> The _counter_end_ for a public call request is determined by the overall count of call requests, reads and writes, note hashes and nullifiers within its scope, including those nested within its child function executions. This calculation will be performed by the sequencer for the executions of public function calls. ### Validating Public Inputs #### Verifying the accumulated data. -1. The following must align with the results after ordering, as verified in a [previous step](#verifying-ordered-arrays): +1. The following must align with the results after siloing, as verified in a [previous step](#siloing-values): - - Note hashes. - - Nullifiers. - - L2-to-L1 messages. - - New contracts. + - _l2_to_l1_messages_ - > Note that these are arrays of siloed values or relevant data. Attributes aiding verification and siloing only exist in the corresponding types in the transient accumulated data. +2. The following must align with the results after ordering, as verified in a [previous step](#verifying-ordered-arrays): -2. The following must match the respective values in the previous kernel's public inputs: + - _note_hashes_ + - _nullifiers_ - - Log hashes. - - Log lengths. +3. The following must match the respective values within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[accumulated_data](./private-kernel-initial.md#accumulateddata)_: -3. The following must be empty: + - _encrypted_logs_hash_ + - _unencrypted_logs_hash_ + - _encrypted_log_preimages_length_ + - _unencrypted_log_preimages_length_ - - Old public data tree snapshot. - - New public data tree snapshot. +4. The following must be empty: -#### Verifying the transient accumulated data. + - _old_public_data_tree_snapshot_ + - _new_public_data_tree_snapshot_ -It ensures that all data in the transient accumulated data is empty, with the exception of the public call requests and new contract contexts. +#### Verifying the transient accumulated data. -The public call requests must adhere to a specific order, as verified in a [previous step](#verifying-ordered-arrays). +It ensures that all data in the _[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_ within _[public_inputs](#public-inputs)_ is empty, with the exception of the _public_call_requests_. -The new contract contexts should be empty when there are no public call requests. In the event of propagation to the public kernels, they must also [conform to a specific order](#verifying-ordered-arrays). +The _public_call_requests_ must [adhere to a specific order](#verifying-ordered-arrays) with [recalibrated counters](#recalibrating-counters), as verified in the previous steps. #### Verifying the constant data. -It verifies that the constant data matches the one in the previous iteration's public inputs. +This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. ## Private Inputs -### Previous Kernel - -The data of the previous kernel iteration: +### _PreviousKernel_ -- Proof of the kernel circuit. It could be one of the following: - - [Initial private kernel circuit](./private-kernel-initial.md). - - [Inner private kernel circuit](./private-kernel-inner.md). - - [Reset private kernel circuit](./private-kernel-reset.md). -- Public inputs of the proof. -- Verification key of the kernel circuit. -- Membership witness for the verification key. +The format aligns with the _[PreviousKernel](./private-kernel-inner.md#previouskernel)_ of the inner private kernel circuit. -### Hints +### _Hints_ Data that aids in the verifications carried out in this circuit: -- Sorted indices of public call requests. -- Counters for public call requests. +| Field | Type | Description | +| --------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- | +| _note_hash_indices_ | [_field_; _C_] | Indices of _note_hashes_ for _note_hash_contexts_. _C_ equals the length of _note_hashes_. | +| _note_hash_hints_ | [_field_; _C_] | Indices of _note_hash_contexts_ for ordered _note_hashes_. _C_ equals the length of _note_hash_contexts_. | +| _nullifier_hints_ | [_field_; _C_] | Indices of _nullifier_contexts_ for ordered _nullifiers_. _C_ equals the length of _nullifier_contexts_. | +| _public_call_request_hints_ | [_field_; _C_] | Indices of _public_call_requests_ for ordered _public_call_requests_. _C_ equals the length of _public_call_requests_. | ## Public Inputs -The structure of this public inputs aligns with that of the [iterative public kernel circuit](./public-kernel-iterative.md) and the [tail public kernel circuit](./public-kernel-tail.md). - -### Constant Data - -These are constants that remain the same throughout the entire transaction: - -- Historical data - representing the states of the block at which the transaction is constructed, including: - - Hash of the global variables. - - Roots of the trees: - - Note hash tree. - - Nullifier tree. - - Contract tree. - - L1-to-l2 message tree. - - Public data tree. -- Transaction context - - A flag indicating whether it is a fee paying transaction. - - A flag indicating whether it is a fee rebate transaction. - - Chain ID. - - Version of the transaction. - -### Accumulated Data - -It contains data accumulated during the execution of the transaction: - -- Note hashes. -- Nullifiers. -- L2-to-L1 messages. -- New contracts. -- Log hashes. -- Log lengths. -- Old public data tree snapshot. -- New public data tree snapshot. - -### Transient Accumulated Data - -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. -- New contract contexts. -- Read requests. -- Update requests. -- Public call requests. +The format aligns with the _[Public Inputs](./public-kernel-tail.md#public-inputs)_ of the tail public kernel circuit. diff --git a/yellow-paper/docs/circuits/public-kernel-initial.md b/yellow-paper/docs/circuits/public-kernel-initial.md new file mode 100644 index 00000000000..75b677810cc --- /dev/null +++ b/yellow-paper/docs/circuits/public-kernel-initial.md @@ -0,0 +1,61 @@ +# Public Kernel Circuit - Initial + +:::info Disclaimer +This is a draft. These requirements need to be considered by the wider team, and might change significantly before a mainnet release. +::: + +## Requirements + +The **initial** public kernel iteration undergoes processes to prepare the necessary data for the executions of the public function calls. + +### Verification of the Previous Iteration + +#### Verifying the previous kernel proof. + +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. + +The preceding proof can only be: + +- [Tail private kernel proof](./private-kernel-tail.md). + +### Public Inputs Data Reset + +#### Recalibrating counters. + +While the counters outputted from the tail private kernel circuit preserve the correct ordering of the _public_call_requests_, they do not reflect the actual number of side effects each public call entails. This circuit allows the recalibration of counters for _public_call_requests_, ensuring subsequent public kernels can be executed with the correct counter range. + +For each _request_ at index _i_ in the _public_call_requests_ within _[public_inputs](#public-inputs).[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_: + +1. Its hash must match the corresponding item in the _public_call_requests_ within the previous kernel's public inputs: + - _`request.hash == private_inputs.previous_kernel_public_inputs.public_call_requests[i].hash`_ +2. Its _counter_end_ must be greater than its _counter_start_. +3. Its _counter_start_ must be greater than the _counter_end_ of the item at index _i + 1_. +4. If it's the last item, its _counter_start_ must be _1_. + +> It's crucial for the _counter_start_ of the last item to be _1_, as it's assumed in the [tail public kernel circuit](./public-kernel-tail.md#grouping-storage-writes) that no storage writes have a counter _1_. + +### Validating Public Inputs + +#### Verifying the accumulated data. + +It ensures that the _accumulated_data_ in the _[public_inputs](#public-inputs)_ matches the _accumulated_data_ in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./public-kernel-tail.md#public-inputs)_. + +#### Verifying the transient accumulated data. + +It ensures that all data in the _[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_ within _[public_inputs](#public-inputs)_ is empty, with the exception of the _public_call_requests_. + +The values in _public_call_requests_ are verified in a [previous step](#recalibrating-counters). + +#### Verifying the constant data. + +This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. + +## Private Inputs + +### _PreviousKernel_ + +The format aligns with the _[PreviousKernel](./private-kernel-tail.md#previouskernel)_ of the tail public kernel circuit. + +## Public Inputs + +The format aligns with the _[Public Inputs](./public-kernel-tail.md#public-inputs)_ of the tail public kernel circuit. diff --git a/yellow-paper/docs/circuits/public-kernel-inner.md b/yellow-paper/docs/circuits/public-kernel-inner.md new file mode 100644 index 00000000000..9ddc4a33e7a --- /dev/null +++ b/yellow-paper/docs/circuits/public-kernel-inner.md @@ -0,0 +1,248 @@ +# Public Kernel Circuit - Inner + +:::info Disclaimer +This is a draft. These requirements need to be considered by the wider team, and might change significantly before a mainnet release. +::: + +## Requirements + +In the public kernel iteration, the process involves taking a previous iteration and public call data, verifying their integrity, and preparing the necessary data for subsequent circuits to operate. + +### Verification of the Previous Iteration + +#### Verifying the previous kernel proof. + +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. + +The preceding proof can be: + +- [Initial public kernel proof](./public-kernel-initial.md). +- Inner public kernel proof. + +### Processing Public Function Call + +#### Ensuring the function being called exists in the contract. + +This section follows the same [process](./private-kernel-initial.md#ensuring-the-function-being-called-exists-in-the-contract) as outlined in the initial private kernel circuit. + +#### Ensuring the contract instance being called is deployed. + +It verifies the public deployment of the contract instance by conducting a membership proof, where: + +- The leaf is a nullifier emitting from the deployer contract, computed as _`hash(deployer_address, contract_address)`_, where: + - _deployer_address_ is defined in _[private_inputs](#private-inputs).[public_call](#publiccall).[contract_data](../contract-deployment/instances.md#structure)_. + - _contract_data_ is defined in _[private_inputs](#private-inputs).[public_call](#publiccall).[call_stack_item](#publiccallstackitem)_. +- The index and sibling path are provided in _contract_deployment_membership_witness_ through _[private_inputs](#private-inputs).[public_call](#publiccall)_. +- The root is the _nullifier_tree_root_ in the _[block_header](./private-function.md#blockheader)_ within _[public_inputs](#public-inputs).[constant_data](./private-kernel-initial.md#constantdata)_. + +#### Ensuring the function is legitimate: + +For the _[function_data](./private-kernel-initial.md#functiondata)_ in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem)_, this circuit verifies that: + +- It must be a public function: + - _`function_data.function_type == public`_ + +#### Ensuring the current call matches the call request. + +The top item in the _public_call_requests_ of the _[previous_kernel](#previouskernel)_ must pertain to the current function call. + +This circuit will: + +1. Pop the request from the stack: + + - _`call_request = previous_kernel.public_inputs.transient_accumulated_data.public_call_requests.pop()`_ + +2. Compare the hash with that of the current function call: + + - _`call_request.hash == public_call.call_stack_item.hash()`_ + - The hash of the _call_stack_item_ is computed as: + - _`hash(contract_address, function_data.hash(), public_inputs.hash(), counter_start, counter_end)`_ + - Where _function_data.hash()_ and _public_inputs.hash()_ are the hashes of the serialized field elements. + +#### Ensuring this function is called with the correct context. + +This section follows the same [process](./private-kernel-inner.md#ensuring-this-function-is-called-with-the-correct-context) as outlined in the inner private kernel circuit. + +#### Verifying the public function proof. + +It verifies that the public function was executed with the provided proof data, verification key, and the public inputs of the VM circuit. The result of the execution is specified in the public inputs, which will be used in subsequent steps to enforce the conditions they must satisfy. + +#### Verifying the public inputs of the public function circuit. + +It ensures the public function's intention by checking the following in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_: + +- The _block_header_ must match the one in the _[constant_data](./private-kernel-initial.md#constantdata)_. +- If it is a static call (_`public_inputs.call_context.is_static_call == true`_), it ensures that the function does not induce any state changes by verifying that the following arrays are empty: + - _note_hashes_ + - _nullifiers_ + - _l2_to_l1_messages_ + - _storage_writes_ + +#### Verifying the counters. + +It verifies that each value listed below is associated with a legitimate counter. + +1. For the _[call_stack_item](#privatecallstackitem)_: + + - The _counter_start_ and _counter_end_ must match those in the _call_request_ [popped](#ensuring-the-current-call-matches-the-call-request) from the _public_call_requests_ in a previous step. + +2. For items in each ordered array in _[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_: + + - The counter of the first item must be greater than the _counter_start_ of the current call. + - The counter of each subsequent item must be greater than the counter of the previous item. + - The counter of the last item must be less than the _counter_end_ of the current call. + + The ordered arrays include: + + - _storage_reads_ + - _storage_writes_ + +3. For the last _N_ non-empty requests in _public_call_requests_ within _[public_inputs](#public-inputs).[transient_accumulated_data](#transientaccumulateddata)_: + + - The _counter_end_ of each request must be greater than its _counter_start_. + - The _counter_start_ of the first request must be greater than the _counter_start_ of the _call_stack_item_. + - The _counter_start_ of the second and subsequent requests must be greater than the _counter_end_ of the previous request. + - The _counter_end_ of the last request must be less than the _counter_end_ of the _call_stack_item_. + + > _N_ is the number of non-zero hashes in the _public_call_stack_item_hashes_ in _[private_inputs](#private-inputs).[public_call](#publiccall).[public_inputs](#publicfunctionpublicinputs)_. + +### Validating Public Inputs + +#### Verifying the accumulated data. + +1. It verifies that the following in the _[accumulated_data](#accumulateddata)_ align with their corresponding values in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_. + + - _note_hashes_ + - _nullifiers_ + - _l2_to_l1_messages_ + - _encrypted_logs_hash_ + - _encrypted_log_preimages_length_ + - _old_public_data_tree_snapshot_ + - _new_public_data_tree_snapshot_ + +2. It checks that the hash and the length for **unencrypted** logs are accumulated as follows: + + - _`unencrypted_logs_hash = hash(prev_hash, cur_hash)`_ + - If either hash is zero, the new hash will be `prev_hash | cur_hash` + - _`unencrypted_log_preimages_length = prev_length + cur_length`_ + + Where: + + - _prev_hash_ and _prev_length_ are the values in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./public-kernel-tail.md#public-inputs).[accumulated_data](./public-kernel-tail.md#accumulateddata)_. + - _cur_hash_ and _cur_length_ are the values in _[private_inputs](#private-inputs).[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_. + +#### Verifying the transient accumulated data. + +The _[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_ in this circuit's _[public_inputs](#public-inputs)_ includes values from both the previous iterations and the _[public_call](#publiccall)_. + +For each array in the _transient_accumulated_data_, this circuit verifies that it is populated with the values from the previous iterations, specifically: + +- _`public_inputs.transient_accumulated_data.ARRAY[0..N] == private_inputs.previous_kernel.public_inputs.transient_accumulated_data.ARRAY[0..N]`_ + +> It's important to note that the top item in the _public_call_requests_ from the _previous_kernel_ won't be included, as it has been removed in a [previous step](#ensuring-the-current-call-matches-the-call-request). + +For the subsequent items appended after the values from the previous iterations, they constitute the values from _[private_inputs](#private-inputs).[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_ (_public_function_public_inputs_), and must undergo the following verifications: + +1. Ensure that the specified values in the following arrays match those in the corresponding arrays in the _public_function_public_inputs_: + + - _note_hash_contexts_ + - _value_, _counter_ + - _nullifier_contexts_ + - _value_, _counter_ + - _l2_to_l1_message_contexts_ + - _value_ + - _storage_reads_ + - _value_, _counter_ + - _storage_writes_ + - _value_, _counter_ + +2. For _public_call_requests_: + + - The hashes align with the values in the _public_call_stack_item_hashes_ within _public_function_public_inputs_, but in **reverse** order. + - The _caller_contract_address_ equals the _contract_address_ in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem)_. + - The _caller_context_ aligns with the values in the _call_context_ within _public_function_public_inputs_. + + > It's important that the call requests are arranged in reverse order to ensure they are executed in chronological order. + +3. The _contract_address_ for each non-empty item in the following arrays must equal the _storage_contract_address_ defined in _public_function_public_inputs.call_context_: + + - _note_hash_contexts_ + - _nullifier_contexts_ + - _l2_to_l1_message_contexts_ + - _storage_reads_ + - _storage_writes_ + + > Ensuring the alignment of the contract addresses is crucial, as it is later used to [silo the values](./public-kernel-tail.md#siloing-values) and to establish associations with values within the same contract. + +4. The _portal_contract_address_ for each non-empty item in _l2_to_l1_message_contexts_ must equal the _portal_contract_address_ defined in _public_function_public_inputs.call_context_. + +5. For each _storage_write_ in _storage_writes_, verify that it is associated with an _override_counter_. The value of the _override_counter_ can be: + + - Zero: if the _storage_slot_ does not change later in the same transaction. + - Greater than _storage_write.counter_: if the _storage_slot_ is written again later in the same transaction. + + > Override counters are used in the [tail public kernel circuit](./public-kernel-tail.md) to ensure a read happens **before** the value is changed in a subsequent write. + + > Zero serves as an indicator for an unchanged update, as this value can never act as the counter of a write. + +#### Verifying the constant data. + +This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. + +## Private Inputs + +### _PreviousKernel_ + +The format aligns with the _[PreviousKernel](./private-kernel-tail.md#previouskernel)_ of the tail public kernel circuit. + +### _PublicCall_ + +Data that holds details about the current public function call. + +| Field | Type | Description | +| ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------- | +| _call_stack_item_ | _[PublicCallStackItem](#publiccallstackitem)_ | Information about the current public function call. | +| _proof_ | _Proof_ | Proof of the public function circuit. | +| _vk_ | _VerificationKey_ | Verification key of the public function circuit. | +| _bytecode_hash_ | _field_ | Hash of the function bytecode. | +| _contract_data_ | _[ContractInstance](../contract-deployment/instances.md#structure)_ | Data of the contract instance being called. | +| _contract_class_data_ | _[ContractClassData](./private-kernel-initial.md#contractclassdata)_ | Data of the contract class. | +| _function_leaf_membership_witness_ | _[MembershipWitness](./private-kernel-inner.md#membershipwitness)_ | Membership witness for the function being called. | +| _contract_deployment_membership_witness_ | _[MembershipWitness](./private-kernel-inner.md#membershipwitness)_ | Membership witness for the deployment of the contract being called. | + +## Public Inputs + +The format aligns with the _[Public Inputs](./public-kernel-tail.md#public-inputs)_ of the tail public kernel circuit. + +## Types + +### _PublicCallStackItem_ + +| Field | Type | Description | +| ------------------ | ----------------------------------------------------------- | --------------------------------------------------------- | +| _contract_address_ | _AztecAddress_ | Address of the contract on which the function is invoked. | +| _function_data_ | _[FunctionData](#functiondata)_ | Data of the function being called. | +| _public_inputs_ | _[PublicFunctionPublicInputs](#publicfunctionpublicinputs)_ | Public inputs of the public vm circuit. | +| _counter_start_ | _field_ | Counter at which the function call was initiated. | +| _counter_end_ | _field_ | Counter at which the function call ended. | + +### _PublicFunctionPublicInputs_ + +| Field | Type | Description | +| ---------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- | +| _call_context_ | _[CallContext](./private-function.md#callcontext)_ | Context of the call corresponding to this function execution. | +| _args_hash_ | _field_ | Hash of the function arguments. | +| _return_values_ | [_field_; _C_] | Return values of this function call. | +| _note_hashes_ | [_[NoteHash](./private-function.md#notehash)_; _C_] | New note hashes created in this function call. | +| _nullifiers_ | [_[Nullifier](./private-function.md#nullifier)_; _C_] | New nullifiers created in this function call. | +| _l2_to_l1_messages_ | [_field_; _C_] | New L2 to L1 messages created in this function call. | +| _storage_reads_ | [_[StorageRead](./public-kernel-tail.md#storageread)_; _C_] | Data read from the public data tree. | +| _storage_writes_ | [_[StorageWrite](./public-kernel-tail.md#storagewrite)_; _C_] | Data written to the public data tree. | +| _unencrypted_logs_hash_ | _field_ | Hash of the unencrypted logs emitted in this function call. | +| _unencrypted_log_preimages_length_ | _field_ | Length of the unencrypted log preimages emitted in this function call. | +| _public_call_stack_item_hashes_ | [_field_; _C_] | Hashes of the public function calls initiated by this function. | +| _block_header_ | _[BlockHeader](#blockheader)_ | Information about the trees used for the transaction. | +| _chain_id_ | _field_ | Chain ID of the transaction. | +| _version_ | _field_ | Version of the transaction. | + +> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. diff --git a/yellow-paper/docs/circuits/public-kernel-iterative.md b/yellow-paper/docs/circuits/public-kernel-iterative.md deleted file mode 100644 index 4745ebec840..00000000000 --- a/yellow-paper/docs/circuits/public-kernel-iterative.md +++ /dev/null @@ -1,280 +0,0 @@ -# Public Kernel Circuit - Iterative - -:::info Disclaimer -This is a draft. These requirements need to be considered by the wider team, and might change significantly before a mainnet release. -::: - -## Requirements - -In the public kernel iteration, the process involves taking a previous iteration and public call data, verifying their integrity, and preparing the necessary data for subsequent circuits to operate. - -### Verification of the Previous Iteration - -#### Verifying the previous kernel proof. - -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs. - -The preceding proof can be: - -- [tail private kernel proof](./private-kernel-tail.md). -- Iterative public kernel proof. - -### Processing Public Function Call - -#### Ensuring the contract instance being called is deployed. - -1. The nullifier representing the contract exists in the contract tree. - - - Specifically, this nullifier is the contract address siloed with the address of a precompiled deployment contract. - -2. The contract is listed in the new contracts within the public inputs. - - - The index of this contract in the new contracts array is supplied as a hint through private inputs. - - The counter of the new contract context must be less than the _counter_start_ of the current call. - -#### Ensuring the function being called exists in the contract. - -The contract address contains the contract class ID, which is a hash of the root of its function tree and additional values. This circuit leverages these characteristics to establish the validity of the function's association with the contract address. - -Each leaf of the function tree is a hash representing a function. The preimage includes: - -- Function data. -- Hash of the verification key. -- Hash of the function bytecode. - -To ensure the function's existence, the circuit executes the following steps: - -1. Computes the hash of the verification key. -2. Calculates the function leaf: `hash(...function_data, vk_hash, bytecode_hash)` -3. Derives the function tree root with the leaf and the specified sibling path. -4. Computes the contract class ID using the function tree root and additional information. -5. Generates the contract address using the contract class ID and other relevant details. -6. Validates that the contract address matches the address specified in the call data. - -#### Ensuring the function is legitimate: - -- It must be a public function. - -#### Ensuring the current call matches the call request. - -The top item in the previous iteration's public call requests must pertain to the current function call. - -This circuit will pop the request from the stack, comparing the hash with that of the current function call. - -The preimage of the hash contains: - -- Contract address. -- Function data. -- Public function circuit's public inputs. - -#### Ensuring this function is called with the correct context. - -1. If it is a standard call: - - - The storage contract address of the current iteration must be the same as its contract address. - - The _msg_sender_ of the current iteration must be the same as the caller's contract address. - -2. If it is a delegate call: - - - The caller context in the call request must not be empty. Specifically, the following values of the caller should not be zeros: - - _msg_sender_. - - Storage contract address. - - The _msg_sender_ of the current iteration must equal the caller's _msg_sender_. - - The storage contract address of the current iteration must equal the caller's storage contract address. - - The storage contract address of the current iteration must NOT equal the contract address. - -3. If it is an internal call: - - - The _msg_sender_ of the current iteration must equal the storage contract address. - -#### Verifying the public function proof. - -It verifies that the public function was executed with the provided proof data, verification key, and the public inputs of the VM circuit. The result of the execution is specified in the public inputs, which will be used in subsequent steps to enforce the conditions they must satisfy. - -#### Verifying the public inputs of the public function circuit. - -It ensures the function's intention by checking the following: - -- The contract address for each non-empty item in the following arrays must equal the storage contract address of the current call: - - Note hash contexts. - - Nullifier contexts. - - L2-to-L1 message contexts. - - Read requests. - - Update requests. -- The portal contract address for each non-empty L2-to-L1 message must equal the portal contract address of the current call. - -> Ensuring the alignment of the contract addresses is crucial, as it is later used to silo the value and to establish associations with values within the same contract. - -If it is a static call, it must ensure that the function does not induce any state changes by verifying that the following arrays are empty: - -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. -- Update requests. - -#### Verifying the call requests. - -For the public call requests initiated in the current function call, it ensures that for each request at index _i_: - -- Its hash equals the value at index _i_ within the call request hashes array in public function circuit's public inputs. -- If the hash is not zero, its caller context must align with the call context of the current function call, including: - - _msg_sender_ - - Storage contract address. - -#### Verifying the counters. - -It verifies that each relevant value is associated with a legitimate counter. - -1. For the current call: - - - The _counter_end_ of the current call must be greater than its _counter_start_. - - Both counters must match the ones defined in the top item in the previous iteration's public call requests. - -2. For the public call requests: - - - The _counter_end_ of each request must be greater than its _counter_start_. - - The _counter_start_ of the first request must be greater than the _counter_start_ of the current call. - - The _counter_start_ of the second and subsequent requests must be greater than the _counter_end_ of the previous request. - - The _counter_end_ of the last request must be less than the _counter_end_ of the current call. - -3. For items in each ordered array created in the current call: - - - The counter of the first item much be greater than the _counter_start_ of the current call. - - The counter of each subsequent item much be greater than the counter of the previous item. - - The counter of the last item much be less than the _counter_end_ of the current call. - - The ordered arrays include: - - - Read requests. - - Update requests. - -### Validating Public Inputs - -#### Verifying the accumulated data. - -1. It ensures that the following values match those in the previous iteration's public inputs: - - - Note hashes. - - Nullifiers. - - L2-to-L1 messages. - - New contracts. - - **Encrypted** log hash. - - **Encrypted** log length. - - Old public data tree snapshot. - - New public data tree snapshot. - -2. It checks that the hash and the length for **unencrypted** logs are accumulated as follows: - - - New log hash = `hash(prev_hash, cur_hash)` - - If either hash is zero, the new hash will be `prev_hash | cur_hash` - - New log length = `prev_length + cur_length` - -#### Verifying the transient accumulated data. - -1. It verifies that the following values match the result of combining the values in the previous iteration's public inputs with those in the public function circuit's public inputs: - - - Note hash contexts. - - Nullifier contexts. - - L2-to-L1 message contexts. - - Read requests. - - Update requests. - -2. For the newly added update requests from public function circuit's public inputs, this circuit also checks that each is associated with an override counter, provided as a hint via the private inputs. This override counter can be: - - - Zero: if the slot does not change later in the same transaction. - - Greater than zero: if the slot is updated later in the same transaction. - - It pertains to a subsequent update request altering the same slot. Therefor, the counter value must be greater than the counter of the update request. - - > Override counters are used in the [tail public kernel circuit](./public-kernel-tail.md) to ensure a read happens **before** the value is changed in a later update. - - > Zero serves as an indicator for an unchanged update, as this value can never act as the counter of an update request. It corresponds to the _counter_start_ of the first function call. - -3. It verifies that the public call requests include: - - - All requests from the previous iteration's public inputs except for the top one. - - All requests present in the public call data, appended to the above in **reverse** order. - -#### Verifying the constant data. - -It verifies that the constant data matches the one in the previous iteration's public inputs. - -## Private Inputs - -### Previous Kernel - -The data of the previous kernel iteration: - -- Proof of the kernel circuit. It could be one of the following: - - [Tail private kernel circuit](./private-kernel-tail.md). - - Iterative public kernel circuit. -- Public inputs of the proof. -- Verification key of the kernel circuit. -- Membership witness for the verification key. - -### Public Call Data - -The call data holds details about the current public function call: - -- Contract address. -- Function data. -- Public call requests. -- Public function circuit public inputs. -- Proof of the public function circuit. -- Verification key. -- Hash of the function bytecode. - -### Hints - -Data that aids in the verifications carried out in this circuit or later iterations: - -- Index of the new contract. -- Membership witness for the function leaf. -- Membership witness for the contract leaf. -- Update requests override counters. - -## Public Inputs - -The structure of this public inputs aligns with that of the [tail private kernel circuit](./private-kernel-tail.md) and the [tail public kernel circuit](./public-kernel-tail.md). - -### Constant Data - -These are constants that remain the same throughout the entire transaction: - -- Historical data - representing the states of the block at which the transaction is constructed, including: - - Hash of the global variables. - - Roots of the trees: - - Note hash tree. - - Nullifier tree. - - Contract tree. - - L1-to-l2 message tree. - - Public data tree. -- Transaction context - - A flag indicating whether it is a fee paying transaction. - - A flag indicating whether it is a fee rebate transaction. - - Chain ID. - - Version of the transaction. - -### Accumulated Data - -It contains data accumulated during the execution of the transaction up to this point: - -- Note hashes. -- Nullifiers. -- L2-to-L1 messages. -- New contracts. -- Log hashes. -- Log lengths. -- Old public data tree snapshot. -- New public data tree snapshot. - -### Transient Accumulated Data - -It includes data from the current function call, aggregated with the results from the previous iterations: - -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. -- New contract contexts. -- Read requests. -- Update requests. -- Public call requests. diff --git a/yellow-paper/docs/circuits/public-kernel-tail.md b/yellow-paper/docs/circuits/public-kernel-tail.md index 38fd39a28c0..74176c8e346 100644 --- a/yellow-paper/docs/circuits/public-kernel-tail.md +++ b/yellow-paper/docs/circuits/public-kernel-tail.md @@ -6,59 +6,36 @@ This is a draft. These requirements need to be considered by the wider team, and ## Requirements -The **tail** circuit refrains from processing individual public function calls. Instead, it integrates the results of iterative public kernel circuit and performs additional verification and processing necessary for generating the final public inputs. +The **tail** circuit refrains from processing individual public function calls. Instead, it integrates the results of inner public kernel circuit and performs additional verification and processing necessary for generating the final public inputs. ### Verification of the Previous Iteration #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs. +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. The preceding proof can only be: -- [Iterative public kernel proof](./public-kernel-iterative.md). +- [Inner public kernel proof](./public-kernel-inner.md). #### Ensuring the previous iteration is the last. The following must be empty to ensure all the public function calls are processed: -- Public call requests. +- _public_call_requests_ within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./public-kernel-tail.md#public-inputs).[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_. ### Processing Final Outputs #### Siloing values. -1. It silos the following in the transient accumulated data with each item's contract address: +This section follows the same [process](./private-kernel-tail.md#siloing-values) as outlined in the tail private kernel circuit. - - Note hash contexts. - - Nullifier contexts. +Additionally, it silos the _storage_slot_ of each non-empty item in the following arrays: - The siloed value is computed as: `hash(contract_address, value)`. +- _storage_reads_ +- _storage_writes_ - Siloing with a contract address ensures that data produced by a contract is accurately attributed to the correct contract and cannot be misconstrued as data created in a different contract. - -2. It then applies nonces to the note hashes: - - - The nonce for a note hash is computed as: `hash(first_nullifier, index)`, where: - - `first_nullifier` is the hash of the transaction request. - - `index` is the position of the note hash in the note hashes array in the public inputs. - - Siloing with a nonce guarantees that each final note hash is a unique value in the note hash tree. - -3. It generates the final hashes for L2-L1 messages, calculated as: - - `hash(contract_address, version_id, portal_contract_address, chain_id, message)` - - Where _version_id_ and _portal_contract_address_ equal the values defined in the constant data. - -4. It silos the storage slot of each item in the following array with the item's contract address: - - - Read requests. - - Update requests. - - The siloed storage slot is computed as: `hash(contract_address, storage_slot)`. - -> While siloing could occur in each kernel iteration, it is _typically_ more efficient to be done once in the tail circuit. +The siloed storage slot is computed as: `hash(contract_address, storage_slot)`. #### Verifying ordered arrays. @@ -66,163 +43,193 @@ The iterations of the public kernel may yield values in an unordered state due t This circuit ensures the correct ordering of the following: -- Note hashes. -- Nullifiers. -- L2-to-L1 messages. -- New contracts. -- Read requests. -- Update requests. +- _note_hashes_ +- _nullifiers_ +- _storage_reads_ +- _storage_writes_ -The corresponding _unordered_arrays_ for the above are sourced from the [siloed results](#siloing-values). +1. For _note_hashes_ and _nullifiers_, they undergo the same [process](./private-kernel-tail.md#verifying-ordered-arrays) as outlined in the tail private kernel circuit. With the exception that the loop starts from index _offset + i_, where _offset_ is the number of non-zero values in the _note_hashes_ and _nullifiers_ arrays within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./public-kernel-tail.md#public-inputs).[accumulated_data](./public-kernel-tail.md#accumulateddata)_. -An _ordered_requests_ array and a _hints_ array are provided for every _unordered_array_ via private inputs. +2. For _storage_reads_, an _ordered_storage_reads_ and _storage_read_hints_ are provided as [hints](#hints) through _private_inputs_. This circuit checks that: -For each hint _hints[i]_ at index _i_, locate the item at index _i_ in _ordered_array_: + For each _read_ at index _i_ in _ordered_storage_reads_, the associated _mapped_read_ is at _`storage_reads[storage_read_hints[i]]`_. -- If the item is not empty: - - It must correspond to the item at index _hints[i]_ in _unordered_array_. - - For _i_ != 0, the counter must be greater than the counter of the item at index _hints[i - 1]_ in _unordered_array_. -- If the item is empty: - - All the subsequent items (index >= _i_) must be empty in both _ordered_array_ and _unordered_array_. + - If _`read.is_empty() == false`_, verify that: + - All values in _mapped_read_ align with those in _read_. + - If _i > 0_, verify that: + - _`read.counter > read[storage_read_hints[i - 1]].counter`_ + - Else: + - All the subsequent reads in both _storage_reads_ and _ordered_storage_reads_ must be empty. + +3. For _storage_writes_, an _ordered_storage_writes_ and _storage_write_hints_ are provided as [hints](#hints) through _private_inputs_. The verification is the same as the process for _storage_reads_. #### Verifying public data snaps. -The public data snaps array is provided through private inputs, serving as hints for read requests to prove that the value in the tree aligns with the read operation. For update requests, it substantiates the presence or absence of the storage slot in the public data tree. +The _public_data_snaps_ is provided through _private_inputs_, serving as hints for _storage_reads_ to prove that the value in the tree aligns with the read operation. For _storage_writes_, it substantiates the presence or absence of the storage slot in the public data tree. -A public data snap contains: +A _[public_data_snap](#publicdatasnap)_ contains: -- A leaf in the public data tree, containing the storage slot and its value. -- An override counter, indicating the counter of an update request that overrides the value of the storage slot. Zero if the value is not overridden in this transaction. +- A _storage_slot_ and its _value_. +- An _override_counter_, indicating the counter of the first _storage_write_ that writes to the storage slot. Zero if the storage slot is not written in this transaction. - A flag _exists_ indicating its presence or absence in the public data tree. -This circuit ensures the uniqueness of each snap within the provided public data snaps array. It verifies that the storage slot of each item (except for the one at index 0) must be greater than the storage slot of the previous item in the array. - -> It is crucial for each snap to be unique, as duplicated snaps would disrupt a group of update requests for the same storage slot. This could facilitate the unauthorized act of reading the old value after it has been updated. +This circuit ensures the uniqueness of each snap in _public_data_snaps_. It verifies that: -#### Grouping update requests. +For each snap at index _i_, where _i_ > 0: -To facilitate the verification of read requests and streamline update requests, it is imperative to establish connections between update requests targeting the same storage slot. Furthermore, the first update request in a group must be linked to a public data snap, ensuring the dataset has progressed from the right initial state. +- If _snap.is_empty() == false_ + - _`snap.storage_slot > public_data_snaps[i - 1].storage_slot`_ -A new field, _prev_counter_, is introduced to the ordered update requests to indicate whether each request possesses a previous snap or update request. Another field, _exists_, is also added to signify the presence or absence of the storage slot in the tree. +> It is crucial for each snap to be unique, as duplicated snaps would disrupt a group of writes for the same storage slot. This could enable the unauthorized act of reading the old value after it has been updated. -1. For each non-empty public data snap: +#### Grouping storage writes. - - Skip the remaining steps if its override counter is _0_. - - Locate the request within the update requests using an index provided as a hint through private inputs. - - Verify that the storage slot of the request matches the storage slot of the snap. - - Verify that the counter of the request matches the override counter of the snap. - - Ensure that the _prev_counter_ of the request is _0_. - - Set the _prev_counter_ of the request to _1_. - - Set the _exists_ flag of the request to be the same as the snap. +To facilitate the verification of _storage_reads_ and streamline _storage_writes_, it is imperative to establish connections between writes targeting the same storage slot. Furthermore, the first write in a group must be linked to a _public_data_snap_, ensuring the dataset has progressed from the right initial state. - > The value _1_ can be utilized to signify a public data snap, as this value can never serve as the counter of an update request. The _counter_start_ for the first public function call must be greater than or equal to 1. Subsequently, the counters for all subsequent function calls and requests should exceed this initial value. +A new field, _prev_counter_, is incorporated to the _ordered_storage_writes_ to indicate whether each write has a preceding snap or write. Another field, _exists_, is also added to signify the presence or absence of the storage slot in the tree. -2. For each non-empty update request, +1. For each _snap_ at index _i_ in _public_data_snaps_: - - Skip the remaining steps if its override counter is _0_. - - Locate the request within the update requests using an index provided as a hint through private inputs. - - Verify that the storage slot of the request matches the storage slot of the current request. - - Verify that the counter of the request matches the override counter of the current request. - - Ensure that the _prev_counter_ of the request is _0_. - - Set the _prev_counter_ of the request to the counter of the current request. - - Set the _exists_ flag of the request to be the same as the current request. + - Skip the remaining steps if it is empty or if its _override_counter_ is _0_. + - Locate the _write_ at _`ordered_storage_writes[storage_write_indices[i]]`_. + - Verify the following: + - _`write.storage_slot == snap.storage_slot`_ + - _`write.counter == snap.override_counter`_ + - _`write.prev_counter == 0`_ + - Update the hints in _write_: + - _`write.prev_counter = 1`_ + - _`write.exists = snap.exists`_ -3. Following the previous two steps, verify that all non-empty update requests have a non-zero _prev_counter_. + > The value _1_ can be utilized to signify a preceding _snap_, as this value can never serve as the counter of a _storage_write_. Because the _counter_start_ for the first public function call must be 1, the counters for all subsequent side effects should exceed this initial value. -#### Verifying read requests. +2. For each _write_ at index _i_ in _ordered_storage_writes_: -A read request can be reading: + - Skip the remaining steps if its _next_counter_ is _0_. + - Locate the _next_write_ at _`ordered_storage_writes[next_storage_write_indices[i]]`_. + - Verify the following: + - _`write.storage_slot == next_write.storage_slot`_ + - _`write.next_counter == next_write.counter`_ + - _`write.prev_counter == 0`_ + - Update the hints in _next_write_: + - _`next_write.prev_counter = write.counter`_ + - _`next_write.exists = write.exists`_ -- An updated value: initialized or updated in the current transaction. The value being read is in an update request. -- An existing value: initialized or updated in a prior successful transaction. The value being read is the value in the public data tree. -- An uninitialized value: not initialized yet. The read request is reading the value zero. There isn't a leaf in the public data tree representing its storage slot, nor in the update requests. +3. Following the previous two steps, verify that all non-empty writes in _ordered_storage_writes_ have a non-zero _prev_counter_. -For each non-empty read request, it must satisfy one of the following conditions: +#### Verifying storage reads. -1. If reading an updated value, the value is in an update request: +A storage read can be reading: - - Locates the update request within the update requests. - - Its index in the update requests array is provided as a hint through private inputs. - - The storage slot and value of the read request must match those of the update request. - - The counter of the update request must be less than the counter of the read request. - - The override counter of the update request must be zero or greater than the counter of the read request. +- An uninitialized storage slot: the value is zero. There isn't a leaf in the public data tree representing its storage slot, nor in the _storage_writes_. +- An existing storage slot: written in a prior successful transaction. The value being read is the value in the public data tree. +- An updated storage slot: initialized or updated in the current transaction. The value being read is in a _storage_write_. - > A zero override counter indicates that the value is not overridden in the transaction. +For each non-empty _read_ at index _i_ in _ordered_storage_reads_, it must satisfy one of the following conditions: -2. If reading an existing or an uninitialized value, the value is in a public data snap: +1. If reading an uninitialized or an existing storage slot, the value is in a _snap_: - - Locate the snap within the public data snaps. - - Its index in the public data snaps array is provided as a hint through private inputs. - - The storage slot and value of the read request must match those of the snap. - - The override counter of the snap must be zero or greater than the counter of the read request. + - Locate the _snap_ at _`public_data_snaps[persistent_read_hints[i]]`_. + - Verify the following: + - _`read.storage_slot == snap.storage_slot`_ + - _`read.value == snap.value`_ + - _`(read.counter < snap.override_counter) | (snap.override_counter == 0)`_ + - If _`snap.exists == false`_: + - _`read.value == 0`_ Depending on the value of the _exists_ flag in the snap, verify its presence or absence in the public data tree: - If _exists_ is true: - It must pass a membership check on the leaf. - If _exists_ is false: - - The value must be zero. - - It must pass a non-membership check on the low leaf. + - It must pass a non-membership check on the low leaf. The preimage of the low leaf is at _`storage_read_low_leaf_preimages[i]`_. + + > The (non-)membership checks are executed against the root in _old_public_data_tree_snapshot_. The membership witnesses for the leaves are in _storage_read_membership_witnesses_, provided as [hints](#hints) through _private_inputs_. + +2. If reading an updated storage slot, the value is in a _storage_write_: + + - Locates the _storage_write_ at _`ordered_storage_writes[transient_read_hints[i]]`_. + - Verify the following: + - _`read.storage_slot == storage_write.storage_slot`_ + - _`read.value == storage_write.value`_ + - _`read.counter > storage_write.counter`_ + - _`(read.counter < storage_write.next_counter) | (storage_write.next_counter == 0)`_ - > The membership checks are executed against the root in **old** public data tree snapshot, as defined in the public inputs. The membership witnesses for the leaves and the low leaves are provided as hints through private inputs. + > A zero _next_counter_ indicates that the value is not written again in the transaction. #### Updating the public data tree. -It updates the current public data tree with the update requests. For each non-empty request in the **ordered** and **siloed** update requests array, the circuit processes it base on its type: +It updates the public data tree with the values in _storage_writes_. The _latest_root_ of the tree is _old_public_data_tree_snapshot.root_. -1. Transient update. +For each non-empty _write_ at index _i_ in _ordered_storage_writes_, the circuit processes it base on its type: - If the override counter of a request is not zero, the value is overridden by another update request that occurs later in the same transaction. This transient value can be ignored as the final state of the tree won't be affected by it. +1. Transient write. + + If _`write.next_counter != 0`_, the same storage slot is written again by another storage write that occurs later in the same transaction. This transient _write_ can be ignored as the final state of the tree won't be affected by it. 2. Updating an existing storage slot. - For a non-transient update request, if the _exists_ flag is true, it is updating an existing storage slot. The circuit does the following for such an update: + For a non-transient _write_ (_write.next_counter == 0_), if _`write.exists == true`_, it is updating an existing storage slot. The circuit does the following for such a write: - Performs a membership check, where: - - The leaf contains the existing storage slot. - - The leaf's old value and the sibling path are provided as hints through private inputs. - - The root is the latest root after processing the previous request. - - Derives the new latest root with the new value in the leaf. + - The leaf if for the existing storage slot. + - _`leaf.storage_slot = write.storage_slot`_ + - The old value is the value in a _snap_: + - _`leaf.value = public_data_snaps[public_data_snap_indices[i]].value`_ + - The index and the sibling path are in _storage_write_membership_witnesses_, provided as [hints](#hints) through _private_inputs_. + - The root is the _latest_root_ after processing the previous write. + - Derives the _latest_root_ for the _latest_public_data_tree_ with the updated leaf, where _`leaf.value = write.value`_. 3. Creating a new storage slot. - For a non-transient update request, if the _exists_ flag is false, it is inserting to a new storage slot. The circuit adds it to a subtree: + For a non-transient _write_ (_write.next_counter == 0_), if _`write.exists == false`_, it is initializing a storage slot. The circuit adds it to a subtree: + + - Perform a membership check on the low leaf in the _latest_public_data_tree_ and in the subtree. One check must succeed. + - The low leaf preimage is at _storage_write_low_leaf_preimages[i]_. + - The membership witness for the public data tree is at _storage_write_membership_witnesses[i]_. + - The membership witness for the subtree is at _subtree_membership_witnesses[i]_. + - The above are provided as [hints](#hints) through _private_inputs_. + - Update the low leaf to point to the new leaf: + - _`low_leaf.next_slot = write.storage_slot`_ + - _`low_leaf.next_index = old_public_data_tree_snapshot.next_available_leaf_index + number_of_new_leaves`_ + - If the low leaf is in the _latest_public_data_tree_, derive the _latest_root_ from the updated low leaf. + - If the low leaf is in the subtree, derive the _subtree_root_ from the updated low leaf. + - Append the new leaf to the subtree. Derive the _subtree_root_. + - Increment _number_of_new_leaves_ by 1. - - Perform a membership check on the low leaf in the latest public data tree or the subtree. - - The leaf preimage and its membership witness are provided as hints through private inputs. - - Update the low leaf to point to the new leaf containing the new storage slot. - - The low leaf could be in the public data tree or the subtree. - - Append the new leaf to the subtree. +> The subtree and _number_of_new_leaves_ are initialized to empty and 0 at the beginning of the process. -After all the update requests are processed: +After all the storage writes are processed: - Batch insert the subtree to the public data tree. - - The insertion index is the index in the **old** public data tree snapshot. -- Verify that the latest root matches the root in the **new** public data tree snapshot in the public inputs. -- Verify that the index in the **new** public data tree snapshot equals the index in the **old** public data tree snapshot plus the number of the new leaves appended to the subtree. + - The insertion index is _`old_public_data_tree_snapshot.next_available_leaf_index`_. +- Verify the following: + - _`latest_root == new_public_data_tree_snapshot.root`_ + - _`new_public_data_tree_snapshot.next_available_leaf_index == old_public_data_tree_snapshot.next_available_leaf_index + number_of_new_leaves`_ ### Validating Public Inputs #### Verifying the accumulated data. -1. The following must align with the results after ordering, as verified in a [previous step](#verifying-ordered-arrays): +1. The following must align with the results after siloing, as verified in a [previous step](#siloing-values): - - Note hashes. - - Nullifiers. - - L2-to-L1 messages. - - New contracts. + - _l2_to_l1_messages_ - > Note that these are arrays of siloed values or relevant data. Attributes aiding verification and siloing only exist in the corresponding types in the transient accumulated data. +2. The following must align with the results after ordering, as verified in a [previous step](#verifying-ordered-arrays): -2. The following must match the respective values in the previous kernel's public inputs: + - _note_hashes_ + - _nullifiers_ - - Log hashes. - - Log lengths. +3. The following must match the respective values within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[accumulated_data](./private-kernel-initial.md#accumulateddata)_: -3. The following is referenced and verified in a [previous step](#updating-the-public-data-tree): + - _encrypted_logs_hash_ + - _unencrypted_logs_hash_ + - _encrypted_log_preimages_length_ + - _unencrypted_log_preimages_length_ - - Old public data tree snapshot. - - New public data tree snapshot. +4. The following is referenced and verified in a [previous step](#updating-the-public-data-tree): + + - _old_public_data_tree_snapshot_ + - _new_public_data_tree_snapshot_ #### Verifying the transient accumulated data. @@ -230,83 +237,142 @@ It ensures that the transient accumulated data is empty. #### Verifying the constant data. -It verifies that the constant data matches the one in the previous iteration's public inputs. +This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. ## Private Inputs -### Previous Kernel - -The data of the previous kernel iteration: +### _PreviousKernel_ -- Proof of the kernel circuit. It must be: - - [Iterative public kernel circuit](./public-kernel-iterative.md). -- Public inputs of the proof. -- Verification key of the circuit. -- Membership witness for the verification key. +| Field | Type | Description | +| -------------------- | -------------------------------------------------------------------- | -------------------------------------------- | +| _public_inputs_ | _[PrivateKernelPublicInputs](#public-inputs)_ | Public inputs of the proof. | +| _proof_ | _Proof_ | Proof of the kernel circuit. | +| _vk_ | _VerificationKey_ | Verification key of the kernel circuit. | +| _membership_witness_ | _[MembershipWitness](./private-kernel-initial.md#membershipwitness)_ | Membership witness for the verification key. | -### Hints +### _Hints_ Data that aids in the verifications carried out in this circuit: -- Sorted indices of read requests. -- Ordered read requests. -- Sorted indices of update requests. -- Ordered update requests. -- Public data snaps. -- Indices of update requests for public data snaps. -- Indices of update requests for transient update requests. -- Hints for read requests, including: - - A flag indicating whether it's reading an update request or a leaf in the public data tree. - - Index of the update request or a public data snap. - - Membership witness. -- Indices of update requests for transient updates. -- Membership witnesses for update requests. -- Membership witnesses of low leaves in public data tree for update requests. -- Membership witnesses of low leaves in subtree for update requests. +| Field | Type | Description | +| ------------------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| _note_hash_indices_ | [_field_; _C_] | Indices of _note_hashes_ for _note_hash_contexts_. _C_ equals the length of _note_hashes_. | +| _note_hash_hints_ | [_field_; _C_] | Indices of _note_hash_contexts_ for ordered _note_hashes_. _C_ equals the length of _note_hash_contexts_. | +| _nullifier_hints_ | [_field_; _C_] | Indices of _nullifier_contexts_ for ordered _nullifiers_. _C_ equals the length of _nullifier_contexts_. | +| _ordered_storage_reads_ | [_[StorageReadContext](#storagereadcontext)_; _C_] | Ordered _storage_reads_. _C_ equals the length of _storage_reads_. | +| _storage_read_hints_ | [_field_; _C_] | Indices of reads for _ordered_storage_reads_. _C_ equals the length of _storage_reads_. | +| _ordered_storage_writes_ | [_[StorageWriteContext](#storagewritecontext)_; _C_] | Ordered _storage_writes_. _C_ equals the length of _storage_writes_. | +| _storage_write_hints_ | [_field_; _C_] | Indices of writes for _ordered_storage_writes_. _C_ equals the length of _storage_writes_. | +| _public_data_snaps_ | [_[PublicDataSnap](#publicdatasnap)_; _C_] | Data that aids verification of storage reads and writes. _C_ equals the length of _ordered_storage_writes_ + _ordered_storage_reads_. | +| _storage_write_indices_ | [_field_; _C_] | Indices of _ordered_storage_writes_ for _public_data_snaps_. _C_ equals the length of _public_data_snaps_. | +| _transient_read_hints_ | [_field_; _C_] | Indices of _ordered_storage_writes_ for transient reads. _C_ equals the length of _ordered_storage_reads_. | +| _persistent_read_hints_ | [_field_; _C_] | Indices of _ordered_storage_writes_ for persistent reads. _C_ equals the length of _ordered_storage_reads_. | +| _public_data_snap_indices_ | [_field_; _C_] | Indices of _public_data_snaps_ for persistent write. _C_ equals the length of _ordered_storage_writes_. | +| _storage_read_low_leaf_preimages_ | [_[PublicDataLeafPreimage](#publicdataleafpreimage)_; _C_] | Preimages for public data leaf. _C_ equals the length of _ordered_storage_writes_. | +| _storage_read_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _C_] | Membership witnesses for persistent reads. _C_ equals the length of _ordered_storage_writes_. | +| _storage_write_low_leaf_preimages_ | [_[PublicDataLeafPreimage](#publicdataleafpreimage)_; _C_] | Preimages for public data. _C_ equals the length of _ordered_storage_writes_. | +| _storage_write_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _C_] | Membership witnesses for public data tree. _C_ equals the length of _ordered_storage_writes_. | +| _subtree_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _C_] | Membership witnesses for the public data subtree. _C_ equals the length of _ordered_storage_writes_. | ## Public Inputs -The structure of this public inputs aligns with that of the [tail private kernel circuit](./private-kernel-tail.md) and the [iterative public kernel circuit](./public-kernel-iterative.md). - -### Accumulated Data - -It contains data accumulated during the execution of the entire transaction: - -- Note hashes. -- Nullifiers. -- L2-to-L1 messages. -- New contracts. -- Log hashes. -- Log lengths. -- Old public data tree snapshot. -- New public data tree snapshot. - -### Constant Data - -These are constants that remain the same throughout the entire transaction: - -- Historical data - representing the states of the block at which the transaction is constructed, including: - - Hash of the global variables. - - Roots of the trees: - - Note hash tree. - - Nullifier tree. - - Contract tree. - - L1-to-l2 message tree. - - Public data tree. -- Transaction context - - A flag indicating whether it is a fee paying transaction. - - A flag indicating whether it is a fee rebate transaction. - - Chain ID. - - Version of the transaction. - -### Transient Accumulated Data - -It includes data that aids in processing each kernel iteration. They must be empty for this circuit. - -- Note hash contexts. -- Nullifier contexts. -- L2-to-L1 message contexts. -- New contract contexts. -- Read requests. -- Update requests. -- Public call requests. +### _ConstantData_ + +These are constants that remain the same throughout the entire transaction. Its format aligns with the _[ConstantData](./private-kernel-initial.md#constantdata)_ of the initial private kernel circuit. + +### _AccumulatedData_ + +Data accumulated during the execution of the transaction. + +| Field | Type | Description | +| ---------------------------------- | ------------------------------- | ----------------------------------------------------------- | +| _note_hashes_ | [_field_; _C_] | Note hashes created in the transaction. | +| _nullifiers_ | [_field_; _C_] | Nullifiers created in the transaction. | +| _l2_to_l1_messages_ | [_field_; _C_] | L2-to-L1 messages created in the transaction. | +| _encrypted_logs_hash_ | _field_ | Hash of the accumulated encrypted logs. | +| _unencrypted_logs_hash_ | _field_ | Hash of the accumulated unencrypted logs. | +| _encrypted_log_preimages_length_ | _field_ | Length of the accumulated encrypted log preimages. | +| _unencrypted_log_preimages_length_ | _field_ | Length of the accumulated unencrypted log preimages. | +| _old_public_data_tree_snapshot_ | _[TreeSnapshot](#treesnapshot)_ | Snapshot of the public data tree prior to this transaction. | +| _new_public_data_tree_snapshot_ | _[TreeSnapshot](#treesnapshot)_ | Snapshot of the public data tree after this transaction. | + +> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. + +### _TransientAccumulatedData_ + +| Field | Type | Description | +| --------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------ | +| _note_hash_contexts_ | [_[NoteHashContext](./private-kernel-initial.md#notehashcontext)_; _C_] | Note hashes with extra data aiding verification. | +| _nullifier_contexts_ | [_[NullifierContext](./private-kernel-initial.md#nullifiercontext)_; _C_] | Nullifiers with extra data aiding verification. | +| _l2_to_l1_message_contexts_ | [_[L2toL1MessageContext](./private-kernel-initial.md#l2tol1messagecontext)_; _C_] | L2-to-l1 messages with extra data aiding verification. | +| _storage_reads_ | [_[StorageRead](#storageread)_; _C_] | Reads of the public data. | +| _storage_writes_ | [_[StorageWrite](#storagewrite)_; _C_] | Writes of the public data. | +| _public_call_requests_ | [_[CallRequest](./private-kernel-initial.md#callrequest)_; _C_] | Requests to call publics functions. | + +> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. + +## Types + +### _TreeSnapshot_ + +| Field | Type | Description | +| --------------------------- | ----- | --------------------------------- | +| _root_ | field | Root of the tree. | +| _next_available_leaf_index_ | field | The index to insert new value to. | + +### _StorageRead_ + +| Field | Type | Description | +| ------------------ | -------------- | ----------------------------------- | +| _contract_address_ | _AztecAddress_ | Address of the contract. | +| _storage_slot_ | field | Storage slot. | +| _value_ | field | Value read from the storage slot. | +| _counter_ | _field_ | Counter at which the read happened. | + +### _StorageWrite_ + +| Field | Type | Description | +| ------------------ | -------------- | -------------------------------------- | +| _contract_address_ | _AztecAddress_ | Address of the contract. | +| _storage_slot_ | field | Storage slot. | +| _value_ | field | New value written to the storage slot. | +| _counter_ | _field_ | Counter at which the write happened. | + +### _StorageReadContext_ + +| Field | Type | Description | +| ------------------ | -------------- | ----------------------------------- | +| _contract_address_ | _AztecAddress_ | Address of the contract. | +| _storage_slot_ | field | Storage slot. | +| _value_ | field | Value read from the storage slot. | +| _counter_ | _field_ | Counter at which the read happened. | + +### _StorageWriteContext_ + +| Field | Type | Description | +| ------------------ | -------------- | ---------------------------------------------------------------------- | +| _contract_address_ | _AztecAddress_ | Address of the contract. | +| _storage_slot_ | field | Storage slot. | +| _value_ | field | New value written to the storage slot. | +| _counter_ | _field_ | Counter at which the write happened. | +| _prev_counter_ | _field_ | Counter of the previous write to the storage slot. | +| _next_counter_ | _field_ | Counter of the next write to the storage slot. | +| _exists_ | _bool_ | A flag indicating whether the storage slot is in the public data tree. | + +### _PublicDataSnap_ + +| Field | Type | Description | +| ------------------ | ------- | ------------------------------------------------------------------------ | +| _storage_slot_ | field | Storage slot. | +| _value_ | field | Value of the storage slot. | +| _override_counter_ | _field_ | Counter at which the _storage_slot_ is first written in the transaction. | +| _exists_ | _bool_ | A flag indicating whether the storage slot is in the public data tree. | + +### _PublicDataLeafPreimage_ + +| Field | Type | Description | +| -------------- | ------- | ------------------------------ | +| _storage_slot_ | field | Storage slot. | +| _value_ | field | Value of the storage slot. | +| _next_slot_ | _field_ | Storage slot of the next leaf. | +| _next_index_ | _field_ | Index of the next leaf. |