diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr index 89ac1f84364..8c0f76f5eb8 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr @@ -1,7 +1,4 @@ -use dep::protocol_types::{ - constants::PUBLIC_DATA_TREE_HEIGHT, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, - utils::arr_copy_slice -}; +use dep::protocol_types::{constants::PUBLIC_DATA_TREE_HEIGHT, data::PublicDataTreeLeafPreimage, utils::arr_copy_slice}; global LEAF_PREIMAGE_LENGTH: u32 = 4; global PUBLIC_DATA_WITNESS: Field = 45; @@ -18,7 +15,10 @@ unconstrained fn get_public_data_witness_oracle( _public_data_tree_index: Field ) -> [Field; PUBLIC_DATA_WITNESS] {} -unconstrained pub fn get_public_data_witness(block_number: u32, public_data_tree_index: Field) -> PublicDataWitness { +unconstrained pub fn get_public_data_witness( + block_number: u32, + public_data_tree_index: Field +) -> PublicDataWitness { let fields = get_public_data_witness_oracle(block_number, public_data_tree_index); PublicDataWitness { index: fields[0], diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer.nr index d19f34dbef3..a34644c7d31 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer.nr @@ -1,37 +1,47 @@ mod combine_data; +mod generate_output_hints; +mod generate_overridable_public_data_writes; +mod generate_public_data_leaves; -use crate::components::public_tail_output_composer::combine_data::combine_data; +use generate_output_hints::{LinkedIndexHint, OutputHints, SiloedNoteHashHint}; + +use crate::components::public_tail_output_composer::{combine_data::combine_data, generate_output_hints::generate_output_hints}; use dep::types::{ abis::{kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}}, - partial_state_reference::PartialStateReference + data::PublicDataLeafHint, partial_state_reference::PartialStateReference }; -struct PublicTailOutputComposer { +struct PublicTailOutputComposer { previous_kernel: PublicKernelCircuitPublicInputs, start_state: PartialStateReference, + public_data_leaf_hints: [PublicDataLeafHint; NUM_PUBLIC_DATA_LEAVES], } -impl PublicTailOutputComposer { +impl PublicTailOutputComposer { pub fn new( previous_kernel: PublicKernelCircuitPublicInputs, - start_state: PartialStateReference + start_state: PartialStateReference, + public_data_leaf_hints: [PublicDataLeafHint; NUM_PUBLIC_DATA_LEAVES] ) -> Self { - PublicTailOutputComposer { previous_kernel, start_state } + PublicTailOutputComposer { previous_kernel, start_state, public_data_leaf_hints } } - pub fn finish(self) -> KernelCircuitPublicInputs { + pub fn finish(self) -> (KernelCircuitPublicInputs, OutputHints) { + let output_hints = generate_output_hints(self.previous_kernel, self.public_data_leaf_hints); + let end = combine_data( self.previous_kernel.end_non_revertible, - self.previous_kernel.end + self.previous_kernel.end, + output_hints ); - KernelCircuitPublicInputs { + (KernelCircuitPublicInputs { rollup_validation_requests: self.previous_kernel.validation_requests.for_rollup, end, constants: self.previous_kernel.constants, start_state: self.start_state, revert_code: self.previous_kernel.revert_code, fee_payer: self.previous_kernel.fee_payer - } + }, output_hints) } } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/combine_data.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/combine_data.nr index 778ccbac6ce..e8e93c8a02d 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/combine_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/combine_data.nr @@ -1,39 +1,22 @@ +use crate::components::public_tail_output_composer::generate_output_hints::OutputHints; use dep::types::{ abis::{ accumulated_data::{CombinedAccumulatedData, PublicAccumulatedData}, log_hash::{LogHash, ScopedLogHash}, nullifier::Nullifier }, - constants::MAX_NOTE_HASHES_PER_TX, hash::silo_note_hash, utils::arrays::{array_merge, dedupe_array, sort_by_counter_asc} }; -unconstrained pub fn combine_data( +unconstrained pub fn combine_data( non_revertible: PublicAccumulatedData, - revertible: PublicAccumulatedData + revertible: PublicAccumulatedData, + output_hints: OutputHints ) -> CombinedAccumulatedData { - let mut note_hashes = [0; MAX_NOTE_HASHES_PER_TX]; - let sorted_unsiloed_note_hashes = sort_by_counter_asc(array_merge(non_revertible.note_hashes, revertible.note_hashes)); - let tx_hash = non_revertible.nullifiers[0].value; - for i in 0..sorted_unsiloed_note_hashes.len() { - let note_hash = sorted_unsiloed_note_hashes[i]; - note_hashes[i] = if note_hash.counter() == 0 { - // If counter is zero, the note hash is either empty or is emitted from private and has been siloed in private_kernel_tail_to_public. - note_hash.value() - } else { - silo_note_hash(note_hash, tx_hash, i) - }; - } - let nullifiers = sort_by_counter_asc(array_merge(non_revertible.nullifiers, revertible.nullifiers)).map(|n: Nullifier| n.value); let l2_to_l1_msgs = sort_by_counter_asc(array_merge(non_revertible.l2_to_l1_msgs, revertible.l2_to_l1_msgs)); - let public_data_update_requests = dedupe_array( - array_merge( - non_revertible.public_data_update_requests, - revertible.public_data_update_requests - ) - ); + let public_data_update_requests = dedupe_array(output_hints.public_data_writes); let note_encrypted_logs_hashes = sort_by_counter_asc( array_merge( @@ -60,7 +43,7 @@ unconstrained pub fn combine_data( let unencrypted_log_preimages_length = unencrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length); CombinedAccumulatedData { - note_hashes, + note_hashes: output_hints.siloed_note_hashes, nullifiers, l2_to_l1_msgs, note_encrypted_logs_hashes, diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_output_hints.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_output_hints.nr new file mode 100644 index 00000000000..8938c0e166c --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_output_hints.nr @@ -0,0 +1,112 @@ +use crate::components::public_tail_output_composer::{ + generate_overridable_public_data_writes::{generate_overridable_public_data_writes, LinkedIndexHint}, + generate_public_data_leaves::generate_public_data_leaves +}; +use dep::types::{ + abis::{ + kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, + public_data_write::OverridablePublicDataWrite +}, + constants::{ + MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_L2_TO_L1_MSGS_PER_TX, + MAX_UNENCRYPTED_LOGS_PER_TX, MAX_NOTE_ENCRYPTED_LOGS_PER_TX, MAX_ENCRYPTED_LOGS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX +}, + data::{OverridablePublicDataTreeLeaf, PublicDataLeafHint}, hash::silo_note_hash, traits::Empty, + utils::arrays::{array_merge, CombinedOrderHint, get_combined_order_hints_asc, sort_by_counter_asc, SortedResult} +}; + +struct SiloedNoteHashHint { + siloed_note_hash: Field, + index: u32, +} + +impl Empty for SiloedNoteHashHint { + fn empty() -> Self { + SiloedNoteHashHint { siloed_note_hash: 0, index: 0 } + } +} + +impl Eq for SiloedNoteHashHint { + fn eq(self, other: Self) -> bool { + (self.siloed_note_hash == other.siloed_note_hash) & (self.index == other.index) + } +} + +struct OutputHints { + siloed_note_hashes: [Field; MAX_NOTE_HASHES_PER_TX], + siloed_note_hash_hints: [SiloedNoteHashHint; MAX_NOTE_HASHES_PER_TX], + sorted_note_hash_hints: [CombinedOrderHint; MAX_NOTE_HASHES_PER_TX], + sorted_nullifier_hints: [CombinedOrderHint; MAX_NULLIFIERS_PER_TX], + sorted_l2_to_l1_msg_hints: [CombinedOrderHint; MAX_L2_TO_L1_MSGS_PER_TX], + sorted_note_encrypted_log_hash_hints: [CombinedOrderHint; MAX_NOTE_ENCRYPTED_LOGS_PER_TX], + sorted_encrypted_log_hash_hints: [CombinedOrderHint; MAX_ENCRYPTED_LOGS_PER_TX], + sorted_unencrypted_log_hash_hints: [CombinedOrderHint; MAX_UNENCRYPTED_LOGS_PER_TX], + public_data_writes: [OverridablePublicDataWrite; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + public_data_leaves: [OverridablePublicDataTreeLeaf; NUM_PUBLIC_DATA_LEAVES], + unique_slot_index_hints: SortedResult, + public_data_linked_index_hints: [LinkedIndexHint; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], +} + +unconstrained pub fn generate_output_hints( + previous_kernel: PublicKernelCircuitPublicInputs, + public_data_leaf_hints: [PublicDataLeafHint; NUM_PUBLIC_DATA_LEAVES] +) -> OutputHints { + let non_revertible = previous_kernel.end_non_revertible; + let revertible = previous_kernel.end; + + // Note hashes. + let mut siloed_note_hashes = [0; MAX_NOTE_HASHES_PER_TX]; + let mut siloed_note_hash_hints = [SiloedNoteHashHint::empty(); MAX_NOTE_HASHES_PER_TX]; + let sorted_unsiloed_note_hashes = sort_by_counter_asc(array_merge(non_revertible.note_hashes, revertible.note_hashes)); + let tx_hash = non_revertible.nullifiers[0].value; + for i in 0..sorted_unsiloed_note_hashes.len() { + let note_hash = sorted_unsiloed_note_hashes[i]; + let siloed_note_hash = if note_hash.counter() == 0 { + // If counter is zero, the note hash is either empty or is emitted from private and has been siloed in private_kernel_tail_to_public. + note_hash.value() + } else { + silo_note_hash(note_hash, tx_hash, i) + }; + siloed_note_hashes[i] = siloed_note_hash; + if siloed_note_hash != 0 { + siloed_note_hash_hints[i] = SiloedNoteHashHint { siloed_note_hash, index: i }; + } + } + + // Public data. + let combined_writes = array_merge( + previous_kernel.end_non_revertible.public_data_update_requests, + previous_kernel.end.public_data_update_requests + ); + let (public_data_leaves, unique_slot_index_hints) = generate_public_data_leaves( + previous_kernel.validation_requests.public_data_reads, + combined_writes, + public_data_leaf_hints + ); + let (public_data_writes, public_data_linked_index_hints) = generate_overridable_public_data_writes(combined_writes, public_data_leaves); + + OutputHints { + siloed_note_hashes, + siloed_note_hash_hints, + sorted_note_hash_hints: get_combined_order_hints_asc(non_revertible.note_hashes, revertible.note_hashes), + sorted_nullifier_hints: get_combined_order_hints_asc(non_revertible.nullifiers, revertible.nullifiers), + sorted_l2_to_l1_msg_hints: get_combined_order_hints_asc(non_revertible.l2_to_l1_msgs, revertible.l2_to_l1_msgs), + sorted_note_encrypted_log_hash_hints: get_combined_order_hints_asc( + non_revertible.note_encrypted_logs_hashes, + revertible.note_encrypted_logs_hashes + ), + sorted_encrypted_log_hash_hints: get_combined_order_hints_asc( + non_revertible.encrypted_logs_hashes, + revertible.encrypted_logs_hashes + ), + sorted_unencrypted_log_hash_hints: get_combined_order_hints_asc( + non_revertible.unencrypted_logs_hashes, + revertible.unencrypted_logs_hashes + ), + public_data_writes, + public_data_leaves, + unique_slot_index_hints, + public_data_linked_index_hints + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_overridable_public_data_writes.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_overridable_public_data_writes.nr new file mode 100644 index 00000000000..9ecb9d69902 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_overridable_public_data_writes.nr @@ -0,0 +1,58 @@ +use dep::types::{ + abis::{public_data_update_request::PublicDataUpdateRequest, public_data_write::OverridablePublicDataWrite}, + data::OverridablePublicDataTreeLeaf, traits::Empty, utils::arrays::{array_length, find_index_hint} +}; + +struct LinkedIndexHint { + is_first_write: bool, + prev_index: u32, +} + +impl Empty for LinkedIndexHint { + fn empty() -> Self { + LinkedIndexHint { is_first_write: false, prev_index: 0 } + } +} + +unconstrained pub fn generate_overridable_public_data_writes( + public_data_writes: [PublicDataUpdateRequest; NUM_WRITES], + public_data_leaves: [OverridablePublicDataTreeLeaf; NUM_LEAVES] +) -> ([OverridablePublicDataWrite; NUM_WRITES], [LinkedIndexHint; NUM_WRITES]) { + let mut overridable_public_data_writes = [OverridablePublicDataWrite::empty(); NUM_WRITES]; + let mut hints = [LinkedIndexHint::empty(); NUM_WRITES]; + + let writes_len = array_length(public_data_writes); + for i in 0..writes_len { + let write = public_data_writes[i]; + let mut override_counter = 0; + let mut is_first_write = false; + let mut prev_index = 0; + let mut prev_counter = 0; + + for j in 0..writes_len { + let other = public_data_writes[j]; + if (j != i) & (other.leaf_slot == write.leaf_slot) { + if other.counter > write.counter { + if (override_counter == 0) | (other.counter < override_counter) { + override_counter = other.counter; + } + } else if other.counter < write.counter { + if other.counter > prev_counter { + prev_counter = other.counter; + prev_index = j; + } + } + } + } + + if prev_counter == 0 { + is_first_write = true; + prev_index = find_index_hint(public_data_leaves, |leaf: OverridablePublicDataTreeLeaf| leaf.leaf.slot == write.leaf_slot); + } + + overridable_public_data_writes[i] = OverridablePublicDataWrite { write, override_counter }; + hints[i] = LinkedIndexHint { is_first_write, prev_index }; + } + + (overridable_public_data_writes, hints) +} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_public_data_leaves.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_public_data_leaves.nr new file mode 100644 index 00000000000..6df5c91dc6d --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_composer/generate_public_data_leaves.nr @@ -0,0 +1,100 @@ +use dep::types::{ + abis::{public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest}, + data::{OverridablePublicDataTreeLeaf, PublicDataLeafHint, PublicDataTreeLeaf}, traits::Empty, + utils::arrays::{get_sorted_result, SortedResult} +}; + +struct SlotIndex { + slot: Field, + index: u32, + counter: u32, +} + +impl Eq for SlotIndex { + fn eq(self, other: Self) -> bool { + (self.slot == other.slot) & (self.index == other.index) & (self.counter == other.counter) + } +} + +impl Empty for SlotIndex { + fn empty() -> Self { + SlotIndex { slot: 0, index: 0, counter: 0 } + } +} + +fn compare_by_slot_then_index(a: SlotIndex, b: SlotIndex) -> bool { + if a.slot == b.slot { + (a.index == 0) | (b.index > a.index) + } else { + (b.slot == 0) | ((a.slot != 0) & a.slot.lt(b.slot)) + } +} + +fn compare_by_index(a: SlotIndex, b: SlotIndex) -> bool { + (a.slot != 0) & ((b.slot == 0) | (a.index < b.index)) +} + +unconstrained pub fn generate_public_data_leaves( + reads: [PublicDataRead; NUM_READS], + writes: [PublicDataUpdateRequest; NUM_WRITES], + hints: [PublicDataLeafHint; NUM_HINTS] +) -> ([OverridablePublicDataTreeLeaf; NUM_HINTS], SortedResult) { + // Combine reads and writes. The combined data has the slots and original indexes. + // Original indexes for writes are modified to have an offset of NUM_READS, ensuring that writes are placed after reads. + let mut combined_data: BoundedVec = BoundedVec::new(); + for i in 0..reads.len() { + let read = reads[i]; + if read.leaf_slot != 0 { + combined_data.push(SlotIndex { slot: read.leaf_slot, index: i, counter: 0 }); + } + } + for i in 0..writes.len() { + let write = writes[i]; + if write.leaf_slot != 0 { + combined_data.push(SlotIndex { slot: write.leaf_slot, index: i + NUM_READS, counter: write.counter }); + } + } + + // Sort the combined data by slot then index. + // Find the data with unique slots from the sorted result. + let sorted_combined_data = combined_data.storage.sort_via(compare_by_slot_then_index); + let mut prev_slot = 0; + let mut override_counter = 0; + let mut data_with_unique_slot: BoundedVec = BoundedVec::new(); + for i in 0..combined_data.len() { + let data = sorted_combined_data[i]; + if data.slot != prev_slot { + // Found an unique slot. + data_with_unique_slot.push(data); + prev_slot = data.slot; + override_counter = 0; + } + if data.index >= NUM_READS { + // Found a write. + // If it's the first write, update the override_counter of the data with the same slot. + if (override_counter == 0) | (data.counter < override_counter) { + override_counter = data.counter; + data_with_unique_slot.storage[data_with_unique_slot.len() - 1].counter = override_counter; + } + } + } + let sorted_unique_slots = data_with_unique_slot.storage.map(|d: SlotIndex| d.slot); + + // Sort the data by original index. + let mut leaves = [OverridablePublicDataTreeLeaf::empty(); NUM_HINTS]; + let sorted_result = get_sorted_result(data_with_unique_slot.storage, compare_by_index); + for i in 0..data_with_unique_slot.len() { + let sorted = sorted_result.sorted_array[i]; + // The provided hints should've been sorted. + let hint = hints[i]; + let exists = hint.preimage.slot == sorted.slot; + let value = if exists { hint.preimage.value } else { 0 }; + leaves[i] = OverridablePublicDataTreeLeaf { leaf: PublicDataTreeLeaf { slot: sorted.slot, value }, override_counter: sorted.counter }; + } + + (leaves, SortedResult { + sorted_array: sorted_unique_slots, + sorted_index_hints: sorted_result.original_index_hints, // We sort the sorted_unique_slots to get the hints so original_index_hints becomes the sorted_index_hints and vice versa. + original_index_hints: sorted_result.sorted_index_hints + }) +} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator.nr index 2dba886c754..4f6afcd9f52 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator.nr @@ -1,31 +1,46 @@ -mod output_hints; - -use crate::components::public_tail_output_validator::output_hints::{generate_output_hints, OutputHints, SiloedNoteHashHint}; +mod validate_linked_public_data_writes; +mod validate_public_data_leaf_memberships; +mod validate_unique_leaf_slots; + +use crate::components::{ + public_tail_output_validator::{ + validate_linked_public_data_writes::validate_linked_public_data_writes, + validate_public_data_leaf_memberships::validate_public_data_leaf_memberships, + validate_unique_leaf_slots::validate_unique_leaf_slots +}, + public_tail_output_composer::{OutputHints, SiloedNoteHashHint} +}; use dep::types::{ abis::{ kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}, - log_hash::{LogHash, ScopedLogHash}, note_hash::{NoteHash, ScopedNoteHash}, nullifier::Nullifier + log_hash::{LogHash, ScopedLogHash}, note_hash::ScopedNoteHash, nullifier::Nullifier, + public_data_update_request::PublicDataUpdateRequest, public_data_write::OverridablePublicDataWrite }, - hash::silo_note_hash, messaging::l2_to_l1_message::ScopedL2ToL1Message, + data::PublicDataLeafHint, hash::silo_note_hash, messaging::l2_to_l1_message::ScopedL2ToL1Message, partial_state_reference::PartialStateReference, - utils::arrays::{array_length, assert_combined_sorted_transformed_value_array_asc, assert_combined_deduped_array} + utils::arrays::{ + assert_combined_sorted_transformed_value_array_asc, assert_combined_transformed_array, + assert_deduped_array +} }; -struct PublicTailOutputValidator { +struct PublicTailOutputValidator { output: KernelCircuitPublicInputs, previous_kernel: PublicKernelCircuitPublicInputs, start_state: PartialStateReference, - hints: OutputHints + hints: OutputHints, + public_data_leaf_hints: [PublicDataLeafHint; NUM_PUBLIC_DATA_LEAVES], } -impl PublicTailOutputValidator { +impl PublicTailOutputValidator { pub fn new( output: KernelCircuitPublicInputs, previous_kernel: PublicKernelCircuitPublicInputs, - start_state: PartialStateReference + start_state: PartialStateReference, + hints: OutputHints, + public_data_leaf_hints: [PublicDataLeafHint; NUM_PUBLIC_DATA_LEAVES] ) -> Self { - let hints = generate_output_hints(previous_kernel, output); - PublicTailOutputValidator { output, previous_kernel, start_state, hints } + PublicTailOutputValidator { output, previous_kernel, start_state, hints, public_data_leaf_hints } } pub fn validate(self) { @@ -139,12 +154,39 @@ impl PublicTailOutputValidator { } fn validate_deduped_public_data_writes(self) { - assert_combined_deduped_array( + validate_public_data_leaf_memberships( + self.hints.public_data_leaves, + self.public_data_leaf_hints, + self.start_state.public_data_tree.root + ); + + // Check that public_data_writes are simply the writes in the two accumulated data arrays combined, + // with override_counter added to each non-empty write as hint. + // override_counter is checked in validate_linked_public_data_writes. + assert_combined_transformed_array( self.previous_kernel.end_non_revertible.public_data_update_requests, self.previous_kernel.end.public_data_update_requests, - self.hints.sorted_public_data_update_requests, - self.output.end.public_data_update_requests, - self.hints.deduped_public_data_update_request_hints + self.hints.public_data_writes, + |from: PublicDataUpdateRequest, to: OverridablePublicDataWrite| to.write == from + ); + + // All non-zero slots in leaves must be unique, ensuring that writes with the same leaf slot are grouped into a single linked list. + validate_unique_leaf_slots( + self.hints.public_data_leaves, + self.hints.unique_slot_index_hints + ); + + // Validate the override_counter of writes, ensuring that they in the correct list in the correct order. + validate_linked_public_data_writes( + self.hints.public_data_writes, + self.hints.public_data_leaves, + self.hints.public_data_linked_index_hints + ); + + // Check that the output contains the last write in each linked list. + assert_deduped_array( + self.hints.public_data_writes, + self.output.end.public_data_update_requests ); } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/output_hints.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/output_hints.nr deleted file mode 100644 index 95d26ece7e2..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/output_hints.nr +++ /dev/null @@ -1,89 +0,0 @@ -use dep::types::{ - abis::{ - kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}, - public_data_update_request::PublicDataUpdateRequest -}, - constants::{ - MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_L2_TO_L1_MSGS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, MAX_NOTE_ENCRYPTED_LOGS_PER_TX, - MAX_ENCRYPTED_LOGS_PER_TX -}, - hash::silo_note_hash, traits::Empty, - utils::arrays::{ - array_merge, CombinedOrderHint, DedupedHints, get_combined_order_hints_asc, get_deduped_hints, - sort_by_position_then_counter -} -}; - -struct SiloedNoteHashHint { - siloed_note_hash: Field, - index: u32, -} - -impl Empty for SiloedNoteHashHint { - fn empty() -> Self { - SiloedNoteHashHint { siloed_note_hash: 0, index: 0 } - } -} - -impl Eq for SiloedNoteHashHint { - fn eq(self, other: Self) -> bool { - (self.siloed_note_hash == other.siloed_note_hash) & (self.index == other.index) - } -} - -struct OutputHints { - siloed_note_hash_hints: [SiloedNoteHashHint; MAX_NOTE_HASHES_PER_TX], - sorted_note_hash_hints: [CombinedOrderHint; MAX_NOTE_HASHES_PER_TX], - sorted_nullifier_hints: [CombinedOrderHint; MAX_NULLIFIERS_PER_TX], - sorted_l2_to_l1_msg_hints: [CombinedOrderHint; MAX_L2_TO_L1_MSGS_PER_TX], - sorted_note_encrypted_log_hash_hints: [CombinedOrderHint; MAX_NOTE_ENCRYPTED_LOGS_PER_TX], - sorted_encrypted_log_hash_hints: [CombinedOrderHint; MAX_ENCRYPTED_LOGS_PER_TX], - sorted_unencrypted_log_hash_hints: [CombinedOrderHint; MAX_UNENCRYPTED_LOGS_PER_TX], - sorted_public_data_update_requests: [PublicDataUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - deduped_public_data_update_request_hints: DedupedHints, -} - -unconstrained pub fn generate_output_hints( - previous_kernel: PublicKernelCircuitPublicInputs, - output: KernelCircuitPublicInputs -) -> OutputHints { - let non_revertible = previous_kernel.end_non_revertible; - let revertible = previous_kernel.end; - - let mut siloed_note_hash_hints = output.end.note_hashes.map(|siloed_note_hash| SiloedNoteHashHint { siloed_note_hash, index: 0 }); - for i in 0..siloed_note_hash_hints.len() { - if siloed_note_hash_hints[i].siloed_note_hash != 0 { - siloed_note_hash_hints[i].index = i; - } - } - - OutputHints { - siloed_note_hash_hints, - sorted_note_hash_hints: get_combined_order_hints_asc(non_revertible.note_hashes, revertible.note_hashes), - sorted_nullifier_hints: get_combined_order_hints_asc(non_revertible.nullifiers, revertible.nullifiers), - sorted_l2_to_l1_msg_hints: get_combined_order_hints_asc(non_revertible.l2_to_l1_msgs, revertible.l2_to_l1_msgs), - sorted_note_encrypted_log_hash_hints: get_combined_order_hints_asc( - non_revertible.note_encrypted_logs_hashes, - revertible.note_encrypted_logs_hashes - ), - sorted_encrypted_log_hash_hints: get_combined_order_hints_asc( - non_revertible.encrypted_logs_hashes, - revertible.encrypted_logs_hashes - ), - sorted_unencrypted_log_hash_hints: get_combined_order_hints_asc( - non_revertible.unencrypted_logs_hashes, - revertible.unencrypted_logs_hashes - ), - sorted_public_data_update_requests: sort_by_position_then_counter( - array_merge( - non_revertible.public_data_update_requests, - revertible.public_data_update_requests - ) - ), - deduped_public_data_update_request_hints: get_deduped_hints( - non_revertible.public_data_update_requests, - revertible.public_data_update_requests - ) - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_linked_public_data_writes.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_linked_public_data_writes.nr new file mode 100644 index 00000000000..6d9ce90ca5f --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_linked_public_data_writes.nr @@ -0,0 +1,178 @@ +use crate::components::public_tail_output_composer::LinkedIndexHint; +use dep::types::{abis::public_data_write::OverridablePublicDataWrite, data::OverridablePublicDataTreeLeaf}; + +// Check that each non-empty write is correctly linked to either a leaf or another write. +// All the writes with the same leaf slot will form a single linked list. +// Note that public_data_leaves must be verified to contain unique slots before passing to this function. +fn validate_linked_public_data_writes( + public_data_writes: [OverridablePublicDataWrite; NUM_WRITES], + public_data_leaves: [OverridablePublicDataTreeLeaf; NUM_LEAVES], + hints: [LinkedIndexHint; NUM_WRITES] +) { + for i in 0..public_data_writes.len() { + let write = public_data_writes[i]; + let hint = hints[i]; + if write.counter() != 0 { + if hint.is_first_write { + // It's linked to a leaf in the tree. + let data_hint = public_data_leaves[hint.prev_index]; + assert_eq(data_hint.leaf.slot, write.inner().leaf_slot, "hinted leaf has different slot"); + assert_eq( + data_hint.override_counter, write.counter(), "hinted leaf does not link to current write" + ); + } else { + // It's linked to another write for the same leaf slot. + let prev = public_data_writes[hint.prev_index]; + assert_eq(prev.inner().leaf_slot, write.inner().leaf_slot, "hinted write has different slot"); + assert(prev.counter() < write.counter(), "previous write must have a smaller counter"); + assert_eq( + prev.override_counter, write.counter(), "hinted previous write does not link to current write" + ); + } + } + } +} + +mod tests { + use crate::components::{ + public_tail_output_composer::LinkedIndexHint, + public_tail_output_validator::validate_linked_public_data_writes::validate_linked_public_data_writes + }; + use dep::types::{ + abis::{public_data_write::OverridablePublicDataWrite, public_data_update_request::PublicDataUpdateRequest}, + data::{OverridablePublicDataTreeLeaf, PublicDataTreeLeaf}, tests::utils::pad_end + }; + + struct TestBuilder { + public_data_writes: [OverridablePublicDataWrite; 8], + public_data_leaves: [OverridablePublicDataTreeLeaf; 12], + hints: [LinkedIndexHint; 8] + } + + impl TestBuilder { + pub fn new_empty() -> Self { + let public_data_writes = pad_end([], OverridablePublicDataWrite::empty()); + let public_data_leaves = pad_end([], OverridablePublicDataTreeLeaf::empty()); + let hints = pad_end([], LinkedIndexHint::empty()); + TestBuilder { public_data_writes, public_data_leaves, hints } + } + + pub fn new() -> Self { + let public_data_writes = pad_end( + [ + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 33, new_value: 300, counter: 30 }, override_counter: 0 }, + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 22, new_value: 202, counter: 40 }, override_counter: 50 }, + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 11, new_value: 100, counter: 10 }, override_counter: 0 }, + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 22, new_value: 201, counter: 20 }, override_counter: 40 }, + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 22, new_value: 203, counter: 50 }, override_counter: 0 } + ], + OverridablePublicDataWrite::empty() + ); + + let public_data_leaves = pad_end( + [ + OverridablePublicDataTreeLeaf { leaf: PublicDataTreeLeaf { slot: 0, value: 0 }, override_counter: 0 }, + OverridablePublicDataTreeLeaf { leaf: PublicDataTreeLeaf { slot: 22, value: 200 }, override_counter: 20 }, + OverridablePublicDataTreeLeaf { leaf: PublicDataTreeLeaf { slot: 44, value: 0 }, override_counter: 0 }, + OverridablePublicDataTreeLeaf { leaf: PublicDataTreeLeaf { slot: 11, value: 0 }, override_counter: 10 }, + OverridablePublicDataTreeLeaf { leaf: PublicDataTreeLeaf { slot: 33, value: 0 }, override_counter: 30 } + ], + OverridablePublicDataTreeLeaf::empty() + ); + + let hints = pad_end( + [ + LinkedIndexHint { is_first_write: true, prev_index: 4 }, + LinkedIndexHint { is_first_write: false, prev_index: 3 }, + LinkedIndexHint { is_first_write: true, prev_index: 3 }, + LinkedIndexHint { is_first_write: true, prev_index: 1 }, + LinkedIndexHint { is_first_write: false, prev_index: 1 } + ], + LinkedIndexHint::empty() + ); + + TestBuilder { public_data_writes, public_data_leaves, hints } + } + + pub fn execute(self) { + validate_linked_public_data_writes(self.public_data_writes, self.public_data_leaves, self.hints); + } + } + + #[test] + fn validate_linked_public_data_writes_empty_succeeds() { + let builder = TestBuilder::new_empty(); + builder.execute(); + } + + #[test] + fn validate_linked_public_data_writes_succeeds() { + let builder = TestBuilder::new(); + builder.execute(); + } + + #[test(should_fail_with="hinted leaf has different slot")] + fn validate_linked_public_data_writes_link_prev_to_empty_leaf_fails() { + let mut builder = TestBuilder::new(); + + // Link the first write to an empty leaf. + builder.hints[0].prev_index = 7; + + builder.execute(); + } + + #[test(should_fail_with="hinted leaf does not link to current write")] + fn validate_linked_public_data_writes_link_two_writes_to_same_leaf_fails() { + let mut builder = TestBuilder::new(); + + // Link the write to a leaf with the same slot. + builder.hints[1] = builder.hints[3]; + + builder.execute(); + } + + #[test(should_fail_with="hinted write has different slot")] + fn validate_linked_public_data_writes_link_prev_to_empty_write_fails() { + let mut builder = TestBuilder::new(); + + // Link the write to an empty write. + builder.hints[1].prev_index = 7; + + builder.execute(); + } + + #[test(should_fail_with="previous write must have a smaller counter")] + fn validate_linked_public_data_writes_not_sorted_by_counter_fails() { + let mut builder = TestBuilder::new(); + + // Change from leaf -> write[3] -> write[1] -> write[4] to leaf -> write[1] -> write[3] -> write[4] + let leaf_index = builder.hints[3].prev_index; + // Link the leaf to the 1st write. + builder.public_data_leaves[leaf_index].override_counter = builder.public_data_writes[1].counter(); + // Link the 1st write to the leaf and the 3rd write. + builder.hints[1].is_first_write = true; + builder.hints[1].prev_index = leaf_index; + builder.public_data_writes[1].override_counter = builder.public_data_writes[3].counter(); + // Link the 3rd write to the 1st write and the 4th write. + builder.hints[3].is_first_write = false; + builder.hints[3].prev_index = 1; + builder.public_data_writes[3].override_counter = builder.public_data_writes[4].counter(); + // Link the 4th write to the 3rd write. + builder.hints[4].prev_index = 3; + + builder.execute(); + } + + #[test(should_fail_with="hinted previous write does not link to current write")] + fn validate_linked_public_data_writes_link_two_writes_to_same_prev_write_fails() { + let mut builder = TestBuilder::new(); + + // Link a write to the previous write of its previous write. + let prev_write_index = builder.hints[4].prev_index; + builder.hints[4].prev_index = 3; + // Clear the counter and link of its previous write. + builder.public_data_writes[prev_write_index].override_counter = 0; + + builder.execute(); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_public_data_leaf_memberships.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_public_data_leaf_memberships.nr new file mode 100644 index 00000000000..6bc8a1e2c1e --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_public_data_leaf_memberships.nr @@ -0,0 +1,35 @@ +use dep::types::{ + data::{OverridablePublicDataTreeLeaf, PublicDataLeafHint}, + merkle_tree::conditionally_assert_check_membership +}; + +// Perform membership check for all non-zero leaf slots, ensuring that the values being read are correct and the public data tree is updated with the correct low leaves. +// TODO: Update public data tree in the tail circuit. Otherwise, change this to just check the leaves for public data reads. +fn validate_public_data_leaf_memberships( + leaves: [OverridablePublicDataTreeLeaf; N], + leaf_hints: [PublicDataLeafHint; N], + tree_root: Field +) { + for i in 0..leaves.len() { + let leaf = leaves[i].leaf; + let hint = leaf_hints[i]; + if leaf.slot != 0 { + let exists_in_tree = leaf.slot == hint.preimage.slot; + if exists_in_tree { + assert( + leaf.value == hint.preimage.value, "Hinted public data value does not match the value in leaf preimage" + ); + } else { + assert(leaf.value == 0, "Value must be 0 for non-existent public data"); + } + + conditionally_assert_check_membership( + leaf.slot, + exists_in_tree, + hint.preimage, + hint.membership_witness, + tree_root + ); + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_unique_leaf_slots.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_unique_leaf_slots.nr new file mode 100644 index 00000000000..f9216be6590 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/validate_unique_leaf_slots.nr @@ -0,0 +1,157 @@ +use dep::types::{data::OverridablePublicDataTreeLeaf, utils::arrays::SortedResult}; + +// Validate that every non-zero slot in leaves is unique. +fn validate_unique_leaf_slots( + leaves: [OverridablePublicDataTreeLeaf; N], + unique_slot_index_hints: SortedResult +) { + let sorted_leaf_slots = unique_slot_index_hints.sorted_array; + let sorted_leaf_slot_indexes = unique_slot_index_hints.sorted_index_hints; + let original_leaf_indexes = unique_slot_index_hints.original_index_hints; + let mut prev_slot = 0; + for i in 0..N { + let leaf = leaves[i].leaf; + let sorted_index = if leaf.slot != 0 { + sorted_leaf_slot_indexes[i] + } else { + i // This ensures that sorted_leaf_slots is padded with the same amount of zero slots as leaves. + }; + let hinted_leaf_slot = sorted_leaf_slots[sorted_index]; + assert_eq(hinted_leaf_slot, leaf.slot, "mismatch hinted slot"); + assert_eq(original_leaf_indexes[sorted_index], i, "sorted value does not link to leaf"); // This ensures that each sorted_leaf_slot can only be refered to once. + + let curr_leaf_alot = sorted_leaf_slots[i]; + if leaf.slot != 0 { + assert(prev_slot.lt(curr_leaf_alot), "hinted slots are not sorted"); // This ensures that all non-zero slots are unique. + } + prev_slot = curr_leaf_alot; + } +} + +mod tests { + use crate::components::public_tail_output_validator::validate_unique_leaf_slots::validate_unique_leaf_slots; + use dep::types::{ + data::{OverridablePublicDataTreeLeaf, PublicDataTreeLeaf}, tests::utils::pad_end, + utils::arrays::SortedResult + }; + + global NUM_TEST_LEAVES = 10; + + struct TestBuilder { + leaves: [OverridablePublicDataTreeLeaf; NUM_TEST_LEAVES], + sorted_array: [Field; NUM_TEST_LEAVES], + sorted_index_hints: [u32; NUM_TEST_LEAVES], + original_index_hints: [u32; NUM_TEST_LEAVES], + } + + impl TestBuilder { + pub fn new() -> Self { + let leaves = pad_end([], OverridablePublicDataTreeLeaf::empty()); + let sorted_array = pad_end([], 0); + let sorted_index_hints = pad_end([], 0); + let mut original_index_hints = pad_end([], 0); + for i in 0..original_index_hints.len() { + original_index_hints[i] = i; + } + TestBuilder { leaves, sorted_array, sorted_index_hints, original_index_hints } + } + + pub fn update_leaves(&mut self, slots: [Field; N]) { + let leaves = slots.map( + |slot: Field| OverridablePublicDataTreeLeaf { leaf: PublicDataTreeLeaf { slot, value: 1 }, override_counter: 0 } + ); + self.leaves = pad_end(leaves, OverridablePublicDataTreeLeaf::empty()); + } + + pub fn update_sorted_array(&mut self, sorted_values: [Field; N]) { + self.sorted_array = pad_end(sorted_values, 0); + } + + pub fn update_sorted_index_hints(&mut self, sorted_indexes: [u32; N]) { + self.sorted_index_hints = pad_end(sorted_indexes, 0); + } + + pub fn update_original_index_hints(&mut self, original_indexes: [u32; N]) { + let mut original_index_hints = pad_end(original_indexes, 0); + for i in original_indexes.len()..original_index_hints.len() { + original_index_hints[i] = i; + } + self.original_index_hints = original_index_hints; + } + + pub fn execute(self) { + let hints = SortedResult { + sorted_array: self.sorted_array, + sorted_index_hints: self.sorted_index_hints, + original_index_hints: self.original_index_hints + }; + validate_unique_leaf_slots(self.leaves, hints); + } + } + + #[test] + fn validate_unique_leaf_slots_empty_succeeds() { + let builder = TestBuilder::new(); + builder.execute() + } + + #[test] + fn validate_unique_leaf_slots_succeeds() { + let mut builder = TestBuilder::new(); + + builder.update_leaves([22, 44, 33, 11]); + builder.update_sorted_array([11, 22, 33, 44]); + builder.update_sorted_index_hints([1, 3, 2, 0]); + builder.update_original_index_hints([3, 0, 2, 1]); + + builder.execute() + } + + #[test(should_fail_with="sorted value does not link to leaf")] + fn validate_unique_leaf_slots_duplicates_remove_hint_fails() { + let mut builder = TestBuilder::new(); + + builder.update_leaves([22, 11, 11]); + builder.update_sorted_array([11, 22]); + builder.update_sorted_index_hints([1, 0, 0]); + builder.update_original_index_hints([1, 0]); + + builder.execute() + } + + #[test(should_fail_with="hinted slots are not sorted")] + fn validate_unique_leaf_slots_duplicates_extra_hint_fails() { + let mut builder = TestBuilder::new(); + + builder.update_leaves([22, 11, 11]); + builder.update_sorted_array([11, 11, 22]); + builder.update_sorted_index_hints([2, 0, 1]); + builder.update_original_index_hints([1, 2, 0]); + + builder.execute() + } + + #[test(should_fail_with="hinted slots are not sorted")] + fn validate_unique_leaf_slots_duplicates_extra_hint_in_padded_first_zero_fails() { + let mut builder = TestBuilder::new(); + + builder.update_leaves([22, 11, 11]); + builder.update_sorted_array([0, 11, 22, 0, 11]); // The first zero slot triggers the assert. + builder.update_sorted_index_hints([2, 1, 4]); + builder.update_original_index_hints([3, 1, 0, 4, 2]); + + builder.execute() + } + + #[test(should_fail_with="mismatch hinted slot")] + fn validate_unique_leaf_slots_duplicates_extra_hint_in_padded_first_non_zero_fails() { + let mut builder = TestBuilder::new(); + + builder.update_leaves([22, 11, 11]); + builder.update_sorted_array([1, 11, 22, 0, 11]); // Change the first slot to be non-zero. + builder.update_sorted_index_hints([2, 1, 4]); + builder.update_original_index_hints([3, 1, 0, 3, 2]); + + builder.execute() + } +} 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 68c39dbf448..c39c9252792 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,23 +1,25 @@ use crate::{ components::{ previous_kernel_validator::PreviousKernelValidator, - public_tail_output_composer::PublicTailOutputComposer, + public_tail_output_composer::{OutputHints, PublicTailOutputComposer}, public_tail_output_validator::PublicTailOutputValidator }, public_kernel_phase::PublicKernelPhase }; use dep::reset_kernel_lib::{ - NullifierReadRequestHints, NullifierNonExistentReadRequestHints, PublicDataReadRequestHints, - PublicValidationRequestProcessor, TreeLeafReadRequestHint + NullifierReadRequestHints, NullifierNonExistentReadRequestHints, PublicValidationRequestProcessor, + public_data_read_request_hints::{build_public_data_read_request_hints, PublicDataReadRequestHints}, + TreeLeafReadRequestHint }; use dep::types::{ abis::{kernel_circuit_public_inputs::KernelCircuitPublicInputs, public_kernel_data::PublicKernelData}, constants::{ - L1_TO_L2_MSG_TREE_HEIGHT, MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX, MAX_PUBLIC_DATA_HINTS, - MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, NOTE_HASH_TREE_HEIGHT, - PUBLIC_KERNEL_SETUP_INDEX, PUBLIC_KERNEL_APP_LOGIC_INDEX, PUBLIC_KERNEL_TEARDOWN_INDEX + L1_TO_L2_MSG_TREE_HEIGHT, MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PUBLIC_DATA_HINTS, MAX_PUBLIC_DATA_READS_PER_TX, + NOTE_HASH_TREE_HEIGHT, PUBLIC_KERNEL_SETUP_INDEX, PUBLIC_KERNEL_APP_LOGIC_INDEX, + PUBLIC_KERNEL_TEARDOWN_INDEX }, - data::public_data_hint::PublicDataHint, partial_state_reference::PartialStateReference + data::PublicDataLeafHint, partial_state_reference::PartialStateReference }; global ALLOWED_PREVIOUS_CIRCUITS = [ @@ -32,14 +34,25 @@ struct PublicKernelTailCircuitPrivateInputs { nullifier_read_request_hints: NullifierReadRequestHints, nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints, l1_to_l2_msg_read_request_hints: [TreeLeafReadRequestHint; MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX], - public_data_hints: [PublicDataHint; MAX_PUBLIC_DATA_HINTS], - public_data_read_request_hints: PublicDataReadRequestHints, + public_data_hints: [PublicDataLeafHint; MAX_PUBLIC_DATA_HINTS], start_state: PartialStateReference, } impl PublicKernelTailCircuitPrivateInputs { - unconstrained fn generate_output(self) -> KernelCircuitPublicInputs { - PublicTailOutputComposer::new(self.previous_kernel.public_inputs, self.start_state).finish() + unconstrained fn generate_output_and_hints(self) -> (KernelCircuitPublicInputs, OutputHints, PublicDataReadRequestHints) { + let (output, output_hints) = PublicTailOutputComposer::new( + self.previous_kernel.public_inputs, + self.start_state, + self.public_data_hints + ).finish(); + + let public_data_read_request_hints = build_public_data_read_request_hints( + self.previous_kernel.public_inputs.validation_requests.public_data_reads, + output_hints.public_data_writes, + output_hints.public_data_leaves + ); + + (output, output_hints, public_data_read_request_hints) } pub fn execute(self) -> KernelCircuitPublicInputs { @@ -47,21 +60,29 @@ impl PublicKernelTailCircuitPrivateInputs { previous_kernel_validator.validate_phase(PublicKernelPhase.TAIL); previous_kernel_validator.validate_proof(ALLOWED_PREVIOUS_CIRCUITS); - let previous_public_inputs = self.previous_kernel.public_inputs; + let (output, output_hints, public_data_read_request_hints) = unsafe { + self.generate_output_and_hints() + }; + PublicValidationRequestProcessor::new( - previous_public_inputs, + self.previous_kernel.public_inputs, self.start_state, self.note_hash_read_request_hints, self.nullifier_read_request_hints, self.nullifier_non_existent_read_request_hints, self.l1_to_l2_msg_read_request_hints, - self.public_data_read_request_hints, - self.public_data_hints + output_hints.public_data_writes, + output_hints.public_data_leaves, + public_data_read_request_hints ).validate(); - let output = self.generate_output(); - - PublicTailOutputValidator::new(output, previous_public_inputs, self.start_state).validate(); + PublicTailOutputValidator::new( + output, + self.previous_kernel.public_inputs, + self.start_state, + output_hints, + self.public_data_hints + ).validate(); output } @@ -72,42 +93,41 @@ mod tests { use dep::reset_kernel_lib::{ tests::{ nullifier_non_existent_read_request_hints_builder::NullifierNonExistentReadRequestHintsBuilder, - nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder, - public_data_read_request_hints_builder::PublicDataReadRequestHintsBuilder + nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder }, - PublicDataHint, reset::read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus}, + reset::read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus}, TreeLeafReadRequestHint }; use dep::types::{ abis::{ - kernel_circuit_public_inputs::KernelCircuitPublicInputs, public_kernel_data::PublicKernelData, - nullifier::ScopedNullifier, nullifier_leaf_preimage::NullifierLeafPreimage + kernel_circuit_public_inputs::KernelCircuitPublicInputs, nullifier::ScopedNullifier, + nullifier_leaf_preimage::NullifierLeafPreimage, + public_data_update_request::PublicDataUpdateRequest }, address::AztecAddress, constants::{ L1_TO_L2_MSG_TREE_HEIGHT, MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_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, - NOTE_HASH_SUBTREE_HEIGHT, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, 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, MAX_ENCRYPTED_LOGS_PER_TX, - MAX_UNENCRYPTED_LOGS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - NOTE_HASH_TREE_HEIGHT, PUBLIC_KERNEL_APP_LOGIC_INDEX, BASE_ROLLUP_INDEX, - PUBLIC_KERNEL_SETUP_INDEX, PUBLIC_KERNEL_TEARDOWN_INDEX + MAX_PUBLIC_DATA_HINTS, NOTE_HASH_SUBTREE_HEIGHT, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, + 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, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_TREE_HEIGHT, + PUBLIC_KERNEL_APP_LOGIC_INDEX, BASE_ROLLUP_INDEX, PUBLIC_KERNEL_SETUP_INDEX, + PUBLIC_KERNEL_TEARDOWN_INDEX }, + data::{PublicDataLeafHint, PublicDataTreeLeafPreimage}, hash::{compute_siloed_nullifier, silo_note_hash}, - public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, tests::{ fixture_builder::FixtureBuilder, merkle_tree_utils::NonEmptyMerkleTree, utils::{assert_array_eq, pad_end, swap_items} }, traits::is_empty, partial_state_reference::PartialStateReference, - utils::arrays::{array_length, array_merge}, merkle_tree::MembershipWitness + utils::arrays::{array_merge, find_index_hint}, merkle_tree::MembershipWitness }; - fn build_note_hash_tree(pre_existing_note_hashes: [Field; N]) -> NonEmptyMerkleTree { + fn build_note_hash_tree(leaf_preimages: [Field; N]) -> NonEmptyMerkleTree { NonEmptyMerkleTree::new( - pad_end(pre_existing_note_hashes, 0), + pad_end(leaf_preimages, 0), [0; NOTE_HASH_TREE_HEIGHT], [0; NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT], [0; NOTE_HASH_SUBTREE_HEIGHT] @@ -126,18 +146,12 @@ mod tests { ) } - fn get_settled_public_data_leaves() -> [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] { - let mut settled_public_data_leaves = [PublicDataTreeLeafPreimage::empty(); MAX_TOTAL_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(); + fn build_public_data_tree(leaf_preimages: [PublicDataTreeLeafPreimage; N]) -> NonEmptyMerkleTree { NonEmptyMerkleTree::new( - settled_public_data_leaves.map(|preimage: PublicDataTreeLeafPreimage| preimage.hash()), + pad_end( + leaf_preimages.map(|preimage: PublicDataTreeLeafPreimage| preimage.hash()), + 0 + ), [0; PUBLIC_DATA_TREE_HEIGHT], [0; PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT], [0; PUBLIC_DATA_SUBTREE_HEIGHT] @@ -150,17 +164,16 @@ mod tests { note_hash_read_request_hints: BoundedVec, MAX_NOTE_HASH_READ_REQUESTS_PER_TX>, 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, l1_to_l2_msg_read_request_hints: BoundedVec, MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX>, - start_state: PartialStateReference, note_hash_tree: NonEmptyMerkleTree, - pre_existing_note_hashes: [Field; 2], + note_hash_leaf_preimages: [Field; 2], + public_data_tree: NonEmptyMerkleTree, + public_data_leaf_preimages: [PublicDataTreeLeafPreimage; 6], + start_state: PartialStateReference, } impl PublicKernelTailCircuitPrivateInputsBuilder { - pub fn new() -> Self { + pub fn new() -> PublicKernelTailCircuitPrivateInputsBuilder { let previous_kernel = FixtureBuilder::new().in_vk_tree(PUBLIC_KERNEL_APP_LOGIC_INDEX); let previous_revertible = FixtureBuilder::new(); let nullifier_non_existent_read_request_hints_builder = NullifierNonExistentReadRequestHintsBuilder::new(); @@ -171,18 +184,17 @@ mod tests { note_hash_read_request_hints: BoundedVec::new(), nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(), 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(), l1_to_l2_msg_read_request_hints: BoundedVec::new(), - start_state: PartialStateReference::empty(), note_hash_tree: NonEmptyMerkleTree::empty(), - pre_existing_note_hashes: [598589, 714714] + note_hash_leaf_preimages: [598589, 714714], + public_data_tree: NonEmptyMerkleTree::empty(), + public_data_leaf_preimages: pad_end([], PublicDataTreeLeafPreimage::empty()), + start_state: PartialStateReference::empty() } } pub fn with_note_hash_tree(&mut self) -> Self { - self.note_hash_tree = build_note_hash_tree(self.pre_existing_note_hashes); + self.note_hash_tree = build_note_hash_tree(self.note_hash_leaf_preimages); self.start_state.note_hash_tree.root = self.note_hash_tree.get_root(); self.previous_kernel.historical_header.state.partial.note_hash_tree.root = 11111; *self @@ -197,18 +209,25 @@ mod tests { } pub fn with_public_data_tree(&mut self) -> Self { - let public_data_tree = build_public_data_tree(); + let public_data_leaf_preimages = [ + PublicDataTreeLeafPreimage { slot: 0, value: 0, next_slot: 1111, next_index: 3 }, + PublicDataTreeLeafPreimage { slot: 2222, value: 200, next_slot: 3333, next_index: 2 }, + PublicDataTreeLeafPreimage { slot: 3333, value: 300, next_slot: 0, next_index: 0 }, + PublicDataTreeLeafPreimage { slot: 1111, value: 100, next_slot: 2222, next_index: 1 } + ]; + let public_data_tree = build_public_data_tree(public_data_leaf_preimages); + self.public_data_leaf_preimages = pad_end(public_data_leaf_preimages, PublicDataTreeLeafPreimage::empty()); self.public_data_tree = public_data_tree; self.start_state.public_data_tree.root = public_data_tree.get_root(); *self } - pub fn add_note_hash_read_request(&mut self, pre_existing_note_hash_index: u32) { + pub fn add_note_hash_read_request(&mut self, leaf_index: u32) { self.previous_kernel.add_note_hash_tree_leaf_read_requests( - self.pre_existing_note_hashes[pre_existing_note_hash_index], - pre_existing_note_hash_index as Field + self.note_hash_leaf_preimages[leaf_index], + leaf_index as Field ); - let sibling_path = self.note_hash_tree.get_sibling_path(pre_existing_note_hash_index); + let sibling_path = self.note_hash_tree.get_sibling_path(leaf_index); self.note_hash_read_request_hints.push(TreeLeafReadRequestHint { sibling_path }); } @@ -267,35 +286,42 @@ 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: u32) { - let leaf_preimage = get_settled_public_data_leaves()[leaf_index]; - let membership_witness = MembershipWitness { 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); - } + fn create_public_data_leaf_hint(&mut self, leaf_slot: Field) -> PublicDataLeafHint { + let low_leaf_index = find_index_hint( + self.public_data_leaf_preimages, + |p: PublicDataTreeLeafPreimage| !leaf_slot.lt(p.slot) & (p.next_slot.eq(0) | leaf_slot.lt(p.next_slot)) + ); - pub fn add_public_data_hint_for_non_existent_public_data(&mut self, leaf_slot: Field, low_leaf_index: u32) { - let leaf_preimage = get_settled_public_data_leaves()[low_leaf_index]; + let preimage = self.public_data_leaf_preimages[low_leaf_index]; let membership_witness = MembershipWitness { 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); + + PublicDataLeafHint { preimage, membership_witness } } - pub fn add_pending_public_data_read_request(&mut self, public_date_update_request_index: u32) { - 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 generate_public_data_leaf_hints(&mut self) -> [PublicDataLeafHint; MAX_PUBLIC_DATA_HINTS] { + let mut public_data_hints = BoundedVec::new(); + let mut unique_slots: BoundedVec = BoundedVec::new(); + + for i in 0..self.previous_kernel.public_data_reads.len() { + let read = self.previous_kernel.public_data_reads.get(i); + if !unique_slots.any(|s| s == read.leaf_slot) { + public_data_hints.push(self.create_public_data_leaf_hint(read.leaf_slot)); + unique_slots.push(read.leaf_slot); + } + } + + for i in 0..self.previous_kernel.public_data_update_requests.len() { + let write = self.previous_kernel.public_data_update_requests.get(i); + if !unique_slots.any(|s| s == write.leaf_slot) { + public_data_hints.push(self.create_public_data_leaf_hint(write.leaf_slot)); + unique_slots.push(write.leaf_slot); + } + } + + public_data_hints.storage } fn sync_counters(&mut self) { @@ -313,15 +339,15 @@ mod tests { previous_kernel.public_inputs.end = self.previous_revertible.to_public_accumulated_data(); self.set_nullifiers_for_non_existent_read_request_hints(); + let public_data_hints = self.generate_public_data_leaf_hints(); let kernel = PublicKernelTailCircuitPrivateInputs { previous_kernel, + note_hash_read_request_hints: self.note_hash_read_request_hints.storage, 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(), - public_data_hints: self.public_data_hints.storage, - public_data_read_request_hints: self.public_data_read_request_hints_builder.to_hints(), - note_hash_read_request_hints: self.note_hash_read_request_hints.storage, l1_to_l2_msg_read_request_hints: self.l1_to_l2_msg_read_request_hints.storage, + public_data_hints, start_state: self.start_state }; @@ -345,7 +371,7 @@ mod tests { } #[test] - fn measuring_of_log_lengths() { + unconstrained fn measuring_of_log_lengths() { let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); // Logs for the previous call stack. let prev_encrypted_logs_hash = 80; @@ -467,90 +493,52 @@ mod tests { } #[test] - unconstrained fn validate_public_data_hints() { + unconstrained fn public_data_reads_and_writes_succeeds() { 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.previous_kernel.add_public_data_read_request(22, 0); - builder.succeeded(); - } + builder.previous_kernel.add_public_data_update_request(11, 500); + builder.previous_kernel.add_public_data_update_request(22, 700); - #[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.previous_kernel.add_public_data_read_request(22, 700); + builder.previous_kernel.add_public_data_read_request(11, 500); + builder.previous_kernel.add_public_data_read_request(3333, 300); - builder.add_public_data_hint_for_settled_public_data(1); + // Override the previous value at leaf slot 22. + builder.previous_kernel.add_public_data_update_request(22, 701); - let mut hint = builder.public_data_hints.pop(); - hint.value += 1; - builder.public_data_hints.push(hint); + // Override the value of the leaf. + builder.previous_kernel.add_public_data_update_request(3333, 301); - builder.failed(); - } + // Read the new values. + builder.previous_kernel.add_public_data_read_request(22, 701); + builder.previous_kernel.add_public_data_read_request(3333, 301); - #[test] - unconstrained fn validate_public_data_hints_uninitialized_value() { - let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_public_data_tree(); + let prev_writes = builder.previous_kernel.public_data_update_requests.storage; - builder.add_public_data_hint_for_non_existent_public_data(25, 0); + // Shuffle the items so that they are not sorted by counter. + swap_items(&mut builder.previous_kernel.public_data_update_requests, 0, 3); + swap_items(&mut builder.previous_kernel.public_data_update_requests, 1, 3); + swap_items(&mut builder.previous_kernel.public_data_reads, 1, 4); + swap_items(&mut builder.previous_kernel.public_data_reads, 0, 3); - builder.succeeded(); + let public_inputs = builder.execute(); + assert_array_eq( + public_inputs.end.public_data_update_requests, + [ + PublicDataUpdateRequest { leaf_slot: 3333, new_value: 301, counter: prev_writes[4].counter }, + PublicDataUpdateRequest { leaf_slot: 11, new_value: 500, counter: prev_writes[1].counter }, + PublicDataUpdateRequest { leaf_slot: 22, new_value: 701, counter: prev_writes[3].counter } + ] + ); } - #[test(should_fail_with="Value must be 0 for non-existent public data")] - unconstrained fn validate_public_data_hints_failed_non_zero_uninitialized_value() { + #[test(should_fail_with="value in OverridablePublicDataTreeLeaf does not match read request")] + unconstrained fn reading_uninitialized_public_data_non_zero_value_fails() { 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.previous_kernel.add_public_data_read_request(1234, 1); 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 319f2100ddd..76b1b626f2e 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 @@ -2,20 +2,18 @@ use note_hash_read_request_reset::NoteHashReadRequestHints; use nullifier_non_existent_read_request_reset::NullifierNonExistentReadRequestHints; use nullifier_read_request_reset::NullifierReadRequestHints; use private_validation_request_processor::PrivateValidationRequestProcessor; -use public_data_read_request_reset::PublicDataReadRequestHints; use public_validation_request_processor::PublicValidationRequestProcessor; use reset::{ key_validation_hint::KeyValidationHint, transient_data::{TransientDataIndexHint, verify_squashed_transient_data}, tree_leaf_read_request::TreeLeafReadRequestHint }; -use dep::types::data::public_data_hint::PublicDataHint; mod note_hash_read_request_reset; mod nullifier_non_existent_read_request_reset; mod nullifier_read_request_reset; mod private_validation_request_processor; -mod public_data_read_request_reset; +mod public_data_read_request_hints; mod public_validation_request_processor; mod reset; mod tests; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_data_read_request_hints.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_data_read_request_hints.nr new file mode 100644 index 00000000000..3f1eebc06cc --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_data_read_request_hints.nr @@ -0,0 +1,50 @@ +use crate::reset::{mutable_data_read_request::ReadIndexHint, read_request::ReadRequestStatus}; +use dep::types::{ + abis::{public_data_read::PublicDataRead, public_data_write::OverridablePublicDataWrite}, + data::OverridablePublicDataTreeLeaf, utils::arrays::find_index_hint +}; + +struct PublicDataReadRequestHints { + read_request_statuses: [ReadRequestStatus; NUM_READS], + pending_read_hints: [ReadIndexHint; NUM_READS], + leaf_data_read_hints: [ReadIndexHint; NUM_READS], +} + +unconstrained pub fn build_public_data_read_request_hints( + reads: [PublicDataRead; NUM_READS], + writes: [OverridablePublicDataWrite; NUM_WRITES], + leaf_data: [OverridablePublicDataTreeLeaf; NUM_LEAVES] +) -> PublicDataReadRequestHints { + let mut read_request_statuses = [ReadRequestStatus::empty(); NUM_READS]; + let mut pending_read_hints = [ReadIndexHint::nada(NUM_READS); NUM_READS]; + let mut leaf_data_read_hints = [ReadIndexHint::nada(NUM_READS); NUM_READS]; + let mut num_pending_reads = 0; + let mut num_leaf_data_reads = 0; + for i in 0..reads.len() { + let read = reads[i]; + if read.counter != 0 { + let write_index = find_index_hint( + writes, + |w: OverridablePublicDataWrite| (w.inner().leaf_slot == read.leaf_slot) & (read.counter > w.counter()) & ((read.counter < w.override_counter) | (w.override_counter == 0)) + ); + if write_index != writes.len() { + pending_read_hints[num_pending_reads] = ReadIndexHint { read_request_index: i, value_index: write_index }; + read_request_statuses[i] = ReadRequestStatus::pending(num_pending_reads); + num_pending_reads += 1; + } else { + let leaf_data_index = find_index_hint( + leaf_data, + |d: OverridablePublicDataTreeLeaf| d.leaf.slot == read.leaf_slot + ); + assert( + leaf_data_index != leaf_data.len(), "cannot find a public data leaf or a pending write for the read request" + ); + leaf_data_read_hints[num_leaf_data_reads] = ReadIndexHint { read_request_index: i, value_index: leaf_data_index }; + read_request_statuses[i] = ReadRequestStatus::settled(num_leaf_data_reads); + num_leaf_data_reads += 1; + } + } + } + + PublicDataReadRequestHints { read_request_statuses, pending_read_hints, leaf_data_read_hints } +} 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 deleted file mode 100644 index 7c6b6634074..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/public_data_read_request_reset.nr +++ /dev/null @@ -1,9 +0,0 @@ -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 index 01fb966217d..a07fbef6114 100644 --- 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 @@ -7,25 +7,25 @@ use crate::{ }, nullifier_read_request_reset::NullifierReadRequestHints, nullifier_non_existent_read_request_reset::NullifierNonExistentReadRequestHints, - public_data_read_request_reset::PublicDataReadRequestHints + public_data_read_request_hints::PublicDataReadRequestHints }; use dep::types::{ abis::{ kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, nullifier::Nullifier, - public_data_update_request::PublicDataUpdateRequest, read_request::ScopedReadRequest, + public_data_write::OverridablePublicDataWrite, read_request::ScopedReadRequest, validation_requests::PublicValidationRequests }, - data::public_data_hint::PublicDataHint, constants::{ - L1_TO_L2_MSG_TREE_HEIGHT, MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, - MAX_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX, L1_TO_L2_MSG_TREE_HEIGHT, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, + MAX_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PUBLIC_DATA_READS_PER_TX, NOTE_HASH_TREE_HEIGHT }, - hash::compute_siloed_nullifier, partial_state_reference::PartialStateReference, traits::is_empty, - utils::arrays::{array_merge, array_to_bounded_vec, assert_sorted_array} + data::OverridablePublicDataTreeLeaf, hash::compute_siloed_nullifier, + partial_state_reference::PartialStateReference, traits::is_empty, + utils::arrays::{array_to_bounded_vec, assert_combined_array, assert_sorted_array, combine_arrays} }; -struct PublicValidationRequestProcessor { +struct PublicValidationRequestProcessor { validation_requests: PublicValidationRequests, note_hash_read_request_hints: [TreeLeafReadRequestHint; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], note_hash_tree_root: Field, @@ -35,13 +35,12 @@ struct PublicValidationRequestProcessor { nullifier_tree_root: Field, l1_to_l2_msg_read_request_hints: [TreeLeafReadRequestHint; MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX], l1_to_l2_msg_tree_root: Field, - pending_public_data_writes: [PublicDataUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - public_data_read_request_hints: PublicDataReadRequestHints, - public_data_hints: [PublicDataHint; NUM_PUBLIC_DATA_HINTS], - public_data_tree_root: Field, + pending_public_data_writes: [OverridablePublicDataWrite; NUN_PUBLIC_DATA_WRITES], + public_data_leaves: [OverridablePublicDataTreeLeaf; NUM_PUBLIC_DATA_LEAVES], + public_data_read_request_hints: PublicDataReadRequestHints, } -impl PublicValidationRequestProcessor { +impl PublicValidationRequestProcessor { pub fn new( public_inputs: PublicKernelCircuitPublicInputs, start_state: PartialStateReference, @@ -49,33 +48,34 @@ impl PublicValidationRequestProcessor, nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints, l1_to_l2_msg_read_request_hints: [TreeLeafReadRequestHint; MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX], - public_data_read_request_hints: PublicDataReadRequestHints, - public_data_hints: [PublicDataHint; NUM_PUBLIC_DATA_HINTS] + pending_public_data_writes: [OverridablePublicDataWrite; NUN_PUBLIC_DATA_WRITES], + public_data_leaves: [OverridablePublicDataTreeLeaf; NUM_PUBLIC_DATA_LEAVES], + public_data_read_request_hints: PublicDataReadRequestHints ) -> Self { - let end_non_revertible = public_inputs.end_non_revertible; - let end = public_inputs.end; - - let pending_nullifiers = array_merge(end_non_revertible.nullifiers, end.nullifiers); - - let pending_public_data_writes = array_merge( - end_non_revertible.public_data_update_requests, - end.public_data_update_requests + let non_revertible_nullifiers = public_inputs.end_non_revertible.nullifiers; + let revertible_nullifiers = public_inputs.end.nullifiers; + let pending_nullifiers = unsafe { + combine_arrays(non_revertible_nullifiers, revertible_nullifiers) + }; + assert_combined_array( + non_revertible_nullifiers, + revertible_nullifiers, + pending_nullifiers ); PublicValidationRequestProcessor { validation_requests: public_inputs.validation_requests, - pending_nullifiers, note_hash_read_request_hints, note_hash_tree_root: start_state.note_hash_tree.root, + pending_nullifiers, nullifier_read_request_hints, nullifier_non_existent_read_request_hints, nullifier_tree_root: start_state.nullifier_tree.root, l1_to_l2_msg_read_request_hints, l1_to_l2_msg_tree_root: public_inputs.constants.historical_header.state.l1_to_l2_message_tree.root, pending_public_data_writes, - public_data_read_request_hints, - public_data_hints, - public_data_tree_root: start_state.public_data_tree.root + public_data_leaves, + public_data_read_request_hints } } @@ -149,22 +149,15 @@ impl PublicValidationRequestProcessor Self { - LeafDataReadHint { read_request_index: read_request_len, data_hint_index: 0 } + ReadIndexHint { read_request_index: read_request_len, value_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; +fn validate_pending_read_requests( + read_requests: [R; READ_REQUEST_LEN], + pending_values: [V; PENDING_VALUE_LEN], + index_hints: [ReadIndexHint; NUM_HINTS] +) where R: Ordered, V: Readable + Ordered + Overridable { + for i in 0..index_hints.len() { + let index_hint = index_hints[i]; + let read_request_index = index_hint.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]; + let pending_value = pending_values[index_hint.value_index]; + pending_value.assert_match_read_request(read_request); assert( - read_request.leaf_slot.eq(pending_value.leaf_slot), "Hinted slot of data write does not match read request" + read_request.counter() > pending_value.counter(), "Read request counter must be greater than the counter of the data write" ); assert( - read_request.value.eq(pending_value.new_value), "Hinted value of data write does not match read request" + (read_request.counter() < pending_value.override_counter()) + | (pending_value.override_counter() == 0), "Read request counter must be less than the counter of the next data write" ); - // 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; +fn validate_leaf_data_read_requests( + read_requests: [R; READ_REQUEST_LEN], + leaf_data: [L; LEAF_DATA_LEN], + index_hints: [ReadIndexHint; NUM_HINTS] +) where R: Ordered, L: Readable + Overridable { + for i in 0..index_hints.len() { + let index_hint = index_hints[i]; + let read_request_index = index_hint.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]; + let data = leaf_data[index_hint.value_index]; + data.assert_match_read_request(read_request); assert( - read_request.leaf_slot == data_hint.leaf_slot(), "Hinted slot does not match read request" + (read_request.counter() < data.override_counter()) | (data.override_counter() == 0), "Hinted leaf is overridden before the 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], +fn ensure_all_read_requests_are_verified( + read_requests: [R; 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] -) { + pending_read_hints: [ReadIndexHint; NUM_PENDING_READS], + leaf_data_read_hints: [ReadIndexHint; NUM_LEAF_DATA_READS] +) where R: Ordered { for i in 0..READ_REQUEST_LEN { let read_request = read_requests[i]; - if !is_empty(read_request) { + if read_request.counter() != 0 { 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 { + } else { 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< + R, let READ_REQUEST_LEN: u32, + V, let PENDING_VALUE_LEN: u32, - H, + L, let NUM_LEAF_DATA_HINTS: u32, let NUM_PENDING_READS: u32, let NUM_LEAF_DATA_READS: u32 >( - read_requests: [PublicDataRead; READ_REQUEST_LEN], + read_requests: [R; 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); + pending_values: [V; PENDING_VALUE_LEN], + leaf_data: [L; NUM_LEAF_DATA_HINTS], + pending_read_hints: [ReadIndexHint; NUM_PENDING_READS], + leaf_data_read_hints: [ReadIndexHint; NUM_LEAF_DATA_READS] +) where R: Ordered, V: Readable + Ordered + Overridable, L: Readable + Overridable { + validate_pending_read_requests(read_requests, pending_values, pending_read_hints); - validate_leaf_data_read_requests(read_requests, leaf_data_hints, leaf_data_read_hints); + validate_leaf_data_read_requests(read_requests, leaf_data, leaf_data_read_hints); ensure_all_read_requests_are_verified( read_requests, @@ -115,210 +109,359 @@ pub fn reset_mutable_data_read_requests< mod tests { use crate::reset::{ mutable_data_read_request::{ - ensure_all_read_requests_are_verified, reset_mutable_data_read_requests, LeafDataReadHint, + ensure_all_read_requests_are_verified, ReadIndexHint, reset_mutable_data_read_requests, validate_pending_read_requests, validate_leaf_data_read_requests }, - read_request::{PendingReadHint, ReadRequestState, ReadRequestStatus} + read_request::{ReadRequestState, ReadRequestStatus} }; use dep::types::{ - abis::{public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest}, - data::leaf_data_hint::LeafDataHint + abis::{ + public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest, + public_data_write::OverridablePublicDataWrite, side_effect::{Overridable, Readable} + }, + tests::utils::pad_end, traits::Empty }; - struct TestLeafDataHint { - leaf_slot: Field, + struct TestLeafData { + leaf_index: Field, value: Field, + override_counter: u32, } - impl LeafDataHint for TestLeafDataHint { - fn leaf_slot(self) -> Field { - self.leaf_slot + impl Empty for TestLeafData { + fn empty() -> Self { + TestLeafData { leaf_index: 0, value: 0, override_counter: 0 } } + } - fn value(self) -> Field { - self.value + impl Overridable for TestLeafData { + fn override_counter(self) -> u32 { + self.override_counter } + } - fn override_counter(_self: Self) -> u32 { - 0 + impl Readable for TestLeafData { + fn assert_match_read_request(self, read_request: PublicDataRead) { + assert_eq(self.leaf_index, read_request.leaf_slot, "leaf_index in TestLeafData does not match"); + assert_eq(self.value, read_request.value, "value in TestLeafData does not match"); } } - global data_writes = [ - PublicDataUpdateRequest { leaf_slot: 22, new_value: 200, counter: 0 }, - PublicDataUpdateRequest { leaf_slot: 11, new_value: 100, counter: 1 }, - PublicDataUpdateRequest { leaf_slot: 33, new_value: 300, counter: 2 }, - PublicDataUpdateRequest { leaf_slot: 44, new_value: 400, counter: 3 } - ]; - - 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: [u32; N]) -> ([PublicDataRead; N], [PendingReadHint; N]) { - let read_requests = data_write_indices.map( - |data_write_index: u32| 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) + global READ_REQUEST_LEN = 10; + + struct TestBuilder { + read_requests: [PublicDataRead; READ_REQUEST_LEN], + read_request_statuses: [ReadRequestStatus; READ_REQUEST_LEN], + data_writes: [OverridablePublicDataWrite; 6], + leaf_data: [TestLeafData; 12], + pending_read_hints: [ReadIndexHint; 5], + leaf_data_read_hints: [ReadIndexHint; 4], + num_pending_reads: u32, + num_leaf_data_reads: u32, + counter: u32, } - fn create_leaf_data_read_requests(data_hint_indices: [u32; N]) -> ([PublicDataRead; N], [LeafDataReadHint; N]) { - let read_requests = data_hint_indices.map( - |data_hint_index: u32| 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] }); + impl TestBuilder { + pub fn new() -> TestBuilder { + let read_requests = [PublicDataRead::empty(); READ_REQUEST_LEN]; + let read_request_statuses = [ReadRequestStatus::empty(); READ_REQUEST_LEN]; + + let leaf_data = pad_end( + [ + TestLeafData { leaf_index: 44, value: 0, override_counter: 40 }, + TestLeafData { leaf_index: 77, value: 700, override_counter: 0 }, + TestLeafData { leaf_index: 11, value: 0, override_counter: 20 }, + TestLeafData { leaf_index: 33, value: 300, override_counter: 30 }, + TestLeafData { leaf_index: 66, value: 600, override_counter: 0 }, + TestLeafData { leaf_index: 22, value: 200, override_counter: 10 }, + TestLeafData { leaf_index: 55, value: 500, override_counter: 0 } + ], + TestLeafData::empty() + ); + + let data_writes = pad_end( + [ + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 22, new_value: 201, counter: 10 }, override_counter: 40 }, + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 11, new_value: 100, counter: 20 }, override_counter: 0 }, + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 33, new_value: 301, counter: 30 }, override_counter: 0 }, + OverridablePublicDataWrite { write: PublicDataUpdateRequest { leaf_slot: 22, new_value: 202, counter: 40 }, override_counter: 0 } + ], + OverridablePublicDataWrite::empty() + ); + + let pending_read_hints = pad_end([], ReadIndexHint::nada(READ_REQUEST_LEN)); + + let leaf_data_read_hints = pad_end([], ReadIndexHint::nada(READ_REQUEST_LEN)); + + TestBuilder { + read_requests, + read_request_statuses, + leaf_data, + data_writes, + pending_read_hints, + leaf_data_read_hints, + num_pending_reads: 0, + num_leaf_data_reads: 0, + counter: 50 + } + } + + pub fn add_pending_read(&mut self, data_write_index: u32) { + let write = self.data_writes[data_write_index].write; + let read_request_index = self.num_pending_reads + self.num_leaf_data_reads; + self.read_requests[read_request_index] = PublicDataRead { + leaf_slot: write.leaf_slot, + value: write.new_value, + counter: self.counter + }; + self.pending_read_hints[self.num_pending_reads] = ReadIndexHint { read_request_index, value_index: data_write_index }; + self.read_request_statuses[read_request_index] = ReadRequestStatus::pending(self.num_pending_reads); + self.num_pending_reads += 1; + self.counter += 1; + } + + pub fn add_leaf_data_read(&mut self, data_hint_index: u32) { + let data_hint = self.leaf_data[data_hint_index]; + let read_request_index = self.num_pending_reads + self.num_leaf_data_reads; + self.read_requests[read_request_index] = PublicDataRead { + leaf_slot: data_hint.leaf_index, + value: data_hint.value, + counter: self.counter + }; + self.leaf_data_read_hints[self.num_leaf_data_reads] = ReadIndexHint { read_request_index, value_index: data_hint_index }; + self.read_request_statuses[read_request_index] = ReadRequestStatus::settled(self.num_leaf_data_reads); + self.num_leaf_data_reads += 1; + self.counter += 1; + } + + pub fn validate_pending_read_requests(self) { + validate_pending_read_requests(self.read_requests, self.data_writes, self.pending_read_hints); + } + + pub fn validate_leaf_data_read_requests(self) { + validate_leaf_data_read_requests(self.read_requests, self.leaf_data, self.leaf_data_read_hints) + } + + pub fn ensure_all_read_requests_are_verified(self) { + ensure_all_read_requests_are_verified( + self.read_requests, + self.read_request_statuses, + self.pending_read_hints, + self.leaf_data_read_hints + ) + } + + pub fn reset(self) { + reset_mutable_data_read_requests( + self.read_requests, + self.read_request_statuses, + self.data_writes, + self.leaf_data, + self.pending_read_hints, + self.leaf_data_read_hints + ); } - (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); + let mut builder = TestBuilder::new(); + + builder.add_pending_read(1); + builder.add_pending_read(2); + builder.add_pending_read(3); + + builder.validate_pending_read_requests(); } #[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); + let mut builder = TestBuilder::new(); + + builder.add_pending_read(2); + builder.add_pending_read(2); + builder.add_pending_read(2); + + builder.validate_pending_read_requests(); + } + + #[test(should_fail_with="Read request counter must be less than the counter of the next data write")] + fn reset_pending_reads_overriden_value_fails() { + let mut builder = TestBuilder::new(); + + // 0th write is overriden by the 3rd write. + builder.add_pending_read(0); + + builder.validate_pending_read_requests(); } #[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); + fn reset_pending_reads_overriden_value_before_next_succeeds() { + let mut builder = TestBuilder::new(); + + // 0th write is overriden by the 3rd write. + builder.add_pending_read(0); + + // Tweak the counter of the read request to be before the next value. + builder.read_requests[0].counter = builder.data_writes[3].write.counter - 1; + + builder.validate_pending_read_requests(); } - #[test(should_fail_with="Hinted slot of data write does not match read request")] + #[test(should_fail_with="leaf_slot in OverridablePublicDataWrite 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); + let mut builder = TestBuilder::new(); + + builder.add_pending_read(2); + builder.read_requests[0].leaf_slot += 1; + + builder.validate_pending_read_requests(); } - #[test(should_fail_with="Hinted value of data write does not match read request")] + #[test(should_fail_with="value in OverridablePublicDataWrite 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); + let mut builder = TestBuilder::new(); + + builder.add_pending_read(2); + builder.read_requests[0].value += 1; + + builder.validate_pending_read_requests(); + } + + #[test(should_fail_with="Read request counter must be greater than the counter of the data write")] + fn reset_pending_reads_value_write_after_fails() { + let mut builder = TestBuilder::new(); + + builder.add_pending_read(2); + builder.read_requests[0].counter = builder.data_writes[2].write.counter - 1; + + builder.validate_pending_read_requests(); } #[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); + let mut builder = TestBuilder::new(); + + builder.add_leaf_data_read(1); + builder.add_leaf_data_read(4); + builder.add_leaf_data_read(6); + + builder.validate_leaf_data_read_requests(); } #[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); + let mut builder = TestBuilder::new(); + + builder.add_leaf_data_read(4); + builder.add_leaf_data_read(4); + builder.add_leaf_data_read(4); + + builder.validate_leaf_data_read_requests(); + } + + #[test(should_fail_with="Hinted leaf is overridden before the read request")] + fn reset_leaf_data_reads_overriden_value_fails() { + let mut builder = TestBuilder::new(); + + // 2nd leaf is overriden by a pending write. + builder.add_leaf_data_read(2); + + builder.validate_leaf_data_read_requests(); } #[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); + fn reset_leaf_data_reads_overriden_value_before_next_succeeds() { + let mut builder = TestBuilder::new(); + + // 2nd leaf is overriden by a pending write. + builder.add_leaf_data_read(2); + + // Tweak the counter of the read request to be before the pending write. + builder.read_requests[0].counter = builder.leaf_data[2].override_counter - 1; + + builder.validate_leaf_data_read_requests(); } - #[test(should_fail_with=""Hinted slot does not match read request")] + #[test(should_fail_with=""leaf_index in TestLeafData does not match")] 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); + let mut builder = TestBuilder::new(); + + builder.add_leaf_data_read(4); + builder.read_requests[0].leaf_slot += 1; + + builder.validate_leaf_data_read_requests(); } - #[test(should_fail_with=""Hinted value does not match read request")] + #[test(should_fail_with=""value in TestLeafData does not match")] 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); + let mut builder = TestBuilder::new(); + + builder.add_leaf_data_read(4); + builder.read_requests[0].value += 1; + + builder.validate_leaf_data_read_requests(); } #[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 - ); + let mut builder = TestBuilder::new(); + + builder.add_leaf_data_read(4); + builder.add_pending_read(2); + builder.add_pending_read(1); + builder.add_leaf_data_read(1); + + builder.ensure_all_read_requests_are_verified(); } #[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, []); + let mut builder = TestBuilder::new(); + + builder.add_pending_read(2); + builder.read_request_statuses[0].hint_index += 1; + + builder.ensure_all_read_requests_are_verified(); } #[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); + let mut builder = TestBuilder::new(); + + builder.add_leaf_data_read(4); + builder.read_request_statuses[0].hint_index += 1; + + builder.ensure_all_read_requests_are_verified(); } - #[test(should_fail_with="Read request status must be PENDING or SETTLED")] + #[test(should_fail_with="Hinted settled read request does not match status")] 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); + let mut builder = TestBuilder::new(); + + builder.add_pending_read(2); + builder.read_request_statuses[0].state = ReadRequestState.NADA; + + builder.ensure_all_read_requests_are_verified(); } #[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 - ); + let mut builder = TestBuilder::new(); + + builder.add_pending_read(1); + builder.add_leaf_data_read(4); + builder.add_pending_read(2); + builder.add_leaf_data_read(6); + builder.add_leaf_data_read(6); + builder.add_pending_read(1); + + builder.reset(); + } + + #[test] + fn reset_mutable_data_read_requests_no_requests_succeeds() { + let builder = TestBuilder::new(); + builder.reset(); } } diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/read_request.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/read_request.nr index 6a63c52afbe..05feb883582 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/read_request.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/reset/read_request.nr @@ -27,6 +27,16 @@ impl Empty for ReadRequestStatus { } } +impl ReadRequestStatus { + pub fn pending(hint_index: u32) -> Self { + ReadRequestStatus { state: ReadRequestState.PENDING, hint_index } + } + + pub fn settled(hint_index: u32) -> Self { + ReadRequestStatus { state: ReadRequestState.SETTLED, hint_index } + } +} + trait ReadValueHint { fn read_request_index(self) -> u32; } @@ -62,7 +72,7 @@ fn validate_pending_read_requests { for i in 0..NUM_PENDING_READS { let hint = hints[i]; let read_request_index = hint.read_request_index; @@ -82,7 +92,7 @@ fn validate_settled_read_requests + ReadValueHint, - LEAF_PREIMAGE: LeafPreimage + Readable { + LEAF_PREIMAGE: LeafPreimage + Readable { for i in 0..NUM_SETTLED_READS { let hint = hints[i]; let read_request_index = hint.read_request_index(); @@ -144,9 +154,9 @@ pub fn verify_reset_read_requests< tree_root: Field, propagated_read_requests: [ScopedReadRequest; READ_REQUEST_LEN] ) where - P: Readable, + P: Readable, H: SettledReadHint + ReadValueHint, - LEAF_PREIMAGE: LeafPreimage + Readable { + LEAF_PREIMAGE: LeafPreimage + Readable { validate_pending_read_requests(read_requests, pending_values, pending_read_hints); validate_settled_read_requests(read_requests, settled_read_hints, tree_root); @@ -197,7 +207,7 @@ mod tests { counter: u32, } - impl Readable for TestValue { + impl Readable for TestValue { fn assert_match_read_request(self, read_request: ScopedReadRequest) { let siloed_value = silo_test_value(read_request.value()); assert_eq(self.value, siloed_value, "Hinted test value does not match"); @@ -226,7 +236,7 @@ mod tests { } } - impl Readable for TestLeafPreimage { + impl Readable for TestLeafPreimage { fn assert_match_read_request(self, read_request: ScopedReadRequest) { let siloed_value = silo_test_value(read_request.value()); assert_eq(siloed_value, self.value, "Provided leaf preimage is not for target value"); diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/mod.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/mod.nr index dd0fa37a7cd..0d94bcd3941 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/mod.nr @@ -1,7 +1,6 @@ mod note_hash_read_request_hints_builder; mod nullifier_non_existent_read_request_hints_builder; mod nullifier_read_request_hints_builder; -mod public_data_read_request_hints_builder; use note_hash_read_request_hints_builder::NoteHashReadRequestHintsBuilder; use nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr index ea0d8affd55..31b7aa2eb9f 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr @@ -6,7 +6,7 @@ use dep::types::{ NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT }, merkle_tree::MembershipWitness, tests::{merkle_tree_utils::NonEmptyMerkleTree}, - utils::{arrays::{find_index_hint, get_sorted_hints}, field::full_field_greater_than} + utils::{arrays::{find_index_hint, get_sorted_result}} }; struct NullifierNonExistentReadRequestHintsBuilder { @@ -50,7 +50,7 @@ impl NullifierNonExistentReadRequestHintsBuilder { } pub fn to_hints(self) -> NullifierNonExistentReadRequestHints { - let sorted_result = get_sorted_hints( + let sorted_result = get_sorted_result( self.pending_nullifiers, |a: Nullifier, b: Nullifier| (b.value == 0) | ((a.value != 0) & a.value.lt(b.value)) ); diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/public_data_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/public_data_read_request_hints_builder.nr deleted file mode 100644 index d2de7f04cd5..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/public_data_read_request_hints_builder.nr +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{ - public_data_read_request_reset::PublicDataReadRequestHints, - reset::{mutable_data_read_request::LeafDataReadHint, read_request::{PendingReadHint, ReadRequestStatus}} -}; -use dep::types::constants::MAX_PUBLIC_DATA_READS_PER_TX; - -struct PublicDataReadRequestHintsBuilder { - read_request_statuses: [ReadRequestStatus; MAX_PUBLIC_DATA_READS_PER_TX], - pending_read_hints: BoundedVec, - leaf_data_read_hints: BoundedVec, -} - -impl PublicDataReadRequestHintsBuilder { - pub fn new(read_request_len: u32) -> 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/rollup-lib/src/base/base_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr index a9b8418ec09..87d2e315a9b 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 @@ -15,8 +15,7 @@ use dep::types::{ storage::map::derive_storage_slot_in_map, address::AztecAddress, abis::{ append_only_tree_snapshot::AppendOnlyTreeSnapshot, nullifier_leaf_preimage::NullifierLeafPreimage, - public_data_update_request::PublicDataUpdateRequest, public_data_read::PublicDataRead, - kernel_data::KernelData + public_data_update_request::PublicDataUpdateRequest, kernel_data::KernelData }, messaging::l2_to_l1_message::ScopedL2ToL1Message, constants::{ @@ -33,8 +32,8 @@ use dep::types::{ append_only_tree, assert_check_membership, calculate_empty_tree_root, calculate_subtree_root, indexed_tree, MembershipWitness }, - partial_state_reference::PartialStateReference, public_data_tree_leaf::PublicDataTreeLeaf, - public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, traits::is_empty, + partial_state_reference::PartialStateReference, + data::{PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, traits::is_empty, utils::{field::{full_field_less_than, full_field_greater_than}, uint256::U256} }; @@ -467,7 +466,7 @@ mod tests { use dep::types::{ abis::{ append_only_tree_snapshot::AppendOnlyTreeSnapshot, - nullifier_leaf_preimage::NullifierLeafPreimage, public_data_read::PublicDataRead, + nullifier_leaf_preimage::NullifierLeafPreimage, public_data_update_request::PublicDataUpdateRequest, kernel_data::KernelData, accumulated_data::CombinedAccumulatedData, gas::Gas }, @@ -485,8 +484,7 @@ mod tests { PUBLIC_KERNEL_TAIL_INDEX }, contract_class_id::ContractClassId, partial_state_reference::PartialStateReference, - public_data_tree_leaf::PublicDataTreeLeaf, - public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, + data::{PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, tests::{ fixtures, fixture_builder::FixtureBuilder, merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr index 72738cbdf81..87f44a23b0c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr @@ -20,6 +20,7 @@ mod note_hash; mod nullifier; mod public_data_read; mod public_data_update_request; +mod public_data_write; mod accumulated_data; mod validation_requests; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash.nr index 9bb1a681298..2b0504a745b 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash.nr @@ -19,7 +19,7 @@ impl Ordered for NoteHash { impl Eq for NoteHash { fn eq(self, other: NoteHash) -> bool { (self.value == other.value) - & (self.counter == other.counter) + & (self.counter == other.counter) } } @@ -116,7 +116,7 @@ impl Deserialize for ScopedNoteHash { } } -impl Readable for ScopedNoteHash { +impl Readable for ScopedNoteHash { fn assert_match_read_request(self, read_request: ScopedReadRequest) { assert_eq(self.note_hash.value, read_request.value(), "Value of the note hash does not match read request"); assert_eq(self.contract_address, read_request.contract_address, "Contract address of the note hash does not match read request"); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash_leaf_preimage.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash_leaf_preimage.nr index e1a933e2644..2aa6bc09c34 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash_leaf_preimage.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/note_hash_leaf_preimage.nr @@ -27,7 +27,7 @@ impl LeafPreimage for NoteHashLeafPreimage { } } -impl Readable for NoteHashLeafPreimage { +impl Readable for NoteHashLeafPreimage { fn assert_match_read_request(self, read_request: ScopedReadRequest) { let siloed_value = compute_siloed_note_hash(read_request.contract_address, read_request.value()); assert_eq(self.value, siloed_value, "Value of the note hash leaf does not match read request"); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier.nr index 62d44ab9d63..4865a8f744f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier.nr @@ -30,7 +30,7 @@ impl Eq for Nullifier { fn eq(self, other: Nullifier) -> bool { (self.value == other.value) & (self.counter == other.counter) - & (self.note_hash == other.note_hash) + & (self.note_hash == other.note_hash) } } @@ -60,7 +60,7 @@ impl Deserialize for Nullifier { } } -impl Readable for Nullifier { +impl Readable for Nullifier { fn assert_match_read_request(self, read_request: ScopedReadRequest) { // Public kernels output Nullifier instead of ScopedNullifier. // The nullifier value has been siloed. @@ -110,7 +110,7 @@ impl OrderedValue for ScopedNullifier { impl Eq for ScopedNullifier { fn eq(self, other: ScopedNullifier) -> bool { (self.nullifier == other.nullifier) - & (self.contract_address == other.contract_address) + & (self.contract_address == other.contract_address) } } @@ -141,7 +141,7 @@ impl Deserialize for ScopedNullifier { } } -impl Readable for ScopedNullifier { +impl Readable for ScopedNullifier { fn assert_match_read_request(self, read_request: ScopedReadRequest) { assert_eq(self.nullifier.value, read_request.value(), "Value of the nullifier does not match read request"); assert_eq(self.contract_address, read_request.contract_address, "Contract address of the nullifier does not match read request"); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr index c6ce3f9bad0..835510584b9 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr @@ -55,7 +55,7 @@ impl IndexedTreeLeafPreimage for NullifierLeafPreimage { } } -impl Readable for NullifierLeafPreimage { +impl Readable for NullifierLeafPreimage { fn assert_match_read_request(self, read_request: ScopedReadRequest) { let siloed_value = compute_siloed_nullifier(read_request.contract_address, read_request.value()); assert_eq(self.nullifier, siloed_value, "Value of the nullifier leaf does not match read request"); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_read.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_read.nr index 0d12098c4f5..3cceed1ad15 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_read.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_read.nr @@ -1,59 +1,57 @@ -use crate::constants::{GENERATOR_INDEX__PUBLIC_DATA_READ, PUBLIC_DATA_READ_LENGTH}; -use crate::traits::{Empty, Hash, Serialize, Deserialize}; -use crate::contrakt::storage_read::StorageRead; -use crate::data::hash::{compute_public_data_tree_value, compute_public_data_tree_index}; -use crate::address::AztecAddress; +use crate::{ + abis::side_effect::Ordered, address::AztecAddress, constants::PUBLIC_DATA_READ_LENGTH, + contrakt::storage_read::StorageRead, + data::hash::{compute_public_data_tree_value, compute_public_data_tree_index}, + traits::{Empty, Serialize, Deserialize} +}; struct PublicDataRead { - leaf_slot : Field, - value : Field, + leaf_slot: Field, + value: Field, + counter: u32, } impl PublicDataRead { - pub fn from_contract_storage_read( - contract_address: AztecAddress, - read_request: StorageRead - ) -> PublicDataRead { + pub fn from_contract_storage_read(contract_address: AztecAddress, read_request: StorageRead) -> PublicDataRead { PublicDataRead { leaf_slot: compute_public_data_tree_index(contract_address, read_request.storage_slot), - value: compute_public_data_tree_value(read_request.current_value) + value: compute_public_data_tree_value(read_request.current_value), + counter: read_request.counter } } } impl Eq for PublicDataRead { - fn eq(self, public_data_read: PublicDataRead) -> bool { - (public_data_read.leaf_slot == self.leaf_slot) & (public_data_read.value == self.value) + fn eq(self, other: PublicDataRead) -> bool { + (other.leaf_slot == self.leaf_slot) & (other.value == self.value) & (other.counter == self.counter) } } impl Empty for PublicDataRead { fn empty() -> Self { Self { - leaf_slot : 0, - value : 0, + leaf_slot: 0, + value: 0, + counter: 0, } } } -impl Hash for PublicDataRead { - fn hash(self) -> Field { - crate::hash::poseidon2_hash_with_separator([ - self.leaf_slot, - self.value, - ], GENERATOR_INDEX__PUBLIC_DATA_READ) +impl Ordered for PublicDataRead { + fn counter(self) -> u32 { + self.counter } } impl PublicDataRead { pub fn is_empty(self) -> bool { - (self.leaf_slot == 0) & (self.value == 0) + (self.leaf_slot == 0) & (self.value == 0) & (self.counter == 0) } } impl Serialize for PublicDataRead { fn serialize(self) -> [Field; PUBLIC_DATA_READ_LENGTH] { - [self.leaf_slot, self.value] + [self.leaf_slot, self.value, self.counter as Field] } } @@ -62,6 +60,7 @@ impl Deserialize for PublicDataRead { PublicDataRead { leaf_slot: fields[0], value: fields[1], + counter: fields[2] as u32, } } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_update_request.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_update_request.nr index ac0d0738915..367e8a9db1f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_update_request.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_update_request.nr @@ -1,11 +1,14 @@ -use crate::constants::{PUBLIC_DATA_UPDATE_REQUEST_LENGTH, GENERATOR_INDEX__PUBLIC_DATA_UPDATE_REQUEST}; -use crate::traits::{Empty, Hash, Serialize, Deserialize}; -use crate::public_data_tree_leaf::PublicDataTreeLeaf; -use crate::address::AztecAddress; -use crate::contrakt::storage_update_request::StorageUpdateRequest; -use crate::data::hash::{compute_public_data_tree_value, compute_public_data_tree_index}; -use crate::abis::side_effect::{Ordered, Positioned}; +use crate::{ + abis::{side_effect::Ordered}, address::AztecAddress, constants::PUBLIC_DATA_UPDATE_REQUEST_LENGTH, + contrakt::storage_update_request::StorageUpdateRequest, + data::{ + hash::{compute_public_data_tree_value, compute_public_data_tree_index}, + public_data_tree_leaf::PublicDataTreeLeaf +}, + traits::{Empty, Serialize, Deserialize} +}; +// TODO: Rename to PublicDataWrite struct PublicDataUpdateRequest { leaf_slot : Field, new_value : Field, @@ -25,12 +28,6 @@ impl PublicDataUpdateRequest { } } -impl Positioned for PublicDataUpdateRequest { - fn position(self) -> Field { - self.leaf_slot - } -} - impl Ordered for PublicDataUpdateRequest { fn counter(self)-> u32{ self.counter @@ -54,15 +51,6 @@ impl Empty for PublicDataUpdateRequest { } } -impl Hash for PublicDataUpdateRequest { - fn hash(self) -> Field { - crate::hash::poseidon2_hash_with_separator([ - self.leaf_slot, - self.new_value - ], GENERATOR_INDEX__PUBLIC_DATA_UPDATE_REQUEST) - } -} - impl From for PublicDataTreeLeaf { fn from(update_request: PublicDataUpdateRequest) -> PublicDataTreeLeaf { PublicDataTreeLeaf { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_write.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_write.nr new file mode 100644 index 00000000000..251d31a1af6 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_write.nr @@ -0,0 +1,52 @@ +use crate::{ + abis::{ + public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest, + side_effect::{Inner, Ordered, Overridable, Readable} +}, + traits::Empty +}; + +struct OverridablePublicDataWrite { + write: PublicDataUpdateRequest, + override_counter: u32, +} + +impl Eq for OverridablePublicDataWrite { + fn eq(self, other: OverridablePublicDataWrite) -> bool { + (other.write == self.write) & (other.override_counter == self.override_counter) + } +} + +impl Empty for OverridablePublicDataWrite { + fn empty() -> Self { + Self { + write: PublicDataUpdateRequest::empty(), + override_counter: 0, + } + } +} + +impl Ordered for OverridablePublicDataWrite { + fn counter(self)-> u32{ + self.write.counter() + } +} + +impl Readable for OverridablePublicDataWrite { + fn assert_match_read_request(self, read_request: PublicDataRead) { + assert_eq(self.write.leaf_slot, read_request.leaf_slot, "leaf_slot in OverridablePublicDataWrite does not match read request"); + assert_eq(self.write.new_value, read_request.value, "value in OverridablePublicDataWrite does not match read request"); + } +} + +impl Overridable for OverridablePublicDataWrite { + fn override_counter(self) -> u32 { + self.override_counter + } +} + +impl Inner for OverridablePublicDataWrite { + fn inner(self) -> PublicDataUpdateRequest { + self.write + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/side_effect.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/side_effect.nr index e888e6a3fbd..d09391a8cd9 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/side_effect.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/side_effect.nr @@ -19,12 +19,15 @@ trait Scoped where T: Eq { fn inner(self) -> T; } -trait Positioned { - // Like a storage slot - fn position(self) -> Field; +trait Readable { + fn assert_match_read_request(self, read_request: T); } -trait Readable { - fn assert_match_read_request(self, read_request: ScopedReadRequest); +trait Overridable { + // The counter of the next side effect that's overriding the current side effect. + fn override_counter(self) -> u32; } +trait Inner { + fn inner(self) -> T; +} 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 feed56d1d88..ca38920cb2a 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -249,7 +249,7 @@ global PUBLIC_CONTEXT_INPUTS_LENGTH: u32 = CALL_CONTEXT_LENGTH + HEADER_LENGTH + global AGGREGATION_OBJECT_LENGTH: u32 = 16; global SCOPED_READ_REQUEST_LEN = READ_REQUEST_LENGTH + 1; -global PUBLIC_DATA_READ_LENGTH = 2; +global PUBLIC_DATA_READ_LENGTH = 3; global PRIVATE_VALIDATION_REQUESTS_LENGTH = ROLLUP_VALIDATION_REQUESTS_LENGTH + (SCOPED_READ_REQUEST_LEN * MAX_NOTE_HASH_READ_REQUESTS_PER_TX) + (SCOPED_READ_REQUEST_LEN * MAX_NULLIFIER_READ_REQUESTS_PER_TX) + (SCOPED_KEY_VALIDATION_REQUEST_AND_GENERATOR_LENGTH * MAX_KEY_VALIDATION_REQUESTS_PER_TX) + 2; global PUBLIC_VALIDATION_REQUESTS_LENGTH = ROLLUP_VALIDATION_REQUESTS_LENGTH + (TREE_LEAF_READ_REQUEST_LENGTH * MAX_NOTE_HASH_READ_REQUESTS_PER_TX) + (SCOPED_READ_REQUEST_LEN * MAX_NULLIFIER_READ_REQUESTS_PER_TX) + (SCOPED_READ_REQUEST_LEN * MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX) + (PUBLIC_DATA_READ_LENGTH * MAX_PUBLIC_DATA_READS_PER_TX) + (TREE_LEAF_READ_REQUEST_LENGTH * MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_read.nr b/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_read.nr index 6f44e699eea..6ed6251c8e3 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_read.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_read.nr @@ -1,12 +1,8 @@ -use crate::{ - constants::{CONTRACT_STORAGE_READ_LENGTH, GENERATOR_INDEX__PUBLIC_DATA_READ}, - hash::poseidon2_hash_with_separator, traits::{Deserialize, Hash, Empty, Serialize} -}; +use crate::{constants::CONTRACT_STORAGE_READ_LENGTH, traits::{Deserialize, Empty, Serialize}}; struct StorageRead { storage_slot: Field, current_value: Field, - // TODO(dbanks12): use side effects properly in kernel checks counter: u32, } @@ -16,7 +12,7 @@ impl Eq for StorageRead { } } -impl Empty for StorageRead { +impl Empty for StorageRead { fn empty() -> Self { Self { storage_slot: 0, @@ -26,12 +22,6 @@ impl Empty for StorageRead { } } -impl Hash for StorageRead { - fn hash(self) -> Field { - poseidon2_hash_with_separator(self.serialize(), GENERATOR_INDEX__PUBLIC_DATA_READ) - } -} - impl Serialize for StorageRead { fn serialize(self) -> [Field; CONTRACT_STORAGE_READ_LENGTH] { [self.storage_slot, self.current_value, self.counter as Field] diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_update_request.nr b/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_update_request.nr index 569ff8f03e9..767c22f5116 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_update_request.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/contrakt/storage_update_request.nr @@ -1,7 +1,4 @@ -use crate::{ - constants::{CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH, GENERATOR_INDEX__PUBLIC_DATA_UPDATE_REQUEST}, - hash::poseidon2_hash_with_separator, traits::{Deserialize, Hash, Empty, Serialize} -}; +use crate::{constants::CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH, traits::{Deserialize, Empty, Serialize}}; struct StorageUpdateRequest { storage_slot : Field, @@ -26,12 +23,6 @@ impl Empty for StorageUpdateRequest { } } -impl Hash for StorageUpdateRequest { - fn hash(self) -> Field { - poseidon2_hash_with_separator(self.serialize(), GENERATOR_INDEX__PUBLIC_DATA_UPDATE_REQUEST) - } -} - impl Serialize for StorageUpdateRequest { fn serialize(self) -> [Field; CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH] { [self.storage_slot, self.new_value, self.counter as Field] diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/data/leaf_data_hint.nr b/noir-projects/noir-protocol-circuits/crates/types/src/data/leaf_data_hint.nr deleted file mode 100644 index 198dc744ec7..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/data/leaf_data_hint.nr +++ /dev/null @@ -1,6 +0,0 @@ -trait LeafDataHint { - fn leaf_slot(self) -> Field; - fn value(self) -> Field; - fn override_counter(self) -> u32; -} - diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/data/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/data/mod.nr index cf34767d6f9..44c32c7e867 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/data/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/data/mod.nr @@ -1,3 +1,10 @@ -mod leaf_data_hint; mod public_data_hint; +mod public_data_leaf_hint; +mod public_data_tree_leaf; +mod public_data_tree_leaf_preimage; mod hash; + +use public_data_hint::PublicDataHint; +use public_data_leaf_hint::PublicDataLeafHint; +use public_data_tree_leaf::{OverridablePublicDataTreeLeaf, PublicDataTreeLeaf}; +use public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_hint.nr b/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_hint.nr index 081d677d60f..a544965afa8 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_hint.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_hint.nr @@ -1,6 +1,7 @@ use crate::{ - public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, data::leaf_data_hint::LeafDataHint, - traits::Empty, merkle_tree::{MembershipWitness, conditionally_assert_check_membership}, + abis::public_data_read::PublicDataRead, + data::public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, traits::Empty, + merkle_tree::{MembershipWitness, conditionally_assert_check_membership}, constants::PUBLIC_DATA_TREE_HEIGHT }; @@ -8,7 +9,7 @@ struct PublicDataHint { leaf_slot: Field, value: Field, override_counter: u32, - membership_witness: MembershipWitness, // Should be MembershipWitness when we can handle generics when converting to ts types. + membership_witness: MembershipWitness, leaf_preimage: PublicDataTreeLeafPreimage, } @@ -37,20 +38,6 @@ impl PublicDataHint { } } -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 - } -} - impl Empty for PublicDataHint { fn empty() -> Self { PublicDataHint { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_leaf_hint.nr b/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_leaf_hint.nr new file mode 100644 index 00000000000..2d2c0becd9b --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_leaf_hint.nr @@ -0,0 +1,18 @@ +use crate::{ + data::public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, traits::Empty, + merkle_tree::MembershipWitness, constants::PUBLIC_DATA_TREE_HEIGHT +}; + +struct PublicDataLeafHint { + preimage: PublicDataTreeLeafPreimage, + membership_witness: MembershipWitness, +} + +impl Empty for PublicDataLeafHint { + fn empty() -> Self { + PublicDataLeafHint { + preimage: PublicDataTreeLeafPreimage::empty(), + membership_witness: MembershipWitness::empty(), + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_tree_leaf.nr b/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_tree_leaf.nr new file mode 100644 index 00000000000..d1149066df9 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_tree_leaf.nr @@ -0,0 +1,55 @@ +use crate::{abis::{public_data_read::PublicDataRead, side_effect::{Overridable, Readable}}, traits::Empty}; + +struct PublicDataTreeLeaf { + slot: Field, + value: Field, +} + +impl Eq for PublicDataTreeLeaf { + fn eq(self, other: Self) -> bool { + (self.slot == other.slot) & (self.value == other.value) + } +} + +impl Empty for PublicDataTreeLeaf { + fn empty() -> Self { + Self { + slot: 0, + value: 0, + } + } +} + +impl PublicDataTreeLeaf { + pub fn is_empty(self) -> bool { + (self.slot == 0) & (self.value == 0) + } +} + +struct OverridablePublicDataTreeLeaf { + leaf: PublicDataTreeLeaf, + override_counter: u32, +} + +impl Empty for OverridablePublicDataTreeLeaf { + fn empty() -> Self { + OverridablePublicDataTreeLeaf { + leaf: PublicDataTreeLeaf::empty(), + override_counter: 0, + } + } +} + +impl Readable for OverridablePublicDataTreeLeaf { + fn assert_match_read_request(self, read_request: PublicDataRead) { + assert_eq(self.leaf.slot, read_request.leaf_slot, "leaf_slot in OverridablePublicDataTreeLeaf does not match read request"); + assert_eq(self.leaf.value, read_request.value, "value in OverridablePublicDataTreeLeaf does not match read request"); + } +} + +impl Overridable for OverridablePublicDataTreeLeaf { + fn override_counter(self) -> u32 { + self.override_counter + } +} + 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/data/public_data_tree_leaf_preimage.nr similarity index 100% rename from noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf_preimage.nr rename to noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_tree_leaf_preimage.nr diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr index 7e36c845512..3d0cb047ca9 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr @@ -3,9 +3,7 @@ mod address; mod debug_log; mod point; mod scalar; -// This is intentionally spelled like this -// since contract is a reserved keyword, so it cannot -// be used as an ident. +// This is intentionally spelled like this since contract is a reserved keyword, so it cannot be used as an ident. mod contrakt; mod transaction; mod abis; @@ -26,8 +24,6 @@ mod tests; mod state_reference; mod partial_state_reference; -mod public_data_tree_leaf; -mod public_data_tree_leaf_preimage; use abis::kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf.nr b/noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf.nr deleted file mode 100644 index 79f21b9734a..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/public_data_tree_leaf.nr +++ /dev/null @@ -1,27 +0,0 @@ -use crate::traits::Empty; - -struct PublicDataTreeLeaf { - slot: Field, - value: Field, -} - -impl Eq for PublicDataTreeLeaf { - fn eq(self, other: Self) -> bool { - (self.slot == other.slot) & (self.value == other.value) - } -} - -impl Empty for PublicDataTreeLeaf { - fn empty() -> Self { - Self { - slot: 0, - value: 0, - } - } -} - -impl PublicDataTreeLeaf { - pub fn is_empty(self) -> bool { - (self.slot == 0) & (self.value == 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 9a2c9100fa5..05326018fb6 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 @@ -727,15 +727,15 @@ impl FixtureBuilder { } pub fn add_public_data_read_request(&mut self, leaf_slot: Field, value: Field) { - self.public_data_reads.push(PublicDataRead { leaf_slot, value }); + self.public_data_reads.push(PublicDataRead { leaf_slot, value, counter: self.next_counter() }); } pub fn append_public_data_read_requests(&mut self, num_reads: u32) { let index_offset = self.public_data_reads.len(); for i in 0..self.public_data_reads.max_len() { if i < num_reads { - let read_request = self.mock_public_data_read(index_offset + i); - self.add_public_data_read_request(read_request.leaf_slot, read_request.value); + let (leaf_slot, value) = self.mock_public_data_read(index_offset + i); + self.add_public_data_read_request(leaf_slot, value); } } } @@ -808,14 +808,6 @@ impl FixtureBuilder { } } - pub fn add_read_request_for_pending_public_data(&mut self, public_date_update_request_index: u32) -> u32 { - 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 add_request_for_key_validation(&mut self, pk_m: Point, sk_app: Field, sk_app_generator: Field) -> u32 { let new_request_index = self.scoped_key_validation_requests_and_generators.len(); let request = KeyValidationRequest { pk_m, sk_app }; @@ -1048,9 +1040,9 @@ impl FixtureBuilder { KeyValidationRequestAndGenerator { request, sk_app_generator: 3 + value_offset } } - fn mock_public_data_read(self, index: u32) -> PublicDataRead { + fn mock_public_data_read(self, index: u32) -> (Field, Field) { let value_offset = 4545 + self.value_offset + index as Field; - PublicDataRead { leaf_slot: value_offset, value: 1 + value_offset } + (value_offset, value_offset + 1) } fn mock_public_data_write(self, index: u32) -> (Field, Field) { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/mod.nr index 01e031f90fa..129deea3194 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/mod.nr @@ -1,4 +1,5 @@ mod fixture_builder; mod fixtures; mod merkle_tree_utils; +mod types; mod utils; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/types.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/types.nr new file mode 100644 index 00000000000..710d711d9a9 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/types.nr @@ -0,0 +1,80 @@ +use crate::{abis::side_effect::Ordered, traits::Empty}; + +struct TestValue { + value: Field, + counter: u32, +} + +impl Empty for TestValue { + fn empty() -> Self { + TestValue { value: 0, counter: 0 } + } +} + +impl Eq for TestValue { + fn eq(self, other: Self) -> bool { + (self.value == other.value) & (self.counter == other.counter) + } +} + +impl Ordered for TestValue { + fn counter(self) -> u32 { + self.counter + } +} + +struct TestTwoValues { + value_1: Field, + value_2: Field, + counter: u32, +} + +impl Empty for TestTwoValues { + fn empty() -> Self { + TestTwoValues { value_1: 0, value_2: 0, counter: 0 } + } +} + +impl Eq for TestTwoValues { + fn eq(self, other: Self) -> bool { + (self.value_1 == other.value_1) & (self.value_2 == other.value_2) & (self.counter == other.counter) + } +} + +impl Ordered for TestTwoValues { + fn counter(self) -> u32 { + self.counter + } +} + +struct TestCombinedValue { + value: Field, +} + +impl Empty for TestCombinedValue { + fn empty() -> Self { + TestCombinedValue { value: 0 } + } +} + +impl Eq for TestCombinedValue { + fn eq(self, other: Self) -> bool { + (self.value == other.value) + } +} + +pub fn sum_two_values(from: TestTwoValues) -> TestValue { + TestValue { value: from.value_1 + from.value_2, counter: from.counter } +} + +pub fn is_summed_from_two_values(from: TestTwoValues, to: TestValue) -> bool { + ((from.value_1 + from.value_2) == to.value) & (from.counter == to.counter) +} + +pub fn combine_two_values(from: TestTwoValues) -> TestCombinedValue { + TestCombinedValue { value: from.value_1 + from.value_2 } +} + +pub fn is_combined_from_two_values(from: TestTwoValues, to: TestCombinedValue) -> bool { + ((from.value_1 + from.value_2) == to.value) +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/utils.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/utils.nr index 9890994c051..ae5861a65fe 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/utils.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/utils.nr @@ -1,18 +1,8 @@ -use crate::traits::{Empty, is_empty}; - -fn count_non_empty_elements(array: [T; N]) -> u32 where T: Empty + Eq { - let mut length = 0; - for elem in array { - if !is_empty(elem) { - length += 1; - } - } - length -} +use crate::{traits::{Empty, is_empty}, utils::arrays::validate_array}; pub fn assert_array_eq(array: [T; N], expected: [T; S]) where T: Empty + Eq { - assert_eq(count_non_empty_elements(expected), S, "cannot expect empty element in the result"); - assert_eq(count_non_empty_elements(array), S, "mismatch array lengths"); + assert(expected.all(|elem: T| !is_empty(elem)), "cannot expect empty element in the result"); + assert_eq(validate_array(array), S, "mismatch array lengths"); for i in 0..S { assert_eq(array[i], expected[i], "mismatch array elements"); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index d2358e05f5a..8a6d1905131 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -1,29 +1,29 @@ -mod assert_combined_deduped_array; +mod assert_combined_array; mod assert_combined_sorted_transformed_value_array; +mod assert_combined_transformed_array; +mod assert_deduped_array; mod assert_exposed_sorted_transformed_value_array; mod assert_sorted_array; mod assert_sorted_transformed_value_array; mod assert_split_sorted_transformed_value_arrays; mod assert_split_transformed_value_arrays; -mod get_sorted_hints; +mod get_sorted_result; mod get_sorted_tuple; mod sort_by; mod sort_by_counter; // Re-exports. -use assert_combined_deduped_array::{ - assert_combined_deduped_array, dedupe_array::dedupe_array, - get_deduped_hints::{DedupedHints, get_deduped_hints}, - sort_by_position_then_counter::sort_by_position_then_counter -}; +use assert_combined_array::{assert_combined_array, combine_arrays}; use assert_combined_sorted_transformed_value_array::{ assert_combined_sorted_transformed_value_array_asc, get_combined_order_hints::{CombinedOrderHint, get_combined_order_hints_asc} }; +use assert_combined_transformed_array::{assert_combined_transformed_array, combine_and_transform_arrays}; use assert_exposed_sorted_transformed_value_array::{ assert_exposed_sorted_transformed_value_array, get_order_hints::{get_order_hints_asc, get_order_hints_desc, OrderHint} }; +use assert_deduped_array::{assert_deduped_array, dedupe_array}; use assert_sorted_array::assert_sorted_array; use assert_split_sorted_transformed_value_arrays::{ assert_split_sorted_transformed_value_arrays_asc, assert_split_sorted_transformed_value_arrays_desc, @@ -31,7 +31,7 @@ use assert_split_sorted_transformed_value_arrays::{ }; use assert_sorted_transformed_value_array::{assert_sorted_transformed_value_array, assert_sorted_transformed_value_array_capped_size}; use assert_split_transformed_value_arrays::assert_split_transformed_value_arrays; -use get_sorted_hints::get_sorted_hints; +use get_sorted_result::{get_sorted_result, SortedResult}; use sort_by_counter::{sort_by_counter_asc, sort_by_counter_desc}; use crate::traits::{Empty, is_empty}; @@ -74,23 +74,10 @@ pub fn validate_array(array: [T; N]) -> u32 where T: Empty + Eq { length } -unconstrained fn count_non_empty_elements(array: [T; N]) -> u32 where T: Empty + Eq { - let mut length = 0; - let mut seen_empty = false; - for elem in array { - if is_empty(elem) { - seen_empty = true; - } else if !seen_empty { - length += 1; - } - } - length -} - // Helper function to count the number of non-empty elements in a validated array. // Important: Only use it for validated arrays: validate_array(array) should be true. pub fn array_length(array: [T; N]) -> u32 where T: Empty + Eq { - let length = count_non_empty_elements(array); + let length = find_index_hint(array, |elem: T| is_empty(elem)); if length != 0 { assert(!is_empty(array[length - 1])); } @@ -130,7 +117,11 @@ pub fn array_merge(array1: [T; N], array2: [T; N]) -> [T; N] wher result } -pub fn check_permutation(original_array: [T; N], permuted_array: [T; N], original_indexes: [u32; N]) where T: Eq + Empty { +pub fn check_permutation( + original_array: [T; N], + permuted_array: [T; N], + original_indexes: [u32; N] +) where T: Eq + Empty { let mut seen_value = [false; N]; for i in 0..N { let index = original_indexes[i]; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_array.nr new file mode 100644 index 00000000000..90079131ab6 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_array.nr @@ -0,0 +1,141 @@ +use crate::{ + traits::Empty, + utils::arrays::assert_combined_transformed_array::{assert_combined_transformed_array, combine_and_transform_arrays} +}; + +// original_array(_lt/_gte) must be valid, i.e. validate_array(original_array) == true +// This ensures that combined_array is valid when S can only be empty if and only if T is empty. +pub fn assert_combined_array( + original_array_lt: [T; N], + original_array_gte: [T; N], + combined_array: [T; N] +) where T: Empty + Eq { + assert_combined_transformed_array( + original_array_lt, + original_array_gte, + combined_array, + |from: T, to: T| from == to + ) +} + +unconstrained pub fn combine_arrays( + original_array_lt: [T; N], + original_array_gte: [T; N] +) -> [T; N] where T: Empty + Eq { + combine_and_transform_arrays(original_array_lt, original_array_gte, |from: T| from) +} + +mod tests { + use crate::{ + tests::{types::TestValue, utils::pad_end}, + utils::arrays::assert_combined_array::{assert_combined_array, combine_arrays} + }; + + struct TestBuilder { + original_array_lt: [TestValue; N], + original_array_gte: [TestValue; N], + combined_array: [TestValue; N], + } + + impl TestBuilder<10> { + pub fn new_empty() -> Self { + let original_array_lt = pad_end([], TestValue::empty()); + let original_array_gte = pad_end([], TestValue::empty()); + let combined_array = pad_end([], TestValue::empty()); + TestBuilder { original_array_lt, original_array_gte, combined_array } + } + + pub fn new() -> Self { + let original_array_lt = pad_end( + [ + TestValue { value: 11, counter: 2 }, + TestValue { value: 22, counter: 5 }, + TestValue { value: 33, counter: 3 } + ], + TestValue::empty() + ); + + let original_array_gte = pad_end( + [ + TestValue { value: 44, counter: 1 }, + TestValue { value: 55, counter: 4 } + ], + TestValue::empty() + ); + + let combined_array = pad_end( + [ + TestValue { value: 11, counter: 2 }, + TestValue { value: 22, counter: 5 }, + TestValue { value: 33, counter: 3 }, + TestValue { value: 44, counter: 1 }, + TestValue { value: 55, counter: 4 } + ], + TestValue::empty() + ); + + TestBuilder { original_array_lt, original_array_gte, combined_array } + } + + pub fn execute(self) { + assert_combined_array( + self.original_array_lt, + self.original_array_gte, + self.combined_array + ); + } + + pub fn check_and_execute(self) { + let combined = unsafe { + combine_arrays(self.original_array_lt, self.original_array_gte) + }; + assert_eq(combined, self.combined_array); + + self.execute(); + } + } + + #[test] + fn assert_combined_array_empty_succeeds() { + let builder = TestBuilder::new_empty(); + builder.check_and_execute(); + } + + #[test] + fn assert_combined_array_succeeds() { + let builder = TestBuilder::new(); + builder.check_and_execute(); + } + + #[test(should_fail_with="hinted item in the commbined array does not match")] + fn assert_combined_array_extra_item_fails() { + let mut builder = TestBuilder::new(); + + // Add random value to an empty item. + builder.combined_array[7].value = 123; + + builder.execute(); + } + + #[test(should_fail_with="hinted item in the commbined array does not match")] + fn assert_combined_array_missing_item_fails() { + let mut builder = TestBuilder::new(); + + // Clear the last item. + builder.combined_array[4] = TestValue::empty(); + + builder.execute(); + } + + #[test(should_fail_with="hinted item in the commbined array does not match")] + fn assert_combined_array_unordered_fails() { + let mut builder = TestBuilder::new(); + + // Swap the two items. + let tmp = builder.combined_array[3]; + builder.combined_array[3] = builder.combined_array[1]; + builder.combined_array[1] = tmp; + + builder.execute(); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array.nr deleted file mode 100644 index 366ec585a2f..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array.nr +++ /dev/null @@ -1,218 +0,0 @@ -mod assert_combined_permuted_array; -mod assert_deduped_array; -mod dedupe_array; -mod get_deduped_hints; -mod sort_by_position_then_counter; - -use crate::{ - abis::side_effect::{Positioned, Ordered}, traits::{Empty, is_empty}, - utils::arrays::{ - array_length, - assert_combined_deduped_array::{ - assert_combined_permuted_array::assert_combined_permuted_array, - assert_deduped_array::assert_deduped_array, get_deduped_hints::DedupedHints -} -} -}; - -// original_array_(lt/gte) must be valid, i.e. validate_array(original_array) == true -// All non-empty items in the original arrays must be unique, otherwise duplicate values could be merged and -// assert_combined_permuted_array would still pass undetected. -// This is currently used for deduplicating public data writes, where each public data write is unique due to having a unique counter. -pub fn assert_combined_deduped_array( - original_array_lt: [T; N], - original_array_gte: [T; N], - sorted_array: [T; N], - deduped_array: [T; N], - hints: DedupedHints -) where T: Positioned + Ordered + Empty + Eq { - assert_combined_permuted_array( - original_array_lt, - original_array_gte, - sorted_array, - hints.combined_indexes - ); - assert_deduped_array(sorted_array, deduped_array, hints.run_lengths); -} - -mod tests { - use crate::{ - abis::side_effect::{Positioned, Ordered}, tests::utils::pad_end, - utils::arrays::{ - array_merge, - assert_combined_deduped_array::{ - assert_deduped_array::{assert_deduped_array, tests::TestContainer}, - assert_combined_deduped_array, dedupe_array::dedupe_array, - get_deduped_hints::{DedupedHints, get_deduped_hints} - } - } - }; - - fn verify_all( - original_array_lt: [TestContainer; N], - original_array_gte: [TestContainer; N], - sorted_array: [TestContainer; N], - deduped_array: [TestContainer; N], - hints: DedupedHints - ) { - let merged = array_merge(original_array_lt, original_array_gte); - assert_eq(deduped_array, dedupe_array(merged)); - assert_eq(hints, get_deduped_hints(original_array_lt, original_array_gte)); - assert_combined_deduped_array( - original_array_lt, - original_array_gte, - sorted_array, - deduped_array, - hints - ); - } - - #[test] - fn assert_combined_deduped_array_full() { - let original_array_lt = pad_end( - [ - TestContainer { value: 4, position: 3, counter: 2 }, - TestContainer { value: 7, position: 4, counter: 8 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 9, position: 5, counter: 7 } - ], - TestContainer::empty() - ); - let original_array_gte = pad_end( - [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 5, position: 3, counter: 5 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 2, position: 1, counter: 4 } - ], - TestContainer::empty() - ); - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 4 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 2 }, - TestContainer { value: 5, position: 3, counter: 5 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 7, position: 4, counter: 8 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 9, position: 5, counter: 7 } - ]; - let deduped_array = [ - TestContainer { value: 2, position: 1, counter: 4 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 9, position: 5, counter: 7 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let hints = DedupedHints { combined_indexes: [3, 6, 2, 8, 0, 4, 5, 7, 1], run_lengths: [2, 1, 3, 2, 1, 0, 0, 0, 0] }; - verify_all( - original_array_lt, - original_array_gte, - sorted_array, - deduped_array, - hints - ); - } - - #[test] - fn assert_combined_deduped_array_padded_empty() { - let original_array_lt = pad_end( - [ - TestContainer { value: 4, position: 3, counter: 2 }, - TestContainer { value: 7, position: 4, counter: 8 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 9, position: 5, counter: 7 } - ], - TestContainer::empty() - ); - let original_array_gte = pad_end( - [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 5, position: 3, counter: 5 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 2, position: 1, counter: 4 } - ], - TestContainer::empty() - ); - let sorted_array = pad_end( - [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 4 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 2 }, - TestContainer { value: 5, position: 3, counter: 5 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 7, position: 4, counter: 8 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 9, position: 5, counter: 7 } - ], - TestContainer::empty() - ); - let deduped_array = pad_end( - [ - TestContainer { value: 2, position: 1, counter: 4 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 9, position: 5, counter: 7 } - ], - TestContainer::empty() - ); - let hints = DedupedHints { - combined_indexes: [3, 6, 2, 8, 0, 4, 5, 7, 1, 11, 10, 9], - run_lengths: [2, 1, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0] - }; - verify_all( - original_array_lt, - original_array_gte, - sorted_array, - deduped_array, - hints - ); - } - - #[test] - fn assert_combined_deduped_array_no_duplicates() { - let original_array_lt = pad_end( - [ - TestContainer { value: 3, position: 3, counter: 3 }, - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 2, counter: 2 } - ], - TestContainer::empty() - ); - let original_array_gte = pad_end( - [ - TestContainer { value: 4, position: 4, counter: 4 }, - TestContainer { value: 5, position: 5, counter: 5 } - ], - TestContainer::empty() - ); - let sorted_array = pad_end( - [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 2, counter: 2 }, - TestContainer { value: 3, position: 3, counter: 3 }, - TestContainer { value: 4, position: 4, counter: 4 }, - TestContainer { value: 5, position: 5, counter: 5 } - ], - TestContainer::empty() - ); - let deduped_array = sorted_array; - let hints = DedupedHints { combined_indexes: [2, 0, 1, 3, 4, 7, 6, 5], run_lengths: [1, 1, 1, 1, 1, 0, 0, 0] }; - verify_all( - original_array_lt, - original_array_gte, - sorted_array, - deduped_array, - hints - ); - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_combined_permuted_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_combined_permuted_array.nr deleted file mode 100644 index 2b4786b1e07..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_combined_permuted_array.nr +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{traits::{Empty, is_empty}, utils::arrays::array_length}; - -pub fn assert_combined_permuted_array( - original_array_lt: [T; N], - original_array_gte: [T; N], - permuted_array: [T; N], - combined_indexes: [u32; N] -) where T: Empty + Eq { - let num_lt = array_length(original_array_lt); - let num_gte = array_length(original_array_gte); - let total_num = num_lt + num_gte; - - let mut is_lt = true; - let mut should_be_empty = false; - for i in 0..N { - is_lt &= i != num_lt; - should_be_empty |= i == total_num; - - let from = if is_lt { - original_array_lt[i] - } else { - original_array_gte[i - num_lt] - }; - - let combined_index = combined_indexes[i]; - let to = permuted_array[combined_index]; - assert_eq(from, to, "hinted item in the permuted array does not match"); - - if should_be_empty { - assert(is_empty(permuted_array[i]), "permuted array must be padded with empty items"); - } - } -} - -mod tests { - use crate::utils::arrays::assert_combined_deduped_array::assert_combined_permuted_array::assert_combined_permuted_array; - - struct TestBuilder { - original_array_lt: [Field; N], - original_array_gte: [Field; N], - permuted_array: [Field; N], - combined_indexes: [u32; N] - } - - impl TestBuilder<7> { - pub fn new() -> Self { - let original_array_lt = [6, 3, 8, 1, 0, 0, 0]; - let original_array_gte = [4, 9, 5, 0, 0, 0, 0]; - let permuted_array = [5, 8, 9, 3, 1, 6, 4]; - let combined_indexes = [5, 3, 1, 4, 6, 2, 0]; - TestBuilder { original_array_lt, original_array_gte, permuted_array, combined_indexes } - } - } - - impl TestBuilder<10> { - pub fn new_with_padded_zeros() -> Self { - let original_array_lt = [6, 3, 8, 1, 0, 0, 0, 0, 0, 0]; - let original_array_gte = [4, 9, 5, 0, 0, 0, 0, 0, 0, 0]; - let permuted_array = [5, 8, 9, 3, 1, 6, 4, 0, 0, 0]; - let combined_indexes = [5, 3, 1, 4, 6, 2, 0, 7, 8, 9]; - TestBuilder { original_array_lt, original_array_gte, permuted_array, combined_indexes } - } - } - - impl TestBuilder { - pub fn verify(self) { - assert_combined_permuted_array( - self.original_array_lt, - self.original_array_gte, - self.permuted_array, - self.combined_indexes - ); - } - } - - #[test] - fn assert_combined_permuted_array_full() { - let builder = TestBuilder::new(); - builder.verify() - } - - #[test] - fn assert_combined_permuted_array_padded_empty() { - let builder = TestBuilder::new_with_padded_zeros(); - builder.verify() - } - - #[test(should_fail_with="hinted item in the permuted array does not match")] - fn assert_combined_permuted_array_missing_value_fails() { - let mut builder = TestBuilder::new_with_padded_zeros(); - - // Clear a value. - builder.permuted_array[6] = 0; - - builder.verify() - } - - #[test(should_fail_with="hinted item in the permuted array does not match")] - fn assert_combined_permuted_array_duplicated_item_fails() { - let mut builder = TestBuilder::new_with_padded_zeros(); - - // Duplicate a value. - builder.permuted_array[3] = builder.permuted_array[2]; - - builder.verify() - } - - #[test(should_fail_with="permuted array must be padded with empty items")] - fn assert_combined_permuted_array_extra_item_fails() { - let mut builder = TestBuilder::new_with_padded_zeros(); - - // Overwrite the first empty value with a non-empty value. - builder.permuted_array[8] = builder.permuted_array[1]; - // Update the index to point to an empty value. - builder.combined_indexes[8] = 9; - - builder.verify() - } - - #[test(should_fail_with="permuted array must be padded with empty items")] - fn assert_combined_permuted_array_mixed_empty_item_fails() { - let mut builder = TestBuilder::new_with_padded_zeros(); - - // Swap the positions of a non-empty item and an empty item. - builder.permuted_array[8] = builder.permuted_array[4]; - builder.permuted_array[4] = 0; - // Update the index to point to the value. - let original_index = 3; - assert_eq(builder.original_array_lt[original_index], builder.permuted_array[8]); - builder.combined_indexes[original_index] = 8; - builder.combined_indexes[8] = 4; - - builder.verify() - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_deduped_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_deduped_array.nr deleted file mode 100644 index c0fb2c0ea77..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_deduped_array.nr +++ /dev/null @@ -1,343 +0,0 @@ -use crate::{abis::side_effect::{Positioned, Ordered}, traits::Empty, utils::arrays::{array_length, validate_array}}; - -/* - The sorted_array here needs to be sorted based on the `position` field of the container, - *and* a secondary sort based on the `counter` field of the container. - - For example, the storage slot in the case of public data update requests. - The run_lengths array should contain the length of each run of the sorted_array. - The deduped_array should contain the deduplicated array. - - For example, if the original array is writing `(position,value,counter)`s: - [ (1,1,1), (1,2,4), (2,3,3), (3,4,2), (3,5,5), (3,6,6), (4,7,8), (4,8,9), (5,9,7), (0,0,0), ... padding with zeros ] - then run_lengths array is: - [ - 2, // run of 1s - 1, // run of 2 - 3, // run of 3s - 2, // run of 4s - 1, // run of 5 - 0, - 0, - ... padding with zeros - ] - - then the deduped_array should be: - [ (1,2,4), (2,3,3), (3,6,6), (4,8,9), (5,9,7), (0,0,0), ... padding with zeros ] -*/ -pub fn assert_deduped_array( - sorted_array: [T; N], - deduped_array: [T; N], - run_lengths: [u32; N] -) where T: Positioned + Ordered + Empty + Eq { - let num_non_empty_items = array_length(sorted_array); - let deduped_len = validate_array(deduped_array); // This makes sure that the array is padded with empty items. - - // container at the start of the current run - let mut start_run_container = sorted_array[0]; - // the index we are collapsing into - let mut deduped_index = 0; - // the length of the current run we are collapsing - let mut run_counter = 0; - let mut should_check = true; - for i in 0..N { - should_check &= i != num_non_empty_items; - if should_check { - let current_container = sorted_array[i]; - - if run_counter == 0 { - // Start a new run. - run_counter = run_lengths[deduped_index]; - if i != 0 { - assert( - start_run_container.position().lt(current_container.position()), "Containers in a run must be sorted by position" - ); - } - start_run_container = current_container; - } - - assert(run_counter != 0, "Invalid run length"); - run_counter -= 1; - - assert_eq( - current_container.position(), start_run_container.position(), "The position of the current container must match the start of the run" - ); - - if run_counter == 0 { - // End of the run. - assert_eq( - deduped_array[deduped_index], current_container, "The container we are collapsing into must match the current container" - ); - deduped_index += 1; - } else { - // We're in a run, so this container must have a lower counter. - // Note we don't check for overflow here, as the run_lengths array must be correct. - assert( - current_container.counter() < sorted_array[i + 1].counter(), "Containers in a run must be sorted by counter" - ); - } - } - } - - assert_eq(deduped_index, deduped_len, "Final deduped index does not match deduped array length"); -} - -mod tests { - use crate::{ - abis::side_effect::{Positioned, Ordered}, traits::Empty, tests::utils::pad_end, - utils::arrays::assert_combined_deduped_array::{assert_deduped_array::assert_deduped_array} - }; - - struct TestContainer { - value: Field, - position: Field, - counter: u32, - } - - impl Positioned for TestContainer { - fn position(self) -> Field { - self.position - } - } - - impl Ordered for TestContainer { - fn counter(self) -> u32 { - self.counter - } - } - - impl Empty for TestContainer { - fn empty() -> Self { - TestContainer { value: 0, position: 0, counter: 0 } - } - } - - impl Eq for TestContainer { - fn eq(self, other: Self) -> bool { - self.value.eq(other.value) & self.position.eq(other.position) & self.counter.eq(other.counter) - } - } - - #[test] - fn assert_deduped_array_basic_test() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 4 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 2 }, - TestContainer { value: 5, position: 3, counter: 5 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 7, position: 4, counter: 8 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 9, position: 5, counter: 7 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let deduped_array = [ - TestContainer { value: 2, position: 1, counter: 4 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 6, position: 3, counter: 6 }, - TestContainer { value: 8, position: 4, counter: 9 }, - TestContainer { value: 9, position: 5, counter: 7 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let run_lengths = [2, 1, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_empty_arrays() { - let sorted_array = [TestContainer::empty(); 12]; - let deduped_array = [TestContainer::empty(); 12]; - let run_lengths = [0; 12]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_no_duplicates() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 2, counter: 2 }, - TestContainer { value: 3, position: 3, counter: 3 }, - TestContainer { value: 4, position: 4, counter: 4 }, - TestContainer { value: 5, position: 5, counter: 5 } - ]; - let deduped_array = sorted_array; - let run_lengths = [1; 5]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_no_duplicates_padded_empty() { - let sorted_array = pad_end( - [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 2, counter: 2 }, - TestContainer { value: 3, position: 3, counter: 3 }, - TestContainer { value: 4, position: 4, counter: 4 }, - TestContainer { value: 5, position: 5, counter: 5 } - ], - TestContainer::empty() - ); - let deduped_array = sorted_array; - let run_lengths = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_single_run_at_end() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 2, counter: 2 }, - TestContainer { value: 3, position: 3, counter: 3 }, - TestContainer { value: 4, position: 4, counter: 4 }, - TestContainer { value: 5, position: 5, counter: 5 }, - TestContainer { value: 6, position: 6, counter: 7 }, - TestContainer { value: 7, position: 6, counter: 8 }, - TestContainer { value: 8, position: 6, counter: 9 } - ]; - let deduped_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 2, counter: 2 }, - TestContainer { value: 3, position: 3, counter: 3 }, - TestContainer { value: 4, position: 4, counter: 4 }, - TestContainer { value: 5, position: 5, counter: 5 }, - TestContainer { value: 8, position: 6, counter: 9 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let run_lengths = [1, 1, 1, 1, 1, 3, 0, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_all_duplicates() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 1, counter: 3 }, - TestContainer { value: 4, position: 1, counter: 4 }, - TestContainer { value: 5, position: 1, counter: 5 }, - TestContainer { value: 6, position: 1, counter: 6 }, - TestContainer { value: 7, position: 1, counter: 7 }, - TestContainer { value: 8, position: 1, counter: 8 }, - TestContainer { value: 9, position: 1, counter: 9 } - ]; - let deduped_array = pad_end( - [TestContainer { value: 9, position: 1, counter: 9 }], - TestContainer::empty() - ); - let run_lengths = [9, 0, 0, 0, 0, 0, 0, 0, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test(should_fail_with="The position of the current container must match the start of the run")] - fn assert_deduped_array_mismatched_position_in_run() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 4 } - ]; - let deduped_array = [ - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 4 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let run_lengths = [3, 1, 1, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test(should_fail_with="The container we are collapsing into must match the current container")] - fn assert_deduped_array_mismatched_deduped_value() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 4 } - ]; - let deduped_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 4 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let run_lengths = [2, 1, 1, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test(should_fail_with="Containers in a run must be sorted by position")] - fn assert_deduped_array_missed_deduped_value() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 },// This should've been deduped. - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 4 } - ]; - let deduped_array = sorted_array; - let run_lengths = [1, 1, 1, 1]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test(should_fail_with="Containers in a run must be sorted by position")] - fn assert_deduped_array_unsorted_array() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 2, position: 1, counter: 2 },// Not sorted by position. - TestContainer { value: 4, position: 3, counter: 4 } - ]; - let deduped_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 4, position: 3, counter: 4 } - ]; - let run_lengths = [1, 1, 1, 1]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test(should_fail_with="Final deduped index does not match deduped array length")] - fn assert_deduped_array_extra_value() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 4 } - ]; - let deduped_array = [ - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 4, position: 3, counter: 4 }, - TestContainer { value: 1, position: 1, counter: 1 }// This should be empty. - ]; - let run_lengths = [2, 1, 1, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } - - #[test(should_fail_with="invalid array")] - fn assert_deduped_array_padded_non_empty_value() { - let sorted_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer::empty() - ]; - let deduped_array = [ - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer::empty(), - TestContainer { value: 1, position: 1, counter: 1 }// This should be empty. - ]; - let run_lengths = [2, 1, 0, 0]; - assert_deduped_array(sorted_array, deduped_array, run_lengths); - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/dedupe_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/dedupe_array.nr deleted file mode 100644 index 3aa86e380c8..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/dedupe_array.nr +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{ - abis::side_effect::{Ordered, Positioned}, traits::Empty, - utils::arrays::assert_combined_deduped_array::sort_by_position_then_counter::sort_by_position_then_counter -}; - -pub fn dedupe_array(array: [T; N]) -> [T; N] where T: Positioned + Ordered + Empty + Eq { - let sorted = sort_by_position_then_counter(array); - let mut deduped = [T::empty(); N]; - let mut num_deduped = 0; - let mut prev_position = sorted[0].position(); - for item in sorted { - let position = item.position(); - if position != prev_position { - num_deduped += 1; - } - deduped[num_deduped] = item; - prev_position = position; - } - deduped -} - -mod tests { - use crate::{ - tests::utils::pad_end, - utils::arrays::assert_combined_deduped_array::{assert_deduped_array::tests::TestContainer, dedupe_array::dedupe_array} - }; - - #[test] - fn dedupe_array_padded_empty() { - let original_array = [ - TestContainer { value: 11, position: 3, counter: 2 }, - TestContainer { value: 55, position: 4, counter: 9 }, - TestContainer { value: 99, position: 3, counter: 5 }, - TestContainer { value: 66, position: 1, counter: 1 }, - TestContainer { value: 44, position: 4, counter: 8 }, - TestContainer { value: 77, position: 5, counter: 7 }, - TestContainer { value: 33, position: 1, counter: 4 }, - TestContainer { value: 22, position: 3, counter: 6 }, - TestContainer { value: 88, position: 2, counter: 3 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let deduped_array = [ - TestContainer { value: 33, position: 1, counter: 4 }, - TestContainer { value: 88, position: 2, counter: 3 }, - TestContainer { value: 22, position: 3, counter: 6 }, - TestContainer { value: 55, position: 4, counter: 9 }, - TestContainer { value: 77, position: 5, counter: 7 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - assert_eq(dedupe_array(original_array), deduped_array); - } - - #[test] - fn dedupe_array_empty_arrays() { - let original_array = [TestContainer::empty(); 12]; - let deduped_array = [TestContainer::empty(); 12]; - assert_eq(dedupe_array(original_array), deduped_array); - } - - #[test] - fn dedupe_array_no_duplicates() { - let original_array = [ - TestContainer { value: 88, position: 3, counter: 3 }, - TestContainer { value: 11, position: 4, counter: 4 }, - TestContainer { value: 33, position: 2, counter: 2 }, - TestContainer { value: 99, position: 5, counter: 5 }, - TestContainer { value: 66, position: 1, counter: 1 } - ]; - let deduped_array = [ - TestContainer { value: 66, position: 1, counter: 1 }, - TestContainer { value: 33, position: 2, counter: 2 }, - TestContainer { value: 88, position: 3, counter: 3 }, - TestContainer { value: 11, position: 4, counter: 4 }, - TestContainer { value: 99, position: 5, counter: 5 } - ]; - assert_eq(dedupe_array(original_array), deduped_array); - } - - #[test] - fn dedupe_array_no_duplicates_padded_empty() { - let original_array = [ - TestContainer { value: 88, position: 3, counter: 3 }, - TestContainer { value: 11, position: 4, counter: 4 }, - TestContainer { value: 33, position: 2, counter: 2 }, - TestContainer { value: 99, position: 5, counter: 5 }, - TestContainer { value: 66, position: 1, counter: 1 }, - TestContainer::empty(), - TestContainer::empty(), - TestContainer::empty() - ]; - let deduped_array = [ - TestContainer { value: 66, position: 1, counter: 1 }, - TestContainer { value: 33, position: 2, counter: 2 }, - TestContainer { value: 88, position: 3, counter: 3 }, - TestContainer { value: 11, position: 4, counter: 4 }, - TestContainer { value: 99, position: 5, counter: 5 }, - TestContainer::empty(), - TestContainer::empty(), - TestContainer::empty() - ]; - assert_eq(dedupe_array(original_array), deduped_array); - } - - #[test] - fn dedupe_array_all_duplicates() { - let original_array = [ - TestContainer { value: 55, position: 1, counter: 8 }, - TestContainer { value: 33, position: 1, counter: 2 }, - TestContainer { value: 11, position: 1, counter: 4 }, - TestContainer { value: 88, position: 1, counter: 3 }, - TestContainer { value: 99, position: 1, counter: 5 }, - TestContainer { value: 77, position: 1, counter: 9 }, - TestContainer { value: 66, position: 1, counter: 1 }, - TestContainer { value: 44, position: 1, counter: 7 }, - TestContainer { value: 22, position: 1, counter: 6 } - ]; - let deduped_array = pad_end( - [TestContainer { value: 77, position: 1, counter: 9 }], - TestContainer::empty() - ); - assert_eq(dedupe_array(original_array), deduped_array); - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/get_deduped_hints.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/get_deduped_hints.nr deleted file mode 100644 index 2f88b95392e..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/get_deduped_hints.nr +++ /dev/null @@ -1,49 +0,0 @@ -use crate::{ - abis::side_effect::{Ordered, Positioned}, traits::Empty, - utils::arrays::{ - array_merge, - assert_combined_deduped_array::sort_by_position_then_counter::compare_by_position_then_counter, - get_sorted_tuple::get_sorted_tuple -} -}; - -struct DedupedHints { - combined_indexes: [u32; N], - run_lengths: [u32; N], -} - -impl Eq for DedupedHints { - fn eq(self, other: Self) -> bool { - (self.combined_indexes == other.combined_indexes) & (self.run_lengths == other.run_lengths) - } -} - -pub fn get_deduped_hints( - original_array_lt: [T; N], - original_array_gte: [T; N] -) -> DedupedHints where T: Positioned + Ordered + Empty + Eq { - let mut combined_indexes = [0; N]; - let mut run_lengths = BoundedVec::new(); - - let merged = array_merge(original_array_lt, original_array_gte); - let sorted = get_sorted_tuple(merged, compare_by_position_then_counter); - let mut prev_position = sorted[0].elem.position(); - let mut run_length = 0; - for i in 0..sorted.len() { - combined_indexes[sorted[i].original_index] = i; - - let position = sorted[i].elem.position(); - if position != 0 { - if position != prev_position { - run_lengths.push(run_length); - run_length = 1; - } else { - run_length += 1; - } - prev_position = position; - } - } - run_lengths.push(run_length); - - DedupedHints { combined_indexes, run_lengths: run_lengths.storage } -} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/sort_by_position_then_counter.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/sort_by_position_then_counter.nr deleted file mode 100644 index b017e68ec0f..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/sort_by_position_then_counter.nr +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{abis::side_effect::{Positioned, Ordered}}; - -pub fn compare_by_position_then_counter(a: T, b: T) -> bool where T: Positioned + Ordered { - if a.position() == b.position() { - (a.counter() == 0) | (b.counter() > a.counter()) - } else { - (b.position() == 0) | ((a.position() != 0) & a.position().lt(b.position())) - } -} - -pub fn sort_by_position_then_counter(array: [T; N]) -> [T; N] where T: Positioned + Ordered { - array.sort_via(|a, b| compare_by_position_then_counter(a, b)) -} - -mod tests { - use crate::utils::arrays::assert_combined_deduped_array::{ - assert_deduped_array::tests::TestContainer, - sort_by_position_then_counter::sort_by_position_then_counter - }; - - #[test] - fn sort_by_position_then_counter_empty_padded() { - let original_array = [ - TestContainer { value: 55, position: 4, counter: 8 }, - TestContainer { value: 11, position: 3, counter: 5 }, - TestContainer { value: 88, position: 1, counter: 4 }, - TestContainer { value: 44, position: 3, counter: 2 }, - TestContainer { value: 33, position: 1, counter: 1 }, - TestContainer { value: 66, position: 5, counter: 7 }, - TestContainer { value: 99, position: 4, counter: 9 }, - TestContainer { value: 77, position: 2, counter: 3 }, - TestContainer { value: 22, position: 3, counter: 6 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let expected = [ - TestContainer { value: 33, position: 1, counter: 1 }, - TestContainer { value: 88, position: 1, counter: 4 }, - TestContainer { value: 77, position: 2, counter: 3 }, - TestContainer { value: 44, position: 3, counter: 2 }, - TestContainer { value: 11, position: 3, counter: 5 }, - TestContainer { value: 22, position: 3, counter: 6 }, - TestContainer { value: 55, position: 4, counter: 8 }, - TestContainer { value: 99, position: 4, counter: 9 }, - TestContainer { value: 66, position: 5, counter: 7 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - assert_eq(sort_by_position_then_counter(original_array), expected); - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array.nr index 6eb7869fb59..5218f06bd68 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array.nr @@ -9,7 +9,9 @@ use crate::{ }; fn get_num_private_items(array: [T; N]) -> u32 where T: Ordered + Empty + Eq { - let length = count_private_items(array); + let length = unsafe { + count_private_items(array) + }; if length != 0 { let last_private_item = array[length - 1]; assert(!is_empty(last_private_item) & (last_private_item.counter() == 0)); @@ -83,7 +85,8 @@ pub fn assert_combined_sorted_transformed_value_array_asc mod tests { use crate::{ - abis::side_effect::Ordered, tests::utils::pad_end, traits::Empty, + tests::utils::pad_end, + tests::types::{combine_two_values, is_combined_from_two_values, TestCombinedValue, TestTwoValues}, utils::arrays::{ array_merge, assert_combined_sorted_transformed_value_array::{ @@ -94,99 +97,49 @@ mod tests { } }; - struct TestItem { - name: Field, - price: Field, - tax: Field, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { name: 0, price: 0, tax: 0, counter: 0 } - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.price == other.price) & (self.tax == other.tax) & (self.counter == other.counter) - } - } - - struct TestValue { - name: Field, - total: Field, - } - - impl Empty for TestValue { - fn empty() -> Self { - TestValue { name: 0, total: 0 } - } - } - - impl Eq for TestValue { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.total == other.total) - } - } - - fn transform(item: TestItem) -> TestValue { - TestValue { name: item.name, total: item.price + item.tax } - } - - fn is_transformed(item: TestItem, value: TestValue) -> bool { - (item.name == value.name) & ((item.price + item.tax) == value.total) - } - - struct TestDataBuilder { - original_array_lt: [T; N], - original_array_gte: [T; N], - sorted_transformed_value_array: [S; N], + struct TestDataBuilder { + original_array_lt: [TestTwoValues; N], + original_array_gte: [TestTwoValues; N], + sorted_transformed_value_array: [TestCombinedValue; N], hints: [CombinedOrderHint; N], } - impl TestDataBuilder { + impl TestDataBuilder<12> { pub fn new() -> Self { let original_array_lt = pad_end( [ - TestItem { name: 7, price: 40, tax: 7, counter: 0 }, - TestItem { name: 4, price: 70, tax: 6, counter: 0 }, - TestItem { name: 6, price: 80, tax: 1, counter: 22 }, - TestItem { name: 3, price: 30, tax: 4, counter: 11 } + TestTwoValues { value_1: 40, value_2: 7, counter: 0 }, + TestTwoValues { value_1: 70, value_2: 6, counter: 0 }, + TestTwoValues { value_1: 80, value_2: 1, counter: 22 }, + TestTwoValues { value_1: 30, value_2: 4, counter: 11 } ], - TestItem::empty() + TestTwoValues::empty() ); let original_array_gte = pad_end( [ - TestItem { name: 9, price: 20, tax: 2, counter: 0 }, - TestItem { name: 8, price: 90, tax: 3, counter: 0 }, - TestItem { name: 5, price: 50, tax: 9, counter: 55 }, - TestItem { name: 1, price: 60, tax: 8, counter: 33 }, - TestItem { name: 2, price: 10, tax: 5, counter: 44 } + TestTwoValues { value_1: 20, value_2: 2, counter: 0 }, + TestTwoValues { value_1: 90, value_2: 3, counter: 0 }, + TestTwoValues { value_1: 50, value_2: 9, counter: 55 }, + TestTwoValues { value_1: 60, value_2: 8, counter: 33 }, + TestTwoValues { value_1: 10, value_2: 5, counter: 44 } ], - TestItem::empty() + TestTwoValues::empty() ); let sorted_transformed_value_array = pad_end( [ - TestValue { name: 7, total: 47 }, - TestValue { name: 4, total: 76 }, - TestValue { name: 9, total: 22 }, - TestValue { name: 8, total: 93 }, - TestValue { name: 3, total: 34 }, - TestValue { name: 6, total: 81 }, - TestValue { name: 1, total: 68 }, - TestValue { name: 2, total: 15 }, - TestValue { name: 5, total: 59 } + TestCombinedValue { value: 47 }, + TestCombinedValue { value: 76 }, + TestCombinedValue { value: 22 }, + TestCombinedValue { value: 93 }, + TestCombinedValue { value: 34 }, + TestCombinedValue { value: 81 }, + TestCombinedValue { value: 68 }, + TestCombinedValue { value: 15 }, + TestCombinedValue { value: 59 } ], - TestValue::empty() + TestCombinedValue::empty() ); let hints = [ @@ -208,34 +161,34 @@ mod tests { } } - impl TestDataBuilder { + impl TestDataBuilder<8> { pub fn new_without_prepended() -> Self { let original_array_lt = pad_end( [ - TestItem { name: 6, price: 80, tax: 1, counter: 22 }, - TestItem { name: 3, price: 30, tax: 4, counter: 11 } + TestTwoValues { value_1: 80, value_2: 1, counter: 22 }, + TestTwoValues { value_1: 30, value_2: 4, counter: 11 } ], - TestItem::empty() + TestTwoValues::empty() ); let original_array_gte = pad_end( [ - TestItem { name: 5, price: 50, tax: 9, counter: 55 }, - TestItem { name: 1, price: 60, tax: 8, counter: 33 }, - TestItem { name: 2, price: 10, tax: 5, counter: 44 } + TestTwoValues { value_1: 50, value_2: 9, counter: 55 }, + TestTwoValues { value_1: 60, value_2: 8, counter: 33 }, + TestTwoValues { value_1: 10, value_2: 5, counter: 44 } ], - TestItem::empty() + TestTwoValues::empty() ); let sorted_transformed_value_array = pad_end( [ - TestValue { name: 3, total: 34 }, - TestValue { name: 6, total: 81 }, - TestValue { name: 1, total: 68 }, - TestValue { name: 2, total: 15 }, - TestValue { name: 5, total: 59 } + TestCombinedValue { value: 34 }, + TestCombinedValue { value: 81 }, + TestCombinedValue { value: 68 }, + TestCombinedValue { value: 15 }, + TestCombinedValue { value: 59 } ], - TestValue::empty() + TestCombinedValue::empty() ); let hints = [ @@ -253,7 +206,7 @@ mod tests { } } - impl TestDataBuilder { + impl TestDataBuilder { pub fn swap_items(&mut self, from: u32, to: u32) { let tmp = self.sorted_transformed_value_array[from]; self.sorted_transformed_value_array[from] = self.sorted_transformed_value_array[to]; @@ -271,7 +224,7 @@ mod tests { self.original_array_lt, self.original_array_gte, self.sorted_transformed_value_array, - is_transformed, + is_combined_from_two_values, self.hints ); } @@ -284,7 +237,7 @@ mod tests { let hints = get_combined_order_hints_asc(builder.original_array_lt, builder.original_array_gte); assert_eq(hints, builder.hints); - let sorted = sort_by_counter_asc(array_merge(builder.original_array_lt, builder.original_array_gte)).map(transform); + let sorted = sort_by_counter_asc(array_merge(builder.original_array_lt, builder.original_array_gte)).map(combine_two_values); assert_eq(sorted, builder.sorted_transformed_value_array); builder.execute(); @@ -297,7 +250,7 @@ mod tests { let hints = get_combined_order_hints_asc(builder.original_array_lt, builder.original_array_gte); assert_eq(hints, builder.hints); - let sorted = sort_by_counter_asc(array_merge(builder.original_array_lt, builder.original_array_gte)).map(transform); + let sorted = sort_by_counter_asc(array_merge(builder.original_array_lt, builder.original_array_gte)).map(combine_two_values); assert_eq(sorted, builder.sorted_transformed_value_array); builder.execute(); @@ -308,7 +261,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Tweak the value at index 1. - builder.sorted_transformed_value_array[1].total += 1; + builder.sorted_transformed_value_array[1].value += 1; builder.execute(); } @@ -382,7 +335,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Clear an item. - builder.sorted_transformed_value_array[8] = TestValue::empty(); + builder.sorted_transformed_value_array[8] = TestCombinedValue::empty(); builder.hints[8] = CombinedOrderHint { counter: 0, original_index: 6 }; builder.execute(); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array/get_combined_order_hints.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array/get_combined_order_hints.nr index 94ec60bbbf6..c0a7511edbf 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array/get_combined_order_hints.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array/get_combined_order_hints.nr @@ -20,7 +20,7 @@ impl Eq for CombinedOrderHint { } } -unconstrained fn count_private_items(array: [T; N]) -> u32 where T: Ordered + Empty + Eq { +unconstrained pub fn count_private_items(array: [T; N]) -> u32 where T: Ordered + Empty + Eq { let mut length = 0; for item in array { if !is_empty(item) & (item.counter() == 0) { @@ -60,50 +60,27 @@ pub fn get_combined_order_hints_asc( mod tests { use crate::{ - abis::side_effect::Ordered, tests::utils::pad_end, traits::Empty, + tests::{types::TestValue, utils::pad_end}, utils::arrays::assert_combined_sorted_transformed_value_array::get_combined_order_hints::{CombinedOrderHint, get_combined_order_hints_asc} }; - struct TestItem { - value: Field, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.value == other.value) & (self.counter == other.counter) - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { value: 0, counter: 0 } - } - } - #[test] fn get_combined_order_hints_asc_full_non_empty() { let array_lt = pad_end( [ - TestItem { value: 600, counter: 9 }, - TestItem { value: 400, counter: 3 }, - TestItem { value: 500, counter: 6 } + TestValue { value: 600, counter: 9 }, + TestValue { value: 400, counter: 3 }, + TestValue { value: 500, counter: 6 } ], - TestItem::empty() + TestValue::empty() ); let array_gte = pad_end( [ - TestItem { value: 200, counter: 13 }, - TestItem { value: 100, counter: 19 }, - TestItem { value: 300, counter: 16 } + TestValue { value: 200, counter: 13 }, + TestValue { value: 100, counter: 19 }, + TestValue { value: 300, counter: 16 } ], - TestItem::empty() + TestValue::empty() ); let expected_hints = [ CombinedOrderHint { counter: 3, original_index: 1 }, @@ -120,12 +97,12 @@ mod tests { fn get_combined_order_hints_asc_padded_empty() { let array_lt = pad_end( [ - TestItem { value: 500, counter: 6 }, - TestItem { value: 400, counter: 3 } + TestValue { value: 500, counter: 6 }, + TestValue { value: 400, counter: 3 } ], - TestItem::empty() + TestValue::empty() ); - let array_gte = pad_end([TestItem { value: 100, counter: 19 }], TestItem::empty()); + let array_gte = pad_end([TestValue { value: 100, counter: 19 }], TestValue::empty()); let expected_hints = [ CombinedOrderHint { counter: 3, original_index: 1 }, CombinedOrderHint { counter: 6, original_index: 0 }, @@ -138,13 +115,13 @@ mod tests { #[test] fn get_combined_order_hints_asc_lt_empty() { - let array_lt = [TestItem::empty(); 4]; + let array_lt = [TestValue::empty(); 4]; let array_gte = pad_end( [ - TestItem { value: 200, counter: 13 }, - TestItem { value: 100, counter: 19 } + TestValue { value: 200, counter: 13 }, + TestValue { value: 100, counter: 19 } ], - TestItem::empty() + TestValue::empty() ); let expected_hints = [ CombinedOrderHint { counter: 13, original_index: 0 }, @@ -159,12 +136,12 @@ mod tests { fn get_combined_order_hints_asc_gte_empty() { let array_lt = pad_end( [ - TestItem { value: 400, counter: 3 }, - TestItem { value: 500, counter: 6 } + TestValue { value: 400, counter: 3 }, + TestValue { value: 500, counter: 6 } ], - TestItem::empty() + TestValue::empty() ); - let array_gte = [TestItem::empty(); 4]; + let array_gte = [TestValue::empty(); 4]; let expected_hints = [ CombinedOrderHint { counter: 3, original_index: 0 }, CombinedOrderHint { counter: 6, original_index: 1 }, @@ -176,8 +153,8 @@ mod tests { #[test] fn get_combined_order_hints_asc_all_empty() { - let array_lt = [TestItem::empty(); 3]; - let array_gte = [TestItem::empty(); 3]; + let array_lt = [TestValue::empty(); 3]; + let array_gte = [TestValue::empty(); 3]; let expected_hints = [ CombinedOrderHint { counter: 0, original_index: 0 }, CombinedOrderHint { counter: 0, original_index: 1 }, @@ -190,20 +167,20 @@ mod tests { fn get_combined_order_hints_asc_prepended_zero_counters() { let array_lt = pad_end( [ - TestItem { value: 700, counter: 0 }, - TestItem { value: 500, counter: 0 }, - TestItem { value: 100, counter: 16 }, - TestItem { value: 400, counter: 13 } + TestValue { value: 700, counter: 0 }, + TestValue { value: 500, counter: 0 }, + TestValue { value: 100, counter: 16 }, + TestValue { value: 400, counter: 13 } ], - TestItem::empty() + TestValue::empty() ); let array_gte = pad_end( [ - TestItem { value: 300, counter: 0 }, - TestItem { value: 600, counter: 0 }, - TestItem { value: 200, counter: 19 } + TestValue { value: 300, counter: 0 }, + TestValue { value: 600, counter: 0 }, + TestValue { value: 200, counter: 19 } ], - TestItem::empty() + TestValue::empty() ); let expected_hints = [ CombinedOrderHint { counter: 0, original_index: 0 }, @@ -222,15 +199,15 @@ mod tests { #[test] fn get_combined_order_hints_asc_prepended_zero_counters_lt_empty() { - let array_lt = [TestItem::empty(); 6]; + let array_lt = [TestValue::empty(); 6]; let array_gte = pad_end( [ - TestItem { value: 300, counter: 0 }, - TestItem { value: 600, counter: 0 }, - TestItem { value: 200, counter: 19 }, - TestItem { value: 400, counter: 13 } + TestValue { value: 300, counter: 0 }, + TestValue { value: 600, counter: 0 }, + TestValue { value: 200, counter: 19 }, + TestValue { value: 400, counter: 13 } ], - TestItem::empty() + TestValue::empty() ); let expected_hints = [ CombinedOrderHint { counter: 0, original_index: 0 }, @@ -247,14 +224,14 @@ mod tests { fn get_combined_order_hints_asc_prepended_zero_counters_gte_empty() { let array_lt = pad_end( [ - TestItem { value: 300, counter: 0 }, - TestItem { value: 600, counter: 0 }, - TestItem { value: 200, counter: 19 }, - TestItem { value: 400, counter: 13 } + TestValue { value: 300, counter: 0 }, + TestValue { value: 600, counter: 0 }, + TestValue { value: 200, counter: 19 }, + TestValue { value: 400, counter: 13 } ], - TestItem::empty() + TestValue::empty() ); - let array_gte = [TestItem::empty(); 6]; + let array_gte = [TestValue::empty(); 6]; let expected_hints = [ CombinedOrderHint { counter: 0, original_index: 0 }, CombinedOrderHint { counter: 0, original_index: 1 }, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_transformed_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_transformed_array.nr new file mode 100644 index 00000000000..bd81809c9b9 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_transformed_array.nr @@ -0,0 +1,164 @@ +use crate::{traits::Empty, utils::arrays::array_length}; + +// original_array(_lt/_gte) must be valid, i.e. validate_array(original_array) == true +// This ensures that combined_array is valid when S can only be empty if and only if T is empty. +pub fn assert_combined_transformed_array( + original_array_lt: [T; N], + original_array_gte: [T; N], + combined_array: [S; N], + is_transformed: fn[Env](T, S) -> bool +) where T: Empty + Eq { + let num_lt = array_length(original_array_lt); + let mut is_lt = true; + for i in 0..N { + is_lt &= i != num_lt; + + let from = if is_lt { + original_array_lt[i] + } else { + original_array_gte[i - num_lt] + }; + + let to = combined_array[i]; + + assert(is_transformed(from, to), "hinted item in the commbined array does not match"); + } +} + +unconstrained pub fn combine_and_transform_arrays( + original_array_lt: [T; N], + original_array_gte: [T; N], + transform: fn[Env](T) -> S +) -> [S; N] where T: Empty + Eq { + let mut combined = original_array_lt.map(transform); + + let num_lt = array_length(original_array_lt); + for i in 0..N { + if i >= num_lt { + let from = original_array_gte[i - num_lt]; + combined[i] = transform(from); + } + } + + combined +} + +mod tests { + use crate::{ + tests::{types::{is_summed_from_two_values, sum_two_values, TestTwoValues, TestValue}, utils::pad_end}, + utils::arrays::assert_combined_transformed_array::{assert_combined_transformed_array, combine_and_transform_arrays} + }; + + struct TestBuilder { + original_array_lt: [TestTwoValues; N], + original_array_gte: [TestTwoValues; N], + combined_array: [TestValue; N], + } + + impl TestBuilder<10> { + pub fn new_empty() -> Self { + let original_array_lt = pad_end([], TestTwoValues::empty()); + let original_array_gte = pad_end([], TestTwoValues::empty()); + let combined_array = pad_end([], TestValue::empty()); + TestBuilder { original_array_lt, original_array_gte, combined_array } + } + + pub fn new() -> Self { + let original_array_lt = pad_end( + [ + TestTwoValues { value_1: 10, value_2: 1, counter: 2 }, + TestTwoValues { value_1: 20, value_2: 2, counter: 5 }, + TestTwoValues { value_1: 30, value_2: 3, counter: 3 } + ], + TestTwoValues::empty() + ); + + let original_array_gte = pad_end( + [ + TestTwoValues { value_1: 40, value_2: 4, counter: 1 }, + TestTwoValues { value_1: 50, value_2: 5, counter: 4 } + ], + TestTwoValues::empty() + ); + + let combined_array = pad_end( + [ + TestValue { value: 11, counter: 2 }, + TestValue { value: 22, counter: 5 }, + TestValue { value: 33, counter: 3 }, + TestValue { value: 44, counter: 1 }, + TestValue { value: 55, counter: 4 } + ], + TestValue::empty() + ); + + TestBuilder { original_array_lt, original_array_gte, combined_array } + } + + pub fn execute(self) { + assert_combined_transformed_array( + self.original_array_lt, + self.original_array_gte, + self.combined_array, + is_summed_from_two_values + ); + } + + pub fn check_and_execute(self) { + let combined = unsafe { + combine_and_transform_arrays( + self.original_array_lt, + self.original_array_gte, + sum_two_values + ) + }; + assert_eq(combined, self.combined_array); + + self.execute(); + } + } + + #[test] + fn assert_combined_transformed_array_empty_succeeds() { + let builder = TestBuilder::new_empty(); + builder.check_and_execute(); + } + + #[test] + fn assert_combined_transformed_array_succeeds() { + let builder = TestBuilder::new(); + builder.check_and_execute(); + } + + #[test(should_fail_with="hinted item in the commbined array does not match")] + fn assert_combined_transformed_array_extra_item_fails() { + let mut builder = TestBuilder::new(); + + // Add random value to an empty item. + builder.combined_array[7].value = 123; + + builder.execute(); + } + + #[test(should_fail_with="hinted item in the commbined array does not match")] + fn assert_combined_transformed_array_missing_item_fails() { + let mut builder = TestBuilder::new(); + + // Clear the last item. + builder.combined_array[4] = TestValue::empty(); + + builder.execute(); + } + + #[test(should_fail_with="hinted item in the commbined array does not match")] + fn assert_combined_transformed_array_unordered_fails() { + let mut builder = TestBuilder::new(); + + // Swap the two items. + let tmp = builder.combined_array[3]; + builder.combined_array[3] = builder.combined_array[1]; + builder.combined_array[1] = tmp; + + builder.execute(); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_deduped_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_deduped_array.nr new file mode 100644 index 00000000000..3bafbc0de97 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_deduped_array.nr @@ -0,0 +1,153 @@ +use crate::{abis::side_effect::{Inner, Overridable}, traits::{Empty, is_empty}}; + +// Check that deduped_array contains values that are not being overriden, i.e. override_counter == 0. +pub fn assert_deduped_array( + original_array: [S; N], + deduped_array: [T; N] +) where S: Overridable + Inner, T: Eq + Empty { + let mut num_added = 0; + for i in 0..original_array.len() { + let original = original_array[i]; + if original.override_counter() == 0 { + assert_eq(original.inner(), deduped_array[num_added], "mismatch deduped item"); + num_added += 1; + } + } + let mut should_be_empty = false; + for i in 0..original_array.len() { + should_be_empty |= i == num_added; + if should_be_empty { + assert(is_empty(deduped_array[i]), "empty items must be padded to the deduped array"); + } + } +} + +pub fn dedupe_array(original_array: [S; N]) -> [T; N] where S: Overridable + Inner { + let mut deduped = BoundedVec::new(); + for i in 0..original_array.len() { + let original = original_array[i]; + if original.override_counter() == 0 { + deduped.push(original.inner()); + } + } + deduped.storage +} + +mod tests { + use crate::{ + abis::side_effect::{Inner, Overridable}, tests::{types::TestValue, utils::pad_end}, + traits::Empty, utils::arrays::assert_deduped_array::{assert_deduped_array, dedupe_array} + }; + + struct TestItem { + value: TestValue, + override_by: TestValue, + } + + impl Overridable for TestItem { + fn override_counter(self) -> u32 { + self.override_by.counter + } + } + + impl Inner for TestItem { + fn inner(self) -> TestValue { + self.value + } + } + + impl Empty for TestItem { + fn empty() -> Self { + TestItem { value: TestValue::empty(), override_by: TestValue::empty() } + } + } + + impl Eq for TestItem { + fn eq(self, other: Self) -> bool { + (self.value == other.value) & (self.override_by == other.override_by) + } + } + + global NUM_TEST_ITEMS = 10; + + struct TestBuilder { + original_array: [TestItem; NUM_TEST_ITEMS], + deduped_array: [TestValue; NUM_TEST_ITEMS], + } + + impl TestBuilder { + pub fn new_empty() -> Self { + let original_array = pad_end([], TestItem::empty()); + let deduped_array = pad_end([], TestValue::empty()); + TestBuilder { original_array, deduped_array } + } + + pub fn new() -> Self { + let values = [ + TestValue { value: 22, counter: 2 }, + TestValue { value: 11, counter: 1 }, + TestValue { value: 44, counter: 4 }, + TestValue { value: 11, counter: 6 }, + TestValue { value: 11, counter: 3 }, + TestValue { value: 33, counter: 5 } + ]; + let mut original_array = pad_end( + values.map(|value: TestValue| TestItem { value, override_by: TestValue::empty() }), + TestItem::empty() + ); + original_array[1].override_by = values[4]; + original_array[4].override_by = values[3]; + + let deduped_array = pad_end( + [ + values[0], + values[2], + values[3], + values[5] + ], + TestValue::empty() + ); + + TestBuilder { original_array, deduped_array } + } + + pub fn execute(self) { + assert_deduped_array(self.original_array, self.deduped_array); + } + + pub fn check_and_execute(self) { + assert_eq(self.deduped_array, dedupe_array(self.original_array)); + self.execute(); + } + } + + #[test] + fn assert_deduped_array_empty_succeeds() { + let builder = TestBuilder::new_empty(); + builder.check_and_execute(); + } + + #[test] + fn assert_deduped_array_succeeds() { + let builder = TestBuilder::new(); + builder.check_and_execute(); + } + + #[test(should_fail_with="mismatch deduped item")] + fn assert_deduped_array_extra_item_fails() { + let mut builder = TestBuilder::new(); + + builder.deduped_array[7] = builder.original_array[1].inner(); + + builder.execute(); + } + + #[test(should_fail_with="empty items must be padded to the deduped array")] + fn assert_deduped_array_extra_item_at_end_fails() { + let mut builder = TestBuilder::new(); + + builder.deduped_array[NUM_TEST_ITEMS - 1] = builder.original_array[1].inner(); + + builder.execute(); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array.nr index dd0260be56a..4a4929c6bf4 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array.nr @@ -38,80 +38,34 @@ pub fn assert_exposed_sorted_transformed_value_array( mod tests { use crate::{ - abis::side_effect::Ordered, traits::Empty, + tests::types::{is_combined_from_two_values, TestCombinedValue, TestTwoValues}, utils::arrays::{assert_exposed_sorted_transformed_value_array::{assert_exposed_sorted_transformed_value_array, get_order_hints::OrderHint}} }; - struct TestItem { - name: Field, - price: Field, - tax: Field, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { name: 0, price: 0, tax: 0, counter: 0 } - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.price == other.price) & (self.tax == other.tax) & (self.counter == other.counter) - } - } - - struct TestValue { - name: Field, - total: Field, - } - - impl Empty for TestValue { - fn empty() -> Self { - TestValue { name: 0, total: 0 } - } - } - - impl Eq for TestValue { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.total == other.total) - } - } - - fn is_transformed(item: TestItem, value: TestValue) -> bool { - (item.name == value.name) & ((item.price + item.tax) == value.total) - } - - struct TestDataBuilder { - original_array: [T; N], - exposed_sorted_transformed_value_array: [S; N], + struct TestDataBuilder { + original_array: [TestTwoValues; N], + exposed_sorted_transformed_value_array: [TestCombinedValue; N], hints: [OrderHint; N], } - impl TestDataBuilder { + impl TestDataBuilder<6> { pub fn new() -> Self { let original_array = [ - TestItem { name: 100, price: 10, tax: 5, counter: 44 }, - TestItem { name: 200, price: 20, tax: 6, counter: 22 }, - TestItem { name: 300, price: 30, tax: 7, counter: 11 }, - TestItem { name: 400, price: 40, tax: 8, counter: 33 }, - TestItem::empty(), - TestItem::empty() + TestTwoValues { value_1: 10, value_2: 5, counter: 44 }, + TestTwoValues { value_1: 20, value_2: 6, counter: 22 }, + TestTwoValues { value_1: 30, value_2: 7, counter: 11 }, + TestTwoValues { value_1: 40, value_2: 8, counter: 33 }, + TestTwoValues::empty(), + TestTwoValues::empty() ]; let exposed_sorted_transformed_value_array = [ - TestValue { name: 300, total: 37 }, - TestValue { name: 200, total: 26 }, - TestValue { name: 400, total: 48 }, - TestValue { name: 100, total: 15 }, - TestValue::empty(), - TestValue::empty() + TestCombinedValue { value: 37 }, + TestCombinedValue { value: 26 }, + TestCombinedValue { value: 48 }, + TestCombinedValue { value: 15 }, + TestCombinedValue::empty(), + TestCombinedValue::empty() ]; let hints = [ @@ -130,7 +84,7 @@ mod tests { assert_exposed_sorted_transformed_value_array( self.original_array, self.exposed_sorted_transformed_value_array, - is_transformed, + is_combined_from_two_values, self.hints ); } @@ -147,7 +101,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Tweak the value at index 1. - builder.exposed_sorted_transformed_value_array[1].total += 1; + builder.exposed_sorted_transformed_value_array[1].value += 1; builder.execute(); } @@ -202,7 +156,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Add a random item. - builder.exposed_sorted_transformed_value_array[4] = TestValue { name: 500, total: 10 }; + builder.exposed_sorted_transformed_value_array[4] = TestCombinedValue { value: 10 }; builder.execute(); } @@ -212,7 +166,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Add a random item. - builder.exposed_sorted_transformed_value_array[4] = TestValue { name: 500, total: 10 }; + builder.exposed_sorted_transformed_value_array[4] = TestCombinedValue { value: 10 }; // Change the hint to point to an empty item. builder.hints[4].sorted_index = 5; @@ -224,7 +178,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Remove an item. - builder.exposed_sorted_transformed_value_array[3] = TestValue::empty(); + builder.exposed_sorted_transformed_value_array[3] = TestCombinedValue::empty(); builder.execute(); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array/get_order_hints.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array/get_order_hints.nr index 6bfdca7a162..87cc4f1a2fc 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array/get_order_hints.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array/get_order_hints.nr @@ -1,5 +1,5 @@ use crate::{ - abis::side_effect::Ordered, traits::{Empty, is_empty}, + abis::side_effect::Ordered, traits::Empty, utils::arrays::{ sort_by_counter::{compare_by_counter_empty_padded_asc, compare_by_counter_empty_padded_desc}, get_sorted_tuple::get_sorted_tuple @@ -26,19 +26,15 @@ impl Eq for OrderHint { pub fn get_order_hints( array: [T; N], ordering: fn(T, T) -> bool -) -> [OrderHint; N] where T: Ordered + Eq + Empty { +) -> [OrderHint; N] where T: Ordered { let sorted_tuples = get_sorted_tuple(array, ordering); let mut hints = [OrderHint::empty(); N]; for i in 0..N { let elem = sorted_tuples[i].elem; hints[i].counter = elem.counter(); - if !is_empty(elem) { - let original_index = sorted_tuples[i].original_index; - hints[original_index].sorted_index = i; - } else { - hints[i].sorted_index = i; - } + let original_index = sorted_tuples[i].original_index; + hints[original_index].sorted_index = i; } hints @@ -54,39 +50,16 @@ pub fn get_order_hints_desc(array: [T; N]) -> [OrderHint; N] wher mod tests { use crate::{ - abis::side_effect::Ordered, traits::Empty, + tests::types::TestValue, utils::arrays::assert_exposed_sorted_transformed_value_array::get_order_hints::{get_order_hints_asc, get_order_hints_desc, OrderHint} }; - struct TestItem { - value: Field, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.value == other.value) & (self.counter == other.counter) - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { value: 0, counter: 0 } - } - } - #[test] fn get_order_hints_asc_full_non_empty() { let array = [ - TestItem { value: 100, counter: 9 }, - TestItem { value: 200, counter: 3 }, - TestItem { value: 300, counter: 6 } + TestValue { value: 100, counter: 9 }, + TestValue { value: 200, counter: 3 }, + TestValue { value: 300, counter: 6 } ]; let hints = get_order_hints_asc(array); let expected_hints = [ @@ -100,11 +73,11 @@ mod tests { #[test] fn get_order_hints_asc_padded_empty() { let array = [ - TestItem { value: 100, counter: 9 }, - TestItem { value: 200, counter: 3 }, - TestItem { value: 300, counter: 6 }, - TestItem::empty(), - TestItem::empty() + TestValue { value: 100, counter: 9 }, + TestValue { value: 200, counter: 3 }, + TestValue { value: 300, counter: 6 }, + TestValue::empty(), + TestValue::empty() ]; let hints = get_order_hints_asc(array); let expected_hints = [ @@ -120,9 +93,9 @@ mod tests { #[test] fn get_order_hints_desc_full_non_empty() { let array = [ - TestItem { value: 100, counter: 9 }, - TestItem { value: 200, counter: 3 }, - TestItem { value: 300, counter: 6 } + TestValue { value: 100, counter: 9 }, + TestValue { value: 200, counter: 3 }, + TestValue { value: 300, counter: 6 } ]; let hints = get_order_hints_desc(array); let expected_hints = [ @@ -136,11 +109,11 @@ mod tests { #[test] fn get_order_hints_desc_padded_empty() { let array = [ - TestItem { value: 100, counter: 9 }, - TestItem { value: 200, counter: 3 }, - TestItem { value: 300, counter: 6 }, - TestItem::empty(), - TestItem::empty() + TestValue { value: 100, counter: 9 }, + TestValue { value: 200, counter: 3 }, + TestValue { value: 300, counter: 6 }, + TestValue::empty(), + TestValue::empty() ]; let hints = get_order_hints_desc(array); let expected_hints = [ diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_sorted_transformed_value_array.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_sorted_transformed_value_array.nr index c85f82b3ed6..a8be9895087 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_sorted_transformed_value_array.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_sorted_transformed_value_array.nr @@ -53,81 +53,32 @@ pub fn assert_sorted_transformed_value_array_capped_size( mod tests { use crate::{ - abis::side_effect::Ordered, traits::Empty, + tests::types::{is_summed_from_two_values, TestTwoValues, TestValue}, utils::arrays::{assert_sorted_transformed_value_array::{assert_sorted_transformed_value_array, assert_sorted_transformed_value_array_capped_size}} }; - struct TestItem { - name: Field, - price: Field, - tax: Field, - counter: u32, - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { name: 0, price: 0, tax: 0, counter: 0 } - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.price == other.price) & (self.tax == other.tax) & (self.counter == other.counter) - } - } - - struct TestValue { - name: Field, - total: Field, - counter: u32, - } - - impl Ordered for TestValue { - fn counter(self) -> u32 { - self.counter - } - } - - impl Empty for TestValue { - fn empty() -> Self { - TestValue { name: 0, total: 0, counter: 0 } - } - } - - impl Eq for TestValue { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.total == other.total) & (self.counter == other.counter) - } - } - - fn is_transformed(item: TestItem, value: TestValue) -> bool { - (item.name == value.name) - & ((item.price + item.tax) == value.total) - & (item.counter == value.counter) - } - - struct TestDataBuilder { - original_array: [T; N], - sorted_transformed_value_array: [S; N], + struct TestDataBuilder { + original_array: [TestTwoValues; N], + sorted_transformed_value_array: [TestValue; N], sorted_indexes: [u32; N], } - impl TestDataBuilder { + impl TestDataBuilder<6> { pub fn new() -> Self { let original_array = [ - TestItem { name: 100, price: 10, tax: 5, counter: 44 }, - TestItem { name: 200, price: 20, tax: 6, counter: 22 }, - TestItem { name: 300, price: 30, tax: 7, counter: 11 }, - TestItem { name: 400, price: 40, tax: 8, counter: 33 }, - TestItem::empty(), - TestItem::empty() + TestTwoValues { value_1: 10, value_2: 5, counter: 44 }, + TestTwoValues { value_1: 20, value_2: 6, counter: 22 }, + TestTwoValues { value_1: 30, value_2: 7, counter: 11 }, + TestTwoValues { value_1: 40, value_2: 8, counter: 33 }, + TestTwoValues::empty(), + TestTwoValues::empty() ]; let sorted_transformed_value_array = [ - TestValue { name: 300, total: 37, counter: 11 }, - TestValue { name: 200, total: 26, counter: 22 }, - TestValue { name: 400, total: 48, counter: 33 }, - TestValue { name: 100, total: 15, counter: 44 }, + TestValue { value: 37, counter: 11 }, + TestValue { value: 26, counter: 22 }, + TestValue { value: 48, counter: 33 }, + TestValue { value: 15, counter: 44 }, TestValue::empty(), TestValue::empty() ]; @@ -141,7 +92,7 @@ mod tests { assert_sorted_transformed_value_array( self.original_array, self.sorted_transformed_value_array, - is_transformed, + is_summed_from_two_values, self.sorted_indexes ); } @@ -150,7 +101,7 @@ mod tests { assert_sorted_transformed_value_array_capped_size( self.original_array, self.sorted_transformed_value_array, - is_transformed, + is_summed_from_two_values, self.sorted_indexes, capped_size ); @@ -168,7 +119,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Tweak the value at index 1. - builder.sorted_transformed_value_array[1].total += 1; + builder.sorted_transformed_value_array[1].value += 1; builder.execute(); } @@ -198,7 +149,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Add a random item. - builder.sorted_transformed_value_array[4] = TestValue { name: 500, total: 10, counter: 55 }; + builder.sorted_transformed_value_array[4] = TestValue { value: 10, counter: 55 }; builder.execute(); } @@ -208,7 +159,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Add a random item. - builder.sorted_transformed_value_array[4] = TestValue { name: 500, total: 10, counter: 55 }; + builder.sorted_transformed_value_array[4] = TestValue { value: 10, counter: 55 }; // Change the hint to point to an empty item. builder.sorted_indexes[4] = 5; @@ -242,7 +193,7 @@ mod tests { let mut builder = TestDataBuilder::new(); // Add a random item outside of capped_size. - builder.sorted_transformed_value_array[4] = TestValue { name: 500, total: 10, counter: 55 }; + builder.sorted_transformed_value_array[4] = TestValue { value: 10, counter: 55 }; builder.execute_capped(4); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays.nr index 5efe0c698d5..1e87d8f539e 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays.nr @@ -1,7 +1,7 @@ mod get_split_order_hints; use crate::{ - abis::side_effect::Ordered, traits::{Empty, is_empty}, + abis::side_effect::Ordered, traits::Empty, utils::arrays::{ array_length, assert_split_sorted_transformed_value_arrays::get_split_order_hints::SplitOrderHints, validate_array @@ -115,7 +115,7 @@ pub fn assert_split_sorted_transformed_value_arrays_desc( mod tests { use crate::{ - abis::side_effect::Ordered, traits::Empty, + tests::types::{combine_two_values, TestCombinedValue, TestTwoValues}, utils::arrays::{ assert_split_sorted_transformed_value_arrays::{ assert_split_sorted_transformed_value_arrays_asc, @@ -124,80 +124,34 @@ mod tests { } }; - struct TestItem { - name: Field, - price: Field, - tax: Field, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { name: 0, price: 0, tax: 0, counter: 0 } - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.price == other.price) & (self.tax == other.tax) & (self.counter == other.counter) - } - } - - struct TestValue { - name: Field, - total: Field, - } - - impl Empty for TestValue { - fn empty() -> Self { - TestValue { name: 0, total: 0 } - } - } - - impl Eq for TestValue { - fn eq(self, other: Self) -> bool { - (self.name == other.name) & (self.total == other.total) - } - } - - fn transform_value(item: TestItem) -> TestValue { - TestValue { name: item.name, total: item.price + item.tax } - } - global original_array = [ - TestItem { name: 0, price: 1, tax: 0, counter: 33 }, - TestItem { name: 1, price: 10, tax: 6, counter: 44 }, - TestItem { name: 2, price: 20, tax: 7, counter: 11 }, - TestItem { name: 3, price: 30, tax: 8, counter: 0 }, - TestItem { name: 4, price: 40, tax: 9, counter: 22 }, - TestItem::empty(), - TestItem::empty(), - TestItem::empty() + TestTwoValues { value_1: 1, value_2: 0, counter: 33 }, + TestTwoValues { value_1: 10, value_2: 6, counter: 44 }, + TestTwoValues { value_1: 20, value_2: 7, counter: 11 }, + TestTwoValues { value_1: 30, value_2: 8, counter: 0 }, + TestTwoValues { value_1: 40, value_2: 9, counter: 22 }, + TestTwoValues::empty(), + TestTwoValues::empty(), + TestTwoValues::empty() ]; - struct TestDataBuilder { - original_array: [T; N], - transformed_value_array: [S; N], - sorted_transformed_value_array_lt: [S; N], - sorted_transformed_value_array_gte: [S; N], + struct TestDataBuilder { + original_array: [TestTwoValues; N], + transformed_value_array: [TestCombinedValue; N], + sorted_transformed_value_array_lt: [TestCombinedValue; N], + sorted_transformed_value_array_gte: [TestCombinedValue; N], split_counter: u32, hints: SplitOrderHints, ascending: bool, } - impl TestDataBuilder { + impl TestDataBuilder<8> { pub fn empty() -> Self { TestDataBuilder { - original_array: [TestItem::empty(); 8], - transformed_value_array: [TestValue::empty(); 8], - sorted_transformed_value_array_lt: [TestValue::empty(); 8], - sorted_transformed_value_array_gte: [TestValue::empty(); 8], + original_array: [TestTwoValues::empty(); 8], + transformed_value_array: [TestCombinedValue::empty(); 8], + sorted_transformed_value_array_lt: [TestCombinedValue::empty(); 8], + sorted_transformed_value_array_gte: [TestCombinedValue::empty(); 8], split_counter: 0, hints: SplitOrderHints::empty(), ascending: false @@ -205,28 +159,28 @@ mod tests { } pub fn new() -> Self { - let transformed_value_array = original_array.map(|item: TestItem| transform_value(item)); + let transformed_value_array = original_array.map(|item: TestTwoValues| combine_two_values(item)); let split_counter = 15; let sorted_transformed_value_array_lt = [ - TestValue { name: 3, total: 38 }, - TestValue { name: 2, total: 27 }, - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty() + TestCombinedValue { value: 38 }, + TestCombinedValue { value: 27 }, + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty() ]; let sorted_transformed_value_array_gte = [ - TestValue { name: 4, total: 49 }, - TestValue { name: 0, total: 1 }, - TestValue { name: 1, total: 16 }, - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty() + TestCombinedValue { value: 49 }, + TestCombinedValue { value: 1 }, + TestCombinedValue { value: 16 }, + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty() ]; let hints = SplitOrderHints { sorted_counters_lt: [0, 11, 0, 0, 0, 0, 0, 0], @@ -246,28 +200,28 @@ mod tests { } pub fn new_desc() -> Self { - let transformed_value_array = original_array.map(|item: TestItem| transform_value(item)); + let transformed_value_array = original_array.map(|item: TestTwoValues| combine_two_values(item)); let split_counter = 15; let sorted_transformed_value_array_lt = [ - TestValue { name: 2, total: 27 }, - TestValue { name: 3, total: 38 }, - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty() + TestCombinedValue { value: 27 }, + TestCombinedValue { value: 38 }, + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty() ]; let sorted_transformed_value_array_gte = [ - TestValue { name: 1, total: 16 }, - TestValue { name: 0, total: 1 }, - TestValue { name: 4, total: 49 }, - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty(), - TestValue::empty() + TestCombinedValue { value: 16 }, + TestCombinedValue { value: 1 }, + TestCombinedValue { value: 49 }, + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty(), + TestCombinedValue::empty() ]; let hints = SplitOrderHints { sorted_counters_lt: [11, 0, 0, 0, 0, 0, 0, 0], @@ -353,7 +307,7 @@ mod tests { fn assert_split_sorted_transformed_value_array_asc_empty_extra_item_fails() { let mut builder = TestDataBuilder::empty(); - builder.sorted_transformed_value_array_lt[0].total = 1; + builder.sorted_transformed_value_array_lt[0].value = 1; builder.execute(); } @@ -362,7 +316,7 @@ mod tests { fn assert_split_sorted_transformed_value_array_asc_lt_wrong_sorted_value_fails() { let mut builder = TestDataBuilder::new(); - builder.sorted_transformed_value_array_lt[0].total += 1; + builder.sorted_transformed_value_array_lt[0].value += 1; builder.execute(); } @@ -416,7 +370,7 @@ mod tests { // Move the item with counter 44 from _gte to _lt. builder.sorted_transformed_value_array_lt[2] = builder.sorted_transformed_value_array_gte[2]; builder.hints.sorted_counters_lt[2] = 44; - builder.sorted_transformed_value_array_gte[2] = TestValue::empty(); + builder.sorted_transformed_value_array_gte[2] = TestCombinedValue::empty(); builder.hints.sorted_counters_lt[2] = 0; builder.update_sorted_index(44, 2); // Item of counter 44 is now at index 2 of the sorted array. @@ -482,7 +436,7 @@ mod tests { builder.update_sorted_index(counter, to_index); } // Empty the values at index 0. - builder.sorted_transformed_value_array_lt[0] = TestValue::empty(); + builder.sorted_transformed_value_array_lt[0] = TestCombinedValue::empty(); builder.hints.sorted_counters_lt[0] = 0; builder.execute(); @@ -502,7 +456,7 @@ mod tests { builder.update_sorted_index(counter, to_index); } // Empty the values at index 0. - builder.sorted_transformed_value_array_gte[0] = TestValue::empty(); + builder.sorted_transformed_value_array_gte[0] = TestCombinedValue::empty(); builder.hints.sorted_counters_gte[0] = 0; builder.execute(); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays/get_split_order_hints.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays/get_split_order_hints.nr index f00a7650b30..4cd78bf529e 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays/get_split_order_hints.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays/get_split_order_hints.nr @@ -83,49 +83,26 @@ pub fn get_split_order_hints_desc( mod tests { use crate::{ - abis::side_effect::Ordered, traits::Empty, + tests::types::TestValue, utils::arrays::assert_split_sorted_transformed_value_arrays::get_split_order_hints::{get_split_order_hints_asc, get_split_order_hints_desc, SplitOrderHints} }; - struct TestItem { - value: Field, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.value == other.value) & (self.counter == other.counter) - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { value: 0, counter: 0 } - } - } - global full_array = [ - TestItem { value: 100, counter: 11 }, - TestItem { value: 200, counter: 17 }, - TestItem { value: 300, counter: 7 }, - TestItem { value: 400, counter: 5 }, - TestItem { value: 500, counter: 13 } + TestValue { value: 100, counter: 11 }, + TestValue { value: 200, counter: 17 }, + TestValue { value: 300, counter: 7 }, + TestValue { value: 400, counter: 5 }, + TestValue { value: 500, counter: 13 } ]; global padded_array = [ - TestItem { value: 100, counter: 11 }, - TestItem { value: 200, counter: 17 }, - TestItem { value: 300, counter: 7 }, - TestItem { value: 400, counter: 5 }, - TestItem { value: 500, counter: 13 }, - TestItem::empty(), - TestItem::empty() + TestValue { value: 100, counter: 11 }, + TestValue { value: 200, counter: 17 }, + TestValue { value: 300, counter: 7 }, + TestValue { value: 400, counter: 5 }, + TestValue { value: 500, counter: 13 }, + TestValue::empty(), + TestValue::empty() ]; // asc diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_transformed_value_arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_transformed_value_arrays.nr index c1927035aae..472d70034cd 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_transformed_value_arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_transformed_value_arrays.nr @@ -70,41 +70,18 @@ pub fn assert_split_transformed_value_arrays( mod tests { use crate::{ - abis::side_effect::Ordered, tests::utils::pad_end, traits::Empty, + tests::{types::TestValue, utils::pad_end}, utils::arrays::assert_split_transformed_value_arrays::{assert_split_transformed_value_arrays, assert_split_transformed_value_arrays_with_hint} }; - struct TestItem { - value: Field, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.value == other.value) & (self.counter == other.counter) - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { value: 0, counter: 0 } - } - } - global NUM_TEST_ITEMS = 5; - fn is_transformed(from: TestItem, to: Field) -> bool { + fn is_transformed(from: TestValue, to: Field) -> bool { from.value == to } struct TestBuilder { - sorted_array: [TestItem; NUM_TEST_ITEMS], + sorted_array: [TestValue; NUM_TEST_ITEMS], transformed_value_array_lt: [Field; NUM_TEST_ITEMS], transformed_value_array_gte: [Field; NUM_TEST_ITEMS], split_counter: u32 @@ -113,11 +90,11 @@ mod tests { impl TestBuilder { pub fn new() -> Self { let sorted_array = [ - TestItem { value: 40, counter: 2 }, - TestItem { value: 30, counter: 7 }, - TestItem { value: 80, counter: 11 }, - TestItem { value: 10, counter: 13 }, - TestItem { value: 50, counter: 29 } + TestValue { value: 40, counter: 2 }, + TestValue { value: 30, counter: 7 }, + TestValue { value: 80, counter: 11 }, + TestValue { value: 10, counter: 13 }, + TestValue { value: 50, counter: 29 } ]; let transformed_value_array_lt = pad_end([40, 30, 80], 0); @@ -159,7 +136,7 @@ mod tests { fn assert_split_transformed_value_arrays_empty_succeeds() { let mut builder = TestBuilder::new(); - builder.sorted_array = [TestItem::empty(); NUM_TEST_ITEMS]; + builder.sorted_array = [TestValue::empty(); NUM_TEST_ITEMS]; builder.transformed_value_array_lt = [0; NUM_TEST_ITEMS]; builder.transformed_value_array_gte = [0; NUM_TEST_ITEMS]; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_hints.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_hints.nr deleted file mode 100644 index 9e6a490a008..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_hints.nr +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{traits::{Empty, is_empty}, utils::arrays::{get_sorted_tuple::{get_sorted_tuple, SortedTuple}}}; - -struct SortedResult { - sorted_array: [T; N], - sorted_index_hints: [u32; N], -} - -pub fn get_sorted_hints( - values: [T; N], - ordering: fn(T, T) -> bool -) -> SortedResult where T: Eq + Empty { - let sorted = get_sorted_tuple(values, ordering); - - let sorted_array = sorted.map(|t: SortedTuple| t.elem); - let mut sorted_index_hints = [0; N]; - for i in 0..N { - if !is_empty(sorted[i].elem) { - let original_index = sorted[i].original_index; - sorted_index_hints[original_index] = i; - } - } - - SortedResult { sorted_array, sorted_index_hints } -} - -#[test] -fn get_sorted_hints_asc_non_padded() { - let values = [40, 60, 20, 50]; - let res = get_sorted_hints(values, |a: Field, b: Field| a.lt(b)); - assert_eq(res.sorted_array, [20, 40, 50, 60]); - assert_eq(res.sorted_index_hints, [1, 3, 0, 2]); -} - -#[test] -fn get_sorted_hints_desc_non_padded() { - let values = [40, 20, 60, 50]; - let res = get_sorted_hints(values, |a: Field, b: Field| b.lt(a)); - assert_eq(res.sorted_array, [60, 50, 40, 20]); - assert_eq(res.sorted_index_hints, [2, 3, 0, 1]); -} - -#[test] -fn get_sorted_hints_asc_padded() { - let values = [40, 60, 20, 50, 0, 0]; - let res = get_sorted_hints(values, |a: Field, b: Field| (b == 0) | ((a != 0) & a.lt(b))); - assert_eq(res.sorted_array, [20, 40, 50, 60, 0, 0]); - assert_eq(res.sorted_index_hints, [1, 3, 0, 2, 0, 0]); -} - -#[test] -fn get_sorted_hints_desc_padded() { - let values = [40, 20, 60, 50, 0, 0]; - let res = get_sorted_hints(values, |a: Field, b: Field| (b == 0) | b.lt(a)); - assert_eq(res.sorted_array, [60, 50, 40, 20, 0, 0]); - assert_eq(res.sorted_index_hints, [2, 3, 0, 1, 0, 0]); -} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_result.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_result.nr new file mode 100644 index 00000000000..06f78eea325 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_result.nr @@ -0,0 +1,59 @@ +use crate::{utils::arrays::{get_sorted_tuple::{get_sorted_tuple, SortedTuple}}}; + +struct SortedResult { + sorted_array: [T; N], + sorted_index_hints: [u32; N], + original_index_hints: [u32; N], +} + +pub fn get_sorted_result( + values: [T; N], + ordering: fn(T, T) -> bool +) -> SortedResult { + let sorted = get_sorted_tuple(values, ordering); + + let sorted_array = sorted.map(|t: SortedTuple| t.elem); + let original_index_hints = sorted.map(|t: SortedTuple| t.original_index); + let mut sorted_index_hints = [0; N]; + for i in 0..N { + let original_index = sorted[i].original_index; + sorted_index_hints[original_index] = i; + } + + SortedResult { sorted_array, sorted_index_hints, original_index_hints } +} + +#[test] +fn get_sorted_hints_asc_non_padded() { + let values = [40, 60, 20, 50]; + let res = get_sorted_result(values, |a: u32, b: u32| a < b); + assert_eq(res.sorted_array, [20, 40, 50, 60]); + assert_eq(res.sorted_index_hints, [1, 3, 0, 2]); + assert_eq(res.original_index_hints, [2, 0, 3, 1]); +} + +#[test] +fn get_sorted_hints_desc_non_padded() { + let values = [40, 20, 60, 50]; + let res = get_sorted_result(values, |a: u32, b: u32| b < a); + assert_eq(res.sorted_array, [60, 50, 40, 20]); + assert_eq(res.sorted_index_hints, [2, 3, 0, 1]); +} + +#[test] +fn get_sorted_hints_asc_padded() { + let values = [40, 60, 20, 50, 0, 0]; + let res = get_sorted_result(values, |a: u32, b: u32| (a != 0) & ((b == 0) | (a < b))); + assert_eq(res.sorted_array, [20, 40, 50, 60, 0, 0]); + assert_eq(res.sorted_index_hints, [1, 3, 0, 2, 4, 5]); + assert_eq(res.original_index_hints, [2, 0, 3, 1, 4, 5]); +} + +#[test] +fn get_sorted_hints_desc_padded() { + let values = [40, 60, 20, 50, 0, 0]; + let res = get_sorted_result(values, |a: u32, b: u32| (a != 0) & ((b == 0) | (b < a))); + assert_eq(res.sorted_array, [60, 50, 40, 20, 0, 0]); + assert_eq(res.sorted_index_hints, [2, 0, 3, 1, 4, 5]); + assert_eq(res.original_index_hints, [1, 3, 0, 2, 4, 5]); +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counter.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counter.nr index b23a317e59a..2f8da437b5f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counter.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counter.nr @@ -18,57 +18,34 @@ pub fn sort_by_counter_desc(array: [T; N]) -> [T; N] where T: Ord mod tests { use crate::{ - abis::side_effect::Ordered, traits::Empty, + tests::types::TestValue, utils::arrays::sort_by_counter::{ compare_by_counter_empty_padded_asc, compare_by_counter_empty_padded_desc, sort_by_counter_asc, sort_by_counter_desc } }; - struct TestItem { - value: u32, - counter: u32, - } - - impl Ordered for TestItem { - fn counter(self) -> u32 { - self.counter - } - } - - impl Eq for TestItem { - fn eq(self, other: Self) -> bool { - (self.value == other.value) & (self.counter == other.counter) - } - } - - impl Empty for TestItem { - fn empty() -> Self { - TestItem { value: 0, counter: 0 } - } - } - fn sort_by_values_asc(values: [u32; N]) -> [u32; N] { - let items = values.map(|value| TestItem { value, counter: value }); - sort_by_counter_asc(items).map(|item: TestItem| item.value) + let items = values.map(|value| TestValue { value: value as Field, counter: value }); + sort_by_counter_asc(items).map(|item: TestValue| item.counter) } fn sort_by_values_desc(values: [u32; N]) -> [u32; N] { - let items = values.map(|value| TestItem { value, counter: value }); - sort_by_counter_desc(items).map(|item: TestItem| item.value) + let items = values.map(|value| TestValue { value: value as Field, counter: value }); + sort_by_counter_desc(items).map(|item: TestValue| item.counter) } fn compare_test_items_asc(value_1: u32, value_2: u32) -> bool { compare_by_counter_empty_padded_asc( - TestItem { value: value_1, counter: value_1 }, - TestItem { value: value_2, counter: value_2 } + TestValue { value: value_1 as Field, counter: value_1 }, + TestValue { value: value_2 as Field, counter: value_2 } ) } fn compare_test_items_desc(value_1: u32, value_2: u32) -> bool { compare_by_counter_empty_padded_desc( - TestItem { value: value_1, counter: value_1 }, - TestItem { value: value_2, counter: value_2 } + TestValue { value: value_1 as Field, counter: value_1 }, + TestValue { value: value_2 as Field, counter: value_2 } ) } @@ -115,22 +92,22 @@ mod tests { #[test] fn sort_by_counter_asc_with_zero_counters() { let original = [ - TestItem { value: 55, counter: 1 }, - TestItem { value: 11, counter: 0 }, - TestItem { value: 33, counter: 2 }, - TestItem { value: 44, counter: 0 }, - TestItem { value: 22, counter: 0 }, - TestItem::empty(), - TestItem::empty() + TestValue { value: 55, counter: 1 }, + TestValue { value: 11, counter: 0 }, + TestValue { value: 33, counter: 2 }, + TestValue { value: 44, counter: 0 }, + TestValue { value: 22, counter: 0 }, + TestValue::empty(), + TestValue::empty() ]; let expected = [ - TestItem { value: 11, counter: 0 }, - TestItem { value: 44, counter: 0 }, - TestItem { value: 22, counter: 0 }, - TestItem { value: 55, counter: 1 }, - TestItem { value: 33, counter: 2 }, - TestItem::empty(), - TestItem::empty() + TestValue { value: 11, counter: 0 }, + TestValue { value: 44, counter: 0 }, + TestValue { value: 22, counter: 0 }, + TestValue { value: 55, counter: 1 }, + TestValue { value: 33, counter: 2 }, + TestValue::empty(), + TestValue::empty() ]; assert_eq(sort_by_counter_asc(original), expected); } @@ -138,22 +115,22 @@ mod tests { #[test] fn sort_by_counter_desc_with_zero_counters() { let original = [ - TestItem { value: 55, counter: 1 }, - TestItem { value: 11, counter: 0 }, - TestItem { value: 33, counter: 2 }, - TestItem { value: 44, counter: 0 }, - TestItem { value: 22, counter: 0 }, - TestItem::empty(), - TestItem::empty() + TestValue { value: 55, counter: 1 }, + TestValue { value: 11, counter: 0 }, + TestValue { value: 33, counter: 2 }, + TestValue { value: 44, counter: 0 }, + TestValue { value: 22, counter: 0 }, + TestValue::empty(), + TestValue::empty() ]; let expected = [ - TestItem { value: 33, counter: 2 }, - TestItem { value: 55, counter: 1 }, - TestItem { value: 22, counter: 0 }, - TestItem { value: 44, counter: 0 }, - TestItem { value: 11, counter: 0 }, - TestItem::empty(), - TestItem::empty() + TestValue { value: 33, counter: 2 }, + TestValue { value: 55, counter: 1 }, + TestValue { value: 22, counter: 0 }, + TestValue { value: 44, counter: 0 }, + TestValue { value: 11, counter: 0 }, + TestValue::empty(), + TestValue::empty() ]; assert_eq(sort_by_counter_desc(original), expected); } 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 index 3e038e13c9d..d88703a1d75 100644 --- 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 @@ -1,4 +1,5 @@ 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'; @@ -10,117 +11,106 @@ import { 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; - let sideEffectCounter = 0; - - const nextSideEffectCounter = () => sideEffectCounter++; 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), + new PublicDataTreeLeafPreimage(new Fr(0), new Fr(0), new Fr(11), 2n), + new PublicDataTreeLeafPreimage(new Fr(22), new Fr(200), new Fr(0), 0n), + new PublicDataTreeLeafPreimage(new Fr(11), new Fr(100), new Fr(22), 1n), ]; - const makePublicDataRead = (leafSlot: number, value: number) => new PublicDataRead(new Fr(leafSlot), new Fr(value)); + const makePublicDataRead = (leafSlot: number, value: number) => + new PublicDataRead(new Fr(leafSlot), new Fr(value), 0); const makePublicDataWrite = (leafSlot: number, value: number) => - new PublicDataUpdateRequest(new Fr(leafSlot), new Fr(value), nextSideEffectCounter()); + new PublicDataUpdateRequest(new Fr(leafSlot), new Fr(value), 0); const oracle = { getMatchOrLowPublicDataMembershipWitness: (leafSlot: bigint) => { - const leafPreimage = publicDataLeaves.find(l => l.slot.toBigInt() <= leafSlot); + const leafPreimage = publicDataLeaves.find( + l => l.slot.toBigInt() <= leafSlot && (l.nextSlot.isZero() || l.nextSlot.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); + const buildAndCheckHints = async (expectedSlots: number[]) => { + const hints = await buildPublicDataHints(oracle, publicDataReads, publicDataUpdateRequests); + const partialHints = expectedSlots.map(s => + expect.objectContaining({ + preimage: publicDataLeaves.find(l => l.slot.equals(new Fr(s))), + }), + ); + const emptyPartialHint = expect.objectContaining({ preimage: PublicDataTreeLeafPreimage.empty() }); + expect(hints).toEqual(padArrayEnd(partialHints, emptyPartialHint, MAX_PUBLIC_DATA_HINTS)); }; beforeEach(() => { publicDataReads = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, PublicDataRead.empty); publicDataUpdateRequests = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataUpdateRequest.empty); - expectedHints = makeTuple(MAX_PUBLIC_DATA_HINTS, ExpectedHint.empty); }); it('returns empty hints', async () => { - await buildAndCheckHints(); + 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(); + await buildAndCheckHints([11, 22]); }); 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(); + await buildAndCheckHints([22, 11]); }); 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(); + await buildAndCheckHints([0, 11]); }); 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([11, 22]); + }); - await buildAndCheckHints(); + it('skip hints for repeated reads', async () => { + publicDataReads[0] = makePublicDataRead(22, 200); // 22 + publicDataReads[1] = makePublicDataRead(39, 0); // 22 + publicDataReads[2] = makePublicDataRead(22, 200); // No hint needed because slot 22 was read. + publicDataReads[3] = makePublicDataRead(39, 0); // No hint needed because slot 39 was read. + publicDataReads[4] = makePublicDataRead(12, 0); // 11 + publicDataReads[5] = makePublicDataRead(39, 0); // // No hint needed because slot 39 was read. + await buildAndCheckHints([22, 22, 11]); }); - 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); + it('skip hints for repeated writes', async () => { + publicDataUpdateRequests[0] = makePublicDataWrite(11, 111); // 11 + publicDataUpdateRequests[1] = makePublicDataWrite(5, 500); // 0 + publicDataUpdateRequests[2] = makePublicDataWrite(11, 112); // No hint needed because slot 11 was written. + publicDataUpdateRequests[3] = makePublicDataWrite(17, 700); // 11 + publicDataUpdateRequests[4] = makePublicDataWrite(11, 113); // No hint needed because slot 11 was written. + publicDataUpdateRequests[5] = makePublicDataWrite(5, 222); // No hint needed because slot 5 was written. + publicDataUpdateRequests[6] = makePublicDataWrite(37, 700); // 22 + await buildAndCheckHints([11, 0, 11, 22]); + }); - await buildAndCheckHints(); + it('builds hints for mixed reads and writes', async () => { + publicDataReads[0] = makePublicDataRead(22, 200); // 22 + publicDataReads[1] = makePublicDataRead(7, 0); // 0 + publicDataReads[2] = makePublicDataRead(41, 0); // 22 + publicDataReads[3] = makePublicDataRead(11, 100); // 11 + publicDataReads[4] = makePublicDataRead(39, 0); // 22 + publicDataUpdateRequests[0] = makePublicDataWrite(11, 111); // No hint needed because slot 11 was read. + publicDataUpdateRequests[1] = makePublicDataWrite(5, 500); // 0 + publicDataUpdateRequests[2] = makePublicDataWrite(17, 700); // 11 + publicDataUpdateRequests[3] = makePublicDataWrite(22, 222); // No hint needed because slot 22 was read. + publicDataUpdateRequests[4] = makePublicDataWrite(39, 700); // No hint needed because slot 39 was read. + await buildAndCheckHints([22, 0, 22, 11, 22, 0, 11]); }); }); 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 index 940848ae82e..53200bab364 100644 --- a/yarn-project/circuits.js/src/hints/build_public_data_hints.ts +++ b/yarn-project/circuits.js/src/hints/build_public_data_hints.ts @@ -9,6 +9,7 @@ import { type PUBLIC_DATA_TREE_HEIGHT, } from '../constants.gen.js'; import { + PublicDataLeafHint, type PublicDataRead, type PublicDataTreeLeafPreimage, type PublicDataUpdateRequest, @@ -25,18 +26,22 @@ type PublicDataMembershipWitnessOracle = { getMatchOrLowPublicDataMembershipWitness(leafSlot: bigint): Promise; }; +async function buildPublicDataLeafHint(oracle: PublicDataMembershipWitnessOracle, leafSlot: bigint) { + const { membershipWitness, leafPreimage } = await oracle.getMatchOrLowPublicDataMembershipWitness(leafSlot); + return new PublicDataLeafHint(leafPreimage, membershipWitness); +} + export async function buildPublicDataHints( oracle: PublicDataMembershipWitnessOracle, publicDataReads: Tuple, publicDataUpdateRequests: Tuple, -): Promise> { - const publicDataLeafSlots = [...publicDataReads, ...publicDataUpdateRequests] - .filter(r => !r.isEmpty()) - .map(r => r.leafSlot.toBigInt()); - const uniquePublicDataLeafSlots = [...new Set(publicDataLeafSlots)]; - - const hints = await Promise.all(uniquePublicDataLeafSlots.map(slot => buildPublicDataHint(oracle, slot))); - return padArrayEnd(hints, PublicDataHint.empty(), MAX_PUBLIC_DATA_HINTS); +): Promise> { + const leafSlots = [...publicDataReads.map(r => r.leafSlot), ...publicDataUpdateRequests.map(w => w.leafSlot)] + .filter(s => !s.isZero()) + .map(s => s.toBigInt()); + const uniqueLeafSlots = [...new Set(leafSlots)]; + const hints = await Promise.all(uniqueLeafSlots.map(slot => buildPublicDataLeafHint(oracle, slot))); + return padArrayEnd(hints, PublicDataLeafHint.empty(), MAX_PUBLIC_DATA_HINTS); } export async function buildPublicDataHint(oracle: PublicDataMembershipWitnessOracle, leafSlot: bigint) { 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 deleted file mode 100644 index 4afea4a13b9..00000000000 --- a/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -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; - let counter = 0; - const nextCounter = () => counter++; - - const makePublicDataWrite = (leafSlot: number, value: number) => - new PublicDataUpdateRequest(new Fr(leafSlot), new Fr(value), nextCounter()); - 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 deleted file mode 100644 index 4f7b15f312b..00000000000 --- a/yarn-project/circuits.js/src/hints/build_public_data_read_request_hints.ts +++ /dev/null @@ -1,45 +0,0 @@ -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/build_transient_data_hints.test.ts b/yarn-project/circuits.js/src/hints/build_transient_data_hints.test.ts index ab4a63ba807..a4c0c921638 100644 --- a/yarn-project/circuits.js/src/hints/build_transient_data_hints.test.ts +++ b/yarn-project/circuits.js/src/hints/build_transient_data_hints.test.ts @@ -1,6 +1,7 @@ +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr } from '@aztec/foundation/fields'; + import { - AztecAddress, - Fr, NoteHash, Nullifier, ReadRequest, @@ -8,8 +9,7 @@ import { type ScopedNullifier, ScopedReadRequest, TransientDataIndexHint, -} from '@aztec/circuits.js'; - +} from '../structs/index.js'; import { buildTransientDataHints } from './build_transient_data_hints.js'; describe('buildTransientDataHints', () => { diff --git a/yarn-project/circuits.js/src/hints/index.ts b/yarn-project/circuits.js/src/hints/index.ts index 92b57bd25e1..67970d7bdc4 100644 --- a/yarn-project/circuits.js/src/hints/index.ts +++ b/yarn-project/circuits.js/src/hints/index.ts @@ -2,5 +2,4 @@ export * from './build_note_hash_read_request_hints.js'; export * from './build_nullifier_non_existent_read_request_hints.js'; export * from './build_nullifier_read_request_hints.js'; export * from './build_public_data_hints.js'; -export * from './build_public_data_read_request_hints.js'; export * from './build_transient_data_hints.js'; diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 19cb59bdeec..fdc31489a7a 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -60,8 +60,8 @@ export * from './public_call_stack_item.js'; export * from './public_call_stack_item_compressed.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_leaf_hint.js'; +export * from './public_data_read.js'; export * from './public_data_update_request.js'; export * from './public_validation_requests.js'; export * from './read_request.js'; 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 7c2be1c3da5..c0bb9b4d82f 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 @@ -13,8 +13,7 @@ import { 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 { PublicDataLeafHint } from '../public_data_leaf_hint.js'; import { type NullifierReadRequestHints, nullifierReadRequestHintsFromBuffer } from '../read_request_hints/index.js'; import { TreeLeafReadRequestHint } from '../tree_leaf_read_request_hint.js'; import { PublicKernelData } from './public_kernel_data.js'; @@ -44,8 +43,7 @@ export class PublicKernelTailCircuitPrivateInputs { TreeLeafReadRequestHint, typeof MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX >, - public readonly publicDataHints: Tuple, - public readonly publicDataReadRequestHints: PublicDataReadRequestHints, + public readonly publicDataHints: Tuple, public readonly startState: PartialStateReference, ) {} @@ -57,7 +55,6 @@ export class PublicKernelTailCircuitPrivateInputs { this.nullifierNonExistentReadRequestHints, this.l1ToL2MsgReadRequestHints, this.publicDataHints, - this.publicDataReadRequestHints, this.startState, ); } @@ -86,8 +83,7 @@ export class PublicKernelTailCircuitPrivateInputs { reader.readArray(MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX, { fromBuffer: buf => TreeLeafReadRequestHint.fromBuffer(buf, L1_TO_L2_MSG_TREE_HEIGHT), }), - reader.readArray(MAX_PUBLIC_DATA_HINTS, PublicDataHint), - reader.readObject(PublicDataReadRequestHints), + reader.readArray(MAX_PUBLIC_DATA_HINTS, PublicDataLeafHint), reader.readObject(PartialStateReference), ); } diff --git a/yarn-project/circuits.js/src/structs/public_data_leaf_hint.ts b/yarn-project/circuits.js/src/structs/public_data_leaf_hint.ts new file mode 100644 index 00000000000..aba7c583e37 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/public_data_leaf_hint.ts @@ -0,0 +1,28 @@ +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 './trees/index.js'; + +export class PublicDataLeafHint { + constructor( + public preimage: PublicDataTreeLeafPreimage, + public membershipWitness: MembershipWitness, + ) {} + + static empty() { + return new PublicDataLeafHint(PublicDataTreeLeafPreimage.empty(), MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT)); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new PublicDataLeafHint( + reader.readObject(PublicDataTreeLeafPreimage), + MembershipWitness.fromBuffer(reader, PUBLIC_DATA_TREE_HEIGHT), + ); + } + + toBuffer() { + return serializeToBuffer(this.preimage, this.membershipWitness); + } +} diff --git a/yarn-project/circuits.js/src/structs/public_data_read_request.ts b/yarn-project/circuits.js/src/structs/public_data_read.ts similarity index 56% rename from yarn-project/circuits.js/src/structs/public_data_read_request.ts rename to yarn-project/circuits.js/src/structs/public_data_read.ts index 4244f93ff9e..e5a8010fd7e 100644 --- a/yarn-project/circuits.js/src/structs/public_data_read_request.ts +++ b/yarn-project/circuits.js/src/structs/public_data_read.ts @@ -1,7 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; -// TODO: Rename to PublicDataReadRequest /** * Read operations from the public state tree. */ @@ -16,51 +15,34 @@ export class PublicDataRead { */ public readonly value: Fr, /** - * Optional side effect counter tracking position of this event in tx execution. + * Side effect counter tracking position of this event in tx execution. */ - public readonly sideEffectCounter?: number, + public readonly counter: number, ) {} - static from(args: { - /** - * Index of the leaf in the public data tree. - */ - leafIndex: Fr; - /** - * Returned value from the public data tree. - */ - value: Fr; - }) { - return new PublicDataRead(args.leafIndex, args.value); - } - toBuffer() { - return serializeToBuffer(this.leafSlot, this.value); + return serializeToBuffer(this.leafSlot, this.value, this.counter); } isEmpty() { - return this.leafSlot.isZero() && this.value.isZero(); + return this.leafSlot.isZero() && this.value.isZero() && !this.counter; } static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); - return new PublicDataRead(reader.readField(), reader.readField()); + return new PublicDataRead(reader.readField(), reader.readField(), reader.readU32()); } static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); - return new PublicDataRead(Fr.fromBuffer(reader), Fr.fromBuffer(reader)); + return new PublicDataRead(Fr.fromBuffer(reader), Fr.fromBuffer(reader), reader.readNumber()); } static empty() { - return new PublicDataRead(Fr.ZERO, Fr.ZERO); - } - - toFriendlyJSON() { - return `Leaf=${this.leafSlot.toFriendlyJSON()}: ${this.value.toFriendlyJSON()}`; + return new PublicDataRead(Fr.ZERO, Fr.ZERO, 0); } equals(other: PublicDataRead) { - return this.leafSlot.equals(other.leafSlot) && this.value.equals(other.value); + return this.leafSlot.equals(other.leafSlot) && this.value.equals(other.value) && this.counter == other.counter; } } 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 deleted file mode 100644 index 4d2cc36b1fd..00000000000 --- a/yarn-project/circuits.js/src/structs/public_data_read_request_hints.ts +++ /dev/null @@ -1,86 +0,0 @@ -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/index.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/public_validation_requests.ts b/yarn-project/circuits.js/src/structs/public_validation_requests.ts index 17e988c2f74..b9e7d179b5a 100644 --- a/yarn-project/circuits.js/src/structs/public_validation_requests.ts +++ b/yarn-project/circuits.js/src/structs/public_validation_requests.ts @@ -12,7 +12,7 @@ import { MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PUBLIC_DATA_READS_PER_TX, } from '../constants.gen.js'; -import { PublicDataRead } from './public_data_read_request.js'; +import { PublicDataRead } from './public_data_read.js'; import { ScopedReadRequest } from './read_request.js'; import { RollupValidationRequests } from './rollup_validation_requests.js'; import { TreeLeafReadRequest } from './tree_leaf_read_request.js'; diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 192869a48cb..90dbbec53de 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -108,7 +108,6 @@ import { PublicCircuitPublicInputs, PublicDataHint, PublicDataRead, - PublicDataReadRequestHintsBuilder, PublicDataTreeLeaf, PublicDataTreeLeafPreimage, PublicDataUpdateRequest, @@ -148,6 +147,7 @@ import { GasSettings } from '../structs/gas_settings.js'; import { GlobalVariables } from '../structs/global_variables.js'; import { Header } from '../structs/header.js'; import { + PublicDataLeafHint, PublicValidationRequests, ScopedL2ToL1Message, ScopedNoteHash, @@ -295,7 +295,7 @@ export function makeEmptyPublicDataUpdateRequest(): PublicDataUpdateRequest { * @returns A public data read. */ export function makePublicDataRead(seed = 1): PublicDataRead { - return new PublicDataRead(fr(seed), fr(seed + 1)); + return new PublicDataRead(fr(seed), fr(seed + 1), 0); } /** @@ -303,7 +303,7 @@ export function makePublicDataRead(seed = 1): PublicDataRead { * @returns An empty public data read. */ export function makeEmptyPublicDataRead(): PublicDataRead { - return new PublicDataRead(fr(0), fr(0)); + return new PublicDataRead(fr(0), fr(0), 0); } /** @@ -721,8 +721,7 @@ export function makePublicKernelTailCircuitPrivateInputs(seed = 1): PublicKernel s => makeTreeLeafReadRequestHint(s, L1_TO_L2_MSG_TREE_HEIGHT), seed + 0x80, ), - makeTuple(MAX_PUBLIC_DATA_HINTS, PublicDataHint.empty, seed + 0x100), - PublicDataReadRequestHintsBuilder.empty(), + makeTuple(MAX_PUBLIC_DATA_HINTS, PublicDataLeafHint.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 e84b9efbc7f..f6a2af89cf9 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -34,7 +34,6 @@ import { KeyValidationRequestAndGenerator, type L1_TO_L2_MSG_TREE_HEIGHT, L2ToL1Message, - type LeafDataReadHint, LogHash, MAX_ENCRYPTED_LOGS_PER_TX, MAX_KEY_VALIDATION_REQUESTS_PER_TX, @@ -98,8 +97,8 @@ import { PublicCallStackItemCompressed, type PublicCircuitPublicInputs, type PublicDataHint, + type PublicDataLeafHint, PublicDataRead, - type PublicDataReadRequestHints, type PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, PublicDataUpdateRequest, @@ -170,7 +169,6 @@ import type { KeyValidationRequestAndGenerator as KeyValidationRequestAndGeneratorNoir, KeyValidationRequest as KeyValidationRequestsNoir, L2ToL1Message as L2ToL1MessageNoir, - LeafDataReadHint as LeafDataReadHintNoir, LogHash as LogHashNoir, MaxBlockNumber as MaxBlockNumberNoir, MembershipWitness as MembershipWitnessNoir, @@ -219,8 +217,8 @@ import type { PublicCallStackItem as PublicCallStackItemNoir, PublicCircuitPublicInputs as PublicCircuitPublicInputsNoir, PublicDataHint as PublicDataHintNoir, + PublicDataLeafHint as PublicDataLeafHintNoir, PublicDataRead as PublicDataReadNoir, - PublicDataReadRequestHints as PublicDataReadRequestHintsNoir, PublicDataTreeLeaf as PublicDataTreeLeafNoir, PublicDataTreeLeafPreimage as PublicDataTreeLeafPreimageNoir, PublicDataUpdateRequest as PublicDataUpdateRequestNoir, @@ -1069,7 +1067,11 @@ export function mapPublicDataUpdateRequestToNoir( * @returns The parsed public data read. */ export function mapPublicDataReadFromNoir(publicDataRead: PublicDataReadNoir): PublicDataRead { - return new PublicDataRead(mapFieldFromNoir(publicDataRead.leaf_slot), mapFieldFromNoir(publicDataRead.value)); + return new PublicDataRead( + mapFieldFromNoir(publicDataRead.leaf_slot), + mapFieldFromNoir(publicDataRead.value), + mapNumberFromNoir(publicDataRead.counter), + ); } /** @@ -1081,6 +1083,7 @@ export function mapPublicDataReadToNoir(publicDataRead: PublicDataRead): PublicD return { leaf_slot: mapFieldToNoir(publicDataRead.leafSlot), value: mapFieldToNoir(publicDataRead.value), + counter: mapNumberToNoir(publicDataRead.counter), }; } @@ -1098,13 +1101,6 @@ function mapPendingReadHintToNoir(hint: PendingReadHint): PendingReadHintNoir { }; } -function mapLeafDataReadHintToNoir(hint: LeafDataReadHint): LeafDataReadHintNoir { - return { - read_request_index: mapNumberToNoir(hint.readRequestIndex), - data_hint_index: mapNumberToNoir(hint.dataHintIndex), - }; -} - function mapTreeLeafReadRequestHintToNoir( hint: TreeLeafReadRequestHint, ): TreeLeafReadRequestHintNoir { @@ -1196,11 +1192,10 @@ function mapPublicDataHintToNoir(hint: PublicDataHint): PublicDataHintNoir { }; } -function mapPublicDataReadRequestHintsToNoir(hints: PublicDataReadRequestHints): PublicDataReadRequestHintsNoir { +function mapPublicDataLeafHintToNoir(hint: PublicDataLeafHint): PublicDataLeafHintNoir { return { - read_request_statuses: mapTuple(hints.readRequestStatuses, mapReadRequestStatusToNoir), - pending_read_hints: mapTuple(hints.pendingReadHints, mapPendingReadHintToNoir), - leaf_data_read_hints: mapTuple(hints.leafDataReadHints, mapLeafDataReadHintToNoir), + preimage: mapPublicDataTreePreimageToNoir(hint.preimage), + membership_witness: mapMembershipWitnessToNoir(hint.membershipWitness), }; } @@ -1799,8 +1794,7 @@ export function mapPublicKernelTailCircuitPrivateInputsToNoir( inputs.l1ToL2MsgReadRequestHints, (hint: TreeLeafReadRequestHint) => mapTreeLeafReadRequestHintToNoir(hint), ), - public_data_hints: mapTuple(inputs.publicDataHints, mapPublicDataHintToNoir), - public_data_read_request_hints: mapPublicDataReadRequestHintsToNoir(inputs.publicDataReadRequestHints), + public_data_hints: mapTuple(inputs.publicDataHints, mapPublicDataLeafHintToNoir), start_state: mapPartialStateReferenceToNoir(inputs.startState), }; } diff --git a/yarn-project/simulator/src/public/hints_builder.ts b/yarn-project/simulator/src/public/hints_builder.ts index 439aa7a5fa3..637d70d30e9 100644 --- a/yarn-project/simulator/src/public/hints_builder.ts +++ b/yarn-project/simulator/src/public/hints_builder.ts @@ -7,7 +7,6 @@ import { type MAX_NULLIFIERS_PER_TX, type MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, - type MAX_PUBLIC_DATA_HINTS, type MAX_PUBLIC_DATA_READS_PER_TX, type MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MembershipWitness, @@ -15,7 +14,6 @@ import { NULLIFIER_TREE_HEIGHT, type Nullifier, PUBLIC_DATA_TREE_HEIGHT, - type PublicDataHint, type PublicDataRead, type PublicDataTreeLeafPreimage, type PublicDataUpdateRequest, @@ -25,7 +23,6 @@ import { buildNullifierNonExistentReadRequestHints, buildPublicDataHint, buildPublicDataHints, - buildPublicDataReadRequestHints, buildSiloedNullifierReadRequestHints, } from '@aztec/circuits.js'; import { makeTuple } from '@aztec/foundation/array'; @@ -91,14 +88,6 @@ export class HintsBuilder { return buildPublicDataHint(this, slot); } - 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) { diff --git a/yarn-project/simulator/src/public/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor.test.ts index 1b79581195b..7778147a3ef 100644 --- a/yarn-project/simulator/src/public/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor.test.ts @@ -396,13 +396,11 @@ describe('public_processor', () => { computePublicDataTreeLeafSlot(nonRevertibleRequests[0].callContext.storageContractAddress, contractSlotA), fr(0x101), ), - new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotE), fr(0x301)), new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotC), fr(0x201)), - new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotF), fr(0x351)), new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotD), fr(0x251)), + new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotE), fr(0x301)), + new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotF), fr(0x351)), ]; - // sort increasing by leafIndex - expectedWrites.sort((a, b) => (a.leafIndex.lt(b.leafIndex) ? -1 : 1)); expect(txEffect.publicDataWrites.slice(0, numPublicDataWrites)).toEqual(expectedWrites); // we keep the non-revertible logs @@ -559,7 +557,7 @@ describe('public_processor', () => { PublicExecutionResultBuilder.fromFunctionCall({ from: teardownRequest.callContext.storageContractAddress, tx: makeFunctionCall('', nestedContractAddress, makeSelector(5)), - contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotC, fr(0x202), 16)], + contractStorageUpdateRequests: [new ContractStorageUpdateRequest(contractSlotC, fr(0x202), 17)], revertReason: new SimulationError('Simulation Failed', []), }).build(teardownResultSettings), ], @@ -598,12 +596,12 @@ describe('public_processor', () => { const numPublicDataWrites = 3; expect(arrayNonEmptyLength(txEffect.publicDataWrites, PublicDataWrite.isEmpty)).toBe(numPublicDataWrites); expect(txEffect.publicDataWrites.slice(0, numPublicDataWrites)).toEqual([ - new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotB), fr(0x151)), new PublicDataWrite( computePublicDataTreeLeafSlot(nonRevertibleRequests[0].callContext.storageContractAddress, contractSlotA), fr(0x101), ), new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotA), fr(0x102)), + new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotB), fr(0x151)), ]); // we keep the non-revertible logs @@ -714,12 +712,12 @@ describe('public_processor', () => { const numPublicDataWrites = 3; expect(arrayNonEmptyLength(txEffect.publicDataWrites, PublicDataWrite.isEmpty)).toBe(numPublicDataWrites); expect(txEffect.publicDataWrites.slice(0, numPublicDataWrites)).toEqual([ - new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotB), fr(0x151)), new PublicDataWrite( computePublicDataTreeLeafSlot(nonRevertibleRequests[0].callContext.storageContractAddress, contractSlotA), fr(0x101), ), new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotA), fr(0x102)), + new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotB), fr(0x151)), ]); // we keep the non-revertible logs @@ -892,8 +890,8 @@ describe('public_processor', () => { expect(arrayNonEmptyLength(txEffect.publicDataWrites, PublicDataWrite.isEmpty)).toEqual(numPublicDataWrites); expect(txEffect.publicDataWrites.slice(0, numPublicDataWrites)).toEqual([ new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotB), fr(0x152)), - new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotC), fr(0x201)), new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotA), fr(0x103)), + new PublicDataWrite(computePublicDataTreeLeafSlot(nestedContractAddress, contractSlotC), fr(0x201)), ]); expect(txEffect.encryptedLogs.getTotalLogCount()).toBe(0); expect(txEffect.unencryptedLogs.getTotalLogCount()).toBe(0); diff --git a/yarn-project/simulator/src/public/tail_phase_manager.ts b/yarn-project/simulator/src/public/tail_phase_manager.ts index 155a8fec14a..08b0916d30c 100644 --- a/yarn-project/simulator/src/public/tail_phase_manager.ts +++ b/yarn-project/simulator/src/public/tail_phase_manager.ts @@ -112,12 +112,6 @@ export class TailPhaseManager extends AbstractPhaseManager { pendingPublicDataWrites, ); - const publicDataReadRequestHints = this.hintsBuilder.getPublicDataReadRequestHints( - validationRequests.publicDataReads, - pendingPublicDataWrites, - publicDataHints, - ); - const currentState = await this.db.getStateReference(); return new PublicKernelTailCircuitPrivateInputs( @@ -127,7 +121,6 @@ export class TailPhaseManager extends AbstractPhaseManager { nullifierNonExistentReadRequestHints, l1ToL2MsgReadRequestHints, publicDataHints, - publicDataReadRequestHints, currentState.partial, ); }