diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index fe139215971a..12d91a18524f 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -39,6 +39,7 @@ library Constants { uint256 internal constant MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4; uint256 internal constant NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1; uint256 internal constant NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1; + uint256 internal constant MAX_PUBLIC_DATA_HINTS = 64; uint256 internal constant NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP = 16; uint256 internal constant VK_TREE_HEIGHT = 3; uint256 internal constant FUNCTION_TREE_HEIGHT = 5; diff --git a/noir-projects/noir-protocol-circuits/Nargo.toml b/noir-projects/noir-protocol-circuits/Nargo.toml index d3f3c3d55b0c..48cc2fc96f66 100644 --- a/noir-projects/noir-protocol-circuits/Nargo.toml +++ b/noir-projects/noir-protocol-circuits/Nargo.toml @@ -22,6 +22,7 @@ members = [ "crates/public-kernel-teardown-simulated", "crates/public-kernel-tail", "crates/public-kernel-tail-simulated", + "crates/reset-kernel-lib", "crates/rollup-lib", "crates/rollup-merge", "crates/rollup-base", 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 ac229a9beb90..551605f44260 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 @@ -59,7 +59,7 @@ mod tests { use crate::private_kernel_tail::PrivateKernelTailCircuitPrivateInputs; use dep::reset_kernel_lib::{ tests::nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder, - read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} + reset::read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::constants::{ MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, @@ -177,6 +177,14 @@ mod tests { } } + #[test] + unconstrained fn execution_succeeded() { + let mut builder = PrivateKernelTailInputsBuilder::new(); + let public_inputs = builder.execute(); + + assert(is_empty(public_inputs.start_state)); + } + #[test] unconstrained fn native_matching_one_read_request_to_commitment_works() { let mut builder = PrivateKernelTailInputsBuilder::new(); diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr index d3569bdf3b5f..1a6aef17c5b3 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail_to_public.nr @@ -59,7 +59,7 @@ mod tests { use crate::private_kernel_tail_to_public::PrivateKernelTailToPublicCircuitPrivateInputs; use dep::reset_kernel_lib::{ tests::nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder, - read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} + reset::read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::constants::{ MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, 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 006b531e94df..61ea76033e58 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 @@ -79,11 +79,8 @@ pub fn initialize_emitted_end_values( circuit_outputs.end_non_revertible.encrypted_logs_hash = start_non_revertible.encrypted_logs_hash; circuit_outputs.end_non_revertible.encrypted_log_preimages_length = start_non_revertible.encrypted_log_preimages_length; - // TODO - should be propagated only in initialize_end_values() and clear them in the tail circuit. The - // max_block_number must be propagated to the rollup however as a RollupValidationRequest. let start = previous_kernel.public_inputs.validation_requests; circuit_outputs.validation_requests.max_block_number = start.for_rollup.max_block_number; - circuit_outputs.validation_requests.public_data_reads = array_to_bounded_vec(start.public_data_reads); } // Initialises the circuit outputs with the end state of the previous iteration. @@ -110,6 +107,7 @@ pub fn initialize_end_values( circuit_outputs.validation_requests.max_block_number = previous_kernel.public_inputs.validation_requests.for_rollup.max_block_number; circuit_outputs.validation_requests.nullifier_read_requests = array_to_bounded_vec(start.nullifier_read_requests); circuit_outputs.validation_requests.nullifier_non_existent_read_requests = array_to_bounded_vec(start.nullifier_non_existent_read_requests); + circuit_outputs.validation_requests.public_data_reads = array_to_bounded_vec(start.public_data_reads); } fn perform_static_call_checks(public_call: PublicCallData) { @@ -244,7 +242,7 @@ pub fn update_public_end_non_revertible_values( propagate_new_nullifiers_non_revertible(public_call, circuit_outputs); propagate_new_note_hashes_non_revertible(public_call, circuit_outputs); - propagate_new_l2_to_l1_messages(public_call, circuit_outputs); + propagate_new_l2_to_l1_messages_non_revertible(public_call, circuit_outputs); propagate_valid_non_revertible_public_data_update_requests(public_call, circuit_outputs); } @@ -457,6 +455,31 @@ fn propagate_new_nullifiers( circuit_outputs.end.new_nullifiers.extend_from_bounded_vec(siloed_new_nullifiers); } +fn propagate_new_l2_to_l1_messages_non_revertible( + public_call: PublicCallData, + public_inputs: &mut PublicKernelCircuitPublicInputsBuilder +) { + // new l2 to l1 messages + let public_call_public_inputs = public_call.call_stack_item.public_inputs; + let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address; + + let new_l2_to_l1_msgs = public_call_public_inputs.new_l2_to_l1_msgs; + let mut new_l2_to_l1_msgs_to_insert : BoundedVec = BoundedVec::new(); + for i in 0..MAX_NEW_L2_TO_L1_MSGS_PER_CALL { + let msg = new_l2_to_l1_msgs[i]; + if !is_empty(msg) { + let new_l2_to_l1_msgs = compute_l2_to_l1_hash( + storage_contract_address, + public_inputs.constants.tx_context.version, + public_inputs.constants.tx_context.chain_id, + msg + ); + new_l2_to_l1_msgs_to_insert.push(new_l2_to_l1_msgs) + } + } + public_inputs.end_non_revertible.new_l2_to_l1_msgs.extend_from_bounded_vec(new_l2_to_l1_msgs_to_insert); +} + fn propagate_new_l2_to_l1_messages(public_call: PublicCallData, public_inputs: &mut PublicKernelCircuitPublicInputsBuilder) { // new l2 to l1 messages let public_call_public_inputs = public_call.call_stack_item.public_inputs; 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 index 23ab10347794..d84e309b6a91 100644 --- 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 @@ -1,29 +1,28 @@ use crate::common; use dep::reset_kernel_lib::{ - NullifierReadRequestHints, NullifierNonExistentReadRequestHints, reset_non_existent_read_requests, - reset_read_requests + NullifierReadRequestHints, NullifierNonExistentReadRequestHints, PublicDataReadRequestHints, + PublicValidationRequestProcessor, PublicDataHint }; use dep::types::{ abis::{ - kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, - kernel_data::PublicKernelData, side_effect::SideEffectLinkedToNoteHash + accumulated_data::CombinedAccumulatedData, kernel_circuit_public_inputs::KernelCircuitPublicInputs, + kernel_data::PublicKernelData }, - constants::MAX_NEW_NULLIFIERS_PER_TX, - utils::{arrays::{array_length, array_merge, array_to_bounded_vec, assert_sorted_array}}, - hash::silo_nullifier, traits::is_empty + constants::MAX_PUBLIC_DATA_HINTS, + merkle_tree::{conditionally_assert_check_membership, MembershipWitness}, + partial_state_reference::PartialStateReference, utils::{arrays::array_length} }; struct PublicKernelTailCircuitPrivateInputs { previous_kernel: PublicKernelData, nullifier_read_request_hints: NullifierReadRequestHints, nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints, + public_data_hints: [PublicDataHint; MAX_PUBLIC_DATA_HINTS], + public_data_read_request_hints: PublicDataReadRequestHints, + start_state: PartialStateReference, } impl PublicKernelTailCircuitPrivateInputs { - fn propagate_revert_code(self, public_inputs: &mut PublicKernelCircuitPublicInputsBuilder) { - public_inputs.revert_code = self.previous_kernel.public_inputs.revert_code; - } - fn validate_inputs(self) { let previous_public_inputs = self.previous_kernel.public_inputs; assert(previous_public_inputs.needs_setup() == false, "Previous kernel needs setup"); @@ -37,106 +36,96 @@ impl PublicKernelTailCircuitPrivateInputs { ); } - 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; - - let requests = self.previous_kernel.public_inputs.validation_requests.nullifier_read_requests; - - let pending_nullifiers = array_merge(end_non_revertible.new_nullifiers, end.new_nullifiers); - - let hints = self.nullifier_read_request_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" - ); - } - - fn validate_nullifier_non_existent_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; - - // The values of the read requests here need to be siloed. - // Notice that it's not the case for regular read requests, which can be run between two kernel iterations, and will to be verified against unsiloed pending values. - let mut read_requests = self.previous_kernel.public_inputs.validation_requests.nullifier_non_existent_read_requests; - for i in 0..read_requests.len() { - let read_request = read_requests[i]; - if !is_empty(read_request) { - read_requests[i].value = silo_nullifier(read_request.contract_address, read_request.value); + fn validate_public_data_hints(self) { + let public_data_hints = self.public_data_hints; + let public_data_tree_root = self.start_state.public_data_tree.root; + for i in 0..public_data_hints.len() { + let hint = public_data_hints[i]; + // We only need to check leaf_slot to decide if a (non-)membership check is required. + // It will fail if a PublicDataHint with 0 leaf_slot is used to verify a non-empty public read or write. + if hint.leaf_slot != 0 { + let exists_in_tree = hint.leaf_slot == hint.leaf_preimage.slot; + if exists_in_tree { + assert( + hint.value == hint.leaf_preimage.value, "Hinted public data value does not match the value in leaf preimage" + ); + } else { + assert(hint.value == 0, "Value must be 0 for non-existent public data"); + } + + conditionally_assert_check_membership( + hint.leaf_slot, + exists_in_tree, + hint.leaf_preimage, + MembershipWitness { leaf_index: hint.membership_witness.leaf_index, sibling_path: hint.membership_witness.sibling_path }, + public_data_tree_root + ); } } + } - let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; - - let hints = self.nullifier_non_existent_read_request_hints; - - let pending_nullifiers = array_merge(end_non_revertible.new_nullifiers, end.new_nullifiers); - assert_sorted_array( - pending_nullifiers, - hints.sorted_pending_values, - hints.sorted_pending_value_index_hints, - |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value) - ); - let sorted_pending_nullifiers = array_to_bounded_vec(hints.sorted_pending_values); - - reset_non_existent_read_requests( - read_requests, - hints.non_membership_hints, - nullifier_tree_root, - sorted_pending_nullifiers, - hints.next_pending_value_indices - ); + fn propagate_accumulated_data(self) -> CombinedAccumulatedData { + let previous_public_inputs = self.previous_kernel.public_inputs; + // TODO: Sort the combined data. + CombinedAccumulatedData::combine( + previous_public_inputs.end_non_revertible, + previous_public_inputs.end + ) } pub fn public_kernel_tail(self) -> KernelCircuitPublicInputs { - let mut public_inputs = PublicKernelCircuitPublicInputsBuilder::empty(); - self.validate_inputs(); - self.propagate_revert_code(&mut public_inputs); - - common::initialize_emitted_end_values(self.previous_kernel, &mut public_inputs); + self.validate_public_data_hints(); - self.validate_nullifier_read_requests(&mut public_inputs); + let previous_public_inputs = self.previous_kernel.public_inputs; + let request_processor = PublicValidationRequestProcessor::new( + previous_public_inputs, + self.nullifier_read_request_hints, + self.nullifier_non_existent_read_request_hints, + self.start_state.nullifier_tree.root, + self.public_data_read_request_hints, + self.public_data_hints + ); + request_processor.validate(); - self.validate_nullifier_non_existent_read_requests(&mut public_inputs); + let end = self.propagate_accumulated_data(); - public_inputs.finish_tail() + KernelCircuitPublicInputs { + aggregation_object: previous_public_inputs.aggregation_object, + rollup_validation_requests: previous_public_inputs.validation_requests.for_rollup, + end, + constants: previous_public_inputs.constants, + start_state: self.start_state, + revert_code: previous_public_inputs.revert_code + } } } mod tests { - use crate::{public_kernel_tail::PublicKernelTailCircuitPrivateInputs}; + use crate::public_kernel_tail::PublicKernelTailCircuitPrivateInputs; use dep::reset_kernel_lib::{ tests::{ nullifier_non_existent_read_request_hints_builder::NullifierNonExistentReadRequestHintsBuilder, - nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder + nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder, + public_data_read_request_hints_builder::PublicDataReadRequestHintsBuilder }, - read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} + PublicDataHint, reset::read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::{ abis::{ - kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, - kernel_data::PublicKernelData, nullifier_leaf_preimage::NullifierLeafPreimage + kernel_circuit_public_inputs::KernelCircuitPublicInputs, kernel_data::PublicKernelData, + nullifier_leaf_preimage::NullifierLeafPreimage, membership_witness::PublicDataMembershipWitness }, constants::{ - MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, - NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT + MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PUBLIC_DATA_HINTS, + MAX_PUBLIC_DATA_READS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, + NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT, PUBLIC_DATA_SUBTREE_HEIGHT, + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT }, - hash::silo_nullifier, + hash::silo_nullifier, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, tests::{fixture_builder::FixtureBuilder, merkle_tree_utils::NonEmptyMerkleTree}, - utils::arrays::array_merge + partial_state_reference::PartialStateReference, utils::arrays::array_merge }; fn build_nullifier_tree() -> NonEmptyMerkleTree { @@ -151,11 +140,33 @@ mod tests { ) } + fn get_settled_public_data_leaves() -> [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] { + let mut settled_public_data_leaves = [PublicDataTreeLeafPreimage::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; + settled_public_data_leaves[0] = PublicDataTreeLeafPreimage { slot: 22, value: 200, next_slot: 33, next_index: 1 }; + settled_public_data_leaves[1] = PublicDataTreeLeafPreimage { slot: 33, value: 300, next_slot: 0, next_index: 0 }; + settled_public_data_leaves[2] = PublicDataTreeLeafPreimage { slot: 11, value: 100, next_slot: 22, next_index: 0 }; + settled_public_data_leaves + } + + fn build_public_data_tree() -> NonEmptyMerkleTree { + let settled_public_data_leaves = get_settled_public_data_leaves(); + NonEmptyMerkleTree::new( + settled_public_data_leaves.map(|preimage: PublicDataTreeLeafPreimage| preimage.hash()), + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT], + [0; PUBLIC_DATA_SUBTREE_HEIGHT] + ) + } + struct PublicKernelTailCircuitPrivateInputsBuilder { previous_kernel: FixtureBuilder, previous_revertible: FixtureBuilder, nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder, nullifier_non_existent_read_request_hints_builder: NullifierNonExistentReadRequestHintsBuilder, + public_data_read_request_hints_builder: PublicDataReadRequestHintsBuilder, + public_data_hints: BoundedVec, + public_data_tree: NonEmptyMerkleTree, + start_state: PartialStateReference, } impl PublicKernelTailCircuitPrivateInputsBuilder { @@ -168,7 +179,11 @@ mod tests { previous_kernel, previous_revertible, nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX), - nullifier_non_existent_read_request_hints_builder + nullifier_non_existent_read_request_hints_builder, + public_data_read_request_hints_builder: PublicDataReadRequestHintsBuilder::new(MAX_PUBLIC_DATA_READS_PER_TX), + public_data_hints: BoundedVec::new(), + public_data_tree: NonEmptyMerkleTree::empty(), + start_state: PartialStateReference::empty() }; builder.set_nullifiers_for_non_existent_read_request_hints(); builder @@ -176,8 +191,17 @@ mod tests { pub fn with_nullifier_tree(&mut self) -> Self { let nullifier_tree = build_nullifier_tree(); - self.previous_kernel.historical_header.state.partial.nullifier_tree.root = nullifier_tree.get_root(); self.nullifier_non_existent_read_request_hints_builder.set_nullifier_tree(nullifier_tree); + let tree_root = nullifier_tree.get_root(); + self.start_state.nullifier_tree.root = tree_root; + self.previous_kernel.historical_header.state.partial.nullifier_tree.root = tree_root; + *self + } + + pub fn with_public_data_tree(&mut self) -> Self { + let public_data_tree = build_public_data_tree(); + self.public_data_tree = public_data_tree; + self.start_state.public_data_tree.root = public_data_tree.get_root(); *self } @@ -236,6 +260,37 @@ mod tests { self.nullifier_non_existent_read_request_hints_builder.add_value_read(siloed_nullifier); } + pub fn add_public_data_hint_for_settled_public_data(&mut self, leaf_index: u64) { + let leaf_preimage = get_settled_public_data_leaves()[leaf_index]; + let membership_witness = PublicDataMembershipWitness { leaf_index: leaf_index as Field, sibling_path: self.public_data_tree.get_sibling_path(leaf_index) }; + let hint = PublicDataHint { + leaf_slot: leaf_preimage.slot, + value: leaf_preimage.value, + override_counter: 0, + membership_witness, + leaf_preimage + }; + self.public_data_hints.push(hint); + } + + pub fn add_public_data_hint_for_non_existent_public_data(&mut self, leaf_slot: Field, low_leaf_index: u64) { + let leaf_preimage = get_settled_public_data_leaves()[low_leaf_index]; + let membership_witness = PublicDataMembershipWitness { + leaf_index: low_leaf_index as Field, + sibling_path: self.public_data_tree.get_sibling_path(low_leaf_index) + }; + let hint = PublicDataHint { leaf_slot, value: 0, override_counter: 0, membership_witness, leaf_preimage }; + self.public_data_hints.push(hint); + } + + pub fn add_pending_public_data_read_request(&mut self, public_date_update_request_index: u64) { + let read_request_index = self.previous_kernel.add_read_request_for_pending_public_data(public_date_update_request_index); + let hint_index = self.public_data_read_request_hints_builder.pending_read_hints.len(); + let hint = PendingReadHint { read_request_index, pending_value_index: public_date_update_request_index }; + self.public_data_read_request_hints_builder.pending_read_hints.push(hint); + self.public_data_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + } + fn sync_counters(&mut self) { let counter_non_revertible = self.previous_kernel.counter; let counter_revertible = self.previous_revertible.counter; @@ -253,7 +308,10 @@ mod tests { let kernel = PublicKernelTailCircuitPrivateInputs { previous_kernel, nullifier_read_request_hints: self.nullifier_read_request_hints_builder.to_hints(), - nullifier_non_existent_read_request_hints: self.nullifier_non_existent_read_request_hints_builder.to_hints() + nullifier_non_existent_read_request_hints: self.nullifier_non_existent_read_request_hints_builder.to_hints(), + public_data_hints: self.public_data_hints.storage, + public_data_read_request_hints: self.public_data_read_request_hints_builder.to_hints(), + start_state: self.start_state }; kernel.public_kernel_tail() @@ -356,6 +414,95 @@ mod tests { builder.read_non_existent_nullifier(1); + builder.failed(); + } + + #[test] + unconstrained fn validate_public_data_hints() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_public_data_tree(); + + builder.add_public_data_hint_for_settled_public_data(1); + builder.add_public_data_hint_for_settled_public_data(0); + builder.add_public_data_hint_for_settled_public_data(2); + + builder.succeeded(); + } + + #[test(should_fail_with="Hinted public data value does not match the value in leaf preimage")] + unconstrained fn validate_public_data_hints_failed_mismatch_value() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_public_data_tree(); + + builder.add_public_data_hint_for_settled_public_data(1); + + let mut hint = builder.public_data_hints.pop(); + hint.value += 1; + builder.public_data_hints.push(hint); + + builder.failed(); + } + + #[test] + unconstrained fn validate_public_data_hints_uninitialized_value() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_public_data_tree(); + + builder.add_public_data_hint_for_non_existent_public_data(25, 0); + + builder.succeeded(); + } + + #[test(should_fail_with="Value must be 0 for non-existent public data")] + unconstrained fn validate_public_data_hints_failed_non_zero_uninitialized_value() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_public_data_tree(); + + builder.add_public_data_hint_for_non_existent_public_data(25, 0); + + let mut hint = builder.public_data_hints.pop(); + hint.value = 1; + builder.public_data_hints.push(hint); + + builder.failed(); + } + + #[test] + unconstrained fn pending_public_data_read_requests() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.previous_kernel.append_public_data_update_requests(3); + + builder.add_pending_public_data_read_request(1); + builder.add_pending_public_data_read_request(0); + builder.add_pending_public_data_read_request(2); + builder.succeeded(); } + + #[test(should_fail_with="Hinted slot of data write does not match read request")] + unconstrained fn pending_public_data_read_requests_failed_wrong_write_index() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.previous_kernel.append_public_data_update_requests(2); + + builder.add_pending_public_data_read_request(1); + + let mut hint = builder.public_data_read_request_hints_builder.pending_read_hints.pop(); + hint.pending_value_index += 1; + builder.public_data_read_request_hints_builder.pending_read_hints.push(hint); + + builder.failed(); + } + + #[test(should_fail_with="Hinted value of data write does not match read request")] + unconstrained fn pending_public_data_read_requests_failed_wrong_write_value() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + + builder.previous_kernel.append_public_data_update_requests(1); + + builder.add_pending_public_data_read_request(0); + + let mut public_data_write = builder.previous_kernel.public_data_update_requests.pop(); + public_data_write.new_value += 1; + builder.previous_kernel.public_data_update_requests.push(public_data_write); + + builder.failed(); + } } 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 index a156e40f1a66..8a7ffb5e38bb 100644 --- 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 @@ -1,12 +1,15 @@ -use non_existent_read_request_reset::reset_non_existent_read_requests; use nullifier_non_existent_read_request_reset::NullifierNonExistentReadRequestHints; use nullifier_read_request_reset::NullifierReadRequestHints; use private_validation_request_processor::PrivateValidationRequestProcessor; -use read_request_reset::reset_read_requests; +use public_data_read_request_reset::PublicDataReadRequestHints; +use public_validation_request_processor::PublicValidationRequestProcessor; +use types::public_data_hint::PublicDataHint; -mod non_existent_read_request_reset; mod nullifier_non_existent_read_request_reset; mod nullifier_read_request_reset; mod private_validation_request_processor; -mod read_request_reset; +mod public_data_read_request_reset; +mod public_validation_request_processor; +mod reset; mod tests; +mod types; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr index cc76145b18e6..5f9f7548782d 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr @@ -1,7 +1,7 @@ -use crate::non_existent_read_request_reset::{NonMembershipHint}; +use crate::reset::non_existent_read_request::NonMembershipHint; use dep::types::{ abis::{nullifier_leaf_preimage::NullifierLeafPreimage, side_effect::SideEffectLinkedToNoteHash}, - merkle_tree::{MembershipWitness}, + merkle_tree::MembershipWitness, constants::{MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT} }; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr index cb2e0abc875e..de2da03dc8cd 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr @@ -1,5 +1,5 @@ // This will be moved to a separate Read Request Reset Circuit. -use crate::read_request_reset::{PendingReadHint, ReadRequestStatus, ReadValueHint, SettledReadHint}; +use crate::reset::read_request::{PendingReadHint, ReadRequestStatus, ReadValueHint, SettledReadHint}; use dep::types::{ abis::{nullifier_leaf_preimage::NullifierLeafPreimage}, constants::{MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT}, @@ -44,7 +44,7 @@ struct NullifierReadRequestHints { mod tests { use crate::nullifier_read_request_reset::NullifierSettledReadHint; - use crate::read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus, reset_read_requests}; + use crate::reset::read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus, reset_read_requests}; use dep::types::{ address::AztecAddress, abis::{ diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/private_validation_request_processor.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/private_validation_request_processor.nr index 9f60134dd45d..469a4d6d9bf7 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/private_validation_request_processor.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/private_validation_request_processor.nr @@ -1,4 +1,4 @@ -use crate::{nullifier_read_request_reset::NullifierReadRequestHints, read_request_reset::reset_read_requests}; +use crate::{nullifier_read_request_reset::NullifierReadRequestHints, reset::read_request::reset_read_requests}; use dep::types::{ abis::{side_effect::{SideEffect, SideEffectLinkedToNoteHash}, validation_requests::ValidationRequests}, constants::{ diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_data_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_data_read_request_reset.nr new file mode 100644 index 000000000000..7c6b66340742 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_data_read_request_reset.nr @@ -0,0 +1,9 @@ +use crate::reset::{mutable_data_read_request::LeafDataReadHint, read_request::{PendingReadHint, ReadRequestStatus}}; +use dep::types::constants::MAX_PUBLIC_DATA_READS_PER_TX; + +// The MAX_PUBLIC_DATA_READS_PER_TX for pending_read_hints and leaf_data_read_hints can change if we create various circuits that deal with different number of reads. +struct PublicDataReadRequestHints { + read_request_statuses: [ReadRequestStatus; MAX_PUBLIC_DATA_READS_PER_TX], + pending_read_hints: [PendingReadHint; MAX_PUBLIC_DATA_READS_PER_TX], + leaf_data_read_hints: [LeafDataReadHint; MAX_PUBLIC_DATA_READS_PER_TX], +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_validation_request_processor.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_validation_request_processor.nr new file mode 100644 index 000000000000..4a74f99fd5b5 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_validation_request_processor.nr @@ -0,0 +1,127 @@ +use crate::{ + reset::{ + non_existent_read_request::reset_non_existent_read_requests, + mutable_data_read_request::reset_mutable_data_read_requests, read_request::reset_read_requests +}, + nullifier_read_request_reset::NullifierReadRequestHints, + nullifier_non_existent_read_request_reset::NullifierNonExistentReadRequestHints, + public_data_read_request_reset::PublicDataReadRequestHints, types::public_data_hint::PublicDataHint +}; +use dep::types::{ + abis::{ + kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, + public_data_update_request::PublicDataUpdateRequest, side_effect::SideEffectLinkedToNoteHash, + validation_requests::ValidationRequests +}, + constants::{MAX_NEW_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX}, + hash::silo_nullifier, traits::is_empty, + utils::arrays::{array_merge, array_to_bounded_vec, assert_sorted_array} +}; + +struct PublicValidationRequestProcessor { + validation_requests: ValidationRequests, + pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX], + pending_public_data_writes: [PublicDataUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + nullifier_read_request_hints: NullifierReadRequestHints, + nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints, + nullifier_tree_root: Field, + public_data_read_request_hints: PublicDataReadRequestHints, + public_data_hints: [PublicDataHint; N], +} + +impl PublicValidationRequestProcessor { + pub fn new( + public_inputs: PublicKernelCircuitPublicInputs, + nullifier_read_request_hints: NullifierReadRequestHints, + nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints, + nullifier_tree_root: Field, + public_data_read_request_hints: PublicDataReadRequestHints, + public_data_hints: [PublicDataHint; N] + ) -> Self { + let end_non_revertible = public_inputs.end_non_revertible; + let end = public_inputs.end; + + let pending_nullifiers = array_merge(end_non_revertible.new_nullifiers, end.new_nullifiers); + + let pending_public_data_writes = array_merge( + end_non_revertible.public_data_update_requests, + end.public_data_update_requests + ); + + PublicValidationRequestProcessor { + validation_requests: public_inputs.validation_requests, + pending_nullifiers, + pending_public_data_writes, + nullifier_read_request_hints, + nullifier_non_existent_read_request_hints, + nullifier_tree_root, + public_data_read_request_hints, + public_data_hints + } + } + + pub fn validate(self) { + self.validate_nullifier_read_requests(); + self.validate_nullifier_non_existent_read_requests(); + self.validate_public_data_read_requests(); + } + + fn validate_nullifier_read_requests(self) { + let requests = self.validation_requests.nullifier_read_requests; + let hints = self.nullifier_read_request_hints; + let unverified_nullifier_read_requests = reset_read_requests( + requests, + self.pending_nullifiers, + hints.read_request_statuses, + hints.pending_read_hints, + hints.settled_read_hints, + self.nullifier_tree_root + ); + assert( + unverified_nullifier_read_requests.len() == 0, "All nullifier read requests must be verified" + ); + } + + fn validate_nullifier_non_existent_read_requests(self) { + // The values of the read requests here need to be siloed. + // Notice that it's not the case for regular read requests, which can be run between two kernel iterations, and will to be verified against unsiloed pending values. + let mut read_requests = self.validation_requests.nullifier_non_existent_read_requests; + for i in 0..read_requests.len() { + let read_request = read_requests[i]; + if !is_empty(read_request) { + read_requests[i].value = silo_nullifier(read_request.contract_address, read_request.value); + } + } + + let hints = self.nullifier_non_existent_read_request_hints; + + assert_sorted_array( + self.pending_nullifiers, + hints.sorted_pending_values, + hints.sorted_pending_value_index_hints, + |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value) + ); + let sorted_pending_nullifiers = array_to_bounded_vec(hints.sorted_pending_values); + + reset_non_existent_read_requests( + read_requests, + hints.non_membership_hints, + self.nullifier_tree_root, + sorted_pending_nullifiers, + hints.next_pending_value_indices + ); + } + + fn validate_public_data_read_requests(self) { + let hints = self.public_data_read_request_hints; + + reset_mutable_data_read_requests( + self.validation_requests.public_data_reads, + hints.read_request_statuses, + self.pending_public_data_writes, + self.public_data_hints, + hints.pending_read_hints, + hints.leaf_data_read_hints + ); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset.nr new file mode 100644 index 000000000000..8b98420b3cd1 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset.nr @@ -0,0 +1,3 @@ +mod mutable_data_read_request; +mod non_existent_read_request; +mod read_request; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/mutable_data_read_request.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/mutable_data_read_request.nr new file mode 100644 index 000000000000..a86ab40f20d6 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/mutable_data_read_request.nr @@ -0,0 +1,320 @@ +use crate::reset::read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus}; +use dep::types::{ + abis::{public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest}, + traits::{Empty, is_empty} +}; + +trait LeafDataHint { + fn leaf_slot(self) -> Field; + fn value(self) -> Field; + fn override_counter(self) -> u32; +} + +struct LeafDataReadHint { + read_request_index: u64, + data_hint_index: u64, +} + +impl LeafDataReadHint { + pub fn nada(read_request_len: u64) -> Self { + LeafDataReadHint { read_request_index: read_request_len, data_hint_index: 0 } + } +} + +fn validate_pending_read_requests( + read_requests: [PublicDataRead; READ_REQUEST_LEN], + data_writes: [PublicDataUpdateRequest; PENDING_VALUE_LEN], + hints: [PendingReadHint; NUM_PENDING_READS] +) { + for i in 0..NUM_PENDING_READS { + let read_request_index = hints[i].read_request_index; + if read_request_index != READ_REQUEST_LEN { + let read_request = read_requests[read_request_index]; + let pending_value = data_writes[hints[i].pending_value_index]; + assert( + read_request.leaf_slot.eq(pending_value.leaf_slot), "Hinted slot of data write does not match read request" + ); + assert( + read_request.value.eq(pending_value.new_value), "Hinted value of data write does not match read request" + ); + // TODO: Add counters and verify the following: + // assert( + // read_request.counter > pending_value.counter, "Read request counter must be greater than the counter of the data write" + // ); + // assert((read_request.counter < pending_value.next_counter) | (pending_value.next_counter == 0), "Read request counter must be less than the counter of the next data write"); + } + } +} + +fn validate_leaf_data_read_requests( + read_requests: [PublicDataRead; READ_REQUEST_LEN], + leaf_data_hints: [H; NUM_LEAF_DATA_HINTS], + hints: [LeafDataReadHint; NUM_LEAF_DATA_READS] +) where H: LeafDataHint { + for i in 0..NUM_LEAF_DATA_READS { + let read_request_index = hints[i].read_request_index; + if read_request_index != READ_REQUEST_LEN { + let read_request = read_requests[read_request_index]; + let data_hint = leaf_data_hints[hints[i].data_hint_index]; + assert( + read_request.leaf_slot == data_hint.leaf_slot(), "Hinted slot does not match read request" + ); + assert(read_request.value == data_hint.value(), "Hinted value does not match read request"); + // TODO: Add counters and verify the following: + // assert((read_request.counter < data_hint.override_counter) | (data_hint.override_counter == 0), "Hinted leaf is overridden before the read request"); + } + } +} + +fn ensure_all_read_requests_are_verified( + read_requests: [PublicDataRead; READ_REQUEST_LEN], + read_request_statuses: [ReadRequestStatus; READ_REQUEST_LEN], + pending_read_hints: [PendingReadHint; NUM_PENDING_READS], + leaf_data_read_hints: [LeafDataReadHint; NUM_LEAF_DATA_READS] +) { + for i in 0..READ_REQUEST_LEN { + let read_request = read_requests[i]; + if !is_empty(read_request) { + let status = read_request_statuses[i]; + if status.state == ReadRequestState.PENDING { + assert( + pending_read_hints[status.hint_index].read_request_index == i, "Hinted pending read request does not match status" + ); + } else if status.state == ReadRequestState.SETTLED { + assert( + leaf_data_read_hints[status.hint_index].read_request_index == i, "Hinted settled read request does not match status" + ); + } else { + assert(false, "Read request status must be PENDING or SETTLED"); + } + } + } +} + +pub fn reset_mutable_data_read_requests( + read_requests: [PublicDataRead; READ_REQUEST_LEN], + read_request_statuses: [ReadRequestStatus; READ_REQUEST_LEN], + data_writes: [PublicDataUpdateRequest; PENDING_VALUE_LEN], + leaf_data_hints: [H; NUM_LEAF_DATA_HINTS], + pending_read_hints: [PendingReadHint; NUM_PENDING_READS], + leaf_data_read_hints: [LeafDataReadHint; NUM_LEAF_DATA_READS] +) where H: LeafDataHint { + validate_pending_read_requests(read_requests, data_writes, pending_read_hints); + + validate_leaf_data_read_requests(read_requests, leaf_data_hints, leaf_data_read_hints); + + ensure_all_read_requests_are_verified( + read_requests, + read_request_statuses, + pending_read_hints, + leaf_data_read_hints + ); +} + +mod tests { + use crate::reset::{ + mutable_data_read_request::{ + ensure_all_read_requests_are_verified, reset_mutable_data_read_requests, LeafDataHint, + LeafDataReadHint, validate_pending_read_requests, validate_leaf_data_read_requests + }, + read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus} + }; + use dep::types::{abis::{public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest}}; + + struct TestLeafDataHint { + leaf_slot: Field, + value: Field, + } + + impl LeafDataHint for TestLeafDataHint { + fn leaf_slot(self) -> Field { + self.leaf_slot + } + + fn value(self) -> Field { + self.value + } + + fn override_counter(_self: Self) -> u32 { + 0 + } + } + + global data_writes = [ + PublicDataUpdateRequest { leaf_slot: 22, new_value: 200 }, + PublicDataUpdateRequest { leaf_slot: 11, new_value: 100 }, + PublicDataUpdateRequest { leaf_slot: 33, new_value: 300 }, + PublicDataUpdateRequest { leaf_slot: 44, new_value: 400 } + ]; + + global leaf_data_hints = [ + TestLeafDataHint { leaf_slot: 7, value: 70 }, + TestLeafDataHint { leaf_slot: 6, value: 60 }, + TestLeafDataHint { leaf_slot: 5, value: 50 }, + ]; + + fn create_pending_read_requests(data_write_indices: [u64; N]) -> ([PublicDataRead; N], [PendingReadHint; N]) { + let read_requests = data_write_indices.map( + |data_write_index: u64| PublicDataRead { leaf_slot: data_writes[data_write_index].leaf_slot, value: data_writes[data_write_index].new_value } + ); + let mut hints = BoundedVec::new(); + for i in 0..N { + hints.push(PendingReadHint { read_request_index: i, pending_value_index: data_write_indices[i] }); + } + (read_requests, hints.storage) + } + + fn create_leaf_data_read_requests(data_hint_indices: [u64; N]) -> ([PublicDataRead; N], [LeafDataReadHint; N]) { + let read_requests = data_hint_indices.map( + |data_hint_index: u64| PublicDataRead { leaf_slot: leaf_data_hints[data_hint_index].leaf_slot, value: leaf_data_hints[data_hint_index].value } + ); + let mut hints = BoundedVec::new(); + for i in 0..N { + hints.push(LeafDataReadHint { read_request_index: i, data_hint_index: data_hint_indices[i] }); + } + (read_requests, hints.storage) + } + + #[test] + fn reset_pending_reads_succeeds() { + let (read_requests, hints) = create_pending_read_requests([2, 0, 1, 3]); + validate_pending_read_requests(read_requests, data_writes, hints); + } + + #[test] + fn reset_pending_reads_repeated_values() { + let (read_requests, hints) = create_pending_read_requests([1, 0, 0, 1]); + validate_pending_read_requests(read_requests, data_writes, hints); + } + + #[test] + fn reset_pending_reads_skips_nada() { + let read_requests = [PublicDataRead { leaf_slot: 88, value: 9999 }]; + let hints = [PendingReadHint::nada(1)]; + validate_pending_read_requests(read_requests, data_writes, hints); + } + + #[test(should_fail_with="Hinted slot of data write does not match read request")] + fn reset_pending_reads_wrong_slot_fails() { + let mut (read_requests, hints) = create_pending_read_requests([1]); + hints[0].pending_value_index = 0; + validate_pending_read_requests(read_requests, data_writes, hints); + } + + #[test(should_fail_with="Hinted value of data write does not match read request")] + fn reset_pending_reads_wrong_value_fails() { + let mut (read_requests, hints) = create_pending_read_requests([1]); + read_requests[0].value += 1; + validate_pending_read_requests(read_requests, data_writes, hints); + } + + #[test] + fn reset_leaf_data_reads_succeeds() { + let (read_requests, hints) = create_leaf_data_read_requests([2, 1, 0]); + validate_leaf_data_read_requests(read_requests, leaf_data_hints, hints); + } + + #[test] + fn reset_leaf_data_reads_repeated_values() { + let (read_requests, hints) = create_leaf_data_read_requests([1, 0, 1, 0]); + validate_leaf_data_read_requests(read_requests, leaf_data_hints, hints); + } + + #[test] + fn reset_leaf_data_reads_skips_nada() { + let read_requests = [PublicDataRead { leaf_slot: 88, value: 9999 }]; + let hints = [LeafDataReadHint::nada(1)]; + validate_leaf_data_read_requests(read_requests, leaf_data_hints, hints); + } + + #[test(should_fail_with=""Hinted slot does not match read request")] + fn reset_leaf_reads_wrong_slot_fails() { + let mut (read_requests, hints) = create_leaf_data_read_requests([1]); + hints[0].data_hint_index = 0; + validate_leaf_data_read_requests(read_requests, leaf_data_hints, hints); + } + + #[test(should_fail_with=""Hinted value does not match read request")] + fn reset_leaf_reads_wrong_value_fails() { + let mut (read_requests, hints) = create_leaf_data_read_requests([1]); + read_requests[0].value += 1; + validate_leaf_data_read_requests(read_requests, leaf_data_hints, hints); + } + + #[test] + fn ensure_all_read_requests_are_verified_succeeds() { + let mut (pending_read_requests, pending_read_hints) = create_pending_read_requests([1]); + let mut (leaf_read_requests, leaf_data_read_hints) = create_leaf_data_read_requests([0, 1]); + let read_requests = [leaf_read_requests[0], pending_read_requests[0], leaf_read_requests[1]]; + pending_read_hints[0].read_request_index = 1; + leaf_data_read_hints[1].read_request_index = 2; + + let statuses = [ + ReadRequestStatus { state: ReadRequestState.SETTLED, hint_index: 0 }, + ReadRequestStatus { state: ReadRequestState.PENDING, hint_index: 0 }, + ReadRequestStatus { state: ReadRequestState.SETTLED, hint_index: 1 } + ]; + + ensure_all_read_requests_are_verified( + read_requests, + statuses, + pending_read_hints, + leaf_data_read_hints + ); + } + + #[test(should_fail_with="Hinted pending read request does not match status")] + fn ensure_all_read_requests_are_verified_wrong_pending_hint_index_fails() { + let (read_requests, hints) = create_pending_read_requests([0, 1]); + let statuses = [ + ReadRequestStatus { state: ReadRequestState.PENDING, hint_index: 0 }, + ReadRequestStatus { state: ReadRequestState.PENDING, hint_index: 0 } + ]; + ensure_all_read_requests_are_verified(read_requests, statuses, hints, []); + } + + #[test(should_fail_with="Hinted settled read request does not match status")] + fn ensure_all_read_requests_are_verified_wrong_leaf_hint_index_fails() { + let (read_requests, hints) = create_leaf_data_read_requests([0, 1]); + let statuses = [ + ReadRequestStatus { state: ReadRequestState.SETTLED, hint_index: 0 }, + ReadRequestStatus { state: ReadRequestState.SETTLED, hint_index: 0 } + ]; + ensure_all_read_requests_are_verified(read_requests, statuses, [], hints); + } + + #[test(should_fail_with="Read request status must be PENDING or SETTLED")] + fn ensure_all_read_requests_are_verified_wrong_status_fails() { + let (read_requests, hints) = create_leaf_data_read_requests([0]); + let statuses = [ReadRequestStatus { state: ReadRequestState.NADA, hint_index: 0 }]; + ensure_all_read_requests_are_verified(read_requests, statuses, [], hints); + } + + #[test] + fn reset_mutable_data_read_requests_succeeds() { + let mut (pending_read_requests, pending_read_hints) = create_pending_read_requests([3, 1]); + let mut (leaf_read_requests, leaf_data_read_hints) = create_leaf_data_read_requests([0, 1]); + let read_requests = [ + leaf_read_requests[0], pending_read_requests[0], pending_read_requests[1], leaf_read_requests[1] + ]; + pending_read_hints[0].read_request_index = 1; + pending_read_hints[1].read_request_index = 2; + leaf_data_read_hints[1].read_request_index = 3; + + let statuses = [ + ReadRequestStatus { state: ReadRequestState.SETTLED, hint_index: 0 }, + ReadRequestStatus { state: ReadRequestState.PENDING, hint_index: 0 }, + ReadRequestStatus { state: ReadRequestState.PENDING, hint_index: 1 }, + ReadRequestStatus { state: ReadRequestState.SETTLED, hint_index: 1 } + ]; + + reset_mutable_data_read_requests( + read_requests, + statuses, + data_writes, + leaf_data_hints, + pending_read_hints, + leaf_data_read_hints + ); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/non_existent_read_request.nr similarity index 94% rename from noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr rename to noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/non_existent_read_request.nr index 4beeee665ae1..afb50e68ce53 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/non_existent_read_request.nr @@ -86,7 +86,7 @@ pub fn reset_non_existent_read_requests, + leaf_data_read_hints: BoundedVec, +} + +impl PublicDataReadRequestHintsBuilder { + pub fn new(read_request_len: u64) -> Self { + PublicDataReadRequestHintsBuilder { + read_request_statuses: [ReadRequestStatus::empty(); MAX_PUBLIC_DATA_READS_PER_TX], + pending_read_hints: BoundedVec { storage: [PendingReadHint::nada(read_request_len); MAX_PUBLIC_DATA_READS_PER_TX], len: 0 }, + leaf_data_read_hints: BoundedVec { storage: [LeafDataReadHint::nada(read_request_len); MAX_PUBLIC_DATA_READS_PER_TX], len: 0 } + } + } + + pub fn to_hints(self) -> PublicDataReadRequestHints { + PublicDataReadRequestHints { + read_request_statuses: self.read_request_statuses, + pending_read_hints: self.pending_read_hints.storage, + leaf_data_read_hints: self.leaf_data_read_hints.storage + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/types.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/types.nr new file mode 100644 index 000000000000..aa16f1fe6782 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/types.nr @@ -0,0 +1 @@ +mod public_data_hint; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/types/public_data_hint.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/types/public_data_hint.nr new file mode 100644 index 000000000000..28a3eb74cb0c --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/types/public_data_hint.nr @@ -0,0 +1,27 @@ +use crate::reset::{mutable_data_read_request::LeafDataHint}; +use dep::types::{ + abis::membership_witness::PublicDataMembershipWitness, + public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage +}; + +struct PublicDataHint { + leaf_slot: Field, + value: Field, + override_counter: u32, + membership_witness: PublicDataMembershipWitness, // Should be MembershipWitness when we can handle generics when converting to ts types. + leaf_preimage: PublicDataTreeLeafPreimage, +} + +impl LeafDataHint for PublicDataHint { + fn leaf_slot(self) -> Field { + self.leaf_slot + } + + fn value(self) -> Field { + self.value + } + + fn override_counter(self) -> u32 { + self.override_counter + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr index a9e9e772f59a..db3a8cddfb0b 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr @@ -29,7 +29,7 @@ use dep::types::{ }, mocked::{AggregationObject, Proof}, partial_state_reference::PartialStateReference, public_data_tree_leaf::PublicDataTreeLeaf, - public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, + public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, traits::is_empty, utils::{field::{full_field_less_than, full_field_greater_than}, uint256::U256} }; @@ -66,6 +66,8 @@ impl BaseRollupInputs { == self.constants.global_variables.version, "kernel version does not match the rollup version" ); + self.validate_kernel_start_state(); + let rollup_validation_requests = self.kernel_data.public_inputs.rollup_validation_requests; // Verify the max block number @@ -177,18 +179,22 @@ impl BaseRollupInputs { calculate_subtree_root(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash())) } - fn validate_and_process_public_state(self) -> AppendOnlyTreeSnapshot { - // TODO(#2521) - data read validation should happen against the current state of the tx and not the start state. - // Blocks all interesting usecases that read and write to the same public state in the same tx. - // https://aztecprotocol.slack.com/archives/C02M7VC7TN0/p1695809629015719?thread_ts=1695653252.007339&cid=C02M7VC7TN0 - - // Process public data reads and public data update requests for left input - // validate_public_data_reads( - // self.start_public_data_tree_root, - // self.kernel_data[0].public_inputs.end.public_data_reads, - // 0, - // self.new_public_data_reads_sibling_paths); + fn validate_kernel_start_state(self) { + let kernel_state = self.kernel_data.public_inputs.start_state; + if !is_empty(kernel_state) { + assert( + kernel_state.note_hash_tree.eq(self.start.note_hash_tree), "Mismatch start state for note hash tree" + ); + assert( + kernel_state.nullifier_tree.eq(self.start.nullifier_tree), "Mismatch start state for nullifier tree" + ); + assert( + kernel_state.public_data_tree.eq(self.start.public_data_tree), "Mismatch start state for public data tree" + ); + } + } + fn validate_and_process_public_state(self) -> AppendOnlyTreeSnapshot { let end_public_data_tree_snapshot = insert_public_data_update_requests( self.start.public_data_tree, self.kernel_data.public_inputs.end.public_data_update_requests.map( @@ -318,43 +324,6 @@ fn insert_public_data_update_requests( ) } -fn validate_public_data_reads( - tree_root: Field, - public_data_reads: [PublicDataRead; MAX_PUBLIC_DATA_READS_PER_TX], - public_data_reads_preimages: [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_READS_PER_TX], - public_data_reads_witnesses: [PublicDataMembershipWitness; MAX_PUBLIC_DATA_READS_PER_TX] -) { - for i in 0..MAX_PUBLIC_DATA_READS_PER_TX { - let read = public_data_reads[i]; - let low_preimage = public_data_reads_preimages[i]; - let witness = public_data_reads_witnesses[i]; - - let is_low_empty = low_preimage.is_empty(); - let is_exact = low_preimage.slot == read.leaf_slot; - - let is_less_than_slot = full_field_less_than(low_preimage.slot, read.leaf_slot); - let is_next_greater_than = full_field_less_than(read.leaf_slot, low_preimage.next_slot); - let is_in_range = is_less_than_slot - & (is_next_greater_than | ((low_preimage.next_index == 0) & (low_preimage.next_slot == 0))); - - if (!read.is_empty()) { - assert(!is_low_empty, "public data read is not empty but low preimage is empty"); - if is_in_range { - assert_eq(read.value, 0, "low leaf for public data read is in range but value is not zero"); - } else { - assert(is_exact, "low leaf for public data read is invalid"); - assert_eq(read.value, low_preimage.value, "low leaf for public data has different value"); - } - assert_check_membership( - low_preimage.hash(), - witness.leaf_index, - witness.sibling_path, - tree_root - ); - } - } -} - #[test] fn consistent_not_hash_subtree_width() { assert_eq( diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr index 93f148a675a6..9cb5a03c9c58 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr @@ -30,7 +30,7 @@ struct CombinedAccumulatedData { } impl CombinedAccumulatedData { - pub fn recombine(non_revertible: PublicAccumulatedData, revertible: PublicAccumulatedData) -> Self { + pub fn combine(non_revertible: PublicAccumulatedData, revertible: PublicAccumulatedData) -> Self { CombinedAccumulatedData { new_note_hashes: array_merge(non_revertible.new_note_hashes, revertible.new_note_hashes).map(|n: SideEffect| n.value), new_nullifiers: array_merge(non_revertible.new_nullifiers, revertible.new_nullifiers).map(|n: SideEffectLinkedToNoteHash| n.value), diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/kernel_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/kernel_circuit_public_inputs.nr index b15b68d23654..41100691cbc1 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/kernel_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/kernel_circuit_public_inputs.nr @@ -1,6 +1,9 @@ -use crate::abis::{ +use crate::{ + abis::{ accumulated_data::CombinedAccumulatedData, combined_constant_data::CombinedConstantData, validation_requests::RollupValidationRequests +}, + partial_state_reference::PartialStateReference }; use crate::mocked::AggregationObject; @@ -9,5 +12,6 @@ struct KernelCircuitPublicInputs { rollup_validation_requests: RollupValidationRequests, end: CombinedAccumulatedData, constants: CombinedConstantData, + start_state: PartialStateReference, revert_code: u8, } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_kernel_circuit_public_inputs_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_kernel_circuit_public_inputs_builder.nr index 5aa04bfc44f4..b4fc52176216 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_kernel_circuit_public_inputs_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_kernel_circuit_public_inputs_builder.nr @@ -8,8 +8,7 @@ use crate::{ }, validation_requests::validation_requests_builder::ValidationRequestsBuilder }, -mocked::AggregationObject, -traits::Empty + mocked::AggregationObject, partial_state_reference::PartialStateReference, traits::Empty }; struct PrivateKernelCircuitPublicInputsBuilder { @@ -37,6 +36,7 @@ impl PrivateKernelCircuitPublicInputsBuilder { rollup_validation_requests: self.validation_requests.to_rollup(), end: self.end.to_combined(), constants: self.constants, + start_state: PartialStateReference::empty(), revert_code: 0 } } @@ -65,4 +65,4 @@ impl Empty for PrivateKernelCircuitPublicInputsBuilder { constants: CombinedConstantData::empty(), } } -} \ No newline at end of file +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/public_kernel_circuit_public_inputs_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/public_kernel_circuit_public_inputs_builder.nr index 6d3dc6fefc40..5a0ff151e9ba 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/public_kernel_circuit_public_inputs_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/public_kernel_circuit_public_inputs_builder.nr @@ -2,14 +2,10 @@ use crate::{ abis::{ accumulated_data::{CombinedAccumulatedData, PublicAccumulatedDataBuilder}, combined_constant_data::CombinedConstantData, - kernel_circuit_public_inputs::{ - kernel_circuit_public_inputs::KernelCircuitPublicInputs, - public_kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs -}, + kernel_circuit_public_inputs::{public_kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs}, validation_requests::ValidationRequestsBuilder }, -mocked::AggregationObject, -traits::Empty + mocked::AggregationObject, traits::Empty }; struct PublicKernelCircuitPublicInputsBuilder { @@ -35,17 +31,6 @@ impl PublicKernelCircuitPublicInputsBuilder { revert_code: self.revert_code } } - - pub fn finish_tail(self) -> KernelCircuitPublicInputs { - KernelCircuitPublicInputs { - aggregation_object: self.aggregation_object, - rollup_validation_requests: self.validation_requests.to_rollup(), - // TODO: Sort by counters. - end: CombinedAccumulatedData::recombine(self.end_non_revertible.finish(), self.end.finish()), - constants: self.constants, - revert_code: self.revert_code - } - } } impl Empty for PublicKernelCircuitPublicInputsBuilder { @@ -59,4 +44,4 @@ impl Empty for PublicKernelCircuitPublicInputsBuilder { revert_code: 0 as u8, } } -} \ No newline at end of file +} 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 96f1a6dcdd29..c03eb14e2cfe 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -50,6 +50,9 @@ global NUM_ENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1; global NUM_UNENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1; // docs:end:constants +// KERNEL CIRCUIT PRIVATE INPUTS CONSTANTS +global MAX_PUBLIC_DATA_HINTS: u64 = 64; // MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + MAX_PUBLIC_DATA_READS_PER_TX; + // ROLLUP CONTRACT CONSTANTS - constants used only in l1-contracts global NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP: u64 = 16; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr index 67b23d3f4499..9ef29b02628f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr @@ -7,8 +7,8 @@ mod root; use leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage}; use membership::{ - assert_check_membership, assert_check_non_membership, check_membership, check_non_membership, - MembershipWitness + assert_check_membership, assert_check_non_membership, check_membership, + conditionally_assert_check_membership, MembershipWitness }; use merkle_tree::MerkleTree; use root::{calculate_empty_tree_root, calculate_subtree_root, root_from_sibling_path}; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr index bd8d3d1b482a..28d880c98918 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr @@ -1,3 +1,5 @@ +mod check_valid_low_leaf; + use crate::{ abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot}, merkle_tree::{ diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree/check_valid_low_leaf.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree/check_valid_low_leaf.nr new file mode 100644 index 000000000000..01b4136bded0 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree/check_valid_low_leaf.nr @@ -0,0 +1,78 @@ +use crate::merkle_tree::leaf_preimage::IndexedTreeLeafPreimage; + +pub fn assert_check_valid_low_leaf( + key: Field, + low_leaf_preimage: LEAF_PREIMAGE +) where LEAF_PREIMAGE: IndexedTreeLeafPreimage { + let low_key = low_leaf_preimage.get_key(); + let next_key = low_leaf_preimage.get_next_key(); + + assert(low_key.lt(key), "Key is not greater than the low leaf"); + assert(key.lt(next_key) | (next_key == 0), "Key is not less than the next leaf"); +} + +mod tests { + use crate::{ + merkle_tree::{ + leaf_preimage::IndexedTreeLeafPreimage, + indexed_tree::check_valid_low_leaf::assert_check_valid_low_leaf + } + }; + + struct TestLeafPreimage { + value: Field, + next_value: Field, + } + + impl IndexedTreeLeafPreimage for TestLeafPreimage { + fn get_key(self) -> Field { + self.value + } + + fn get_next_key(self) -> Field { + self.next_value + } + + fn as_leaf(self) -> Field { + self.value + } + } + + #[test] + fn test_assert_check_valid_low_leaf() { + let key = 12; + let leaf = TestLeafPreimage { value: 11, next_value: 13 }; + assert_check_valid_low_leaf(key, leaf); + } + + #[test] + fn test_assert_check_empty_low_leaf() { + // An all-zero low leaf should be valid. It could be used as the first dummy leaf in a tree. + // It's not possible to prove against an empty leaf at an uninitialized index. + // The membership check will fail because the leaf value hash(0, 0) is not 0. + let key = 12; + let leaf = TestLeafPreimage { value: 0, next_value: 0 }; + assert_check_valid_low_leaf(key, leaf); + } + + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_assert_check_valid_low_leaf_failed_wrong_low_leaf() { + let key = 12; + let leaf = TestLeafPreimage { value: 13, next_value: 15 }; + assert_check_valid_low_leaf(key, leaf); + } + + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_assert_check_valid_low_leaf_failed_is_low_leaf() { + let key = 12; + let leaf = TestLeafPreimage { value: 12, next_value: 15 }; + assert_check_valid_low_leaf(key, leaf); + } + + #[test(should_fail_with="Key is not less than the next leaf")] + fn test_assert_check_valid_low_leaf_failed_wrong_next_key() { + let key = 12; + let leaf = TestLeafPreimage { value: 9, next_value: 11 }; + assert_check_valid_low_leaf(key, leaf); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr index d7847467dc18..d549f2699847 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr @@ -1,4 +1,10 @@ -use crate::{merkle_tree::{leaf_preimage::IndexedTreeLeafPreimage, root::root_from_sibling_path}, traits::Empty}; +use crate::{ + merkle_tree::{ + leaf_preimage::IndexedTreeLeafPreimage, + indexed_tree::check_valid_low_leaf::assert_check_valid_low_leaf, root::root_from_sibling_path +}, + traits::Empty +}; struct MembershipWitness { leaf_index: Field, @@ -19,36 +25,18 @@ pub fn check_membership(leaf: Field, index: Field, sibling_path: [Field; N], calculated_root == root } -pub fn assert_check_membership(leaf: Field, index: Field, sibling_path: [Field; N], root: Field) { +pub fn assert_check_membership(leaf: Field, index: Field, sibling_path: [Field; TREE_HEIGHT], root: Field) { assert(check_membership(leaf, index, sibling_path, root), "membership check failed"); } -struct NonMembershipCheckErrorCodeEnum { - NADA: u64, - IS_EMPTY: u64, - NOT_EXISTS: u64, - NOT_GREATER_THAN_LOW: u64, - NOT_LESS_THAN_NEXT: u64, -} - -global NonMembershipCheckErrorCode = NonMembershipCheckErrorCodeEnum { - NADA: 0, - IS_EMPTY: 1, - NOT_EXISTS: 2, - NOT_GREATER_THAN_LOW: 3, - NOT_LESS_THAN_NEXT: 4, -}; - -fn check_non_membership_internal( +pub fn assert_check_non_membership( key: Field, low_leaf_preimage: LEAF_PREIMAGE, low_leaf_membership_witness: MembershipWitness, tree_root: Field -) -> u64 where +) where LEAF_PREIMAGE: IndexedTreeLeafPreimage { - let low_key = low_leaf_preimage.get_key(); - let next_key = low_leaf_preimage.get_next_key(); - let is_empty_leaf = (low_key == 0) & (next_key == 0); + assert_check_valid_low_leaf(key, low_leaf_preimage); let low_leaf_exists = check_membership( low_leaf_preimage.as_leaf(), @@ -56,52 +44,31 @@ fn check_non_membership_internal( low_leaf_membership_witness.sibling_path, tree_root ); - - if is_empty_leaf { - NonMembershipCheckErrorCode.IS_EMPTY - } else if !low_leaf_exists { - NonMembershipCheckErrorCode.NOT_EXISTS - } else if !low_key.lt(key) { - NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW - } else if !key.lt(next_key) & (next_key != 0) { - NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT - } else { - NonMembershipCheckErrorCode.NADA - } + assert(low_leaf_exists, "Low leaf does not exist"); } -pub fn check_non_membership( +// Prove either membership or non-membership depending on the value of `exists`. +// If `exists` == false, `key` is not in the tree, `leaf_preimage` and `membership_witness` are for the low leaf. +pub fn conditionally_assert_check_membership( key: Field, - low_leaf_preimage: LEAF_PREIMAGE, - low_leaf_membership_witness: MembershipWitness, - tree_root: Field -) -> bool where - LEAF_PREIMAGE: IndexedTreeLeafPreimage { - let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root); - error == NonMembershipCheckErrorCode.NADA -} - -pub fn assert_check_non_membership( - key: Field, - low_leaf_preimage: LEAF_PREIMAGE, - low_leaf_membership_witness: MembershipWitness, + exists: bool, + leaf_preimage: LEAF_PREIMAGE, + membership_witness: MembershipWitness, tree_root: Field ) where LEAF_PREIMAGE: IndexedTreeLeafPreimage { - let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root); - if error != NonMembershipCheckErrorCode.NADA { - assert( - error != NonMembershipCheckErrorCode.IS_EMPTY, "Cannot check non membership against empty leaf" - ); - assert(error != NonMembershipCheckErrorCode.NOT_EXISTS, "Low leaf does not exist"); - assert( - error != NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW, "Key is not greater than the low leaf" - ); - assert( - error != NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT, "Key is not less than the next leaf" - ); - assert(false, "Unknown error"); + if exists { + assert(key == leaf_preimage.get_key(), "Key does not match the key of the leaf preimage"); + } else { + assert_check_valid_low_leaf(key, leaf_preimage); } + + assert_check_membership( + leaf_preimage.as_leaf(), + membership_witness.leaf_index, + membership_witness.sibling_path, + tree_root + ); } mod tests { @@ -109,8 +76,8 @@ mod tests { merkle_tree::{ leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage}, membership::{ - assert_check_membership, assert_check_non_membership, check_membership, check_non_membership, - MembershipWitness + assert_check_membership, assert_check_non_membership, check_membership, + conditionally_assert_check_membership, MembershipWitness } }, tests::merkle_tree_utils::NonEmptyMerkleTree @@ -186,7 +153,7 @@ mod tests { ); } - fn check_non_membership_at_index(low_leaf_index: u64, leaf: Field) -> bool { + fn assert_check_non_membership_at_index(low_leaf_index: u64, key: Field) { let tree = build_tree(); let tree_root = tree.get_root(); let leaf_preimage = if low_leaf_index < leaf_preimages.len() { @@ -195,15 +162,15 @@ mod tests { TestLeafPreimage { value: 0, next_value: 0 } }; - check_non_membership( - leaf, + assert_check_non_membership( + key, leaf_preimage, MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } , tree_root - ) + ); } - fn assert_check_non_membership_at_index(low_leaf_index: u64, leaf: Field) { + fn conditionally_assert_check_membership_at_index(exists: bool, low_leaf_index: u64, key: Field) { let tree = build_tree(); let tree_root = tree.get_root(); let leaf_preimage = if low_leaf_index < leaf_preimages.len() { @@ -212,8 +179,9 @@ mod tests { TestLeafPreimage { value: 0, next_value: 0 } }; - assert_check_non_membership( - leaf, + conditionally_assert_check_membership( + key, + exists, leaf_preimage, MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } , tree_root @@ -270,80 +238,89 @@ mod tests { ); } - #[test] - fn test_check_non_membership() { - assert_eq(check_non_membership_at_index(0, 25), true); - } - #[test] fn test_assert_check_non_membership() { assert_check_non_membership_at_index(0, 25); } - #[test] - fn test_check_non_membership_greater_than_max() { - assert_eq(check_non_membership_at_index(1, 45), true); - } - #[test] fn test_assert_check_non_membership_greater_than_max() { assert_check_non_membership_at_index(1, 45); } - #[test] - fn test_check_non_membership_false_empty_leaf() { - assert_eq(check_non_membership_at_index(4, 25), false); + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_assert_check_non_membership_failed_wrong_low_leaf() { + assert_check_non_membership_at_index(3, 25); } - #[test(should_fail_with="Cannot check non membership against empty leaf")] - fn test_assert_check_non_membership_failed_empty_leaf() { - assert_check_non_membership_at_index(4, 25); + #[test(should_fail_with="Key is not less than the next leaf")] + fn test_assert_check_non_membership_failed_wrong_next_key() { + assert_check_non_membership_at_index(2, 25); } - #[test] - fn test_check_non_membership_false_wrong_low_leaf() { - assert_eq(check_non_membership_at_index(3, 25), false); + #[test(should_fail_with="Low leaf does not exist")] + fn test_assert_check_non_membership_failed_invalid_leaf() { + let tree = build_tree(); + let tree_root = tree.get_root(); + + let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 }; + assert_check_non_membership( + 55, + fake_leaf, + MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , + tree_root + ); } - #[test(should_fail_with="Key is not greater than the low leaf")] - fn test_assert_check_non_membership_failed_wrong_low_leaf() { - assert_check_non_membership_at_index(3, 25); + #[test] + fn test_conditionally_assert_check_membership_exists() { + conditionally_assert_check_membership_at_index(true, 1, leaf_preimages[1].get_key()); } #[test] - fn test_check_non_membership_false_wrong_next_key() { - assert_eq(check_non_membership_at_index(2, 25), false); + fn test_conditionally_assert_check_membership_not_exists() { + conditionally_assert_check_membership_at_index(false, 1, leaf_preimages[1].get_key() + 1); + } + + #[test(should_fail_with="Key does not match the key of the leaf preimage")] + fn test_conditionally_assert_check_membership_exists_value_mismatch() { + conditionally_assert_check_membership_at_index(true, 1, leaf_preimages[1].get_key() + 1); + } + + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_conditionally_assert_check_membership_failed_not_exists_wrong_low_leaf() { + conditionally_assert_check_membership_at_index(false, 3, 25); } #[test(should_fail_with="Key is not less than the next leaf")] - fn test_assert_check_non_membership_failed_wrong_next_key() { - assert_check_non_membership_at_index(2, 25); + fn test_conditionally_assert_check_membership_failed_not_exists_wrong_next_key() { + conditionally_assert_check_membership_at_index(false, 2, 25); } - #[test] - fn test_check_non_membership_false_invalid_leaf() { + #[test(should_fail_with="membership check failed")] + fn test_conditionally_assert_check_membership_failed_exists_invalid_leaf() { let tree = build_tree(); let tree_root = tree.get_root(); - let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 }; - assert_eq( - check_non_membership( - 55, - fake_leaf, - MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , - tree_root - ), false + let exists = true; + conditionally_assert_check_membership( + 50, + exists, + fake_leaf, + MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , + tree_root ); } - #[test(should_fail_with="Low leaf does not exist")] - fn test_assert_check_non_membership_failed_invalid_leaf() { + #[test(should_fail_with="membership check failed")] + fn test_conditionally_assert_check_membership_failed_not_exists_invalid_leaf() { let tree = build_tree(); let tree_root = tree.get_root(); - let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 }; - assert_check_non_membership( + let exists = false; + conditionally_assert_check_membership( 55, + exists, fake_leaf, MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , tree_root diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf_preimage.nr b/noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf_preimage.nr index dcc84fe7026b..992bbdecd180 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf_preimage.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf_preimage.nr @@ -1,4 +1,4 @@ -use crate::traits::{Empty, Hash}; +use crate::{merkle_tree::leaf_preimage::IndexedTreeLeafPreimage, traits::{Empty, Hash}}; struct PublicDataTreeLeafPreimage { slot : Field, @@ -28,6 +28,20 @@ impl Hash for PublicDataTreeLeafPreimage { } } +impl IndexedTreeLeafPreimage for PublicDataTreeLeafPreimage { + fn get_key(self) -> Field { + self.slot + } + + fn get_next_key(self) -> Field { + self.next_slot + } + + fn as_leaf(self) -> Field { + self.hash() + } +} + impl PublicDataTreeLeafPreimage { pub fn is_empty(self) -> bool { (self.slot == 0) & (self.value == 0) & (self.next_slot == 0) & (self.next_index == 0) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr index 9c6a861eddea..45dd91dab7e6 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr @@ -24,7 +24,8 @@ use crate::{ VK_TREE_HEIGHT }, hash::silo_nullifier, header::Header, mocked::{AggregationObject, Proof, VerificationKey}, - tests::fixtures, transaction::tx_context::TxContext, traits::Empty + partial_state_reference::PartialStateReference, tests::fixtures, transaction::tx_context::TxContext, + traits::Empty }; struct FixtureBuilder { @@ -67,6 +68,9 @@ struct FixtureBuilder { // Counters. min_revertible_side_effect_counter: u32, counter: u32, + + // States. + start_state: PartialStateReference, } impl FixtureBuilder { @@ -101,6 +105,7 @@ impl FixtureBuilder { revert_code: 0, min_revertible_side_effect_counter: 0, counter: 0, + start_state: PartialStateReference::empty(), gas_used: Gas::empty(), gas_settings: GasSettings::empty() } @@ -231,6 +236,7 @@ impl FixtureBuilder { rollup_validation_requests, end, constants, + start_state: self.start_state, revert_code: self.revert_code } } @@ -267,17 +273,21 @@ impl FixtureBuilder { } } + pub fn add_public_data_update_request(&mut self, leaf_slot: Field, value: Field) { + let update_request = PublicDataUpdateRequest { leaf_slot, new_value: value }; + self.public_data_update_requests.push(update_request); + } + pub fn append_public_data_update_requests(&mut self, num_updates: u64) { let value_offset = self.public_data_update_requests.len(); for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { if i < num_updates { - let update_request = PublicDataUpdateRequest { - // The default leaf index is its index + 23. - leaf_slot: (value_offset + i + 23) as Field, - // The default value is its index + 678. - new_value: (value_offset + i + 678) as Field - }; - self.public_data_update_requests.push(update_request); + // The default leaf index is its index + 23. + // The default value is its index + 678. + self.add_public_data_update_request( + (value_offset + i + 23) as Field, + (value_offset + i + 678) as Field + ); } } } @@ -326,6 +336,14 @@ impl FixtureBuilder { self.nullifier_non_existent_read_requests.push(read_request); } + pub fn add_read_request_for_pending_public_data(&mut self, public_date_update_request_index: u64) -> u64 { + let new_read_request_index = self.public_data_reads.len(); + let public_write = self.public_data_update_requests.get(public_date_update_request_index); + let read_request = PublicDataRead { leaf_slot: public_write.leaf_slot, value: public_write.new_value }; + self.public_data_reads.push(read_request); + new_read_request_index + } + pub fn set_encrypted_logs(&mut self, hash: Field, preimages_length: Field) { self.encrypted_logs_hash = hash; self.encrypted_log_preimages_length = preimages_length; @@ -419,6 +437,7 @@ impl Empty for FixtureBuilder { revert_code: 0, min_revertible_side_effect_counter: 0, counter: 0, + start_state: PartialStateReference::empty(), gas_settings: GasSettings::empty(), gas_used: Gas::empty(), } diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index f0b3c805ad96..d512d015b486 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -6,6 +6,7 @@ import { PartialPrivateTailPublicInputsForPublic, PrivateKernelTailCircuitPublicInputs, Proof, + type PublicCallRequest, SideEffectLinkedToNoteHash, computeContractClassId, getContractClassFromArtifact, @@ -39,14 +40,21 @@ export const mockTx = ( hasLogs = false, numberOfNonRevertiblePublicCallRequests = MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX / 2, numberOfRevertiblePublicCallRequests = MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX / 2, + publicCallRequests = [], }: { hasLogs?: boolean; numberOfNonRevertiblePublicCallRequests?: number; numberOfRevertiblePublicCallRequests?: number; + publicCallRequests?: PublicCallRequest[]; } = {}, ) => { - const totalPublicCallRequests = numberOfNonRevertiblePublicCallRequests + numberOfRevertiblePublicCallRequests; - const publicCallRequests = times(totalPublicCallRequests, i => makePublicCallRequest(seed + 0x100 + i)); + const totalPublicCallRequests = + numberOfNonRevertiblePublicCallRequests + numberOfRevertiblePublicCallRequests || publicCallRequests.length; + if (publicCallRequests.length && publicCallRequests.length !== totalPublicCallRequests) { + throw new Error( + `Provided publicCallRequests does not match the required number of call requests. Expected ${totalPublicCallRequests}. Got ${publicCallRequests.length}`, + ); + } const isForPublic = totalPublicCallRequests > 0; const data = PrivateKernelTailCircuitPublicInputs.empty(); @@ -59,15 +67,19 @@ export const mockTx = ( data.forPublic.endNonRevertibleData.newNullifiers[0] = firstNullifier; - data.forPublic.end.publicCallStack = makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, i => - i < numberOfRevertiblePublicCallRequests ? publicCallRequests[i].toCallRequest() : CallRequest.empty(), - ); + publicCallRequests = publicCallRequests.length + ? publicCallRequests.slice().sort((a, b) => b.callContext.sideEffectCounter - a.callContext.sideEffectCounter) + : times(totalPublicCallRequests, i => makePublicCallRequest(seed + 0x100 + i)); data.forPublic.endNonRevertibleData.publicCallStack = makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, i => i < numberOfNonRevertiblePublicCallRequests - ? publicCallRequests[i + numberOfRevertiblePublicCallRequests].toCallRequest() + ? publicCallRequests[numberOfRevertiblePublicCallRequests + i].toCallRequest() : CallRequest.empty(), ); + + data.forPublic.end.publicCallStack = makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, i => + i < numberOfRevertiblePublicCallRequests ? publicCallRequests[i].toCallRequest() : CallRequest.empty(), + ); } else { data.forRollup!.end.newNullifiers[0] = firstNullifier.value; } diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 223a3361fb4a..d0cc639cbaf3 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -25,6 +25,7 @@ export const MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8; export const MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4; export const NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1; export const NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1; +export const MAX_PUBLIC_DATA_HINTS = 64; export const NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP = 16; export const VK_TREE_HEIGHT = 3; export const FUNCTION_TREE_HEIGHT = 5; diff --git a/yarn-project/circuits.js/src/hints/build_hints.test.ts b/yarn-project/circuits.js/src/hints/build_hints.test.ts index 7ac9ee39a521..5df210ca1e87 100644 --- a/yarn-project/circuits.js/src/hints/build_hints.test.ts +++ b/yarn-project/circuits.js/src/hints/build_hints.test.ts @@ -22,10 +22,8 @@ import { buildNullifierNonExistentReadRequestHints, buildNullifierReadRequestHin describe('buildNullifierReadRequestHints', () => { const contractAddress = AztecAddress.random(); const settledNullifierInnerValue = 99999; - const settledNullifierValue = makeNullifier(settledNullifierInnerValue).value; const oracle = { - getNullifierMembershipWitness: (value: Fr) => - value.equals(settledNullifierValue) ? ({ membershipWitness: {}, leafPreimage: {} } as any) : undefined, + getNullifierMembershipWitness: () => ({ membershipWitness: {}, leafPreimage: {} } as any), }; let nullifierReadRequests: Tuple; let nullifiers: Tuple; @@ -113,11 +111,6 @@ describe('buildNullifierReadRequestHints', () => { 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.'); - }); }); describe('buildNullifierNonExistentReadRequestHints', () => { diff --git a/yarn-project/circuits.js/src/hints/build_hints.ts b/yarn-project/circuits.js/src/hints/build_hints.ts index 1266082c2d06..302ed3a14f9d 100644 --- a/yarn-project/circuits.js/src/hints/build_hints.ts +++ b/yarn-project/circuits.js/src/hints/build_hints.ts @@ -17,14 +17,14 @@ import { NullifierReadRequestHintsBuilder } from '../structs/read_request_hints. import { SideEffectLinkedToNoteHash } from '../structs/side_effects.js'; import { countAccumulatedItems } from '../utils/index.js'; -export interface NullifierMembershipWitnessWithPreimage { +interface NullifierMembershipWitnessWithPreimage { membershipWitness: MembershipWitness; leafPreimage: IndexedTreeLeafPreimage; } export async function buildNullifierReadRequestHints( oracle: { - getNullifierMembershipWitness(nullifier: Fr): Promise; + getNullifierMembershipWitness(nullifier: Fr): Promise; }, nullifierReadRequests: Tuple, nullifiers: Tuple, @@ -46,10 +46,6 @@ export async function buildNullifierReadRequestHints( 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, diff --git a/yarn-project/circuits.js/src/hints/build_public_data_hints.test.ts b/yarn-project/circuits.js/src/hints/build_public_data_hints.test.ts new file mode 100644 index 000000000000..a041d8fd32f8 --- /dev/null +++ b/yarn-project/circuits.js/src/hints/build_public_data_hints.test.ts @@ -0,0 +1,123 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { Fr } from '@aztec/foundation/fields'; +import { type Tuple } from '@aztec/foundation/serialize'; + +import { + MAX_PUBLIC_DATA_HINTS, + MAX_PUBLIC_DATA_READS_PER_TX, + type MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, +} from '../constants.gen.js'; +import { PublicDataRead, PublicDataTreeLeafPreimage, PublicDataUpdateRequest } from '../structs/index.js'; +import { buildPublicDataHints } from './build_public_data_hints.js'; + +class ExpectedHint { + constructor(public leafSlot: number, public value: number, public matchOrLowLeafSlot: number) {} + + static empty() { + return new ExpectedHint(0, 0, 0); + } + + toExpectedObject() { + return expect.objectContaining({ + leafSlot: new Fr(this.leafSlot), + value: new Fr(this.value), + leafPreimage: expect.objectContaining({ slot: new Fr(this.matchOrLowLeafSlot) }), + }); + } +} + +describe('buildPublicDataHints', () => { + let publicDataReads: Tuple; + let publicDataUpdateRequests: Tuple; + let expectedHints: Tuple; + + const publicDataLeaves = [ + new PublicDataTreeLeafPreimage(new Fr(22), new Fr(200), new Fr(33), 0n), + new PublicDataTreeLeafPreimage(new Fr(11), new Fr(100), new Fr(22), 0n), + new PublicDataTreeLeafPreimage(new Fr(0), new Fr(0), new Fr(11), 0n), + ]; + + const makePublicDataRead = (leafSlot: number, value: number) => new PublicDataRead(new Fr(leafSlot), new Fr(value)); + const makePublicDataWrite = (leafSlot: number, value: number) => + new PublicDataUpdateRequest(new Fr(leafSlot), new Fr(value)); + + const oracle = { + getMatchOrLowPublicDataMembershipWitness: (leafSlot: bigint) => { + const leafPreimage = publicDataLeaves.find(l => l.slot.toBigInt() <= leafSlot); + return { membershipWitness: {}, leafPreimage } as any; + }, + }; + + const buildHints = () => buildPublicDataHints(oracle, publicDataReads, publicDataUpdateRequests); + + const buildAndCheckHints = async () => { + const hints = await buildHints(); + const partialHints = expectedHints.map(h => h.toExpectedObject()); + expect(hints).toEqual(partialHints); + }; + + beforeEach(() => { + publicDataReads = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, PublicDataRead.empty); + publicDataUpdateRequests = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, PublicDataUpdateRequest.empty); + expectedHints = makeTuple(MAX_PUBLIC_DATA_HINTS, ExpectedHint.empty); + }); + + it('returns empty hints', async () => { + await buildAndCheckHints(); + }); + + it('builds hints for reads for uninitialized slots', async () => { + publicDataReads[0] = makePublicDataRead(12, 0); + publicDataReads[1] = makePublicDataRead(39, 0); + expectedHints[0] = new ExpectedHint(12, 0, 11); + expectedHints[1] = new ExpectedHint(39, 0, 22); + + await buildAndCheckHints(); + }); + + it('builds hints for reads for initialized slots', async () => { + publicDataReads[0] = makePublicDataRead(22, 200); + publicDataReads[1] = makePublicDataRead(11, 100); + expectedHints[0] = new ExpectedHint(22, 200, 22); + expectedHints[1] = new ExpectedHint(11, 100, 11); + + await buildAndCheckHints(); + }); + + it('builds hints for writes to uninitialized slots', async () => { + publicDataUpdateRequests[0] = makePublicDataWrite(5, 500); + publicDataUpdateRequests[1] = makePublicDataWrite(17, 700); + expectedHints[0] = new ExpectedHint(5, 0, 0); + expectedHints[1] = new ExpectedHint(17, 0, 11); + + await buildAndCheckHints(); + }); + + it('builds hints for writes to initialized slots', async () => { + publicDataUpdateRequests[0] = makePublicDataWrite(11, 111); + publicDataUpdateRequests[1] = makePublicDataWrite(22, 222); + expectedHints[0] = new ExpectedHint(11, 100, 11); + expectedHints[1] = new ExpectedHint(22, 200, 22); + + await buildAndCheckHints(); + }); + + it('builds hints for mixed reads and writes', async () => { + publicDataReads[0] = makePublicDataRead(22, 200); + publicDataReads[1] = makePublicDataRead(12, 0); + publicDataReads[2] = makePublicDataRead(39, 0); + publicDataReads[3] = makePublicDataRead(11, 100); + publicDataUpdateRequests[0] = makePublicDataWrite(11, 111); + publicDataUpdateRequests[1] = makePublicDataWrite(5, 500); + publicDataUpdateRequests[2] = makePublicDataWrite(17, 700); + publicDataUpdateRequests[3] = makePublicDataWrite(22, 222); + expectedHints[0] = new ExpectedHint(22, 200, 22); + expectedHints[1] = new ExpectedHint(12, 0, 11); + expectedHints[2] = new ExpectedHint(39, 0, 22); + expectedHints[3] = new ExpectedHint(11, 100, 11); + expectedHints[4] = new ExpectedHint(5, 0, 0); + expectedHints[5] = new ExpectedHint(17, 0, 11); + + await buildAndCheckHints(); + }); +}); diff --git a/yarn-project/circuits.js/src/hints/build_public_data_hints.ts b/yarn-project/circuits.js/src/hints/build_public_data_hints.ts new file mode 100644 index 000000000000..22d69a520bb9 --- /dev/null +++ b/yarn-project/circuits.js/src/hints/build_public_data_hints.ts @@ -0,0 +1,49 @@ +import { padArrayEnd } from '@aztec/foundation/collection'; +import { Fr } from '@aztec/foundation/fields'; +import { type Tuple } from '@aztec/foundation/serialize'; + +import { + MAX_PUBLIC_DATA_HINTS, + type MAX_PUBLIC_DATA_READS_PER_TX, + type MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + type PUBLIC_DATA_TREE_HEIGHT, +} from '../constants.gen.js'; +import { + type PublicDataRead, + type PublicDataTreeLeafPreimage, + type PublicDataUpdateRequest, +} from '../structs/index.js'; +import { type MembershipWitness } from '../structs/membership_witness.js'; +import { PublicDataHint } from '../structs/public_data_hint.js'; + +interface PublicDataMembershipWitnessWithPreimage { + membershipWitness: MembershipWitness; + leafPreimage: PublicDataTreeLeafPreimage; +} + +export async function buildPublicDataHints( + oracle: { + getMatchOrLowPublicDataMembershipWitness(leafSlot: bigint): Promise; + }, + publicDataReads: Tuple, + publicDataUpdateRequests: Tuple, +) { + const publicDataLeafSlotSet: Set = new Set(); + [...publicDataReads, ...publicDataUpdateRequests] + .filter(r => !r.isEmpty()) + .forEach(v => { + publicDataLeafSlotSet.add(v.leafSlot.toBigInt()); + }); + const uniquePublicDataLeafSlots = [...publicDataLeafSlotSet]; + + const hints: PublicDataHint[] = []; + for (let i = 0; i < uniquePublicDataLeafSlots.length; i++) { + const leafSlot = uniquePublicDataLeafSlots[i]; + const { membershipWitness, leafPreimage } = await oracle.getMatchOrLowPublicDataMembershipWitness(leafSlot); + const exists = leafPreimage.slot.toBigInt() === leafSlot; + const value = exists ? leafPreimage.value : Fr.ZERO; + hints.push(new PublicDataHint(new Fr(leafSlot), value, 0, membershipWitness, leafPreimage)); + } + + return padArrayEnd(hints, PublicDataHint.empty(), MAX_PUBLIC_DATA_HINTS); +} diff --git a/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.test.ts b/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.test.ts new file mode 100644 index 000000000000..164256ede3ee --- /dev/null +++ b/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.test.ts @@ -0,0 +1,150 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { Fr } from '@aztec/foundation/fields'; +import { type Tuple } from '@aztec/foundation/serialize'; + +import { + MAX_PUBLIC_DATA_HINTS, + MAX_PUBLIC_DATA_READS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, +} from '../constants.gen.js'; +import { + LeafDataReadHint, + PendingReadHint, + PublicDataHint, + PublicDataRead, + PublicDataUpdateRequest, + ReadRequestStatus, +} from '../structs/index.js'; +import { buildPublicDataReadRequestHints } from './build_public_data_read_request_hints.js'; + +describe('buildPublicDataReadRequestHints', () => { + let publicDataReads: Tuple; + let expectedStatuses: Tuple; + let expectedPendingHints: Tuple; + let expectedLeafDataHints: Tuple; + + const makePublicDataWrite = (leafSlot: number, value: number) => + new PublicDataUpdateRequest(new Fr(leafSlot), new Fr(value)); + const makePublicDataHint = (slot: number, value: number) => { + const hint = PublicDataHint.empty(); + hint.leafSlot = new Fr(slot); + hint.value = new Fr(value); + return hint; + }; + const makePublicDataRead = (leafSlot: number, value: number) => new PublicDataRead(new Fr(leafSlot), new Fr(value)); + const makePendingHint = (readRequestIndex: number, hintIndex: number) => + new PendingReadHint(readRequestIndex, hintIndex); + const makeLeafDataHint = (readRequestIndex: number, hintIndex: number) => + new LeafDataReadHint(readRequestIndex, hintIndex); + + const publicDataUpdateRequests = padArrayEnd( + [makePublicDataWrite(55, 5555), makePublicDataWrite(77, 7777), makePublicDataWrite(99, 9999)], + PublicDataUpdateRequest.empty(), + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ); + + const publicDataHints = padArrayEnd( + [ + makePublicDataHint(11, 100), + makePublicDataHint(22, 200), + makePublicDataHint(33, 300), + makePublicDataHint(55, 500), + makePublicDataHint(77, 0), + makePublicDataHint(99, 900), + ], + PublicDataHint.empty(), + MAX_PUBLIC_DATA_HINTS, + ); + + const buildHints = () => buildPublicDataReadRequestHints(publicDataReads, publicDataUpdateRequests, publicDataHints); + + const buildAndCheckHints = () => { + const hints = buildHints(); + expect(hints.readRequestStatuses).toEqual(expectedStatuses); + expect(hints.pendingReadHints).toEqual(expectedPendingHints); + expect(hints.leafDataReadHints).toEqual(expectedLeafDataHints); + }; + + beforeEach(() => { + publicDataReads = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, PublicDataRead.empty); + expectedStatuses = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, ReadRequestStatus.nada); + expectedPendingHints = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => + PendingReadHint.nada(MAX_PUBLIC_DATA_READS_PER_TX), + ); + expectedLeafDataHints = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => + LeafDataReadHint.nada(MAX_PUBLIC_DATA_READS_PER_TX), + ); + }); + + it('returns empty hints', () => { + buildAndCheckHints(); + }); + + it('builds hints for reading pending values', () => { + publicDataReads[0] = makePublicDataRead(77, 7777); + publicDataReads[1] = makePublicDataRead(99, 9999); + publicDataReads[2] = makePublicDataRead(55, 5555); + expectedStatuses[0] = ReadRequestStatus.pending(0); + expectedStatuses[1] = ReadRequestStatus.pending(1); + expectedStatuses[2] = ReadRequestStatus.pending(2); + expectedPendingHints[0] = makePendingHint(0, 1); + expectedPendingHints[1] = makePendingHint(1, 2); + expectedPendingHints[2] = makePendingHint(2, 0); + + buildAndCheckHints(); + }); + + it('builds hints for reading settled or uninitialized values', () => { + publicDataReads[0] = makePublicDataRead(33, 300); + publicDataReads[1] = makePublicDataRead(77, 0); + publicDataReads[2] = makePublicDataRead(55, 500); + publicDataReads[3] = makePublicDataRead(11, 100); + expectedStatuses[0] = ReadRequestStatus.settled(0); + expectedStatuses[1] = ReadRequestStatus.settled(1); + expectedStatuses[2] = ReadRequestStatus.settled(2); + expectedStatuses[3] = ReadRequestStatus.settled(3); + expectedLeafDataHints[0] = makeLeafDataHint(0, 2); + expectedLeafDataHints[1] = makeLeafDataHint(1, 4); + expectedLeafDataHints[2] = makeLeafDataHint(2, 3); + expectedLeafDataHints[3] = makeLeafDataHint(3, 0); + + buildAndCheckHints(); + }); + + it('builds hints for reading pending and settled values', () => { + publicDataReads[0] = makePublicDataRead(55, 500); + publicDataReads[1] = makePublicDataRead(55, 5555); + publicDataReads[2] = makePublicDataRead(77, 0); + publicDataReads[3] = makePublicDataRead(11, 100); + publicDataReads[4] = makePublicDataRead(99, 9999); + publicDataReads[5] = makePublicDataRead(77, 7777); + publicDataReads[6] = makePublicDataRead(11, 100); + expectedStatuses[0] = ReadRequestStatus.settled(0); + expectedStatuses[1] = ReadRequestStatus.pending(0); + expectedStatuses[2] = ReadRequestStatus.settled(1); + expectedStatuses[3] = ReadRequestStatus.settled(2); + expectedStatuses[4] = ReadRequestStatus.pending(1); + expectedStatuses[5] = ReadRequestStatus.pending(2); + expectedStatuses[6] = ReadRequestStatus.settled(3); + expectedPendingHints[0] = makePendingHint(1, 0); + expectedPendingHints[1] = makePendingHint(4, 2); + expectedPendingHints[2] = makePendingHint(5, 1); + expectedLeafDataHints[0] = makeLeafDataHint(0, 3); + expectedLeafDataHints[1] = makeLeafDataHint(2, 4); + expectedLeafDataHints[2] = makeLeafDataHint(3, 0); + expectedLeafDataHints[3] = makeLeafDataHint(6, 0); + + buildAndCheckHints(); + }); + + it('throws if reading unknown slot', () => { + publicDataReads[0] = makePublicDataRead(123, 100); + expect(() => buildHints()).toThrow('Cannot find a pending write or a data hint for the read request.'); + }); + + it('throws if reading unknown value', () => { + publicDataReads[0] = makePublicDataRead(11, 1111); + expect(() => buildHints()).toThrow('Value being read does not match existing public data or pending writes.'); + }); +}); diff --git a/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.ts b/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.ts new file mode 100644 index 000000000000..4f7b15f312b8 --- /dev/null +++ b/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.ts @@ -0,0 +1,45 @@ +import { type Tuple } from '@aztec/foundation/serialize'; + +import { + type MAX_PUBLIC_DATA_HINTS, + type MAX_PUBLIC_DATA_READS_PER_TX, + type MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, +} from '../constants.gen.js'; +import { + type PublicDataRead, + PublicDataReadRequestHintsBuilder, + type PublicDataUpdateRequest, +} from '../structs/index.js'; +import { type PublicDataHint } from '../structs/public_data_hint.js'; +import { countAccumulatedItems } from '../utils/index.js'; + +export function buildPublicDataReadRequestHints( + publicDataReads: Tuple, + publicDataUpdateRequests: Tuple, + publicDataHints: Tuple, +) { + const builder = new PublicDataReadRequestHintsBuilder(); + + const numReadRequests = countAccumulatedItems(publicDataReads); + for (let i = 0; i < numReadRequests; ++i) { + const rr = publicDataReads[i]; + // TODO: Add counters to reads and writes. + const writeIndex = publicDataUpdateRequests.findIndex( + w => w.leafSlot.equals(rr.leafSlot) && w.newValue.equals(rr.value), + ); + if (writeIndex !== -1) { + builder.addPendingReadRequest(i, writeIndex); + } else { + const hintIndex = publicDataHints.findIndex(h => h.leafSlot.equals(rr.leafSlot)); + if (hintIndex === -1) { + throw new Error('Cannot find a pending write or a data hint for the read request.'); + } + if (!publicDataHints[hintIndex].value.equals(rr.value)) { + throw new Error('Value being read does not match existing public data or pending writes.'); + } + builder.addLeafDataReadRequest(i, hintIndex); + } + } + + return builder.toHints(); +} diff --git a/yarn-project/circuits.js/src/hints/index.ts b/yarn-project/circuits.js/src/hints/index.ts index 476edce05e9c..d9378ed85b5f 100644 --- a/yarn-project/circuits.js/src/hints/index.ts +++ b/yarn-project/circuits.js/src/hints/index.ts @@ -1,2 +1,3 @@ export * from './build_hints.js'; -export * from '../utils/index.js'; +export * from './build_public_data_hints.js'; +export * from './build_public_data_read_request_hints.js'; diff --git a/yarn-project/circuits.js/src/index.ts b/yarn-project/circuits.js/src/index.ts index 3b6fdbcd0e63..d4c939ef836a 100644 --- a/yarn-project/circuits.js/src/index.ts +++ b/yarn-project/circuits.js/src/index.ts @@ -6,3 +6,4 @@ export * from './interfaces/index.js'; export * from './keys/index.js'; export * from './structs/index.js'; export * from './types/index.js'; +export * from './utils/index.js'; diff --git a/yarn-project/circuits.js/src/structs/contract_storage_read.ts b/yarn-project/circuits.js/src/structs/contract_storage_read.ts index 2c376a1d3f5c..52d708f0ef17 100644 --- a/yarn-project/circuits.js/src/structs/contract_storage_read.ts +++ b/yarn-project/circuits.js/src/structs/contract_storage_read.ts @@ -1,3 +1,4 @@ +import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; @@ -24,6 +25,7 @@ export class ContractStorageRead { * Note: Not serialized */ public readonly sideEffectCounter?: number, + public contractAddress?: AztecAddress, // TODO: Should not be optional. This is a temporary hack to silo the storage slot with the correct address for nested executions. ) {} static from(args: { @@ -39,8 +41,9 @@ export class ContractStorageRead { * Optional side effect counter tracking position of this event in tx execution. */ sideEffectCounter?: number; + contractAddress?: AztecAddress; }) { - return new ContractStorageRead(args.storageSlot, args.currentValue, args.sideEffectCounter); + return new ContractStorageRead(args.storageSlot, args.currentValue, args.sideEffectCounter, args.contractAddress); } toBuffer() { diff --git a/yarn-project/circuits.js/src/structs/contract_storage_update_request.ts b/yarn-project/circuits.js/src/structs/contract_storage_update_request.ts index 8508fa8c7b8a..378279d55bb1 100644 --- a/yarn-project/circuits.js/src/structs/contract_storage_update_request.ts +++ b/yarn-project/circuits.js/src/structs/contract_storage_update_request.ts @@ -1,3 +1,4 @@ +import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; @@ -24,6 +25,7 @@ export class ContractStorageUpdateRequest { * Optional side effect counter tracking position of this event in tx execution. */ public readonly sideEffectCounter?: number, + public contractAddress?: AztecAddress, // TODO: Should not be optional. This is a temporary hack to silo the storage slot with the correct address for nested executions. ) {} toBuffer() { @@ -50,7 +52,7 @@ export class ContractStorageUpdateRequest { * @returns The array. */ static getFields(fields: FieldsOf) { - return [fields.storageSlot, fields.newValue, fields.sideEffectCounter] as const; + return [fields.storageSlot, fields.newValue, fields.sideEffectCounter, fields.contractAddress] as const; } static empty() { diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index da3ec5a2c961..400c60e8ac9b 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -47,7 +47,9 @@ export * from './proof.js'; export * from './public_call_request.js'; export * from './public_call_stack_item.js'; export * from './public_circuit_public_inputs.js'; +export * from './public_data_hint.js'; export * from './public_data_read_request.js'; +export * from './public_data_read_request_hints.js'; export * from './public_data_update_request.js'; export * from './read_request.js'; export * from './read_request_hints.js'; diff --git a/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts index cb59c58b76d7..84637a61f919 100644 --- a/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts @@ -1,6 +1,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { AggregationObject } from '../aggregation_object.js'; +import { PartialStateReference } from '../partial_state_reference.js'; import { RevertCode } from '../revert_code.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; import { CombinedAccumulatedData } from './combined_accumulated_data.js'; @@ -28,6 +29,7 @@ export class KernelCircuitPublicInputs { * Data which is not modified by the circuits. */ public constants: CombinedConstantData, + public startState: PartialStateReference, /** * Flag indicating whether the transaction reverted. */ @@ -44,6 +46,7 @@ export class KernelCircuitPublicInputs { this.rollupValidationRequests, this.end, this.constants, + this.startState, this.revertCode, ); } @@ -60,6 +63,7 @@ export class KernelCircuitPublicInputs { reader.readObject(RollupValidationRequests), reader.readObject(CombinedAccumulatedData), reader.readObject(CombinedConstantData), + reader.readObject(PartialStateReference), reader.readObject(RevertCode), ); } @@ -70,6 +74,7 @@ export class KernelCircuitPublicInputs { RollupValidationRequests.empty(), CombinedAccumulatedData.empty(), CombinedConstantData.empty(), + PartialStateReference.empty(), RevertCode.OK, ); } diff --git a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts index cc7ac7fb8b4f..f064b5ee6ec2 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts @@ -3,6 +3,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { MAX_NEW_NULLIFIERS_PER_TX } from '../../constants.gen.js'; import { countAccumulatedItems, mergeAccumulatedData } from '../../utils/index.js'; import { AggregationObject } from '../aggregation_object.js'; +import { PartialStateReference } from '../partial_state_reference.js'; import { RevertCode } from '../revert_code.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; import { ValidationRequests } from '../validation_requests.js'; @@ -135,6 +136,7 @@ export class PrivateKernelTailCircuitPublicInputs { this.forRollup.rollupValidationRequests, this.forRollup.end, this.constants, + PartialStateReference.empty(), this.revertCode, ); } 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 index be3037d45653..5399146b7e5c 100644 --- 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 @@ -1,15 +1,16 @@ -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { MAX_PUBLIC_DATA_HINTS } from '../../constants.gen.js'; import { type NullifierNonExistentReadRequestHints, nullifierNonExistentReadRequestHintsFromBuffer, } from '../non_existent_read_request_hints.js'; +import { PartialStateReference } from '../partial_state_reference.js'; +import { PublicDataHint } from '../public_data_hint.js'; +import { PublicDataReadRequestHints } from '../public_data_read_request_hints.js'; import { type NullifierReadRequestHints, nullifierReadRequestHintsFromBuffer } from '../read_request_hints.js'; import { PublicKernelData } from './public_kernel_data.js'; -/** - * Inputs to the public kernel circuit. - */ export class PublicKernelTailCircuitPrivateInputs { constructor( /** @@ -24,6 +25,9 @@ export class PublicKernelTailCircuitPrivateInputs { * Contains hints for the nullifier non existent read requests. */ public readonly nullifierNonExistentReadRequestHints: NullifierNonExistentReadRequestHints, + public readonly publicDataHints: Tuple, + public readonly publicDataReadRequestHints: PublicDataReadRequestHints, + public readonly startState: PartialStateReference, ) {} toBuffer() { @@ -31,6 +35,9 @@ export class PublicKernelTailCircuitPrivateInputs { this.previousKernel, this.nullifierReadRequestHints, this.nullifierNonExistentReadRequestHints, + this.publicDataHints, + this.publicDataReadRequestHints, + this.startState, ); } @@ -40,6 +47,9 @@ export class PublicKernelTailCircuitPrivateInputs { reader.readObject(PublicKernelData), nullifierReadRequestHintsFromBuffer(reader), nullifierNonExistentReadRequestHintsFromBuffer(reader), + reader.readArray(MAX_PUBLIC_DATA_HINTS, PublicDataHint), + reader.readObject(PublicDataReadRequestHints), + reader.readObject(PartialStateReference), ); } diff --git a/yarn-project/circuits.js/src/structs/membership_witness.ts b/yarn-project/circuits.js/src/structs/membership_witness.ts index db0457ef3940..fd623300be17 100644 --- a/yarn-project/circuits.js/src/structs/membership_witness.ts +++ b/yarn-project/circuits.js/src/structs/membership_witness.ts @@ -49,7 +49,7 @@ export class MembershipWitness { * @param leafIndex - Index of the leaf in the Merkle tree. * @returns Membership witness with zero sibling path. */ - public static empty(pathSize: N, leafIndex: bigint): MembershipWitness { + public static empty(pathSize: N, leafIndex = 0n): MembershipWitness { const arr = Array(pathSize) .fill(0) .map(() => Fr.ZERO) as Tuple; diff --git a/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts index 9659c63d0c30..faea5129fbb3 100644 --- a/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts +++ b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts @@ -18,7 +18,7 @@ export class NonMembershipHint LEAF_PREIMAGE, ) { - return new NonMembershipHint(MembershipWitness.empty(treeHeight, 0n), makeEmptyLeafPreimage()); + return new NonMembershipHint(MembershipWitness.empty(treeHeight), makeEmptyLeafPreimage()); } static fromBuffer( diff --git a/yarn-project/circuits.js/src/structs/public_data_hint.ts b/yarn-project/circuits.js/src/structs/public_data_hint.ts new file mode 100644 index 000000000000..52d73a080f00 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/public_data_hint.ts @@ -0,0 +1,47 @@ +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; + +import { PUBLIC_DATA_TREE_HEIGHT } from '../constants.gen.js'; +import { MembershipWitness } from './membership_witness.js'; +import { PublicDataTreeLeafPreimage } from './rollup/public_data_leaf/index.js'; + +export class PublicDataHint { + constructor( + public leafSlot: Fr, + public value: Fr, + public overrideCounter: number, + public membershipWitness: MembershipWitness, + public leafPreimage: PublicDataTreeLeafPreimage, + ) {} + + static empty() { + return new PublicDataHint( + Fr.ZERO, + Fr.ZERO, + 0, + MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT), + PublicDataTreeLeafPreimage.empty(), + ); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new PublicDataHint( + reader.readObject(Fr), + reader.readObject(Fr), + reader.readNumber(), + MembershipWitness.fromBuffer(reader, PUBLIC_DATA_TREE_HEIGHT), + reader.readObject(PublicDataTreeLeafPreimage), + ); + } + + toBuffer() { + return serializeToBuffer( + this.leafSlot, + this.value, + this.overrideCounter, + this.membershipWitness, + this.leafPreimage, + ); + } +} diff --git a/yarn-project/circuits.js/src/structs/public_data_read_request_hints.ts b/yarn-project/circuits.js/src/structs/public_data_read_request_hints.ts new file mode 100644 index 000000000000..8c6b324d63f1 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/public_data_read_request_hints.ts @@ -0,0 +1,86 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; + +import { MAX_PUBLIC_DATA_READS_PER_TX } from '../constants.gen.js'; +import { PendingReadHint, ReadRequestState, ReadRequestStatus } from './read_request_hints.js'; + +export class LeafDataReadHint { + constructor(public readRequestIndex: number, public dataHintIndex: number) {} + + static nada(readRequestLen: number) { + return new LeafDataReadHint(readRequestLen, 0); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new LeafDataReadHint(reader.readNumber(), reader.readNumber()); + } + + toBuffer() { + return serializeToBuffer(this.readRequestIndex, this.dataHintIndex); + } +} + +export class PublicDataReadRequestHints { + constructor( + public readRequestStatuses: Tuple, + public pendingReadHints: Tuple, + public leafDataReadHints: Tuple, + ) {} + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new PublicDataReadRequestHints( + reader.readArray(MAX_PUBLIC_DATA_READS_PER_TX, ReadRequestStatus), + reader.readArray(MAX_PUBLIC_DATA_READS_PER_TX, PendingReadHint), + reader.readArray(MAX_PUBLIC_DATA_READS_PER_TX, LeafDataReadHint), + ); + } + + toBuffer() { + return serializeToBuffer(this.readRequestStatuses, this.pendingReadHints, this.leafDataReadHints); + } +} + +export class PublicDataReadRequestHintsBuilder { + private hints: PublicDataReadRequestHints; + private numPendingReadHints = 0; + private numLeafDataReadHints = 0; + + constructor() { + this.hints = new PublicDataReadRequestHints( + makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, ReadRequestStatus.nada), + makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => PendingReadHint.nada(MAX_PUBLIC_DATA_READS_PER_TX)), + makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => LeafDataReadHint.nada(MAX_PUBLIC_DATA_READS_PER_TX)), + ); + } + + static empty() { + return new PublicDataReadRequestHintsBuilder().toHints(); + } + + addPendingReadRequest(readRequestIndex: number, publicDataWriteIndex: number) { + this.hints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus( + ReadRequestState.PENDING, + this.numPendingReadHints, + ); + this.hints.pendingReadHints[this.numPendingReadHints] = new PendingReadHint(readRequestIndex, publicDataWriteIndex); + this.numPendingReadHints++; + } + + addLeafDataReadRequest(readRequestIndex: number, leafDataDataHintIndex: number) { + this.hints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus( + ReadRequestState.SETTLED, + this.numLeafDataReadHints, + ); + this.hints.leafDataReadHints[this.numLeafDataReadHints] = new LeafDataReadHint( + readRequestIndex, + leafDataDataHintIndex, + ); + this.numLeafDataReadHints++; + } + + toHints() { + return this.hints; + } +} diff --git a/yarn-project/circuits.js/src/structs/read_request_hints.ts b/yarn-project/circuits.js/src/structs/read_request_hints.ts index 6e6439ec426e..fd0be7376148 100644 --- a/yarn-project/circuits.js/src/structs/read_request_hints.ts +++ b/yarn-project/circuits.js/src/structs/read_request_hints.ts @@ -19,6 +19,14 @@ export class ReadRequestStatus { return new ReadRequestStatus(ReadRequestState.NADA, 0); } + static pending(hintIndex: number) { + return new ReadRequestStatus(ReadRequestState.PENDING, hintIndex); + } + + static settled(hintIndex: number) { + return new ReadRequestStatus(ReadRequestState.SETTLED, hintIndex); + } + static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new ReadRequestStatus(reader.readNumber(), reader.readNumber()); @@ -58,7 +66,7 @@ export class SettledReadHint LEAF_PREIMAGE, ) { - return new SettledReadHint(readRequestLen, MembershipWitness.empty(treeHeight, 0n), emptyLeafPreimage()); + return new SettledReadHint(readRequestLen, MembershipWitness.empty(treeHeight), emptyLeafPreimage()); } static fromBuffer( diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 1a1c2473d6e1..665808a44311 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -56,6 +56,7 @@ import { MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, + MAX_PUBLIC_DATA_HINTS, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_READS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, @@ -97,7 +98,9 @@ import { PublicCallRequest, PublicCallStackItem, PublicCircuitPublicInputs, + PublicDataHint, PublicDataRead, + PublicDataReadRequestHintsBuilder, PublicDataTreeLeaf, PublicDataTreeLeafPreimage, PublicDataUpdateRequest, @@ -537,6 +540,7 @@ export function makeKernelCircuitPublicInputs(seed = 1, fullAccumulatedData = tr makeRollupValidationRequests(seed), makeCombinedAccumulatedData(seed, fullAccumulatedData), makeConstantData(seed + 0x100), + makePartialStateReference(seed + 0x200), RevertCode.OK, ); } @@ -791,6 +795,9 @@ export function makePublicKernelTailCircuitPrivateInputs(seed = 1): PublicKernel makePublicKernelData(seed), NullifierReadRequestHintsBuilder.empty(), NullifierNonExistentReadRequestHintsBuilder.empty(), + makeTuple(MAX_PUBLIC_DATA_HINTS, PublicDataHint.empty, seed + 0x100), + PublicDataReadRequestHintsBuilder.empty(), + makePartialStateReference(seed + 0x200), ); } 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 0dbf841e51ea..025f65336572 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -31,6 +31,7 @@ import { KernelCircuitPublicInputs, type KernelData, type L2ToL1Message, + type LeafDataReadHint, MAX_NEW_L2_TO_L1_MSGS_PER_TX, MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, @@ -76,7 +77,9 @@ import { type PublicCallData, type PublicCallStackItem, type PublicCircuitPublicInputs, + type PublicDataHint, PublicDataRead, + type PublicDataReadRequestHints, type PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, PublicDataUpdateRequest, @@ -170,8 +173,11 @@ import { type StorageUpdateRequest as StorageUpdateRequestNoir, } from './types/public_kernel_setup_types.js'; import { + type LeafDataReadHint as LeafDataReadHintNoir, type NullifierNonExistentReadRequestHints as NullifierNonExistentReadRequestHintsNoir, type NullifierNonMembershipHint as NullifierNonMembershipHintNoir, + type PublicDataHint as PublicDataHintNoir, + type PublicDataReadRequestHints as PublicDataReadRequestHintsNoir, type PublicDataUpdateRequest as PublicDataUpdateRequestNoir, type PublicKernelTailCircuitPrivateInputs as PublicKernelTailCircuitPrivateInputsNoir, } from './types/public_kernel_tail_types.js'; @@ -926,6 +932,13 @@ function mapPendingReadHintToNoir(hint: PendingReadHint): PendingReadHintNoir { }; } +function mapLeafDataReadHintToNoir(hint: LeafDataReadHint): LeafDataReadHintNoir { + return { + read_request_index: mapNumberToNoir(hint.readRequestIndex), + data_hint_index: mapNumberToNoir(hint.dataHintIndex), + }; +} + function mapNullifierSettledReadHintToNoir( hint: SettledReadHint, ): NullifierSettledReadHintNoir { @@ -964,6 +977,24 @@ function mapNullifierNonExistentReadRequestHintsToNoir( }; } +function mapPublicDataHintToNoir(hint: PublicDataHint): PublicDataHintNoir { + return { + leaf_slot: mapFieldToNoir(hint.leafSlot), + value: mapFieldToNoir(hint.value), + override_counter: mapNumberToNoir(hint.overrideCounter), + membership_witness: mapPublicDataMembershipWitnessToNoir(hint.membershipWitness), + leaf_preimage: mapPublicDataTreePreimageToNoir(hint.leafPreimage), + }; +} + +function mapPublicDataReadRequestHintsToNoir(hints: PublicDataReadRequestHints): PublicDataReadRequestHintsNoir { + return { + read_request_statuses: mapTuple(hints.readRequestStatuses, mapReadRequestStatusToNoir), + pending_read_hints: mapTuple(hints.pendingReadHints, mapPendingReadHintToNoir), + leaf_data_read_hints: mapTuple(hints.leafDataReadHints, mapLeafDataReadHintToNoir), + }; +} + function mapValidationRequestsToNoir(requests: ValidationRequests): ValidationRequestsNoir { return { for_rollup: mapRollupValidationRequestsToNoir(requests.forRollup), @@ -1221,6 +1252,7 @@ export function mapKernelCircuitPublicInputsFromNoir(inputs: KernelCircuitPublic mapRollupValidationRequestsFromNoir(inputs.rollup_validation_requests), mapCombinedAccumulatedDataFromNoir(inputs.end), mapCombinedConstantDataFromNoir(inputs.constants), + mapPartialStateReferenceFromNoir(inputs.start_state), mapRevertCodeFromNoir(inputs.revert_code), ); } @@ -1231,6 +1263,7 @@ export function mapKernelCircuitPublicInputsToNoir(inputs: KernelCircuitPublicIn rollup_validation_requests: mapRollupValidationRequestsToNoir(inputs.rollupValidationRequests), constants: mapCombinedConstantDataToNoir(inputs.constants), end: mapCombinedAccumulatedDataToNoir(inputs.end), + start_state: mapPartialStateReferenceToNoir(inputs.startState), revert_code: mapRevertCodeToNoir(inputs.revertCode), }; } @@ -1399,6 +1432,9 @@ export function mapPublicKernelTailCircuitPrivateInputsToNoir( nullifier_non_existent_read_request_hints: mapNullifierNonExistentReadRequestHintsToNoir( inputs.nullifierNonExistentReadRequestHints, ), + public_data_hints: mapTuple(inputs.publicDataHints, mapPublicDataHintToNoir), + public_data_read_request_hints: mapPublicDataReadRequestHintsToNoir(inputs.publicDataReadRequestHints), + start_state: mapPartialStateReferenceToNoir(inputs.startState), }; } diff --git a/yarn-project/pxe/src/kernel_prover/hints_builder.ts b/yarn-project/pxe/src/kernel_prover/hints_builder.ts index f57f1d8c7b4a..dea4ae58b962 100644 --- a/yarn-project/pxe/src/kernel_prover/hints_builder.ts +++ b/yarn-project/pxe/src/kernel_prover/hints_builder.ts @@ -84,7 +84,7 @@ export class HintsBuilder { async getNullifierMembershipWitness(nullifier: Fr) { const res = await this.oracle.getNullifierMembershipWitness(nullifier); if (!res) { - return; + throw new Error(`Cannot find the leaf for nullifier ${nullifier.toBigInt()}.`); } const { index, siblingPath, leafPreimage } = res; diff --git a/yarn-project/simulator/src/public/abstract_phase_manager.ts b/yarn-project/simulator/src/public/abstract_phase_manager.ts index cb56906ea2aa..bdced88565f1 100644 --- a/yarn-project/simulator/src/public/abstract_phase_manager.ts +++ b/yarn-project/simulator/src/public/abstract_phase_manager.ts @@ -518,29 +518,9 @@ function patchPublicStorageActionOrdering( // so the returned result will be a subset of the public kernel output. const simPublicDataReads = collectPublicDataReads(execResult); - // verify that each read is in the kernel output - for (const read of simPublicDataReads) { - if (!publicDataReads.find(item => item.equals(read))) { - throw new Error( - `Public data reads from simulator do not match those from public kernel.\nFrom simulator: ${simPublicDataReads - .map(p => p.toFriendlyJSON()) - .join(', ')}\nFrom public kernel: ${publicDataReads.map(i => i.toFriendlyJSON()).join(', ')}`, - ); - } - } const simPublicDataUpdateRequests = collectPublicDataUpdateRequests(execResult); - for (const update of simPublicDataUpdateRequests) { - if (!publicDataUpdateRequests.find(item => item.equals(update))) { - throw new Error( - `Public data update requests from simulator do not match those from public kernel.\nFrom simulator: ${simPublicDataUpdateRequests - .map(p => p.toFriendlyJSON()) - .join(', ')}\nFrom public kernel revertible: ${publicDataUpdateRequests - .map(i => i.toFriendlyJSON()) - .join(', ')}`, - ); - } - } + // We only want to reorder the items from the public inputs of the // most recently processed top/enqueued call. diff --git a/yarn-project/simulator/src/public/execution.ts b/yarn-project/simulator/src/public/execution.ts index 74d33ec15c6b..fe12e76526ae 100644 --- a/yarn-project/simulator/src/public/execution.ts +++ b/yarn-project/simulator/src/public/execution.ts @@ -1,6 +1,5 @@ import { type SimulationError, type UnencryptedFunctionL2Logs } from '@aztec/circuit-types'; import { - type AztecAddress, type ContractStorageRead, type ContractStorageUpdateRequest, type Fr, @@ -85,10 +84,8 @@ export function isPublicExecutionResult( */ export function collectPublicDataReads(execResult: PublicExecutionResult): PublicDataRead[] { // HACK(#1622): part of temporary hack - may be able to remove this function after public state ordering is fixed - const contractAddress = execResult.execution.callContext.storageContractAddress; - const thisExecPublicDataReads = execResult.contractStorageReads.map(read => - contractStorageReadToPublicDataRead(read, contractAddress), + contractStorageReadToPublicDataRead(read), ); const unsorted = [ ...thisExecPublicDataReads, @@ -105,10 +102,8 @@ export function collectPublicDataReads(execResult: PublicExecutionResult): Publi */ export function collectPublicDataUpdateRequests(execResult: PublicExecutionResult): PublicDataUpdateRequest[] { // HACK(#1622): part of temporary hack - may be able to remove this function after public state ordering is fixed - const contractAddress = execResult.execution.callContext.storageContractAddress; - const thisExecPublicDataUpdateRequests = execResult.contractStorageUpdateRequests.map(update => - contractStorageUpdateRequestToPublicDataUpdateRequest(update, contractAddress), + contractStorageUpdateRequestToPublicDataUpdateRequest(update), ); const unsorted = [ ...thisExecPublicDataUpdateRequests, @@ -123,9 +118,9 @@ export function collectPublicDataUpdateRequests(execResult: PublicExecutionResul * @param contractAddress - the contract address of the read * @returns The public data read. */ -function contractStorageReadToPublicDataRead(read: ContractStorageRead, contractAddress: AztecAddress): PublicDataRead { +function contractStorageReadToPublicDataRead(read: ContractStorageRead): PublicDataRead { return new PublicDataRead( - computePublicDataTreeLeafSlot(contractAddress, read.storageSlot), + computePublicDataTreeLeafSlot(read.contractAddress!, read.storageSlot), computePublicDataTreeValue(read.currentValue), read.sideEffectCounter!, ); @@ -139,10 +134,9 @@ function contractStorageReadToPublicDataRead(read: ContractStorageRead, contract */ function contractStorageUpdateRequestToPublicDataUpdateRequest( update: ContractStorageUpdateRequest, - contractAddress: AztecAddress, ): PublicDataUpdateRequest { return new PublicDataUpdateRequest( - computePublicDataTreeLeafSlot(contractAddress, update.storageSlot), + computePublicDataTreeLeafSlot(update.contractAddress!, update.storageSlot), computePublicDataTreeValue(update.newValue), update.sideEffectCounter!, ); diff --git a/yarn-project/simulator/src/public/hints_builder.ts b/yarn-project/simulator/src/public/hints_builder.ts index b2a581d76962..5cc4988fd6fe 100644 --- a/yarn-project/simulator/src/public/hints_builder.ts +++ b/yarn-project/simulator/src/public/hints_builder.ts @@ -1,65 +1,77 @@ import { MerkleTreeId } from '@aztec/circuit-types'; import { type Fr, - MAX_NEW_NULLIFIERS_PER_TX, + type MAX_NEW_NULLIFIERS_PER_TX, type MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, type MAX_NULLIFIER_READ_REQUESTS_PER_TX, - MAX_PUBLIC_DATA_READS_PER_TX, + type MAX_PUBLIC_DATA_HINTS, + type MAX_PUBLIC_DATA_READS_PER_TX, + type MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MembershipWitness, NULLIFIER_TREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT, + type PublicDataHint, type PublicDataRead, - PublicDataTreeLeafPreimage, + type PublicDataTreeLeafPreimage, + type PublicDataUpdateRequest, type ReadRequestContext, type SideEffectLinkedToNoteHash, buildNullifierNonExistentReadRequestHints, buildNullifierReadRequestHints, - mergeAccumulatedData, + buildPublicDataHints, + buildPublicDataReadRequestHints, } from '@aztec/circuits.js'; -import { makeTuple } from '@aztec/foundation/array'; import { type Tuple } from '@aztec/foundation/serialize'; -import { type MerkleTreeOperations } from '@aztec/world-state'; +import { type IndexedTreeId, type MerkleTreeOperations } from '@aztec/world-state'; export class HintsBuilder { constructor(private db: MerkleTreeOperations) {} getNullifierReadRequestHints( nullifierReadRequests: Tuple, - nullifiersNonRevertible: Tuple, - nullifiersRevertible: Tuple, + pendingNullifiers: Tuple, ) { - return buildNullifierReadRequestHints( - this, - nullifierReadRequests, - mergeAccumulatedData(MAX_NEW_NULLIFIERS_PER_TX, nullifiersNonRevertible, nullifiersRevertible), - ); + return buildNullifierReadRequestHints(this, nullifierReadRequests, pendingNullifiers); } getNullifierNonExistentReadRequestHints( nullifierNonExistentReadRequests: Tuple, - nullifiersNonRevertible: Tuple, - nullifiersRevertible: Tuple, + pendingNullifiers: Tuple, ) { - const pendingNullifiers = mergeAccumulatedData( - MAX_NEW_NULLIFIERS_PER_TX, - nullifiersNonRevertible, - nullifiersRevertible, - ); return buildNullifierNonExistentReadRequestHints(this, nullifierNonExistentReadRequests, pendingNullifiers); } + getPublicDataHints( + publicDataReads: Tuple, + publicDataUpdateRequests: Tuple, + ) { + return buildPublicDataHints(this, publicDataReads, publicDataUpdateRequests); + } + + getPublicDataReadRequestHints( + publicDataReads: Tuple, + publicDataUpdateRequests: Tuple, + publicDataHints: Tuple, + ) { + return buildPublicDataReadRequestHints(publicDataReads, publicDataUpdateRequests, publicDataHints); + } + async getNullifierMembershipWitness(nullifier: Fr) { const index = await this.db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); if (index === undefined) { - return; + throw new Error(`Cannot find the leaf for nullifier ${nullifier.toBigInt()}.`); } - return this.getNullifierMembershipWitnessWithPreimage(index); + return this.getMembershipWitnessWithPreimage( + MerkleTreeId.NULLIFIER_TREE, + NULLIFIER_TREE_HEIGHT, + index, + ); } async getLowNullifierMembershipWitness(nullifier: Fr) { const res = await this.db.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt()); - if (res === undefined) { + if (!res) { throw new Error(`Cannot find the low leaf for nullifier ${nullifier.toBigInt()}.`); } @@ -68,52 +80,40 @@ export class HintsBuilder { throw new Error(`Nullifier ${nullifier.toBigInt()} already exists in the tree.`); } - return this.getNullifierMembershipWitnessWithPreimage(index); - } - - private async getNullifierMembershipWitnessWithPreimage(index: bigint) { - const siblingPath = await this.db.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index); - const membershipWitness = new MembershipWitness( + return this.getMembershipWitnessWithPreimage( + MerkleTreeId.NULLIFIER_TREE, NULLIFIER_TREE_HEIGHT, index, - siblingPath.toTuple(), ); + } - const leafPreimage = await this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index); - if (!leafPreimage) { - throw new Error(`Cannot find the leaf preimage at index ${index}.`); + async getMatchOrLowPublicDataMembershipWitness(leafSlot: bigint) { + const res = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); + if (!res) { + throw new Error(`Cannot find the previous value index for public data ${leafSlot}.`); } - return { membershipWitness, leafPreimage }; + const { membershipWitness, leafPreimage } = await this.getMembershipWitnessWithPreimage< + typeof PUBLIC_DATA_TREE_HEIGHT + >(MerkleTreeId.PUBLIC_DATA_TREE, PUBLIC_DATA_TREE_HEIGHT, res.index); + + // Should find a way to stop casting IndexedTreeLeafPreimage as PublicDataTreeLeafPreimage everywhere. + return { membershipWitness, leafPreimage: leafPreimage as PublicDataTreeLeafPreimage }; } - async getPublicDataReadsInfo(publicDataReads: PublicDataRead[]) { - const newPublicDataReadsWitnesses: Tuple< - MembershipWitness, - typeof MAX_PUBLIC_DATA_READS_PER_TX - > = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT, 0n)); - - const newPublicDataReadsPreimages: Tuple = - makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => PublicDataTreeLeafPreimage.empty()); - - for (const i in publicDataReads) { - const leafSlot = publicDataReads[i].leafSlot.value; - const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); - if (!lowLeafResult) { - throw new Error(`Public data tree should have one initial leaf`); - } - const preimage = await this.db.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index); - const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index); - newPublicDataReadsWitnesses[i] = new MembershipWitness( - PUBLIC_DATA_TREE_HEIGHT, - BigInt(lowLeafResult.index), - path.toTuple(), - ); - newPublicDataReadsPreimages[i] = preimage! as PublicDataTreeLeafPreimage; + private async getMembershipWitnessWithPreimage( + treeId: IndexedTreeId, + treeHeight: TREE_HEIGHT, + index: bigint, + ) { + const siblingPath = await this.db.getSiblingPath(treeId, index); + const membershipWitness = new MembershipWitness(treeHeight, index, siblingPath.toTuple()); + + const leafPreimage = await this.db.getLeafPreimage(treeId, index); + if (!leafPreimage) { + throw new Error(`Cannot find the leaf preimage for tree ${treeId} at index ${index}.`); } - return { - newPublicDataReadsWitnesses, - newPublicDataReadsPreimages, - }; + + return { membershipWitness, leafPreimage }; } } diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index 5f97336dcbfd..7cd22cf4df18 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -148,11 +148,13 @@ describe('ACIR public execution simulator', () => { // There should be 2 storage updates, one for the recipient's balance and one for the total supply expect(result.contractStorageUpdateRequests).toEqual([ { + contractAddress, storageSlot: recipientBalanceStorageSlot, newValue: expectedBalance, sideEffectCounter: 3, }, { + contractAddress, storageSlot: totalSupplyStorageSlot, newValue: expectedTotalSupply, sideEffectCounter: 4, @@ -165,6 +167,7 @@ describe('ACIR public execution simulator', () => { // the updates expect(result.contractStorageReads).toEqual([ { + contractAddress, storageSlot: isMinterStorageSlot, currentValue: isMinter, sideEffectCounter: 0, @@ -223,11 +226,13 @@ describe('ACIR public execution simulator', () => { expect(result.contractStorageUpdateRequests).toEqual([ { + contractAddress, storageSlot: senderStorageSlot, newValue: expectedSenderBalance, sideEffectCounter: 1, // 1 read (sender balance) }, { + contractAddress, storageSlot: recipientStorageSlot, newValue: expectedRecipientBalance, sideEffectCounter: 3, // 1 read (sender balance), 1 write (new sender balance), 1 read (recipient balance) diff --git a/yarn-project/simulator/src/public/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor.test.ts index 4d540b0a890e..904321e2d6c3 100644 --- a/yarn-project/simulator/src/public/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor.test.ts @@ -1,45 +1,39 @@ import { type BlockProver, - EncryptedTxL2Logs, type ProcessedTx, PublicDataWrite, - SiblingPath, SimulationError, - Tx, + type Tx, type TxValidator, - UnencryptedTxL2Logs, mockTx, toTxEffect, } from '@aztec/circuit-types'; import { + AppendOnlyTreeSnapshot, ContractStorageUpdateRequest, Fr, GlobalVariables, Header, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PUBLIC_DATA_TREE_HEIGHT, + PartialStateReference, type Proof, type PublicCallRequest, - PublicDataUpdateRequest, + PublicDataTreeLeafPreimage, + StateReference, makeEmptyProof, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; -import { - fr, - makeAztecAddress, - makePrivateKernelTailCircuitPublicInputs, - makePublicCallRequest, - makeSelector, -} from '@aztec/circuits.js/testing'; -import { makeTuple } from '@aztec/foundation/array'; -import { arrayNonEmptyLength, times } from '@aztec/foundation/collection'; +import { fr, makeAztecAddress, makePublicCallRequest, makeSelector } from '@aztec/circuits.js/testing'; +import { arrayNonEmptyLength } from '@aztec/foundation/collection'; +import { openTmpStore } from '@aztec/kv-store/utils'; +import { type AppendOnlyTree, Pedersen, StandardTree, newTree } from '@aztec/merkle-tree'; import { type PublicExecutionResult, type PublicExecutor, WASMSimulator } from '@aztec/simulator'; import { type MerkleTreeOperations, type TreeInfo } from '@aztec/world-state'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { PublicExecutionResultBuilder, addKernelPublicCallStack, makeFunctionCall } from '../mocks/fixtures.js'; +import { PublicExecutionResultBuilder, makeFunctionCall } from '../mocks/fixtures.js'; import { type ContractsDataSourcePublicDB, type WorldStatePublicDB } from './public_executor.js'; import { RealPublicKernelCircuitSimulator } from './public_kernel.js'; import { type PublicKernelCircuitSimulator } from './public_kernel_circuit_simulator.js'; @@ -143,17 +137,68 @@ describe('public_processor', () => { describe('with actual circuits', () => { let publicKernel: PublicKernelCircuitSimulator; + let publicDataTree: AppendOnlyTree; + + const mockTxWithPartialState = ( + { + hasLogs = false, + numberOfNonRevertiblePublicCallRequests = 0, + numberOfRevertiblePublicCallRequests = 0, + publicCallRequests = [], + }: { + hasLogs?: boolean; + numberOfNonRevertiblePublicCallRequests?: number; + numberOfRevertiblePublicCallRequests?: number; + publicCallRequests?: PublicCallRequest[]; + } = {}, + seed = 1, + ) => { + return mockTx(seed, { + hasLogs, + numberOfNonRevertiblePublicCallRequests, + numberOfRevertiblePublicCallRequests, + publicCallRequests, + }); + }; + + beforeAll(async () => { + publicDataTree = await newTree( + StandardTree, + openTmpStore(), + new Pedersen(), + 'PublicData', + Fr, + PUBLIC_DATA_TREE_HEIGHT, + 1, // Add a default low leaf for the public data hints to be proved against. + ); + }); beforeEach(() => { - const path = times(PUBLIC_DATA_TREE_HEIGHT, i => Buffer.alloc(32, i)); - db.getSiblingPath.mockResolvedValue(new SiblingPath(PUBLIC_DATA_TREE_HEIGHT, path)); + const snap = new AppendOnlyTreeSnapshot( + Fr.fromBuffer(publicDataTree.getRoot(true)), + Number(publicDataTree.getNumLeaves(true)), + ); + + const header = Header.empty(); + const stateReference = new StateReference( + header.state.l1ToL2MessageTree, + new PartialStateReference(header.state.partial.noteHashTree, header.state.partial.nullifierTree, snap), + ); + // Clone the whole state because somewhere down the line (AbstractPhaseManager) the public data root is modified in the referenced header directly :/ + header.state = StateReference.fromBuffer(stateReference.toBuffer()); + + db.getStateReference.mockResolvedValue(stateReference); + db.getSiblingPath.mockResolvedValue(publicDataTree.getSiblingPath(0n, false)); + db.getPreviousValueIndex.mockResolvedValue({ index: 0n, alreadyPresent: true }); + db.getLeafPreimage.mockResolvedValue(new PublicDataTreeLeafPreimage(new Fr(0), new Fr(0), new Fr(0), 0n)); + publicKernel = new RealPublicKernelCircuitSimulator(new WASMSimulator()); processor = new PublicProcessor( db, publicExecutor, publicKernel, GlobalVariables.empty(), - Header.empty(), + header, publicContractsDB, publicWorldStateDB, ); @@ -166,7 +211,9 @@ describe('public_processor', () => { }); it('runs a tx with enqueued public calls', async function () { - const tx = mockTx(1, { numberOfNonRevertiblePublicCallRequests: 0, numberOfRevertiblePublicCallRequests: 2 }); + const tx = mockTxWithPartialState({ + numberOfRevertiblePublicCallRequests: 2, + }); publicExecutor.simulate.mockImplementation(execution => { for (const request of tx.enqueuedPublicFunctionCalls) { @@ -192,7 +239,7 @@ describe('public_processor', () => { }); it('runs a tx with an enqueued public call with nested execution', async function () { - const tx = mockTx(1, { numberOfNonRevertiblePublicCallRequests: 0, numberOfRevertiblePublicCallRequests: 1 }); + const tx = mockTxWithPartialState({ numberOfRevertiblePublicCallRequests: 1 }); const callRequest = tx.enqueuedPublicFunctionCalls[0]; const publicExecutionResult = PublicExecutionResultBuilder.fromPublicCallRequest({ @@ -223,7 +270,7 @@ describe('public_processor', () => { it('does not attempt to overfill a block', async function () { const txs = Array.from([1, 2, 3], index => - mockTx(index, { numberOfNonRevertiblePublicCallRequests: 0, numberOfRevertiblePublicCallRequests: 1 }), + mockTxWithPartialState({ numberOfRevertiblePublicCallRequests: 1 }, index), ); let txCount = 0; @@ -255,7 +302,7 @@ describe('public_processor', () => { }); it('does not send a transaction to the prover if validation fails', async function () { - const tx = mockTx(1, { numberOfNonRevertiblePublicCallRequests: 0, numberOfRevertiblePublicCallRequests: 1 }); + const tx = mockTxWithPartialState({ numberOfRevertiblePublicCallRequests: 1 }); publicExecutor.simulate.mockImplementation(execution => { for (const request of tx.enqueuedPublicFunctionCalls) { @@ -283,40 +330,21 @@ describe('public_processor', () => { it('rolls back app logic db updates on failed public execution, but persists setup/teardown', async function () { const baseContractAddressSeed = 0x200; const baseContractAddress = makeAztecAddress(baseContractAddressSeed); - const callRequests: PublicCallRequest[] = [ + const publicCallRequests: PublicCallRequest[] = [ baseContractAddressSeed, baseContractAddressSeed, baseContractAddressSeed, ].map(makePublicCallRequest); - callRequests[0].callContext.sideEffectCounter = 2; - callRequests[1].callContext.sideEffectCounter = 3; - callRequests[2].callContext.sideEffectCounter = 4; - - const kernelOutput = makePrivateKernelTailCircuitPublicInputs(0x10); - kernelOutput.forPublic!.endNonRevertibleData.publicDataUpdateRequests = makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - PublicDataUpdateRequest.empty, - ); - kernelOutput.forPublic!.end.publicDataUpdateRequests = makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - PublicDataUpdateRequest.empty, - ); - - addKernelPublicCallStack(kernelOutput, { - setupCalls: [callRequests[0]], - appLogicCalls: [callRequests[2]], - teardownCall: callRequests[1], + publicCallRequests[0].callContext.sideEffectCounter = 2; + publicCallRequests[1].callContext.sideEffectCounter = 3; + publicCallRequests[2].callContext.sideEffectCounter = 4; + + const tx = mockTxWithPartialState({ + numberOfNonRevertiblePublicCallRequests: 2, + numberOfRevertiblePublicCallRequests: 1, + publicCallRequests, }); - const tx = new Tx( - kernelOutput, - proof, - EncryptedTxL2Logs.empty(), - UnencryptedTxL2Logs.empty(), - // reverse because `enqueuedPublicFunctions` expects the last element to be the front of the queue - callRequests.slice().reverse(), - ); - const contractSlotA = fr(0x100); const contractSlotB = fr(0x150); const contractSlotC = fr(0x200); @@ -325,24 +353,26 @@ describe('public_processor', () => { const simulatorResults: PublicExecutionResult[] = [ // Setup PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[0], - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotA, fr(0x101))], + request: publicCallRequests[0], + contractStorageUpdateRequests: [ + new ContractStorageUpdateRequest(contractSlotA, fr(0x101), 11, baseContractAddress), + ], }).build(), // App Logic PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[2], + request: publicCallRequests[2], nestedExecutions: [ PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), contractStorageUpdateRequests: [ - new ContractStorageUpdateRequest(contractSlotA, fr(0x102)), - new ContractStorageUpdateRequest(contractSlotB, fr(0x151)), + new ContractStorageUpdateRequest(contractSlotA, fr(0x102), 13, baseContractAddress), + new ContractStorageUpdateRequest(contractSlotB, fr(0x151), 14, baseContractAddress), ], }).build(), PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), revertReason: new SimulationError('Simulation Failed', []), }).build(), @@ -351,12 +381,14 @@ describe('public_processor', () => { // Teardown PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[1], + request: publicCallRequests[1], nestedExecutions: [ PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotC, fr(0x201))], + contractStorageUpdateRequests: [ + new ContractStorageUpdateRequest(contractSlotC, fr(0x201), 12, baseContractAddress), + ], }).build(), ], }).build(), @@ -406,32 +438,21 @@ describe('public_processor', () => { it('fails a transaction that reverts in setup', async function () { const baseContractAddressSeed = 0x200; const baseContractAddress = makeAztecAddress(baseContractAddressSeed); - const callRequests: PublicCallRequest[] = [ + const publicCallRequests: PublicCallRequest[] = [ baseContractAddressSeed, baseContractAddressSeed, baseContractAddressSeed, ].map(makePublicCallRequest); - callRequests[0].callContext.sideEffectCounter = 2; - callRequests[1].callContext.sideEffectCounter = 3; - callRequests[2].callContext.sideEffectCounter = 4; - - const kernelOutput = makePrivateKernelTailCircuitPublicInputs(0x10); - - addKernelPublicCallStack(kernelOutput, { - setupCalls: [callRequests[0]], - appLogicCalls: [callRequests[2]], - teardownCall: callRequests[1], + publicCallRequests[0].callContext.sideEffectCounter = 2; + publicCallRequests[1].callContext.sideEffectCounter = 3; + publicCallRequests[2].callContext.sideEffectCounter = 4; + + const tx = mockTxWithPartialState({ + numberOfNonRevertiblePublicCallRequests: 2, + numberOfRevertiblePublicCallRequests: 1, + publicCallRequests, }); - const tx = new Tx( - kernelOutput, - proof, - EncryptedTxL2Logs.empty(), - UnencryptedTxL2Logs.empty(), - // reverse because `enqueuedPublicFunctions` expects the last element to be the front of the queue - callRequests.slice().reverse(), - ); - const contractSlotA = fr(0x100); const contractSlotB = fr(0x150); const contractSlotC = fr(0x200); @@ -440,19 +461,21 @@ describe('public_processor', () => { const simulatorResults: PublicExecutionResult[] = [ // Setup PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[0], - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotA, fr(0x101))], + request: publicCallRequests[0], + contractStorageUpdateRequests: [ + new ContractStorageUpdateRequest(contractSlotA, fr(0x101), 11, baseContractAddress), + ], nestedExecutions: [ PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), contractStorageUpdateRequests: [ - new ContractStorageUpdateRequest(contractSlotA, fr(0x102)), - new ContractStorageUpdateRequest(contractSlotB, fr(0x151)), + new ContractStorageUpdateRequest(contractSlotA, fr(0x102), 12, baseContractAddress), + new ContractStorageUpdateRequest(contractSlotB, fr(0x151), 13, baseContractAddress), ], }).build(), PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), revertReason: new SimulationError('Simulation Failed', []), }).build(), @@ -461,17 +484,19 @@ describe('public_processor', () => { // App Logic PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[2], + request: publicCallRequests[2], }).build(), // Teardown PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[1], + request: publicCallRequests[1], nestedExecutions: [ PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotC, fr(0x201))], + contractStorageUpdateRequests: [ + new ContractStorageUpdateRequest(contractSlotC, fr(0x201), 14, baseContractAddress), + ], }).build(), ], }).build(), @@ -511,32 +536,21 @@ describe('public_processor', () => { it('fails a transaction that reverts in teardown', async function () { const baseContractAddressSeed = 0x200; const baseContractAddress = makeAztecAddress(baseContractAddressSeed); - const callRequests: PublicCallRequest[] = [ + const publicCallRequests: PublicCallRequest[] = [ baseContractAddressSeed, baseContractAddressSeed, baseContractAddressSeed, ].map(makePublicCallRequest); - callRequests[0].callContext.sideEffectCounter = 2; - callRequests[1].callContext.sideEffectCounter = 3; - callRequests[2].callContext.sideEffectCounter = 4; - - const kernelOutput = makePrivateKernelTailCircuitPublicInputs(0x10); - - addKernelPublicCallStack(kernelOutput, { - setupCalls: [callRequests[0]], - appLogicCalls: [callRequests[2]], - teardownCall: callRequests[1], + publicCallRequests[0].callContext.sideEffectCounter = 2; + publicCallRequests[1].callContext.sideEffectCounter = 3; + publicCallRequests[2].callContext.sideEffectCounter = 4; + + const tx = mockTxWithPartialState({ + numberOfNonRevertiblePublicCallRequests: 2, + numberOfRevertiblePublicCallRequests: 1, + publicCallRequests, }); - const tx = new Tx( - kernelOutput, - proof, - EncryptedTxL2Logs.empty(), - UnencryptedTxL2Logs.empty(), - // reverse because `enqueuedPublicFunctions` expects the last element to be the front of the queue - callRequests.slice().reverse(), - ); - const contractSlotA = fr(0x100); const contractSlotB = fr(0x150); const contractSlotC = fr(0x200); @@ -545,15 +559,17 @@ describe('public_processor', () => { const simulatorResults: PublicExecutionResult[] = [ // Setup PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[0], - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotA, fr(0x101))], + request: publicCallRequests[0], + contractStorageUpdateRequests: [ + new ContractStorageUpdateRequest(contractSlotA, fr(0x101), 11, baseContractAddress), + ], nestedExecutions: [ PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), contractStorageUpdateRequests: [ - new ContractStorageUpdateRequest(contractSlotA, fr(0x102)), - new ContractStorageUpdateRequest(contractSlotB, fr(0x151)), + new ContractStorageUpdateRequest(contractSlotA, fr(0x102), 12, baseContractAddress), + new ContractStorageUpdateRequest(contractSlotB, fr(0x151), 13, baseContractAddress), ], }).build(), ], @@ -561,22 +577,24 @@ describe('public_processor', () => { // App Logic PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[2], + request: publicCallRequests[2], }).build(), // Teardown PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[1], + request: publicCallRequests[1], nestedExecutions: [ PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), revertReason: new SimulationError('Simulation Failed', []), }).build(), PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotC, fr(0x201))], + contractStorageUpdateRequests: [ + new ContractStorageUpdateRequest(contractSlotC, fr(0x201), 14, baseContractAddress), + ], }).build(), ], }).build(), @@ -615,42 +633,21 @@ describe('public_processor', () => { it('runs a tx with setup and teardown phases', async function () { const baseContractAddressSeed = 0x200; const baseContractAddress = makeAztecAddress(baseContractAddressSeed); - const callRequests: PublicCallRequest[] = [ + const publicCallRequests: PublicCallRequest[] = [ baseContractAddressSeed, baseContractAddressSeed, baseContractAddressSeed, ].map(makePublicCallRequest); - callRequests[0].callContext.sideEffectCounter = 2; - callRequests[1].callContext.sideEffectCounter = 3; - callRequests[2].callContext.sideEffectCounter = 4; - - const kernelOutput = makePrivateKernelTailCircuitPublicInputs(0x10); - kernelOutput.forPublic!.end.encryptedLogsHash = Fr.ZERO; - kernelOutput.forPublic!.end.unencryptedLogsHash = Fr.ZERO; - kernelOutput.forPublic!.endNonRevertibleData.publicDataUpdateRequests = makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - PublicDataUpdateRequest.empty, - ); - kernelOutput.forPublic!.end.publicDataUpdateRequests = makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - PublicDataUpdateRequest.empty, - ); - - addKernelPublicCallStack(kernelOutput, { - setupCalls: [callRequests[0]], - appLogicCalls: [callRequests[2]], - teardownCall: callRequests[1], + publicCallRequests[0].callContext.sideEffectCounter = 2; + publicCallRequests[1].callContext.sideEffectCounter = 3; + publicCallRequests[2].callContext.sideEffectCounter = 4; + + const tx = mockTxWithPartialState({ + numberOfNonRevertiblePublicCallRequests: 2, + numberOfRevertiblePublicCallRequests: 1, + publicCallRequests, }); - const tx = new Tx( - kernelOutput, - proof, - EncryptedTxL2Logs.empty(), - UnencryptedTxL2Logs.empty(), - // reverse because `enqueuedPublicFunctions` expects the last element to be the front of the queue - callRequests.slice().reverse(), - ); - // const baseContractAddress = makeAztecAddress(30); const contractSlotA = fr(0x100); const contractSlotB = fr(0x150); @@ -659,33 +656,35 @@ describe('public_processor', () => { let simulatorCallCount = 0; const simulatorResults: PublicExecutionResult[] = [ // Setup - PublicExecutionResultBuilder.fromPublicCallRequest({ request: callRequests[0] }).build(), + PublicExecutionResultBuilder.fromPublicCallRequest({ request: publicCallRequests[0] }).build(), // App Logic PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[2], + request: publicCallRequests[2], contractStorageUpdateRequests: [ - new ContractStorageUpdateRequest(contractSlotA, fr(0x101)), - new ContractStorageUpdateRequest(contractSlotB, fr(0x151)), + new ContractStorageUpdateRequest(contractSlotA, fr(0x101), 14, baseContractAddress), + new ContractStorageUpdateRequest(contractSlotB, fr(0x151), 15, baseContractAddress), ], }).build(), // Teardown PublicExecutionResultBuilder.fromPublicCallRequest({ - request: callRequests[1], + request: publicCallRequests[1], nestedExecutions: [ PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), contractStorageUpdateRequests: [ - new ContractStorageUpdateRequest(contractSlotA, fr(0x101)), - new ContractStorageUpdateRequest(contractSlotC, fr(0x201)), + new ContractStorageUpdateRequest(contractSlotA, fr(0x101), 11, baseContractAddress), + new ContractStorageUpdateRequest(contractSlotC, fr(0x201), 12, baseContractAddress), ], }).build(), PublicExecutionResultBuilder.fromFunctionCall({ - from: callRequests[1].contractAddress, + from: publicCallRequests[1].contractAddress, tx: makeFunctionCall(baseContractAddress, makeSelector(5)), - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotA, fr(0x102))], + contractStorageUpdateRequests: [ + new ContractStorageUpdateRequest(contractSlotA, fr(0x102), 13, baseContractAddress), + ], }).build(), ], }).build(), diff --git a/yarn-project/simulator/src/public/state_actions.ts b/yarn-project/simulator/src/public/state_actions.ts index a4b63c54a604..cd25d82f669f 100644 --- a/yarn-project/simulator/src/public/state_actions.ts +++ b/yarn-project/simulator/src/public/state_actions.ts @@ -83,6 +83,7 @@ export class ContractStorageActionsCollector { public collect(): [ContractStorageRead[], ContractStorageUpdateRequest[]] { const reads = Array.from(this.contractStorageReads.entries()).map(([slot, valueAndCounter]) => ContractStorageRead.from({ + contractAddress: this.address, storageSlot: new Fr(slot), ...valueAndCounter, }), @@ -90,6 +91,7 @@ export class ContractStorageActionsCollector { const updateRequests = Array.from(this.contractStorageUpdateRequests.entries()).map(([slot, valuesAndCounter]) => ContractStorageUpdateRequest.from({ + contractAddress: this.address, storageSlot: new Fr(slot), ...valuesAndCounter, }), diff --git a/yarn-project/simulator/src/public/tail_phase_manager.ts b/yarn-project/simulator/src/public/tail_phase_manager.ts index c1899ae2d729..56d12b124c5e 100644 --- a/yarn-project/simulator/src/public/tail_phase_manager.ts +++ b/yarn-project/simulator/src/public/tail_phase_manager.ts @@ -5,6 +5,8 @@ import { type Header, type KernelCircuitPublicInputs, MAX_NEW_NOTE_HASHES_PER_TX, + MAX_NEW_NULLIFIERS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, type Proof, type PublicKernelCircuitPublicInputs, PublicKernelTailCircuitPrivateInputs, @@ -88,27 +90,59 @@ export class TailPhaseManager extends AbstractPhaseManager { previousOutput: PublicKernelCircuitPublicInputs, previousProof: Proof, ): Promise<[PublicKernelTailCircuitPrivateInputs, KernelCircuitPublicInputs]> { + const inputs = await this.buildPrivateInputs(previousOutput, previousProof); + // We take a deep copy (clone) of these to pass to the prover + return [inputs.clone(), await this.publicKernel.publicKernelCircuitTail(inputs)]; + } + + private async buildPrivateInputs(previousOutput: PublicKernelCircuitPublicInputs, previousProof: Proof) { const previousKernel = this.getPreviousKernelData(previousOutput, previousProof); const { validationRequests, endNonRevertibleData, end } = previousOutput; - const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints( - validationRequests.nullifierReadRequests, + + const pendingNullifiers = mergeAccumulatedData( + MAX_NEW_NULLIFIERS_PER_TX, endNonRevertibleData.newNullifiers, end.newNullifiers, ); + + const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints( + validationRequests.nullifierReadRequests, + pendingNullifiers, + ); + const nullifierNonExistentReadRequestHints = await this.hintsBuilder.getNullifierNonExistentReadRequestHints( validationRequests.nullifierNonExistentReadRequests, - endNonRevertibleData.newNullifiers, - end.newNullifiers, + pendingNullifiers, ); - // We take a deep copy (clone) of these to pass to the prover - const inputs = new PublicKernelTailCircuitPrivateInputs( + const pendingPublicDataWrites = mergeAccumulatedData( + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + endNonRevertibleData.publicDataUpdateRequests, + end.publicDataUpdateRequests, + ); + + const publicDataHints = await this.hintsBuilder.getPublicDataHints( + validationRequests.publicDataReads, + pendingPublicDataWrites, + ); + + const publicDataReadRequestHints = this.hintsBuilder.getPublicDataReadRequestHints( + validationRequests.publicDataReads, + pendingPublicDataWrites, + publicDataHints, + ); + + const currentState = await this.db.getStateReference(); + + return new PublicKernelTailCircuitPrivateInputs( previousKernel, nullifierReadRequestHints, nullifierNonExistentReadRequestHints, + publicDataHints, + publicDataReadRequestHints, + currentState.partial, ); - return [inputs.clone(), await this.publicKernel.publicKernelCircuitTail(inputs)]; } private sortNoteHashes(noteHashes: Tuple): Tuple { diff --git a/yarn-project/simulator/src/public/transitional_adaptors.ts b/yarn-project/simulator/src/public/transitional_adaptors.ts index 81bd36011e61..ddd72e75e6d4 100644 --- a/yarn-project/simulator/src/public/transitional_adaptors.ts +++ b/yarn-project/simulator/src/public/transitional_adaptors.ts @@ -112,10 +112,10 @@ export async function convertAvmResults( const execution = executionContext.execution; const contractStorageReads: ContractStorageRead[] = newWorldState.storageReads.map( - read => new ContractStorageRead(read.slot, read.value, read.counter.toNumber()), + read => new ContractStorageRead(read.slot, read.value, read.counter.toNumber(), read.storageAddress), ); const contractStorageUpdateRequests: ContractStorageUpdateRequest[] = newWorldState.storageWrites.map( - write => new ContractStorageUpdateRequest(write.slot, write.value, write.counter.toNumber()), + write => new ContractStorageUpdateRequest(write.slot, write.value, write.counter.toNumber(), write.storageAddress), ); // We need to write the storage updates to the DB, because that's what the ACVM expects. // Assumes the updates are in the right order.