diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 6026b49142b2..f0fbd9ff541c 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -114,7 +114,7 @@ library Constants { uint256 internal constant PARTIAL_STATE_REFERENCE_LENGTH = 8; uint256 internal constant PRIVATE_CALL_STACK_ITEM_LENGTH = 223; uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 218; - uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 195; + uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 199; uint256 internal constant STATE_REFERENCE_LENGTH = 10; uint256 internal constant TX_CONTEXT_DATA_LENGTH = 11; uint256 internal constant TX_REQUEST_LENGTH = 17; diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index dffe714edb0a..15f12340fe21 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -456,6 +456,7 @@ impl PrivateContext { call_context: reader.read_struct(CallContext::deserialize), args_hash: reader.read(), return_values: [0; RETURN_VALUES_LENGTH], + nullifier_read_requests: [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL], contract_storage_update_requests: [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL], contract_storage_reads: [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL], public_call_stack_hashes: [0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL], diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index f8816f231790..11a5aac333e0 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -7,7 +7,7 @@ use dep::protocol_types::{ global_variables::GlobalVariables, function_selector::FunctionSelector, private_circuit_public_inputs::PrivateCircuitPublicInputs, public_call_stack_item::PublicCallStackItem, - public_circuit_public_inputs::PublicCircuitPublicInputs, + public_circuit_public_inputs::PublicCircuitPublicInputs, read_request::ReadRequest, side_effect::{SideEffect, SideEffectLinkedToNoteHash} }, address::{AztecAddress, EthAddress}, @@ -15,7 +15,7 @@ use dep::protocol_types::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, - NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, hash::hash_args, header::Header, messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader @@ -28,6 +28,7 @@ struct PublicContext { args_hash : Field, return_values : BoundedVec, + nullifier_read_requests: BoundedVec, contract_storage_update_requests: BoundedVec, contract_storage_reads: BoundedVec, public_call_stack_hashes: BoundedVec, @@ -95,13 +96,12 @@ impl ContextInterface for PublicContext { impl PublicContext { pub fn new(inputs: PublicContextInputs, args_hash: Field) -> PublicContext { - let empty_storage_read = StorageRead::empty(); - let empty_storage_update = StorageUpdateRequest::empty(); PublicContext { inputs, side_effect_counter: inputs.call_context.start_side_effect_counter, args_hash, return_values: BoundedVec::new(), + nullifier_read_requests: BoundedVec::new(), contract_storage_update_requests: BoundedVec::new(), contract_storage_reads: BoundedVec::new(), public_call_stack_hashes: BoundedVec::new(), @@ -147,6 +147,7 @@ impl PublicContext { let pub_circuit_pub_inputs = PublicCircuitPublicInputs { call_context: self.inputs.call_context, // Done args_hash: self.args_hash, // Done + nullifier_read_requests: self.nullifier_read_requests.storage, contract_storage_update_requests: self.contract_storage_update_requests.storage, contract_storage_reads: self.contract_storage_reads.storage, return_values: self.return_values.storage, @@ -163,6 +164,12 @@ impl PublicContext { pub_circuit_pub_inputs } + pub fn push_nullifier_read_request(&mut self, nullifier: Field) { + let request = ReadRequest { value: nullifier, counter: self.side_effect_counter }; + self.nullifier_read_requests.push(request); + self.side_effect_counter = self.side_effect_counter + 1; + } + pub fn message_portal(&mut self, recipient: EthAddress, content: Field) { let message = L2ToL1Message { recipient, content }; self.new_l2_to_l1_msgs.push(message); diff --git a/noir-projects/noir-protocol-circuits/Nargo.toml b/noir-projects/noir-protocol-circuits/Nargo.toml index 749f1ecda0f0..3be5d5293946 100644 --- a/noir-projects/noir-protocol-circuits/Nargo.toml +++ b/noir-projects/noir-protocol-circuits/Nargo.toml @@ -15,6 +15,8 @@ members = [ "crates/public-kernel-app-logic-simulated", "crates/public-kernel-teardown", "crates/public-kernel-teardown-simulated", + "crates/public-kernel-tail", + "crates/public-kernel-tail-simulated", "crates/rollup-lib", "crates/rollup-merge", "crates/rollup-base", diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/Nargo.toml index 4132c7e2b182..2782f8d7b312 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/Nargo.toml +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/Nargo.toml @@ -6,3 +6,4 @@ compiler_version = ">=0.18.0" [dependencies] types = { path = "../types" } +reset_kernel_lib = { path = "../reset-kernel-lib" } diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr index d05adaea185d..e892008b41a9 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr @@ -8,5 +8,3 @@ use private_kernel_tail::PrivateKernelTailCircuitPrivateInputs; // TODO: rename to be precise as to what its common to. mod common; -mod read_request_reset; -mod nullifier_read_request_reset; diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr index a5d939506c93..b6d6de9fab38 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr @@ -1,8 +1,6 @@ -use crate::{ - common, nullifier_read_request_reset::NullifierReadRequestResetHints, - read_request_reset::reset_read_requests -}; +use crate::common; use dep::std::{cmp::Eq, option::Option, unsafe}; +use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests}; use dep::types::{ abis::{ call_request::CallRequest, nullifier_key_validation_request::NullifierKeyValidationRequestContext, @@ -109,11 +107,7 @@ impl PrivateKernelTailCircuitPrivateInputs { public_inputs.end.note_hash_read_requests = BoundedVec::new(); } - fn assert_sorted_counters( - original: [T; N], - sorted: [T; N], - indexes: [u64; N] - ) where T: Eq + Ordered + Empty { + fn assert_sorted_counters(original: [T; N], sorted: [T; N], indexes: [u64; N]) where T: Eq + Ordered + Empty { let mut prev_was_empty = false; for i in 0..N { @@ -260,9 +254,9 @@ impl PrivateKernelTailCircuitPrivateInputs { mod tests { use dep::std::{cmp::Eq, unsafe}; - use crate::{ - nullifier_read_request_reset::NullifierReadRequestResetHintsBuilder, - private_kernel_tail::PrivateKernelTailCircuitPrivateInputs, + use crate::{private_kernel_tail::PrivateKernelTailCircuitPrivateInputs}; + use dep::reset_kernel_lib::{ + NullifierReadRequestResetHintsBuilder, read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::constants::{ diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/Nargo.toml index 669bac30a780..6806f1835976 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/Nargo.toml +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/Nargo.toml @@ -6,3 +6,4 @@ compiler_version = ">=0.18.0" [dependencies] types = { path = "../types" } +reset_kernel_lib = { path = "../reset-kernel-lib" } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr index 31378ed7a20a..d0a97da56def 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr @@ -1,20 +1,19 @@ use dep::types::{ abis::{ call_request::CallRequest, public_call_stack_item::PublicCallStackItem, - accumulated_data::{CombinedAccumulatedData, CombinedAccumulatedDataBuilder}, kernel_circuit_public_inputs::PublicKernelCircuitPublicInputsBuilder, new_contract_data::NewContractData, kernel_data::{PrivateKernelTailData, PublicKernelData}, - public_call_data::PublicCallData, public_circuit_public_inputs::PublicCircuitPublicInputs, - public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest, + public_call_data::PublicCallData, public_data_read::PublicDataRead, + public_data_update_request::PublicDataUpdateRequest, read_request::ReadRequestContext, side_effect::{SideEffect, SideEffectLinkedToNoteHash} }, address::AztecAddress, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, constants::{ MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, - MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256, MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, - MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256, + MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX }, hash::{silo_note_hash, silo_nullifier, compute_l2_to_l1_hash, accumulate_sha256}, @@ -47,44 +46,56 @@ pub fn initialize_reverted_flag( circuit_outputs.reverted = previous_kernel.public_inputs.reverted | public_call.call_stack_item.public_inputs.reverted; } -pub fn initialize_end_values( +// Initialises the circuit outputs with the end state of the previous iteration. +// Skips data that will be checked and cleared in the tail circuit. +pub fn initialize_emitted_end_values( previous_kernel: PublicKernelData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder ) { - // Initialises the circuit outputs with the end state of the previous iteration circuit_outputs.constants = previous_kernel.public_inputs.constants; - // Ensure the arrays are the same as previously, before we start pushing more data onto them in other - // functions within this circuit: - let start = previous_kernel.public_inputs.end; - if circuit_outputs.reverted == false { + let start = previous_kernel.public_inputs.end; circuit_outputs.end.new_note_hashes = array_to_bounded_vec(start.new_note_hashes); circuit_outputs.end.new_nullifiers = array_to_bounded_vec(start.new_nullifiers); - - circuit_outputs.end.private_call_stack = array_to_bounded_vec(start.private_call_stack); - circuit_outputs.end.public_call_stack = array_to_bounded_vec(start.public_call_stack); circuit_outputs.end.new_l2_to_l1_msgs = array_to_bounded_vec(start.new_l2_to_l1_msgs); - circuit_outputs.end.public_data_update_requests = array_to_bounded_vec(start.public_data_update_requests); + // TODO - should be propagated only in initialize_end_values() and clear them in the tail circuit. circuit_outputs.end.public_data_reads = array_to_bounded_vec(start.public_data_reads); - - // Public kernel does not modify encrypted logs values --> we just copy them to output + circuit_outputs.end.unencrypted_logs_hash = start.unencrypted_logs_hash; + circuit_outputs.end.unencrypted_log_preimages_length = start.unencrypted_log_preimages_length; circuit_outputs.end.encrypted_logs_hash = start.encrypted_logs_hash; circuit_outputs.end.encrypted_log_preimages_length = start.encrypted_log_preimages_length; - circuit_outputs.end.new_contracts = array_to_bounded_vec(previous_kernel.public_inputs.end.new_contracts); } let start_non_revertible = previous_kernel.public_inputs.end_non_revertible; circuit_outputs.end_non_revertible.new_note_hashes = array_to_bounded_vec(start_non_revertible.new_note_hashes); circuit_outputs.end_non_revertible.new_nullifiers = array_to_bounded_vec(start_non_revertible.new_nullifiers); - circuit_outputs.end_non_revertible.public_call_stack = array_to_bounded_vec(start_non_revertible.public_call_stack); - circuit_outputs.end_non_revertible.public_data_update_requests = array_to_bounded_vec(start_non_revertible.public_data_update_requests); circuit_outputs.end_non_revertible.public_data_reads = array_to_bounded_vec(start_non_revertible.public_data_reads); } +// Initialises the circuit outputs with the end state of the previous iteration. +// Includes data that will be checked and cleared in the tail circuit. +pub fn initialize_end_values( + previous_kernel: PublicKernelData, + circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder +) { + initialize_emitted_end_values(previous_kernel, circuit_outputs); + + if circuit_outputs.reverted == false { + let start = previous_kernel.public_inputs.end; + // circuit_outputs.end.private_call_stack = array_to_bounded_vec(start.private_call_stack); // This is enforced in the private tail to always be empty. + circuit_outputs.end.public_call_stack = array_to_bounded_vec(start.public_call_stack); + circuit_outputs.end.nullifier_read_requests = array_to_bounded_vec(start.nullifier_read_requests); + } + + let start_non_revertible = previous_kernel.public_inputs.end_non_revertible; + circuit_outputs.end_non_revertible.public_call_stack = array_to_bounded_vec(start_non_revertible.public_call_stack); + circuit_outputs.end_non_revertible.nullifier_read_requests = array_to_bounded_vec(start_non_revertible.nullifier_read_requests); +} + fn perform_static_call_checks(public_call: PublicCallData) { let public_inputs = public_call.call_stack_item.public_inputs; if public_inputs.call_context.is_static_call { @@ -154,6 +165,7 @@ pub fn update_public_end_non_revertible_values( validate_call_requests(public_call_requests, hashes, public_call); circuit_outputs.end_non_revertible.public_call_stack.extend_from_bounded_vec(public_call_requests); + propagate_nullifier_read_requests_non_revertible(public_call, circuit_outputs); propagate_new_nullifiers_non_revertible(public_call, circuit_outputs); propagate_new_note_hashes_non_revertible(public_call, circuit_outputs); propagate_valid_non_revertible_public_data_update_requests(public_call, circuit_outputs); @@ -174,6 +186,7 @@ pub fn update_public_end_values(public_call: PublicCallData, circuit_outputs: &m validate_call_requests(public_call_requests, hashes, public_call); circuit_outputs.end.public_call_stack.extend_from_bounded_vec(public_call_requests); + propagate_nullifier_read_requests_revertible(public_call, circuit_outputs); propagate_new_nullifiers(public_call, circuit_outputs); propagate_new_note_hashes(public_call, circuit_outputs); @@ -184,6 +197,38 @@ pub fn update_public_end_values(public_call: PublicCallData, circuit_outputs: &m propagate_valid_public_data_reads(public_call, circuit_outputs); } +fn propagate_nullifier_read_requests_non_revertible( + public_call: PublicCallData, + circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder +) { + let public_call_public_inputs = public_call.call_stack_item.public_inputs; + let nullifier_read_requests = public_call_public_inputs.nullifier_read_requests; + let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address; + + for i in 0..MAX_NULLIFIER_READ_REQUESTS_PER_CALL { + let request = nullifier_read_requests[i]; + if !is_empty(request) { + circuit_outputs.end_non_revertible.nullifier_read_requests.push(request.to_context(storage_contract_address)); + } + } +} + +fn propagate_nullifier_read_requests_revertible( + public_call: PublicCallData, + circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder +) { + let public_call_public_inputs = public_call.call_stack_item.public_inputs; + let nullifier_read_requests = public_call_public_inputs.nullifier_read_requests; + let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address; + + for i in 0..MAX_NULLIFIER_READ_REQUESTS_PER_CALL { + let request = nullifier_read_requests[i]; + if !is_empty(request) { + circuit_outputs.end.nullifier_read_requests.push(request.to_context(storage_contract_address)); + } + } +} + fn propagate_valid_public_data_update_requests( public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/lib.nr index 4373405f5812..9a99c84ba69c 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/lib.nr @@ -7,7 +7,9 @@ mod utils; mod public_kernel_setup; mod public_kernel_app_logic; mod public_kernel_teardown; +mod public_kernel_tail; use public_kernel_setup::PublicKernelSetupCircuitPrivateInputs; use public_kernel_app_logic::PublicKernelAppLogicCircuitPrivateInputs; use public_kernel_teardown::PublicKernelTeardownCircuitPrivateInputs; +use public_kernel_tail::PublicKernelTailCircuitPrivateInputs; diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr new file mode 100644 index 000000000000..d6242fe32928 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr @@ -0,0 +1,217 @@ +use crate::common; +use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests}; +use dep::types::{ + abis::{ + kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, + kernel_data::PublicKernelData, side_effect::SideEffectLinkedToNoteHash +}, + constants::MAX_NEW_NULLIFIERS_PER_TX, utils::{arrays::{array_length, array_merge, array_concat}} +}; +use dep::std::unsafe; + +struct PublicKernelTailCircuitPrivateInputs { + previous_kernel: PublicKernelData, + nullifier_read_request_reset_hints: NullifierReadRequestResetHints, +} + +impl PublicKernelTailCircuitPrivateInputs { + fn propagate_reverted_flag(self, public_inputs: &mut PublicKernelCircuitPublicInputsBuilder) { + public_inputs.reverted = self.previous_kernel.public_inputs.reverted; + } + + fn validate_inputs(self) { + let previous_public_inputs = self.previous_kernel.public_inputs; + assert(previous_public_inputs.needs_setup == false, "Previous kernel needs setup"); + assert(previous_public_inputs.needs_app_logic == false, "Previous kernel needs app logic"); + assert(previous_public_inputs.needs_teardown == false, "Previous kernel needs teardown"); + assert_eq( + array_length(previous_public_inputs.end.public_call_stack), 0, "Public call stack must be empty when executing the tail circuit" + ); + assert_eq( + array_length(previous_public_inputs.end_non_revertible.public_call_stack), 0, "Public call stack must be empty when executing the tail circuit" + ); + } + + fn validate_nullifier_read_requests(self, public_inputs: &mut PublicKernelCircuitPublicInputsBuilder) { + let end_non_revertible = self.previous_kernel.public_inputs.end_non_revertible; + let end = self.previous_kernel.public_inputs.end; + + // Total number should not exceed MAX_NULLIFIER_READ_REQUESTS_PER_TX. + let requests = array_merge( + end_non_revertible.nullifier_read_requests, + end.nullifier_read_requests + ); + + let pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] = array_concat(end_non_revertible.new_nullifiers, end.new_nullifiers); + + let hints = self.nullifier_read_request_reset_hints; + + let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; + + let unverified_nullifier_read_requests = reset_read_requests( + requests, + pending_nullifiers, + hints.read_request_statuses, + hints.pending_read_hints, + hints.settled_read_hints, + nullifier_tree_root + ); + + assert( + unverified_nullifier_read_requests.len() == 0, "All nullifier read requests must be verified" + ); + } + + pub fn public_kernel_tail(self) -> PublicKernelCircuitPublicInputs { + let mut public_inputs: PublicKernelCircuitPublicInputsBuilder = unsafe::zeroed(); + + self.validate_inputs(); + + self.propagate_reverted_flag(&mut public_inputs); + + common::initialize_emitted_end_values(self.previous_kernel, &mut public_inputs); + + self.validate_nullifier_read_requests(&mut public_inputs); + + public_inputs.to_inner() + } +} + +mod tests { + use crate::{public_kernel_tail::PublicKernelTailCircuitPrivateInputs}; + use dep::reset_kernel_lib::{ + NullifierReadRequestResetHintsBuilder, + read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} + }; + use dep::types::{ + abis::{ + kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, + kernel_data::PublicKernelData + }, + constants::MAX_NULLIFIER_READ_REQUESTS_PER_TX, + tests::{kernel_data_builder::PreviousKernelDataBuilder} + }; + + struct PublicKernelTailCircuitPrivateInputsBuilder { + previous_kernel: PreviousKernelDataBuilder, + nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder, + } + + impl PublicKernelTailCircuitPrivateInputsBuilder { + pub fn new() -> Self { + let previous_kernel = PreviousKernelDataBuilder::new(true); + + PublicKernelTailCircuitPrivateInputsBuilder { + previous_kernel, + nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX) + } + } + + pub fn append_nullifiers(&mut self, num_nullifiers: u64) { + self.previous_kernel.append_new_nullifiers_from_public(num_nullifiers); + } + + pub fn append_nullifiers_non_revertible(&mut self, num_nullifiers: u64) { + self.previous_kernel.append_new_nullifiers_non_revertible_from_public(num_nullifiers); + } + + pub fn add_nullifier_pending_read(&mut self, nullifier_index: u64) { + let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier(nullifier_index); + let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let pending_value_index = nullifier_index + self.previous_kernel.end_non_revertible.new_nullifiers.len(); + let hint = PendingReadHint { read_request_index, pending_value_index }; + self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + } + + pub fn add_nullifier_pending_read_non_revertible(&mut self, nullifier_index_offset_one: u64) { + let nullifier_index = nullifier_index_offset_one + 1; // + 1 is for the first nullifier + let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier_non_revertible(nullifier_index); + let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let hint = PendingReadHint { read_request_index, pending_value_index: nullifier_index }; + self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + } + + pub fn execute(&mut self) -> PublicKernelCircuitPublicInputs { + let previous_kernel = self.previous_kernel.to_public_kernel_data(); + + let kernel = PublicKernelTailCircuitPrivateInputs { + previous_kernel, + nullifier_read_request_reset_hints: self.nullifier_read_request_reset_hints_builder.to_hints() + }; + + kernel.public_kernel_tail() + } + + pub fn succeeded(&mut self) { + let _ = self.execute(); + } + + pub fn failed(&mut self) { + let _ = self.execute(); + } + } + + #[test] + fn public_kernel_circuit_tail_succeeds() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + builder.succeeded(); + } + + #[test] + unconstrained fn one_pending_nullifier_read_request() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.append_nullifiers(3); + builder.add_nullifier_pending_read(1); + builder.succeeded(); + } + + #[test] + unconstrained fn two_pending_nullifier_read_requests() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.append_nullifiers(3); + builder.add_nullifier_pending_read(1); + builder.add_nullifier_pending_read(0); + + builder.succeeded(); + } + + #[test] + unconstrained fn one_pending_nullifier_read_request_non_revertible() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.append_nullifiers_non_revertible(3); + builder.add_nullifier_pending_read_non_revertible(1); + builder.succeeded(); + } + + #[test(should_fail_with="Hinted value does not match read request")] + unconstrained fn pending_nullifier_read_request_wrong_hint_fails() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.append_nullifiers(3); + builder.add_nullifier_pending_read(1); + let mut hint = builder.nullifier_read_request_reset_hints_builder.pending_read_hints.pop(); + hint.pending_value_index -= 1; + builder.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); + + builder.failed(); + } + + #[test(should_fail_with="Read request counter must be greater than counter of the value being read")] + unconstrained fn pending_nullifier_read_request_reads_before_value_fails() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.append_nullifiers(3); + builder.add_nullifier_pending_read(1); + let nullifier_being_read = builder.previous_kernel.end.new_nullifiers.get(1); + let mut read_request = builder.previous_kernel.end.nullifier_read_requests.pop(); + read_request.counter = nullifier_being_read.counter - 1; + builder.previous_kernel.end.nullifier_read_requests.push(read_request); + + builder.failed(); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/Nargo.toml new file mode 100644 index 000000000000..57448d5086f0 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "public_kernel_tail_simulated" +type = "bin" +authors = [""] +compiler_version = ">=0.18.0" + +[dependencies] +types = { path = "../types" } +public_kernel_lib = { path = "../public-kernel-lib" } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/src/main.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/src/main.nr new file mode 100644 index 000000000000..e225c2d34a16 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/src/main.nr @@ -0,0 +1,6 @@ +use dep::public_kernel_lib::PublicKernelTailCircuitPrivateInputs; +use dep::types::PublicKernelCircuitPublicInputs; + +unconstrained fn main(input: PublicKernelTailCircuitPrivateInputs) -> distinct pub PublicKernelCircuitPublicInputs { + input.public_kernel_tail() +} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/Nargo.toml new file mode 100644 index 000000000000..9071a4db2acb --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "public_kernel_tail" +type = "bin" +authors = [""] +compiler_version = ">=0.18.0" + +[dependencies] +types = { path = "../types" } +public_kernel_lib = { path = "../public-kernel-lib" } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/src/main.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/src/main.nr new file mode 100644 index 000000000000..19b9f802f485 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/src/main.nr @@ -0,0 +1,6 @@ +use dep::public_kernel_lib::PublicKernelTailCircuitPrivateInputs; +use dep::types::PublicKernelCircuitPublicInputs; + +fn main(input: PublicKernelTailCircuitPrivateInputs) -> distinct pub PublicKernelCircuitPublicInputs { + input.public_kernel_tail() +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/Nargo.toml new file mode 100644 index 000000000000..56de9efcafb0 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "reset_kernel_lib" +type = "lib" +authors = [""] +compiler_version = ">=0.18.0" + +[dependencies] +types = { path = "../types" } diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr new file mode 100644 index 000000000000..8e41dc16d8d6 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr @@ -0,0 +1,6 @@ +use read_request_reset::reset_read_requests; +use nullifier_read_request_reset::NullifierReadRequestResetHints; +use nullifier_read_request_reset::NullifierReadRequestResetHintsBuilder; + +mod read_request_reset; +mod nullifier_read_request_reset; diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/nullifier_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr similarity index 100% rename from noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/nullifier_read_request_reset.nr rename to noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr similarity index 100% rename from noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/read_request_reset.nr rename to noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr index fea3f3bde43f..f138d7e1f266 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr @@ -5,20 +5,21 @@ use crate::{ public_accumulated_non_revertible_data::PublicAccumulatedNonRevertibleData }, call_request::CallRequest, public_data_read::PublicDataRead, - public_data_update_request::PublicDataUpdateRequest, + public_data_update_request::PublicDataUpdateRequest, read_request::ReadRequestContext, side_effect::{SideEffect, SideEffectLinkedToNoteHash} } }; use crate::constants::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, - MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX }; struct AccumulatedNonRevertibleDataBuilder { new_note_hashes: BoundedVec, new_nullifiers: BoundedVec, public_call_stack: BoundedVec, + nullifier_read_requests: BoundedVec, public_data_update_requests: BoundedVec, public_data_reads: BoundedVec, } @@ -36,6 +37,7 @@ impl AccumulatedNonRevertibleDataBuilder { new_note_hashes: self.new_note_hashes.storage, new_nullifiers: self.new_nullifiers.storage, public_call_stack: self.public_call_stack.storage, + nullifier_read_requests: self.nullifier_read_requests.storage, public_data_update_requests: self.public_data_update_requests.storage, public_data_reads: self.public_data_reads.storage } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr index 4df61024f02b..697c307e656a 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr @@ -1,14 +1,14 @@ use crate::{ abis::{ call_request::CallRequest, public_data_read::PublicDataRead, - public_data_update_request::PublicDataUpdateRequest, + public_data_update_request::PublicDataUpdateRequest, read_request::ReadRequestContext, side_effect::{SideEffect, SideEffectLinkedToNoteHash} } }; use crate::constants::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, - MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX }; use dep::std::unsafe; @@ -20,6 +20,7 @@ struct PublicAccumulatedNonRevertibleData { new_note_hashes: [SideEffect; MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX], new_nullifiers: [SideEffectLinkedToNoteHash; MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX], public_call_stack: [CallRequest; MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX], + nullifier_read_requests: [ReadRequestContext; MAX_NULLIFIER_READ_REQUESTS_PER_TX], public_data_update_requests: [PublicDataUpdateRequest; MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], public_data_reads: [PublicDataRead; MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX], } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr index bf448d685a1c..51ee94e817b9 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr @@ -69,7 +69,7 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: true, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item request hash" test - assert_eq(call_stack_item.hash(), 0x2f3c7c0a0a0611feaba860e7c1022b18b6a9da1db41e3b4a65e535553e371765); + assert_eq(call_stack_item.hash(), 0x10017014b5fd719261c575bd7acd1e604c0dd3e86d8c6af80294eadfc6d174a7); } #[test] @@ -86,6 +86,6 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: false, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item hash" test - assert_eq(call_stack_item.hash(), 0x21a57c25d064f611e94bcbd6729cdf7d4194c98e8c58ad4703c6315dfbd7e1d9); + assert_eq(call_stack_item.hash(), 0x182201ec06be2dc7eddaa8b828eb293eab9938c4d41cde1e2b1b766ee21d2a54); } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr index a8402f14a64f..a1a1667fdd11 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr @@ -1,11 +1,15 @@ use crate::{ - abis::{call_context::CallContext, side_effect::{SideEffect, SideEffectLinkedToNoteHash}}, + abis::{ + call_context::CallContext, read_request::ReadRequest, + side_effect::{SideEffect, SideEffectLinkedToNoteHash} +}, address::AztecAddress, constants::{ MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, - MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH, - GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, + MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, + RETURN_VALUES_LENGTH, GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, + PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, hash::pedersen_hash, header::Header, messaging::l2_to_l1_message::L2ToL1Message, @@ -18,6 +22,7 @@ struct PublicCircuitPublicInputs{ args_hash: Field, return_values: [Field; RETURN_VALUES_LENGTH], + nullifier_read_requests: [ReadRequest; MAX_NULLIFIER_READ_REQUESTS_PER_CALL], contract_storage_update_requests: [StorageUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL], contract_storage_reads: [StorageRead; MAX_PUBLIC_DATA_READS_PER_CALL], @@ -54,6 +59,9 @@ impl Serialize for PublicCircuitPublicInput fields.extend_from_array(self.call_context.serialize()); fields.push(self.args_hash); fields.extend_from_array(self.return_values); + for i in 0..MAX_NULLIFIER_READ_REQUESTS_PER_CALL { + fields.extend_from_array(self.nullifier_read_requests[i].serialize()); + } for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL { fields.extend_from_array(self.contract_storage_update_requests[i].serialize()); } @@ -88,6 +96,7 @@ impl Deserialize for PublicCircuitPublicInp call_context: reader.read_struct(CallContext::deserialize), args_hash: reader.read(), return_values: reader.read_array([0; RETURN_VALUES_LENGTH]), + nullifier_read_requests: reader.read_struct_array(ReadRequest::deserialize, [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL]), contract_storage_update_requests: reader.read_struct_array(StorageUpdateRequest::deserialize, [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL]), contract_storage_reads: reader.read_struct_array(StorageRead::deserialize, [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL]), public_call_stack_hashes: reader.read_array([0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL]), @@ -126,5 +135,5 @@ fn empty_hash() { let hash = inputs.hash(); // Value from public_circuit_public_inputs.test.ts "computes empty item hash" test - assert_eq(hash, 0x022c1c742521fb12c0d40c223b953d6499be7de29d6cc62f80ae141bbf4cd9a3); + assert_eq(hash, 0x01fcd6e2480909d55f03f4ee87924cbabb0b4706cb742c70422e423b2db5f4eb); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 9626a6a80e48..886772bc64d6 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -168,7 +168,7 @@ global PRIVATE_CALL_STACK_ITEM_LENGTH: u64 = 223; // constant as well PRIVATE_CALL_STACK_ITEM_LENGTH global PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 218; // Change this ONLY if you have changed the PublicCircuitPublicInputs structure. -global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 195; +global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 199; global STATE_REFERENCE_LENGTH: u64 = 10; // 2 for snap + 8 for partial global TX_CONTEXT_DATA_LENGTH: u64 = 11; global TX_REQUEST_LENGTH: u64 = 17; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr b/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr index 69ffb16ecdfe..2b6c09ce8020 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr @@ -1,10 +1,4 @@ -use crate::abis::{ - function_data::FunctionData, private_circuit_public_inputs::PrivateCircuitPublicInputs, - public_circuit_public_inputs::PublicCircuitPublicInputs -}; -use crate::address::AztecAddress; use crate::constants::{GENERATOR_INDEX__CONTRACT_LEAF}; -use crate::traits::Hash; struct ContractClassId { inner: Field diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr index f11f1862bf46..ef17cc8e5658 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr @@ -136,19 +136,14 @@ impl PreviousKernelDataBuilder { } } - fn get_mock_nullifier_value(self, nullifier_index: u64) -> Field { - let first_nullifier = if (self.end.new_nullifiers.len() > 0) { - self.end.new_nullifiers.get(0).value - } else { - 0 as Field - }; - - first_nullifier + nullifier_index as Field + fn get_mock_nullifier_value(_self: Self, nullifier_index: u64) -> Field { + let value_offset = 5678; + value_offset + nullifier_index as Field } - fn get_mock_nullifier_value_non_revertible(self, nullifier_index: u64) -> Field { - let first_nullifier = self.end_non_revertible.new_nullifiers.get(0); - first_nullifier.value + nullifier_index as Field + fn get_mock_nullifier_value_non_revertible(_self: Self, nullifier_index: u64) -> Field { + let value_offset = 987; + value_offset + nullifier_index as Field } pub fn append_new_nullifiers_from_private(&mut self, num_extra_nullifier: u64) { @@ -172,11 +167,12 @@ impl PreviousKernelDataBuilder { pub fn append_new_nullifiers_from_public(&mut self, num_extra_nullifier: u64) { let index_offset = self.end.new_nullifiers.len(); - for i in 1..MAX_NEW_NULLIFIERS_PER_TX { - if i <= num_extra_nullifier { + for i in 0..MAX_NEW_NULLIFIERS_PER_TX { + if i < num_extra_nullifier { + let mock_value = self.get_mock_nullifier_value(index_offset + i); self.end.new_nullifiers.push( SideEffectLinkedToNoteHash { - value: self.get_mock_nullifier_value_non_revertible(index_offset + i), + value: silo_nullifier(self.storage_contract_address, mock_value), note_hash: 0, counter: self.next_sideffect_counter() } @@ -185,13 +181,14 @@ impl PreviousKernelDataBuilder { } } - pub fn append_new_nullifiers_non_revertible(&mut self, num_extra_nullifier: u64) { + pub fn append_new_nullifiers_non_revertible_from_public(&mut self, num_extra_nullifier: u64) { let index_offset = self.end_non_revertible.new_nullifiers.len(); - for i in 1..MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX { - if i <= num_extra_nullifier { + for i in 0..MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX { + if i < num_extra_nullifier { + let mock_value = self.get_mock_nullifier_value_non_revertible(index_offset + i); self.end_non_revertible.new_nullifiers.push( SideEffectLinkedToNoteHash { - value: self.get_mock_nullifier_value(index_offset + i), + value: silo_nullifier(self.storage_contract_address, mock_value), note_hash: 0, counter: self.next_sideffect_counter() } @@ -212,6 +209,18 @@ impl PreviousKernelDataBuilder { read_request_index } + pub fn add_read_request_for_pending_nullifier_non_revertible(&mut self, nullifier_index: u64) -> u64 { + let read_request_index = self.end_non_revertible.nullifier_read_requests.len(); + let unsiloed_nullifier = self.get_mock_nullifier_value_non_revertible(nullifier_index); + let read_request = ReadRequestContext { + value: unsiloed_nullifier, + counter: self.next_sideffect_counter(), + contract_address: self.storage_contract_address + }; + self.end_non_revertible.nullifier_read_requests.push(read_request); + read_request_index + } + // snapshot the side effects // this is useful in the private tail circuit to test side effect splitting pub fn capture_min_revertible_side_effect_counter(&mut self) { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr index f6e8c0a0cc06..ae74ec6bb73a 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr @@ -1,7 +1,7 @@ use crate::{ abis::{ call_context::CallContext, public_circuit_public_inputs::PublicCircuitPublicInputs, - side_effect::{SideEffect, SideEffectLinkedToNoteHash} + read_request::ReadRequest, side_effect::{SideEffect, SideEffectLinkedToNoteHash} }, address::AztecAddress, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, header::Header, @@ -9,14 +9,16 @@ use crate::{ }; use crate::constants::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, - MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, + MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, + RETURN_VALUES_LENGTH }; struct PublicCircuitPublicInputsBuilder { call_context: CallContext, args_hash: Field, return_values: BoundedVec, + nullifier_read_requests: BoundedVec, contract_storage_update_requests: BoundedVec, contract_storage_reads: BoundedVec, public_call_stack_hashes: BoundedVec, @@ -43,6 +45,7 @@ impl PublicCircuitPublicInputsBuilder { call_context: self.call_context, args_hash: self.args_hash, return_values: self.return_values.storage, + nullifier_read_requests: self.nullifier_read_requests.storage, contract_storage_update_requests: self.contract_storage_update_requests.storage, contract_storage_reads: self.contract_storage_reads.storage, public_call_stack_hashes: self.public_call_stack_hashes.storage, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index f9ead080ee05..ec5e186d412b 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -66,6 +66,7 @@ pub fn array_cp(array: [T; N]) -> [T; S] where T: Empty { } pub fn array_concat(array1: [T; N], array2: [T; S]) -> [T; M] where T: Empty + Eq { + assert(N + S <= M, "combined array length exceeds length of return array"); let mut result: [T; M] = [T::empty(); M]; let mut i = 0; for elem in array1 { @@ -83,6 +84,24 @@ pub fn array_concat(array1: [T; N], array2: [T; S]) -> [T; M] where result } +pub fn array_merge(array1: [T; N], array2: [T; N]) -> [T; N] where T: Empty + Eq { + let mut result: [T; N] = [T::empty(); N]; + let mut i = 0; + for elem in array1 { + if !is_empty(elem) { + result[i] = elem; + i += 1; + } + } + for elem in array2 { + if !is_empty(elem) { + result[i] = elem; + i += 1; + } + } + result +} + #[test] fn smoke_validate_array() { let valid_array = []; diff --git a/yarn-project/circuit-types/src/stats/stats.ts b/yarn-project/circuit-types/src/stats/stats.ts index 8f87e3339053..8ad9f63bcafe 100644 --- a/yarn-project/circuit-types/src/stats/stats.ts +++ b/yarn-project/circuit-types/src/stats/stats.ts @@ -62,7 +62,8 @@ export type CircuitSimulationStats = { | 'private-kernel-inner' | 'public-kernel-setup' | 'public-kernel-app-logic' - | 'public-kernel-teardown'; + | 'public-kernel-teardown' + | 'public-kernel-tail'; /** Duration in ms. */ duration: number; /** Size in bytes of circuit inputs. */ diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index ffeb74bf57ec..e814158cf91b 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -99,7 +99,7 @@ export const NULLIFIER_KEY_VALIDATION_REQUEST_CONTEXT_LENGTH = 5; export const PARTIAL_STATE_REFERENCE_LENGTH = 8; export const PRIVATE_CALL_STACK_ITEM_LENGTH = 223; export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 218; -export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 195; +export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 199; export const STATE_REFERENCE_LENGTH = 10; export const TX_CONTEXT_DATA_LENGTH = 11; export const TX_REQUEST_LENGTH = 17; diff --git a/yarn-project/circuits.js/src/hints/build_hints.test.ts b/yarn-project/circuits.js/src/hints/build_hints.test.ts new file mode 100644 index 000000000000..6cf3572bbf77 --- /dev/null +++ b/yarn-project/circuits.js/src/hints/build_hints.test.ts @@ -0,0 +1,120 @@ +import { + AztecAddress, + Fr, + MAX_NEW_NULLIFIERS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_TX, + NullifierReadRequestResetHints, + NullifierReadRequestResetHintsBuilder, + PendingReadHint, + ReadRequestContext, + ReadRequestState, + ReadRequestStatus, + SettledReadHint, + SideEffectLinkedToNoteHash, +} from '@aztec/circuits.js'; +import { siloNullifier } from '@aztec/circuits.js/hash'; +import { makeTuple } from '@aztec/foundation/array'; +import { Tuple } from '@aztec/foundation/serialize'; + +import { HintsBuildingDataOracle, buildNullifierReadRequestResetHints } from './build_hints.js'; + +describe('buildNullifierReadRequestResetHints', () => { + const contractAddress = AztecAddress.random(); + const settledNullifierInnerValue = 99999; + const settledNullifierValue = makeNullifier(settledNullifierInnerValue).value; + const oracle: HintsBuildingDataOracle = { + getNullifierMembershipWitness: value => + value.equals(settledNullifierValue) ? ({ membershipWitness: {}, leafPreimage: {} } as any) : undefined, + }; + let nullifierReadRequests: Tuple; + let nullifiers: Tuple; + let expectedHints: NullifierReadRequestResetHints; + let numReadRequests = 0; + let numPendingReads = 0; + let numSettledReads = 0; + + const innerNullifier = (index: number) => index + 1; + + const makeReadRequest = (value: number, counter = 2) => + new ReadRequestContext(new Fr(value), counter, contractAddress); + + function makeNullifier(value: number, counter = 1) { + const siloedValue = siloNullifier(contractAddress, new Fr(value)); + return new SideEffectLinkedToNoteHash(siloedValue, new Fr(0), new Fr(counter)); + } + + const readPendingNullifier = ({ + nullifierIndex, + readRequestIndex = numReadRequests, + hintIndex = numPendingReads, + }: { + nullifierIndex: number; + readRequestIndex?: number; + hintIndex?: number; + }) => { + nullifierReadRequests[readRequestIndex] = makeReadRequest(innerNullifier(nullifierIndex)); + expectedHints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus(ReadRequestState.PENDING, hintIndex); + expectedHints.pendingReadHints[hintIndex] = new PendingReadHint(readRequestIndex, nullifierIndex); + numReadRequests++; + numPendingReads++; + }; + + const readSettledNullifier = ({ + readRequestIndex = numReadRequests, + hintIndex = numSettledReads, + }: { + readRequestIndex?: number; + hintIndex?: number; + } = {}) => { + nullifierReadRequests[readRequestIndex] = makeReadRequest(settledNullifierInnerValue); + expectedHints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus(ReadRequestState.SETTLED, hintIndex); + expectedHints.settledReadHints[hintIndex] = new SettledReadHint(readRequestIndex, {} as any, {} as any); + numReadRequests++; + numSettledReads++; + }; + + const buildHints = () => buildNullifierReadRequestResetHints(oracle, nullifierReadRequests, nullifiers); + + beforeEach(() => { + nullifierReadRequests = makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty); + nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => makeNullifier(innerNullifier(i))); + expectedHints = NullifierReadRequestResetHintsBuilder.empty(); + numReadRequests = 0; + numPendingReads = 0; + numSettledReads = 0; + }); + + it('builds empty hints', async () => { + const hints = await buildHints(); + expect(hints).toEqual(expectedHints); + }); + + it('builds hints for pending nullifier read requests', async () => { + readPendingNullifier({ nullifierIndex: 2 }); + readPendingNullifier({ nullifierIndex: 1 }); + const hints = await buildHints(); + expect(hints).toEqual(expectedHints); + }); + + it('builds hints for settled nullifier read requests', async () => { + readSettledNullifier(); + readSettledNullifier(); + const hints = await buildHints(); + expect(hints).toEqual(expectedHints); + }); + + it('builds hints for mixed pending and settled nullifier read requests', async () => { + readPendingNullifier({ nullifierIndex: 2 }); + readSettledNullifier(); + readSettledNullifier(); + readPendingNullifier({ nullifierIndex: 1 }); + readPendingNullifier({ nullifierIndex: 1 }); + const hints = await buildHints(); + expect(hints).toEqual(expectedHints); + }); + + it('throws if reading an unknown nullifier', async () => { + nullifierReadRequests[0] = makeReadRequest(88888); + await expect(buildHints()).rejects.toThrow('Read request is reading an unknown nullifier value.'); + }); +}); diff --git a/yarn-project/circuits.js/src/hints/build_hints.ts b/yarn-project/circuits.js/src/hints/build_hints.ts new file mode 100644 index 000000000000..d80f4bd5c375 --- /dev/null +++ b/yarn-project/circuits.js/src/hints/build_hints.ts @@ -0,0 +1,60 @@ +import { Fr } from '@aztec/foundation/fields'; +import { Tuple } from '@aztec/foundation/serialize'; + +import { + MAX_NEW_NULLIFIERS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_TX, + NULLIFIER_TREE_HEIGHT, +} from '../constants.gen.js'; +import { siloNullifier } from '../hash/index.js'; +import { MembershipWitness } from '../structs/membership_witness.js'; +import { ReadRequestContext } from '../structs/read_request.js'; +import { NullifierReadRequestResetHintsBuilder } from '../structs/read_request_reset_hints.js'; +import { NullifierLeafPreimage } from '../structs/rollup/nullifier_leaf/index.js'; +import { SideEffectLinkedToNoteHash } from '../structs/side_effects.js'; +import { countAccumulatedItems } from './utils.js'; + +export interface NullifierMembershipWitnessWithPreimage { + membershipWitness: MembershipWitness; + leafPreimage: NullifierLeafPreimage; +} + +export interface HintsBuildingDataOracle { + getNullifierMembershipWitness(nullifier: Fr): Promise; +} + +export async function buildNullifierReadRequestResetHints( + oracle: HintsBuildingDataOracle, + nullifierReadRequests: Tuple, + nullifiers: Tuple, +) { + const builder = new NullifierReadRequestResetHintsBuilder(); + + const numReadRequests = countAccumulatedItems(nullifierReadRequests); + + const nullifierIndexMap: Map = new Map(); + nullifiers.forEach((n, i) => nullifierIndexMap.set(n.value.toBigInt(), i)); + + for (let i = 0; i < numReadRequests; ++i) { + const readRequest = nullifierReadRequests[i]; + // TODO - Should be comparing un-siloed values and contract addresses. + const value = siloNullifier(readRequest.contractAddress, readRequest.value); + + const pendingValueIndex = nullifierIndexMap.get(value.toBigInt()); + if (pendingValueIndex !== undefined) { + builder.addPendingReadRequest(i, pendingValueIndex); + } else { + const membershipWitnessWithPreimage = await oracle.getNullifierMembershipWitness(value); + if (!membershipWitnessWithPreimage) { + throw new Error('Read request is reading an unknown nullifier value.'); + } + + builder.addSettledReadRequest( + i, + membershipWitnessWithPreimage.membershipWitness, + membershipWitnessWithPreimage.leafPreimage, + ); + } + } + return builder.toHints(); +} diff --git a/yarn-project/circuits.js/src/hints/index.ts b/yarn-project/circuits.js/src/hints/index.ts new file mode 100644 index 000000000000..ef50cf70092d --- /dev/null +++ b/yarn-project/circuits.js/src/hints/index.ts @@ -0,0 +1,2 @@ +export * from './build_hints.js'; +export * from './utils.js'; diff --git a/yarn-project/circuits.js/src/hints/utils.test.ts b/yarn-project/circuits.js/src/hints/utils.test.ts new file mode 100644 index 000000000000..e5897e30a87f --- /dev/null +++ b/yarn-project/circuits.js/src/hints/utils.test.ts @@ -0,0 +1,173 @@ +import { IsEmpty } from '@aztec/circuits.js'; +import { makeTuple } from '@aztec/foundation/array'; +import { Tuple } from '@aztec/foundation/serialize'; + +import { concatAccumulatedData, countAccumulatedItems, mergeAccumulatedData } from './utils.js'; + +class TestItem { + constructor(public value: number) {} + + static empty() { + return new TestItem(0); + } + + isEmpty() { + return this.value === 0; + } +} + +describe('hints utils', () => { + const expectEmptyArrays = (arr: IsEmpty[]) => { + arr.forEach(item => expect(item.isEmpty()).toBe(true)); + }; + + describe('countAccumulatedItems', () => { + it('counts the number of non-empty items', () => { + const arr = makeTuple(20, TestItem.empty); + const num = 6; + for (let i = 0; i < num; ++i) { + arr[i] = new TestItem(i + 1); + } + expect(countAccumulatedItems(arr)).toBe(num); + }); + + it('throws if arr contains non-continuous non-empty items', () => { + const arr = makeTuple(20, TestItem.empty); + arr[1] = new TestItem(123); + expect(() => countAccumulatedItems(arr)).toThrow('Non-empty items must be placed continuously from index 0.'); + }); + }); + + describe('mergeAccumulatedData', () => { + const length = 5; + let arr0: Tuple; + let arr1: Tuple; + + beforeEach(() => { + arr0 = makeTuple(length, TestItem.empty); + arr1 = makeTuple(length, TestItem.empty); + }); + + it('propagates items from arr0', () => { + arr0[0] = new TestItem(12); + arr0[1] = new TestItem(34); + const res = mergeAccumulatedData(length, arr0, arr1); + expect(res.slice(0, 2)).toEqual([arr0[0], arr0[1]]); + expectEmptyArrays(res.slice(2)); + }); + + it('propagates items from arr1', () => { + arr1[0] = new TestItem(1); + arr1[1] = new TestItem(2); + const res = mergeAccumulatedData(length, arr0, arr1); + expect(res.slice(0, 2)).toEqual([arr1[0], arr1[1]]); + expectEmptyArrays(res.slice(2)); + }); + + it('merges items from both arrays', () => { + arr0[0] = new TestItem(12); + arr0[1] = new TestItem(34); + arr1[0] = new TestItem(1); + arr1[1] = new TestItem(2); + const res = mergeAccumulatedData(length, arr0, arr1); + expect(res.slice(0, 4)).toEqual([arr0[0], arr0[1], arr1[0], arr1[1]]); + expectEmptyArrays(res.slice(4)); + }); + + it('throws if arr0 contains non-continuous items', () => { + arr0[0] = new TestItem(12); + arr0[2] = new TestItem(34); + expect(() => mergeAccumulatedData(length, arr0, arr1)).toThrow( + 'Non-empty items must be placed continuously from index 0.', + ); + }); + + it('throws if arr1 contains non-continuous items', () => { + arr1[0] = new TestItem(12); + arr1[2] = new TestItem(34); + expect(() => mergeAccumulatedData(length, arr0, arr1)).toThrow( + 'Non-empty items must be placed continuously from index 0.', + ); + }); + + it('throws if total number of items exceeds limit', () => { + for (let i = 0; i < length; ++i) { + arr0[i] = new TestItem(i + 1); + } + expect(mergeAccumulatedData(length, arr0, arr1)).toBeDefined(); + + arr1[0] = new TestItem(1234); + expect(() => mergeAccumulatedData(length, arr0, arr1)).toThrow( + 'Combined non-empty items exceeded the maximum allowed.', + ); + }); + }); + + describe('concatAccumulatedData', () => { + const length0 = 3; + const length1 = 5; + const length = length0 + length1; + let arr0: Tuple; + let arr1: Tuple; + + beforeEach(() => { + arr0 = makeTuple(length0, TestItem.empty); + arr1 = makeTuple(length1, TestItem.empty); + }); + + it('propagates items from arr0', () => { + arr0[0] = new TestItem(12); + arr0[1] = new TestItem(34); + const nullifiers = concatAccumulatedData(length, arr0, arr1); + expect(nullifiers.slice(0, 2)).toEqual([arr0[0], arr0[1]]); + expectEmptyArrays(nullifiers.slice(2)); + }); + + it('propagates items from arr1', () => { + arr1[0] = new TestItem(1); + arr1[1] = new TestItem(2); + const nullifiers = concatAccumulatedData(length, arr0, arr1); + expect(nullifiers.slice(0, 2)).toEqual([arr1[0], arr1[1]]); + expectEmptyArrays(nullifiers.slice(2)); + }); + + it('combines items from both arrays', () => { + arr0[0] = new TestItem(12); + arr0[1] = new TestItem(34); + arr1[0] = new TestItem(1); + arr1[1] = new TestItem(2); + const nullifiers = concatAccumulatedData(length, arr0, arr1); + expect(nullifiers.slice(0, 4)).toEqual([arr0[0], arr0[1], arr1[0], arr1[1]]); + expectEmptyArrays(nullifiers.slice(4)); + }); + + it('combines all items from both arrays', () => { + arr0 = makeTuple(length0, i => new TestItem(i + 1)); + arr1 = makeTuple(length1, i => new TestItem(i + 999)); + const nullifiers = concatAccumulatedData(length, arr0, arr1); + expect(nullifiers).toEqual([...arr0, ...arr1]); + }); + + it('throws if given length is incorrect', () => { + expect(() => concatAccumulatedData(length + 1, arr0, arr1)).toThrow( + /Provided length does not match combined length./, + ); + }); + + it('throws if arr0 contains non-continuous items', () => { + arr0[0] = new TestItem(12); + arr0[2] = new TestItem(34); + expect(() => concatAccumulatedData(length, arr0, arr1)).toThrow( + 'Non-empty items must be placed continuously from index 0.', + ); + }); + + it('throws if arr1 contains non-continuous items', () => { + arr1[0] = new TestItem(12); + arr1[2] = new TestItem(34); + expect(() => concatAccumulatedData(length, arr0, arr1)).toThrow( + 'Non-empty items must be placed continuously from index 0.', + ); + }); + }); +}); diff --git a/yarn-project/circuits.js/src/hints/utils.ts b/yarn-project/circuits.js/src/hints/utils.ts new file mode 100644 index 000000000000..d98de228d7d3 --- /dev/null +++ b/yarn-project/circuits.js/src/hints/utils.ts @@ -0,0 +1,62 @@ +import { Tuple } from '@aztec/foundation/serialize'; + +export interface IsEmpty { + isEmpty: () => boolean; +} + +// Define these utils here as their design is very specific to kernel's accumulated data and not general enough to be put in foundation. + +// Returns number of non-empty items in an array. +export function countAccumulatedItems(arr: T[]) { + return arr.reduce((num, item, i) => { + if (!item.isEmpty()) { + if (num !== i) { + throw new Error('Non-empty items must be placed continuously from index 0.'); + } + return num + 1; + } + return num; + }, 0); +} + +// Merges two arrays of length N into an array of length N. +export function mergeAccumulatedData( + _length: N, + arr0: Tuple, + arr1: Tuple, +): Tuple { + const numNonEmptyItems0 = countAccumulatedItems(arr0); + const numNonEmptyItems1 = countAccumulatedItems(arr1); + if (numNonEmptyItems0 + numNonEmptyItems1 > arr0.length) { + throw new Error('Combined non-empty items exceeded the maximum allowed.'); + } + + const arr = [...arr0] as Tuple; + arr1.slice(0, numNonEmptyItems1).forEach((item, i) => (arr[i + numNonEmptyItems0] = item)); + return arr; +} + +// Combines an array of length N and an array of length M into an array of length N + M. +// All non-empty items are aggregated continuously from index 0. +export function concatAccumulatedData( + length: NM, + arr0: Tuple, + arr1: Tuple, +): Tuple { + const combinedLength = arr0.length + arr1.length; + if (combinedLength !== length) { + throw new Error(`Provided length does not match combined length. Expected ${combinedLength}. Got ${length}.`); + } + + const numNonEmptyItems0 = countAccumulatedItems(arr0); + const numNonEmptyItems1 = countAccumulatedItems(arr1); + const arr = [...arr0, ...arr1] as Tuple; + if (numNonEmptyItems0 < arr0.length) { + const emptyItem = arr0[numNonEmptyItems0]; + arr1.slice(0, numNonEmptyItems1).forEach((item, i) => { + arr[i + numNonEmptyItems0] = item; + arr[arr0.length + i] = emptyItem; + }); + } + return arr; +} diff --git a/yarn-project/circuits.js/src/index.ts b/yarn-project/circuits.js/src/index.ts index 8ba7029a564a..1fafc4e3bf56 100644 --- a/yarn-project/circuits.js/src/index.ts +++ b/yarn-project/circuits.js/src/index.ts @@ -1,5 +1,6 @@ export * from './constants.gen.js'; export * from './contract/index.js'; +export * from './hints/index.js'; export * from './keys/index.js'; export * from './structs/index.js'; export * from './types/index.js'; diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap index e74aa51e4b0c..f0b947728b1b 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap @@ -1,46 +1,46 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x21a57c25d064f611e94bcbd6729cdf7d4194c98e8c58ad4703c6315dfbd7e1d9"`; +exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x182201ec06be2dc7eddaa8b828eb293eab9938c4d41cde1e2b1b766ee21d2a54"`; -exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x2f3c7c0a0a0611feaba860e7c1022b18b6a9da1db41e3b4a65e535553e371765"`; +exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x10017014b5fd719261c575bd7acd1e604c0dd3e86d8c6af80294eadfc6d174a7"`; exports[`PublicCallStackItem computes hash 1`] = ` Fr { - "asBigInt": 10303122325746643826583747593622617947092841409264941610766787655996494469123n, + "asBigInt": 45410678569143484234609653232067005723125960091308451781456680995218457503n, "asBuffer": { "data": [ - 22, - 199, - 92, - 79, - 4, - 250, - 62, - 84, - 206, - 222, - 239, + 0, 25, - 153, - 82, - 66, - 3, - 105, - 117, - 249, - 128, - 101, - 228, + 179, + 151, + 174, 187, - 37, - 196, - 173, + 246, + 36, + 171, + 201, + 108, + 111, + 243, + 253, + 214, + 195, + 214, + 206, + 229, + 122, + 245, + 52, + 105, + 87, + 65, 44, - 162, - 71, - 186, - 88, + 87, + 45, + 27, + 159, 3, + 159, ], "type": "Buffer", }, diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap index c79421fa0624..7a5dd3fc1f0c 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap @@ -2,41 +2,41 @@ exports[`PublicCircuitPublicInputs computes empty item hash 1`] = ` Fr { - "asBigInt": 982563348178838876008170273361844612000980410060290093415555892275290364323n, + "asBigInt": 899041383159782383308350091877472492566207915893045234143560027519589938411n, "asBuffer": { "data": [ - 2, - 44, - 28, - 116, - 37, - 33, - 251, - 18, - 192, - 212, - 12, - 34, - 59, - 149, - 61, - 100, - 153, - 190, - 125, + 1, + 252, + 214, 226, - 157, - 108, - 198, - 47, - 128, - 174, - 20, - 27, - 191, + 72, + 9, + 9, + 213, + 95, + 3, + 244, + 238, + 135, + 146, 76, - 217, - 163, + 186, + 187, + 11, + 71, + 6, + 203, + 116, + 44, + 112, + 66, + 46, + 66, + 59, + 45, + 181, + 244, + 235, ], "type": "Buffer", }, @@ -45,41 +45,41 @@ Fr { exports[`PublicCircuitPublicInputs hash matches snapshot 1`] = ` Fr { - "asBigInt": 1913686165740086250096887774400945438599678344243836938248205905816240806835n, + "asBigInt": 11132983907867837190629216452861649594433048836605582311662020710335671547249n, "asBuffer": { "data": [ - 4, - 59, - 27, - 164, - 246, - 232, - 134, - 146, - 93, - 64, - 64, + 24, + 157, + 11, + 171, + 221, + 156, + 218, + 207, + 106, + 253, + 198, + 99, + 204, + 123, + 221, 188, - 181, - 94, - 93, - 129, - 25, - 220, - 166, - 209, + 0, + 191, + 126, + 172, + 98, + 53, + 110, + 180, + 142, + 8, + 253, + 250, + 103, 217, - 189, - 205, - 188, - 37, - 22, - 205, - 207, - 149, - 88, - 91, - 179, + 181, + 113, ], "type": "Buffer", }, diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 106a1e558a08..5ec39a492e0b 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -26,6 +26,7 @@ export * from './kernel/public_call_data.js'; export * from './kernel/public_kernel_circuit_private_inputs.js'; export * from './kernel/public_kernel_circuit_public_inputs.js'; export * from './kernel/public_kernel_data.js'; +export * from './kernel/public_kernel_tail_circuit_private_inputs.js'; export * from './kernel/rollup_kernel_circuit_public_inputs.js'; export * from './kernel/rollup_kernel_data.js'; export * from './l2_to_l1_message.js'; diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts index 0332423c6342..65689c76f705 100644 --- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts @@ -766,6 +766,10 @@ export class PrivateAccumulatedNonRevertibleData { export class PublicAccumulatedNonRevertibleData { constructor( + /** + * The nullifier read requests made in this transaction. + */ + public nullifierReadRequests: Tuple, /** * The new non-revertible commitments made in this transaction. */ @@ -792,12 +796,20 @@ export class PublicAccumulatedNonRevertibleData { ) {} toBuffer() { - return serializeToBuffer(this.newNoteHashes, this.newNullifiers, this.publicCallStack); + return serializeToBuffer( + this.nullifierReadRequests, + this.newNoteHashes, + this.newNullifiers, + this.publicCallStack, + this.publicDataUpdateRequests, + this.publicDataReads, + ); } static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new this( + reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext), reader.readArray(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect), reader.readArray(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash), reader.readArray(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest), @@ -816,6 +828,7 @@ export class PublicAccumulatedNonRevertibleData { static empty() { return new this( + makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty), makeTuple(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect.empty), makeTuple(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty), makeTuple(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest.empty), @@ -826,6 +839,7 @@ export class PublicAccumulatedNonRevertibleData { static fromPrivateAccumulatedNonRevertibleData(data: PrivateAccumulatedNonRevertibleData) { return new this( + makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty), data.newNoteHashes, data.newNullifiers, data.publicCallStack, diff --git a/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts new file mode 100644 index 000000000000..37d79805d119 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts @@ -0,0 +1,24 @@ +import { serializeToBuffer } from '@aztec/foundation/serialize'; + +import { NullifierReadRequestResetHints } from '../read_request_reset_hints.js'; +import { PublicKernelData } from './public_kernel_data.js'; + +/** + * Inputs to the public kernel circuit. + */ +export class PublicKernelTailCircuitPrivateInputs { + constructor( + /** + * Kernels are recursive and this is the data from the previous kernel. + */ + public readonly previousKernel: PublicKernelData, + /** + * Contains hints for the nullifier read requests to locate corresponding pending or settled nullifiers. + */ + public nullifierReadRequestResetHints: NullifierReadRequestResetHints, + ) {} + + toBuffer() { + return serializeToBuffer(this.previousKernel, this.nullifierReadRequestResetHints); + } +} diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts index bad67169449d..f4a23e63ac43 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts @@ -11,6 +11,7 @@ import { MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, @@ -23,6 +24,7 @@ import { ContractStorageRead } from './contract_storage_read.js'; import { ContractStorageUpdateRequest } from './contract_storage_update_request.js'; import { Header } from './header.js'; import { L2ToL1Message } from './l2_to_l1_message.js'; +import { ReadRequest } from './read_request.js'; import { SideEffect, SideEffectLinkedToNoteHash } from './side_effects.js'; /** @@ -42,6 +44,10 @@ export class PublicCircuitPublicInputs { * Return values of the call. */ public returnValues: Tuple, + /** + * Nullifier read requests executed during the call. + */ + public nullifierReadRequests: Tuple, /** * Contract storage update requests executed during the call. */ @@ -112,6 +118,7 @@ export class PublicCircuitPublicInputs { CallContext.empty(), Fr.ZERO, makeTuple(RETURN_VALUES_LENGTH, Fr.zero), + makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest.empty), makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest.empty), makeTuple(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead.empty), makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, Fr.zero), @@ -135,6 +142,7 @@ export class PublicCircuitPublicInputs { this.callContext.isEmpty() && this.argsHash.isZero() && isFrArrayEmpty(this.returnValues) && + isArrayEmpty(this.nullifierReadRequests, item => item.isEmpty()) && isArrayEmpty(this.contractStorageUpdateRequests, item => item.isEmpty()) && isArrayEmpty(this.contractStorageReads, item => item.isEmpty()) && isFrArrayEmpty(this.publicCallStackHashes) && @@ -159,6 +167,7 @@ export class PublicCircuitPublicInputs { fields.callContext, fields.argsHash, fields.returnValues, + fields.nullifierReadRequests, fields.contractStorageUpdateRequests, fields.contractStorageReads, fields.publicCallStackHashes, @@ -202,6 +211,7 @@ export class PublicCircuitPublicInputs { reader.readObject(CallContext), reader.readObject(Fr), reader.readArray(RETURN_VALUES_LENGTH, Fr), + reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest), reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest), reader.readArray(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead), reader.readArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, Fr), @@ -223,6 +233,7 @@ export class PublicCircuitPublicInputs { CallContext.fromFields(reader), reader.readField(), reader.readFieldArray(RETURN_VALUES_LENGTH), + reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest), reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest), reader.readArray(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead), reader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL), diff --git a/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts b/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts index d3f04ce89dbd..ef43419940a4 100644 --- a/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts +++ b/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts @@ -174,6 +174,10 @@ export class NullifierReadRequestResetHintsBuilder { ); } + static empty() { + return new NullifierReadRequestResetHintsBuilder().toHints(); + } + addPendingReadRequest(readRequestIndex: number, nullifierIndex: number) { this.hints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus( ReadRequestState.PENDING, @@ -189,7 +193,7 @@ export class NullifierReadRequestResetHintsBuilder { leafPreimage: NullifierLeafPreimage, ) { this.hints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus( - ReadRequestState.PENDING, + ReadRequestState.SETTLED, this.numSettledReadHints, ); this.hints.settledReadHints[this.numSettledReadHints] = new SettledReadHint( diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 6736fb61e248..128800b95b68 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -179,6 +179,14 @@ export function makeSelector(seed: number): FunctionSelector { return new FunctionSelector(seed); } +function makeReadRequest(n: number): ReadRequest { + return new ReadRequest(new Fr(BigInt(n)), n + 1); +} + +function makeReadRequestContext(n: number): ReadRequestContext { + return new ReadRequestContext(new Fr(BigInt(n)), n + 1, AztecAddress.fromBigInt(BigInt(n + 2))); +} + /** * Creates arbitrary NullifierKeyValidationRequest from the given seed. * @param seed - The seed to use for generating the NullifierKeyValidationRequest. @@ -263,7 +271,7 @@ export function makeCombinedAccumulatedData(seed = 1, full = false): CombinedAcc return new CombinedAccumulatedData( tupleGenerator(MAX_NOTE_HASH_READ_REQUESTS_PER_TX, sideEffectFromNumber, seed + 0x80), - tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_TX, readRequestContextFromNumber, seed + 0x90), + tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_TX, makeReadRequestContext, seed + 0x90), tupleGenerator( MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, makeNullifierKeyValidationRequestContext, @@ -294,7 +302,7 @@ export function makeCombinedAccumulatedRevertibleData(seed = 1, full = false): P return new PublicAccumulatedRevertibleData( tupleGenerator(MAX_NOTE_HASH_READ_REQUESTS_PER_TX, sideEffectFromNumber, seed + 0x80), - tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_TX, readRequestContextFromNumber, seed + 0x90), + tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_TX, makeReadRequestContext, seed + 0x90), tupleGenerator( MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, makeNullifierKeyValidationRequestContext, @@ -356,6 +364,7 @@ export function makeCombinedAccumulatedNonRevertibleData(seed = 1, full = false) const tupleGenerator = full ? makeTuple : makeHalfFullTuple; return new PublicAccumulatedNonRevertibleData( + tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_TX, makeReadRequestContext, seed + 0x91), tupleGenerator(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, sideEffectFromNumber, seed + 0x101), tupleGenerator(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, sideEffectLinkedFromNumber, seed + 0x201), tupleGenerator(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, makeCallRequest, seed + 0x501), @@ -423,6 +432,7 @@ export function makePublicCircuitPublicInputs( makeCallContext(seed, storageContractAddress), fr(seed + 0x100), tupleGenerator(RETURN_VALUES_LENGTH, fr, seed + 0x200), + tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, makeReadRequest, seed + 0x400), tupleGenerator(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, makeContractStorageUpdateRequest, seed + 0x400), tupleGenerator(MAX_PUBLIC_DATA_READS_PER_CALL, makeContractStorageRead, seed + 0x500), tupleGenerator(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, fr, seed + 0x600), @@ -863,7 +873,7 @@ export function makePrivateCircuitPublicInputs(seed = 0): PrivateCircuitPublicIn returnValues: makeTuple(RETURN_VALUES_LENGTH, fr, seed + 0x200), minRevertibleSideEffectCounter: fr(0), noteHashReadRequests: makeTuple(MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, sideEffectFromNumber, seed + 0x300), - nullifierReadRequests: makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, readRequestFromNumber, seed + 0x310), + nullifierReadRequests: makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, makeReadRequest, seed + 0x310), nullifierKeyValidationRequests: makeTuple( MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, makeNullifierKeyValidationRequest, @@ -1327,11 +1337,3 @@ export function sideEffectFromNumber(n: number): SideEffect { export function sideEffectLinkedFromNumber(n: number): SideEffectLinkedToNoteHash { return new SideEffectLinkedToNoteHash(new Fr(BigInt(n)), Fr.zero(), Fr.zero()); } - -function readRequestFromNumber(n: number): ReadRequest { - return new ReadRequest(new Fr(BigInt(n)), n + 1); -} - -function readRequestContextFromNumber(n: number): ReadRequestContext { - return new ReadRequestContext(new Fr(BigInt(n)), n + 1, AztecAddress.fromBigInt(BigInt(n + 2))); -} diff --git a/yarn-project/noir-protocol-circuits-types/src/index.ts b/yarn-project/noir-protocol-circuits-types/src/index.ts index 646839d36d0f..6a4143625320 100644 --- a/yarn-project/noir-protocol-circuits-types/src/index.ts +++ b/yarn-project/noir-protocol-circuits-types/src/index.ts @@ -9,6 +9,7 @@ import { PrivateKernelTailCircuitPublicInputs, PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs, + PublicKernelTailCircuitPrivateInputs, RootRollupInputs, RootRollupPublicInputs, } from '@aztec/circuits.js'; @@ -26,6 +27,7 @@ import PrivateKernelTailJson from './target/private_kernel_tail.json' assert { t import PrivateKernelTailSimulatedJson from './target/private_kernel_tail_simulated.json' assert { type: 'json' }; import PublicKernelAppLogicSimulatedJson from './target/public_kernel_app_logic_simulated.json' assert { type: 'json' }; import PublicKernelSetupSimulatedJson from './target/public_kernel_setup_simulated.json' assert { type: 'json' }; +import PublicKernelTailSimulatedJson from './target/public_kernel_tail_simulated.json' assert { type: 'json' }; import PublicKernelTeardownSimulatedJson from './target/public_kernel_teardown_simulated.json' assert { type: 'json' }; import BaseRollupSimulatedJson from './target/rollup_base_simulated.json' assert { type: 'json' }; import MergeRollupJson from './target/rollup_merge.json' assert { type: 'json' }; @@ -41,6 +43,7 @@ import { mapPrivateKernelTailCircuitPublicInputsFromNoir, mapPublicKernelCircuitPrivateInputsToNoir, mapPublicKernelCircuitPublicInputsFromNoir, + mapPublicKernelTailCircuitPrivateInputsToNoir, mapRootRollupInputsToNoir, mapRootRollupPublicInputsFromNoir, } from './type_conversion.js'; @@ -80,6 +83,8 @@ export const PublicKernelAppLogicArtifact = PublicKernelAppLogicSimulatedJson as export const PublicKernelTeardownArtifact = PublicKernelTeardownSimulatedJson as NoirCompiledCircuit; +export const PublicKernelTailArtifact = PublicKernelTailSimulatedJson as NoirCompiledCircuit; + export const BaseRollupArtifact = BaseRollupSimulatedJson as NoirCompiledCircuit; export const MergeRollupArtifact = MergeRollupJson as NoirCompiledCircuit; @@ -200,16 +205,27 @@ export function convertPublicInnerRollupInputsToWitnessMap(inputs: PublicKernelC } /** - * Converts the inputs to the public tail circuit into a witness map + * Converts the inputs to the public teardown circuit into a witness map * @param inputs - The public kernel inputs. * @returns The witness map */ -export function convertPublicTailRollupInputsToWitnessMap(inputs: PublicKernelCircuitPrivateInputs): WitnessMap { +export function convertPublicTeardownRollupInputsToWitnessMap(inputs: PublicKernelCircuitPrivateInputs): WitnessMap { const mapped = mapPublicKernelCircuitPrivateInputsToNoir(inputs); const initialWitnessMap = abiEncode(PublicKernelTeardownSimulatedJson.abi as Abi, { input: mapped as any }); return initialWitnessMap; } +/** + * Converts the inputs to the public tail circuit into a witness map + * @param inputs - The public kernel inputs. + * @returns The witness map + */ +export function convertPublicTailInputsToWitnessMap(inputs: PublicKernelTailCircuitPrivateInputs): WitnessMap { + const mapped = mapPublicKernelTailCircuitPrivateInputsToNoir(inputs); + const initialWitnessMap = abiEncode(PublicKernelTailSimulatedJson.abi as Abi, { input: mapped as any }); + return initialWitnessMap; +} + /** * Converts the outputs to the base rollup circuit. * @param outputs - The base rollup outputs as a witness map. @@ -290,7 +306,7 @@ export function convertPublicInnerRollupOutputFromWitnessMap(outputs: WitnessMap * @param outputs - The public kernel outputs as a witness map. * @returns The public inputs. */ -export function convertPublicTailRollupOutputFromWitnessMap(outputs: WitnessMap): PublicKernelCircuitPublicInputs { +export function convertPublicTeardownRollupOutputFromWitnessMap(outputs: WitnessMap): PublicKernelCircuitPublicInputs { // Decode the witness map into two fields, the return values and the inputs const decodedInputs: DecodedInputs = abiDecode(PublicKernelTeardownSimulatedJson.abi as Abi, outputs); @@ -300,6 +316,21 @@ export function convertPublicTailRollupOutputFromWitnessMap(outputs: WitnessMap) return mapPublicKernelCircuitPublicInputsFromNoir(returnType); } +/** + * Converts the outputs to the public tail circuit. + * @param outputs - The public kernel outputs as a witness map. + * @returns The public inputs. + */ +export function convertPublicTailOutputFromWitnessMap(outputs: WitnessMap): PublicKernelCircuitPublicInputs { + // Decode the witness map into two fields, the return values and the inputs + const decodedInputs: DecodedInputs = abiDecode(PublicKernelTailSimulatedJson.abi as Abi, outputs); + + // Cast the inputs as the return type + const returnType = decodedInputs.return_value as PublicPublicPreviousReturnType; + + return mapPublicKernelCircuitPublicInputsFromNoir(returnType); +} + /** * Executes the private init kernel with the given inputs using the acvm. * diff --git a/yarn-project/noir-protocol-circuits-types/src/scripts/generate_ts_from_abi.ts b/yarn-project/noir-protocol-circuits-types/src/scripts/generate_ts_from_abi.ts index 63837951472d..5821ec3446f6 100644 --- a/yarn-project/noir-protocol-circuits-types/src/scripts/generate_ts_from_abi.ts +++ b/yarn-project/noir-protocol-circuits-types/src/scripts/generate_ts_from_abi.ts @@ -204,6 +204,7 @@ const circuits = [ 'public_kernel_setup', 'public_kernel_app_logic', 'public_kernel_teardown', + 'public_kernel_tail', 'rollup_base', 'rollup_merge', 'rollup_root', diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index 09424a14f9b4..10fa0fabdba3 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -84,6 +84,7 @@ import { PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs, PublicKernelData, + PublicKernelTailCircuitPrivateInputs, ReadRequest, ReadRequestContext, ReadRequestStatus, @@ -164,6 +165,7 @@ import { StorageRead as StorageReadNoir, StorageUpdateRequest as StorageUpdateRequestNoir, } from './types/public_kernel_setup_types.js'; +import { PublicKernelTailCircuitPrivateInputs as PublicKernelTailCircuitPrivateInputsNoir } from './types/public_kernel_tail_types.js'; import { ArchiveRootMembershipWitness as ArchiveRootMembershipWitnessNoir, BaseRollupInputs as BaseRollupInputsNoir, @@ -723,7 +725,7 @@ export function mapPrivateCircuitPublicInputsToNoir( args_hash: mapFieldToNoir(privateCircuitPublicInputs.argsHash), return_values: mapTuple(privateCircuitPublicInputs.returnValues, mapFieldToNoir), note_hash_read_requests: mapTuple(privateCircuitPublicInputs.noteHashReadRequests, mapSideEffectToNoir), - nullifier_read_requests: mapTuple(privateCircuitPublicInputs.nullifierReadRequests, mapSideEffectToNoir), + nullifier_read_requests: mapTuple(privateCircuitPublicInputs.nullifierReadRequests, mapReadRequestToNoir), nullifier_key_validation_requests: mapTuple( privateCircuitPublicInputs.nullifierKeyValidationRequests, mapNullifierKeyValidationRequestToNoir, @@ -1232,6 +1234,7 @@ export function mapPublicAccumulatedNonRevertibleDataToNoir( data: PublicAccumulatedNonRevertibleData, ): PublicAccumulatedNonRevertibleDataNoir { return { + nullifier_read_requests: mapTuple(data.nullifierReadRequests, mapReadRequestContextToNoir), new_note_hashes: mapTuple(data.newNoteHashes, mapSideEffectToNoir), new_nullifiers: mapTuple(data.newNullifiers, mapSideEffectLinkedToNoir), public_call_stack: mapTuple(data.publicCallStack, mapCallRequestToNoir), @@ -1400,6 +1403,15 @@ export function mapPublicKernelCircuitPrivateInputsToNoir( }; } +export function mapPublicKernelTailCircuitPrivateInputsToNoir( + inputs: PublicKernelTailCircuitPrivateInputs, +): PublicKernelTailCircuitPrivateInputsNoir { + return { + previous_kernel: mapPublicKernelDataToNoir(inputs.previousKernel), + nullifier_read_request_reset_hints: mapNullifierReadRequestResetHintsToNoir(inputs.nullifierReadRequestResetHints), + }; +} + export function mapPublicKernelCircuitPublicInputsFromNoir( inputs: PublicKernelCircuitPublicInputsNoir, ): PublicKernelCircuitPublicInputs { @@ -1419,6 +1431,7 @@ export function mapPublicAccumulatedNonRevertibleDataFromNoir( data: PublicAccumulatedNonRevertibleDataNoir, ): PublicAccumulatedNonRevertibleData { return new PublicAccumulatedNonRevertibleData( + mapTupleFromNoir(data.nullifier_read_requests, MAX_NULLIFIER_READ_REQUESTS_PER_TX, mapReadRequestContextFromNoir), mapTupleFromNoir(data.new_note_hashes, MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, mapSideEffectFromNoir), mapTupleFromNoir(data.new_nullifiers, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, mapSideEffectLinkedFromNoir), mapTupleFromNoir( @@ -1549,6 +1562,7 @@ export function mapPublicCircuitPublicInputsToNoir( call_context: mapCallContextToNoir(publicInputs.callContext), args_hash: mapFieldToNoir(publicInputs.argsHash), return_values: mapTuple(publicInputs.returnValues, mapFieldToNoir), + nullifier_read_requests: mapTuple(publicInputs.nullifierReadRequests, mapReadRequestToNoir), contract_storage_update_requests: mapTuple( publicInputs.contractStorageUpdateRequests, mapStorageUpdateRequestToNoir, diff --git a/yarn-project/pxe/src/kernel_oracle/index.ts b/yarn-project/pxe/src/kernel_oracle/index.ts index ae5804d86d6d..d37b84338cd3 100644 --- a/yarn-project/pxe/src/kernel_oracle/index.ts +++ b/yarn-project/pxe/src/kernel_oracle/index.ts @@ -14,6 +14,8 @@ import { Tuple } from '@aztec/foundation/serialize'; import { ContractDataOracle } from '../contract_data_oracle/index.js'; import { ProvingDataOracle } from './../kernel_prover/proving_data_oracle.js'; +// TODO: Block number should not be "latest". +// It should be fixed at the time the proof is being simulated. I.e., it should be the same as the value defined in the constant data. /** * A data oracle that provides information needed for simulating a transaction. */ @@ -50,8 +52,8 @@ export class KernelOracle implements ProvingDataOracle { ); } - getNullifierMembershipWitness(blockNumber: number, nullifier: Fr) { - return this.node.getNullifierMembershipWitness(blockNumber, nullifier); + getNullifierMembershipWitness(nullifier: Fr) { + return this.node.getNullifierMembershipWitness('latest', nullifier); } async getNoteHashTreeRoot(): Promise { diff --git a/yarn-project/pxe/src/kernel_prover/hints_builder.ts b/yarn-project/pxe/src/kernel_prover/hints_builder.ts index 5ebd2abe738d..7f5dfd881e7b 100644 --- a/yarn-project/pxe/src/kernel_prover/hints_builder.ts +++ b/yarn-project/pxe/src/kernel_prover/hints_builder.ts @@ -9,13 +9,12 @@ import { MembershipWitness, NULLIFIER_TREE_HEIGHT, NullifierKeyValidationRequestContext, - NullifierReadRequestResetHintsBuilder, ReadRequestContext, SideEffect, SideEffectLinkedToNoteHash, SideEffectType, + buildNullifierReadRequestResetHints, } from '@aztec/circuits.js'; -import { siloNullifier } from '@aztec/circuits.js/hash'; import { makeTuple } from '@aztec/foundation/array'; import { Tuple } from '@aztec/foundation/serialize'; @@ -75,42 +74,28 @@ export class HintsBuilder { return hints; } - async getNullifierReadRequestResetHints( + getNullifierReadRequestResetHints( nullifierReadRequests: Tuple, nullifiers: Tuple, ) { - // TODO - Should be comparing un-siloed values and contract addresses. - const builder = new NullifierReadRequestResetHintsBuilder(); - const nullifierIndexMap: Map = new Map(); - nullifiers.forEach((n, i) => nullifierIndexMap.set(n.value.toBigInt(), i)); - const siloedReadRequestValues = nullifierReadRequests.map(r => - r.isEmpty() ? Fr.ZERO : siloNullifier(r.contractAddress, r.value), - ); - for (let i = 0; i < nullifierReadRequests.length; ++i) { - const value = siloedReadRequestValues[i]; - if (value.isZero()) { - break; - } - const pendingValueIndex = nullifierIndexMap.get(value.toBigInt()); - if (pendingValueIndex !== undefined) { - builder.addPendingReadRequest(i, pendingValueIndex); - } else { - const membershipWitness = await this.oracle.getNullifierMembershipWitness(0, value); - if (!membershipWitness) { - throw new Error('Read request is reading an unknown nullifier value.'); - } - builder.addSettledReadRequest( - i, - new MembershipWitness( - NULLIFIER_TREE_HEIGHT, - membershipWitness.index, - membershipWitness.siblingPath.toTuple(), - ), - membershipWitness.leafPreimage, - ); - } + return buildNullifierReadRequestResetHints(this, nullifierReadRequests, nullifiers); + } + + async getNullifierMembershipWitness(nullifier: Fr) { + const res = await this.oracle.getNullifierMembershipWitness(nullifier); + if (!res) { + return; } - return builder.toHints(); + + const { index, siblingPath, leafPreimage } = res; + return { + membershipWitness: new MembershipWitness( + NULLIFIER_TREE_HEIGHT, + index, + siblingPath.toTuple(), + ), + leafPreimage, + }; } /** diff --git a/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts b/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts index b9685a7aa6a8..2126f70e38d3 100644 --- a/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts +++ b/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts @@ -60,7 +60,7 @@ export interface ProvingDataOracle { */ getNoteMembershipWitness(leafIndex: bigint): Promise>; - getNullifierMembershipWitness(blockNumber: number, nullifier: Fr): Promise; + getNullifierMembershipWitness(nullifier: Fr): Promise; /** * Get the root of the note hash tree. diff --git a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts index 071fd5a464f7..063ba0b88fdf 100644 --- a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts +++ b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts @@ -13,6 +13,7 @@ import { MAX_NEW_NULLIFIERS_PER_CALL, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, @@ -30,7 +31,9 @@ import { PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs, PublicKernelData, + PublicKernelTailCircuitPrivateInputs, RETURN_VALUES_LENGTH, + ReadRequest, SideEffect, SideEffectLinkedToNoteHash, VK_TREE_HEIGHT, @@ -54,21 +57,25 @@ import { env } from 'process'; import { getVerificationKeys } from '../mocks/verification_keys.js'; import { PublicProver } from '../prover/index.js'; import { PublicKernelCircuitSimulator } from '../simulator/index.js'; +import { HintsBuilder } from './hints_builder.js'; import { FailedTx } from './processed_tx.js'; export enum PublicKernelPhase { SETUP = 'setup', APP_LOGIC = 'app-logic', TEARDOWN = 'teardown', + TAIL = 'tail', } export const PhaseIsRevertible: Record = { [PublicKernelPhase.SETUP]: false, [PublicKernelPhase.APP_LOGIC]: true, [PublicKernelPhase.TEARDOWN]: false, + [PublicKernelPhase.TAIL]: false, }; export abstract class AbstractPhaseManager { + protected hintsBuilder: HintsBuilder; protected log: DebugLogger; constructor( protected db: MerkleTreeOperations, @@ -79,6 +86,7 @@ export abstract class AbstractPhaseManager { protected historicalHeader: Header, public phase: PublicKernelPhase, ) { + this.hintsBuilder = new HintsBuilder(db); this.log = createDebugLogger(`aztec:sequencer:${phase}`); } /** @@ -129,6 +137,7 @@ export abstract class AbstractPhaseManager { [PublicKernelPhase.SETUP]: [], [PublicKernelPhase.APP_LOGIC]: [], [PublicKernelPhase.TEARDOWN]: [], + [PublicKernelPhase.TAIL]: [], }; } @@ -142,12 +151,14 @@ export abstract class AbstractPhaseManager { [PublicKernelPhase.SETUP]: [], [PublicKernelPhase.APP_LOGIC]: publicCallsStack, [PublicKernelPhase.TEARDOWN]: [], + [PublicKernelPhase.TAIL]: [], }; } else { return { [PublicKernelPhase.SETUP]: publicCallsStack.slice(0, firstRevertibleCallIndex - 1), [PublicKernelPhase.APP_LOGIC]: publicCallsStack.slice(firstRevertibleCallIndex), [PublicKernelPhase.TEARDOWN]: [publicCallsStack[firstRevertibleCallIndex - 1]], + [PublicKernelPhase.TAIL]: [], }; } } @@ -206,7 +217,7 @@ export abstract class AbstractPhaseManager { executionStack.push(...result.nestedExecutions); const callData = await this.getPublicCallData(result, isExecutionRequest); - [kernelOutput, kernelProof] = await this.runKernelCircuit(callData, kernelOutput, kernelProof); + [kernelOutput, kernelProof] = await this.runKernelCircuit(kernelOutput, kernelProof, callData); if (kernelOutput.reverted && this.phase === PublicKernelPhase.APP_LOGIC) { this.log.debug( @@ -235,21 +246,38 @@ export abstract class AbstractPhaseManager { } protected async runKernelCircuit( - callData: PublicCallData, previousOutput: PublicKernelCircuitPublicInputs, previousProof: Proof, + callData?: PublicCallData, ): Promise<[PublicKernelCircuitPublicInputs, Proof]> { - const output = await this.getKernelCircuitOutput(callData, previousOutput, previousProof); + const output = await this.getKernelCircuitOutput(previousOutput, previousProof, callData); const proof = await this.publicProver.getPublicKernelCircuitProof(output); return [output, proof]; } - protected getKernelCircuitOutput( - callData: PublicCallData, + protected async getKernelCircuitOutput( previousOutput: PublicKernelCircuitPublicInputs, previousProof: Proof, + callData?: PublicCallData, ): Promise { const previousKernel = this.getPreviousKernelData(previousOutput, previousProof); + + if (this.phase === PublicKernelPhase.TAIL) { + const { endNonRevertibleData, end } = previousOutput; + const nullifierReadRequestResetHints = await this.hintsBuilder.getNullifierReadRequestResetHints( + endNonRevertibleData.nullifierReadRequests, + end.nullifierReadRequests, + endNonRevertibleData.newNullifiers, + end.newNullifiers, + ); + const inputs = new PublicKernelTailCircuitPrivateInputs(previousKernel, nullifierReadRequestResetHints); + return this.publicKernel.publicKernelCircuitTail(inputs); + } + + if (!callData) { + throw new Error(`Cannot run public kernel circuit without call data for phase '${this.phase}'.`); + } + const inputs = new PublicKernelCircuitPrivateInputs(previousKernel, callData); switch (this.phase) { case PublicKernelPhase.SETUP: @@ -296,6 +324,11 @@ export abstract class AbstractPhaseManager { newNullifiers: padArrayEnd(result.newNullifiers, SideEffectLinkedToNoteHash.empty(), MAX_NEW_NULLIFIERS_PER_CALL), newL2ToL1Msgs: padArrayEnd(result.newL2ToL1Messages, L2ToL1Message.empty(), MAX_NEW_L2_TO_L1_MSGS_PER_CALL), returnValues: padArrayEnd(result.returnValues, Fr.ZERO, RETURN_VALUES_LENGTH), + nullifierReadRequests: padArrayEnd( + result.nullifierReadRequests, + ReadRequest.empty(), + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, + ), contractStorageReads: padArrayEnd( result.contractStorageReads, ContractStorageRead.empty(), diff --git a/yarn-project/sequencer-client/src/sequencer/hints_builder.ts b/yarn-project/sequencer-client/src/sequencer/hints_builder.ts new file mode 100644 index 000000000000..d7abe9fa0d95 --- /dev/null +++ b/yarn-project/sequencer-client/src/sequencer/hints_builder.ts @@ -0,0 +1,56 @@ +import { MerkleTreeId } from '@aztec/circuit-types'; +import { + Fr, + MAX_NEW_NULLIFIERS_PER_TX, + MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_TX, + MAX_REVERTIBLE_NULLIFIERS_PER_TX, + MembershipWitness, + NULLIFIER_TREE_HEIGHT, + NullifierLeafPreimage, + ReadRequestContext, + SideEffectLinkedToNoteHash, + buildNullifierReadRequestResetHints, + concatAccumulatedData, + mergeAccumulatedData, +} from '@aztec/circuits.js'; +import { Tuple } from '@aztec/foundation/serialize'; +import { MerkleTreeOperations } from '@aztec/world-state'; + +export class HintsBuilder { + constructor(private db: MerkleTreeOperations) {} + + getNullifierReadRequestResetHints( + nullifierReadRequestsNonRevertible: Tuple, + nullifierReadRequestsRevertible: Tuple, + nullifiersNonRevertible: Tuple, + nullifiersRevertible: Tuple, + ) { + return buildNullifierReadRequestResetHints( + this, + mergeAccumulatedData( + MAX_NULLIFIER_READ_REQUESTS_PER_TX, + nullifierReadRequestsNonRevertible, + nullifierReadRequestsRevertible, + ), + concatAccumulatedData(MAX_NEW_NULLIFIERS_PER_TX, nullifiersNonRevertible, nullifiersRevertible), + ); + } + + async getNullifierMembershipWitness(nullifier: Fr) { + const index = await this.db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); + if (index === undefined) { + return; + } + + const siblingPath = await this.db.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index); + const membershipWitness = new MembershipWitness( + NULLIFIER_TREE_HEIGHT, + index, + siblingPath.toTuple(), + ); + const leafPreimage = (await this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))! as NullifierLeafPreimage; + + return { membershipWitness, leafPreimage }; + } +} diff --git a/yarn-project/sequencer-client/src/sequencer/phase_manager_factory.ts b/yarn-project/sequencer-client/src/sequencer/phase_manager_factory.ts index 845b5cb28a98..129fdc88129a 100644 --- a/yarn-project/sequencer-client/src/sequencer/phase_manager_factory.ts +++ b/yarn-project/sequencer-client/src/sequencer/phase_manager_factory.ts @@ -9,6 +9,7 @@ import { ContractsDataSourcePublicDB } from '../simulator/public_executor.js'; import { AbstractPhaseManager, PublicKernelPhase } from './abstract_phase_manager.js'; import { AppLogicPhaseManager } from './app_logic_phase_manager.js'; import { SetupPhaseManager } from './setup_phase_manager.js'; +import { TailPhaseManager } from './tail_phase_manager.js'; import { TeardownPhaseManager } from './teardown_phase_manager.js'; export class PhaseDidNotChangeError extends Error { @@ -115,6 +116,17 @@ export class PhaseManagerFactory { publicContractsDB, publicStateDB, ); + } else if (currentPhaseManager.phase !== PublicKernelPhase.TAIL) { + return new TailPhaseManager( + db, + publicExecutor, + publicKernel, + publicProver, + globalVariables, + historicalHeader, + publicContractsDB, + publicStateDB, + ); } else { return undefined; } diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts index 9e6bc64a850c..17e639a7000b 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts @@ -237,7 +237,7 @@ describe('public_processor', () => { expect(processed).toEqual([expectedTxByHash(tx)]); expect(failed).toHaveLength(0); expect(publicExecutor.simulate).toHaveBeenCalledTimes(2); - expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(1); + expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(2); expect(publicWorldStateDB.rollback).toHaveBeenCalledTimes(0); }); @@ -288,7 +288,7 @@ describe('public_processor', () => { expect(processed).toEqual([expectedTxByHash(tx)]); expect(failed).toHaveLength(0); expect(publicExecutor.simulate).toHaveBeenCalledTimes(1); - expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(1); + expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(2); expect(publicWorldStateDB.rollback).toHaveBeenCalledTimes(0); }); @@ -390,7 +390,7 @@ describe('public_processor', () => { expect(appLogicSpy).toHaveBeenCalledTimes(2); expect(teardownSpy).toHaveBeenCalledTimes(2); expect(publicExecutor.simulate).toHaveBeenCalledTimes(3); - expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(2); + expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(3); expect(publicWorldStateDB.rollback).toHaveBeenCalledTimes(1); expect(arrayNonEmptyLength(processed[0].data.combinedData.publicCallStack, i => i.isEmpty())).toEqual(0); @@ -502,7 +502,7 @@ describe('public_processor', () => { expect(appLogicSpy).toHaveBeenCalledTimes(1); expect(teardownSpy).toHaveBeenCalledTimes(3); expect(publicExecutor.simulate).toHaveBeenCalledTimes(3); - expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(3); + expect(publicWorldStateDB.commit).toHaveBeenCalledTimes(4); expect(publicWorldStateDB.rollback).toHaveBeenCalledTimes(0); const txEffect = toTxEffect(processed[0]); @@ -611,6 +611,7 @@ class PublicExecutionResultBuilder { return { execution: this._execution, nestedExecutions: this._nestedExecutions, + nullifierReadRequests: [], contractStorageUpdateRequests: this._contractStorageUpdateRequests, returnValues: this._returnValues, newNoteHashes: [], diff --git a/yarn-project/sequencer-client/src/sequencer/tail_phase_manager.ts b/yarn-project/sequencer-client/src/sequencer/tail_phase_manager.ts new file mode 100644 index 000000000000..ffb1e018ba62 --- /dev/null +++ b/yarn-project/sequencer-client/src/sequencer/tail_phase_manager.ts @@ -0,0 +1,49 @@ +import { Tx } from '@aztec/circuit-types'; +import { GlobalVariables, Header, Proof, PublicKernelCircuitPublicInputs } from '@aztec/circuits.js'; +import { PublicExecutor, PublicStateDB } from '@aztec/simulator'; +import { MerkleTreeOperations } from '@aztec/world-state'; + +import { PublicProver } from '../prover/index.js'; +import { PublicKernelCircuitSimulator } from '../simulator/index.js'; +import { ContractsDataSourcePublicDB } from '../simulator/public_executor.js'; +import { AbstractPhaseManager, PublicKernelPhase } from './abstract_phase_manager.js'; +import { FailedTx } from './processed_tx.js'; + +export class TailPhaseManager extends AbstractPhaseManager { + constructor( + protected db: MerkleTreeOperations, + protected publicExecutor: PublicExecutor, + protected publicKernel: PublicKernelCircuitSimulator, + protected publicProver: PublicProver, + protected globalVariables: GlobalVariables, + protected historicalHeader: Header, + protected publicContractsDB: ContractsDataSourcePublicDB, + protected publicStateDB: PublicStateDB, + public readonly phase: PublicKernelPhase = PublicKernelPhase.TAIL, + ) { + super(db, publicExecutor, publicKernel, publicProver, globalVariables, historicalHeader, phase); + } + + async handle(tx: Tx, previousPublicKernelOutput: PublicKernelCircuitPublicInputs, previousPublicKernelProof: Proof) { + this.log(`Processing tx ${tx.getTxHash()}`); + this.log(`Executing tail circuit for tx ${tx.getTxHash()}`); + const [publicKernelOutput, publicKernelProof] = await this.runKernelCircuit( + previousPublicKernelOutput, + previousPublicKernelProof, + ); + + // commit the state updates from this transaction + await this.publicStateDB.commit(); + + return { publicKernelOutput, publicKernelProof, revertReason: undefined }; + } + + async rollback(tx: Tx, err: unknown): Promise { + this.log.warn(`Error processing tx ${tx.getTxHash()}: ${err}`); + await this.publicStateDB.rollback(); + return { + tx, + error: err instanceof Error ? err : new Error('Unknown error'), + }; + } +} diff --git a/yarn-project/sequencer-client/src/simulator/index.ts b/yarn-project/sequencer-client/src/simulator/index.ts index 38a8b441e4ee..b23aa903b148 100644 --- a/yarn-project/sequencer-client/src/simulator/index.ts +++ b/yarn-project/sequencer-client/src/simulator/index.ts @@ -4,6 +4,7 @@ import { MergeRollupInputs, PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs, + PublicKernelTailCircuitPrivateInputs, RootRollupInputs, RootRollupPublicInputs, } from '@aztec/circuits.js'; @@ -54,5 +55,11 @@ export interface PublicKernelCircuitSimulator { * @returns The public inputs as outputs of the simulation. */ publicKernelCircuitTeardown(inputs: PublicKernelCircuitPrivateInputs): Promise; + /** + * Simulates the public kernel tail circuit from its inputs. + * @param inputs - Inputs to the circuit. + * @returns The public inputs as outputs of the simulation. + */ + publicKernelCircuitTail(inputs: PublicKernelTailCircuitPrivateInputs): Promise; } export * from './acvm_wasm.js'; diff --git a/yarn-project/sequencer-client/src/simulator/public_kernel.ts b/yarn-project/sequencer-client/src/simulator/public_kernel.ts index fd4681cbed8d..0d6a3efa0153 100644 --- a/yarn-project/sequencer-client/src/simulator/public_kernel.ts +++ b/yarn-project/sequencer-client/src/simulator/public_kernel.ts @@ -1,17 +1,24 @@ import { CircuitSimulationStats } from '@aztec/circuit-types/stats'; -import { PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs } from '@aztec/circuits.js'; +import { + PublicKernelCircuitPrivateInputs, + PublicKernelCircuitPublicInputs, + PublicKernelTailCircuitPrivateInputs, +} from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { elapsed } from '@aztec/foundation/timer'; import { PublicKernelAppLogicArtifact, PublicKernelSetupArtifact, + PublicKernelTailArtifact, PublicKernelTeardownArtifact, convertPublicInnerRollupInputsToWitnessMap, convertPublicInnerRollupOutputFromWitnessMap, convertPublicSetupRollupInputsToWitnessMap, convertPublicSetupRollupOutputFromWitnessMap, - convertPublicTailRollupInputsToWitnessMap, - convertPublicTailRollupOutputFromWitnessMap, + convertPublicTailInputsToWitnessMap, + convertPublicTailOutputFromWitnessMap, + convertPublicTeardownRollupInputsToWitnessMap, + convertPublicTeardownRollupOutputFromWitnessMap, } from '@aztec/noir-protocol-circuits-types'; import { PublicKernelCircuitSimulator, WASMSimulator } from './index.js'; @@ -91,11 +98,11 @@ export class RealPublicKernelCircuitSimulator implements PublicKernelCircuitSimu if (!input.previousKernel.publicInputs.needsTeardown) { throw new Error(`Expected previous kernel inputs to need teardown`); } - const inputWitness = convertPublicTailRollupInputsToWitnessMap(input); + const inputWitness = convertPublicTeardownRollupInputsToWitnessMap(input); const [duration, witness] = await elapsed(() => this.wasmSimulator.simulateCircuit(inputWitness, PublicKernelTeardownArtifact), ); - const result = convertPublicTailRollupOutputFromWitnessMap(witness); + const result = convertPublicTeardownRollupOutputFromWitnessMap(witness); this.log(`Simulated public kernel teardown circuit`, { eventName: 'circuit-simulation', circuitName: 'public-kernel-teardown', @@ -105,4 +112,27 @@ export class RealPublicKernelCircuitSimulator implements PublicKernelCircuitSimu } satisfies CircuitSimulationStats); return result; } + + /** + * Simulates the public kernel tail circuit from its inputs. + * @param input - Inputs to the circuit. + * @returns The public inputs as outputs of the simulation. + */ + public async publicKernelCircuitTail( + input: PublicKernelTailCircuitPrivateInputs, + ): Promise { + const inputWitness = convertPublicTailInputsToWitnessMap(input); + const [duration, witness] = await elapsed(() => + this.wasmSimulator.simulateCircuit(inputWitness, PublicKernelTailArtifact), + ); + const result = convertPublicTailOutputFromWitnessMap(witness); + this.log(`Simulated public kernel tail circuit`, { + eventName: 'circuit-simulation', + circuitName: 'public-kernel-tail', + duration, + inputSize: input.toBuffer().length, + outputSize: result.toBuffer().length, + } satisfies CircuitSimulationStats); + return result; + } } diff --git a/yarn-project/simulator/src/avm/temporary_executor_migration.ts b/yarn-project/simulator/src/avm/temporary_executor_migration.ts index dc6af29bc2bf..999f97fbb691 100644 --- a/yarn-project/simulator/src/avm/temporary_executor_migration.ts +++ b/yarn-project/simulator/src/avm/temporary_executor_migration.ts @@ -5,6 +5,7 @@ import { ContractStorageUpdateRequest, GlobalVariables, L2ToL1Message, + ReadRequest, SideEffect, SideEffectLinkedToNoteHash, } from '@aztec/circuits.js'; @@ -92,12 +93,14 @@ export function temporaryConvertAvmResults( // TODO(follow up in pr tree): NOT SUPPORTED YET, make sure hashing and log resolution is done correctly // Disabled. const nestedExecutions: PublicExecutionResult[] = []; + const nullifierReadRequests: ReadRequest[] = []; const newNullifiers: SideEffectLinkedToNoteHash[] = []; const unencryptedLogs = FunctionL2Logs.empty(); const newL2ToL1Messages = newWorldState.newL1Messages.map(() => L2ToL1Message.empty()); return { execution, + nullifierReadRequests, newNoteHashes, newL2ToL1Messages, newNullifiers, diff --git a/yarn-project/simulator/src/public/execution.ts b/yarn-project/simulator/src/public/execution.ts index 83cf088f8cd0..cb9c42df332a 100644 --- a/yarn-project/simulator/src/public/execution.ts +++ b/yarn-project/simulator/src/public/execution.ts @@ -8,6 +8,7 @@ import { PublicCallRequest, PublicDataRead, PublicDataUpdateRequest, + ReadRequest, SideEffect, SideEffectLinkedToNoteHash, } from '@aztec/circuits.js'; @@ -27,6 +28,8 @@ export interface PublicExecutionResult { newL2ToL1Messages: L2ToL1Message[]; /** The new nullifiers to be inserted into the nullifier tree. */ newNullifiers: SideEffectLinkedToNoteHash[]; + /** The nullifier read requests emitted in this call. */ + nullifierReadRequests: ReadRequest[]; /** The contract storage reads performed by the function. */ contractStorageReads: ContractStorageRead[]; /** The contract storage update requests performed by the function. */ diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 2f8c430db904..42cd5ec6ac08 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -80,6 +80,7 @@ export async function executePublicFunction( newNoteHashes: [], newL2ToL1Messages: [], newNullifiers: [], + nullifierReadRequests: [], contractStorageReads: [], contractStorageUpdateRequests: [], nestedExecutions: [], @@ -96,11 +97,13 @@ export async function executePublicFunction( const returnWitness = extractReturnWitness(acir, partialWitness); const { returnValues, + nullifierReadRequests: nullifierReadRequestsPadded, newL2ToL1Msgs, newNoteHashes: newNoteHashesPadded, newNullifiers: newNullifiersPadded, } = PublicCircuitPublicInputs.fromFields(returnWitness); + const nullifierReadRequests = nullifierReadRequestsPadded.filter(v => !v.isEmpty()); const newL2ToL1Messages = newL2ToL1Msgs.filter(v => !v.isEmpty()); const newNoteHashes = newNoteHashesPadded.filter(v => !v.isEmpty()); const newNullifiers = newNullifiersPadded.filter(v => !v.isEmpty()); @@ -126,6 +129,7 @@ export async function executePublicFunction( newNoteHashes, newL2ToL1Messages, newNullifiers, + nullifierReadRequests, contractStorageReads, contractStorageUpdateRequests, returnValues,