diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_kernel_circuit_public_inputs_composer.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_kernel_circuit_public_inputs_composer.nr index 2884124dd58..5b72fd5ec4a 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_kernel_circuit_public_inputs_composer.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_kernel_circuit_public_inputs_composer.nr @@ -11,7 +11,7 @@ use dep::types::{ MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL }, traits::is_empty, transaction::tx_request::TxRequest, - utils::arrays::{array_length, array_to_bounded_vec, sort_by_counters_asc, sort_by_counters_desc} + utils::arrays::{array_length, array_to_bounded_vec, sort_by_counter_asc, sort_by_counter_desc} }; struct DataSource { @@ -101,9 +101,9 @@ impl PrivateKernelCircuitPublicInputsComposer { pub fn sort_ordered_values(&mut self) { // Note hashes, nullifiers, note_encrypted_logs_hashes, and encrypted_logs_hashes are sorted in the reset circuit. - self.public_inputs.end.l2_to_l1_msgs.storage = sort_by_counters_asc(self.public_inputs.end.l2_to_l1_msgs.storage); - self.public_inputs.end.unencrypted_logs_hashes.storage = sort_by_counters_asc(self.public_inputs.end.unencrypted_logs_hashes.storage); - self.public_inputs.end.public_call_requests.storage = sort_by_counters_desc(self.public_inputs.end.public_call_requests.storage); + self.public_inputs.end.l2_to_l1_msgs.storage = sort_by_counter_asc(self.public_inputs.end.l2_to_l1_msgs.storage); + self.public_inputs.end.unencrypted_logs_hashes.storage = sort_by_counter_asc(self.public_inputs.end.unencrypted_logs_hashes.storage); + self.public_inputs.end.public_call_requests.storage = sort_by_counter_desc(self.public_inputs.end.public_call_requests.storage); } pub fn finish(self) -> PrivateKernelCircuitPublicInputs { diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer.nr index 39495cade16..c733b4d01b8 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer.nr @@ -16,7 +16,7 @@ use dep::types::{ MAX_ENCRYPTED_LOGS_PER_TX, MAX_NOTE_ENCRYPTED_LOGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX }, - hash::{mask_encrypted_log_hash, silo_note_hash, silo_nullifier}, utils::arrays::sort_by_counters_asc + hash::{mask_encrypted_log_hash, silo_note_hash, silo_nullifier}, utils::arrays::sort_by_counter_asc }; struct ResetOutputComposer< @@ -99,7 +99,7 @@ impl< } fn get_sorted_siloed_note_hashes(self) -> [ScopedNoteHash; MAX_NOTE_HASHES_PER_TX] { - let mut note_hashes = sort_by_counters_asc(self.hints.kept_note_hashes); + let mut note_hashes = sort_by_counter_asc(self.hints.kept_note_hashes); let first_nullifier = self.previous_kernel.end.nullifiers[0].value(); for i in 0..note_hashes.len() { note_hashes[i].note_hash.value = silo_note_hash( @@ -113,7 +113,7 @@ impl< } fn get_sorted_siloed_nullifiers(self) -> [ScopedNullifier; MAX_NULLIFIERS_PER_TX] { - let mut nullifiers = sort_by_counters_asc(self.hints.kept_nullifiers); + let mut nullifiers = sort_by_counter_asc(self.hints.kept_nullifiers); for i in 0..nullifiers.len() { nullifiers[i].nullifier.value = silo_nullifier(nullifiers[i]); nullifiers[i].contract_address = AztecAddress::zero(); @@ -122,7 +122,7 @@ impl< } fn get_sorted_note_encrypted_log_hashes(self) -> [NoteLogHash; MAX_NOTE_ENCRYPTED_LOGS_PER_TX] { - let mut log_hashes = sort_by_counters_asc(self.hints.kept_note_encrypted_log_hashes); + let mut log_hashes = sort_by_counter_asc(self.hints.kept_note_encrypted_log_hashes); for i in 0..log_hashes.len() { log_hashes[i].note_hash_counter = 0; } @@ -130,7 +130,7 @@ impl< } fn get_sorted_masked_encrypted_log_hashes(self) -> [ScopedEncryptedLogHash; MAX_ENCRYPTED_LOGS_PER_TX] { - let mut log_hashes = sort_by_counters_asc(self.previous_kernel.end.encrypted_logs_hashes); + let mut log_hashes = sort_by_counter_asc(self.previous_kernel.end.encrypted_logs_hashes); for i in 0..log_hashes.len() { log_hashes[i].contract_address = mask_encrypted_log_hash(log_hashes[i]); log_hashes[i].log_hash.randomness = 0; diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer/reset_output_hints.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer/reset_output_hints.nr index 9f7ec5b34e7..6def209f88b 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer/reset_output_hints.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer/reset_output_hints.nr @@ -15,7 +15,7 @@ use dep::types::{ MAX_ENCRYPTED_LOGS_PER_TX, MAX_NOTE_ENCRYPTED_LOGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX }, - utils::arrays::{OrderHint, sort_get_order_hints_asc} + utils::arrays::{OrderHint, get_order_hints_asc} }; struct ResetOutputHints { @@ -45,13 +45,13 @@ unconstrained pub fn generate_reset_output_hints TailOutputHints { // l2_to_l1_msgs - let sorted_l2_to_l1_msg_hints = sort_get_order_hints_asc(previous_kernel.end.l2_to_l1_msgs); + let sorted_l2_to_l1_msg_hints = get_order_hints_asc(previous_kernel.end.l2_to_l1_msgs); // unencrypted_logs - let sorted_unencrypted_log_hashes = sort_by_counters_asc(previous_kernel.end.unencrypted_logs_hashes); - let sorted_unencrypted_log_hash_hints = sort_get_order_hints_asc(previous_kernel.end.unencrypted_logs_hashes); + let sorted_unencrypted_log_hashes = sort_by_counter_asc(previous_kernel.end.unencrypted_logs_hashes); + let sorted_unencrypted_log_hash_hints = get_order_hints_asc(previous_kernel.end.unencrypted_logs_hashes); TailOutputHints { sorted_l2_to_l1_msg_hints, sorted_unencrypted_log_hashes, sorted_unencrypted_log_hash_hints } } diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/tail_to_public_output_validator/tail_to_public_output_hints.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/tail_to_public_output_validator/tail_to_public_output_hints.nr index c43687a3303..9342441434c 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/tail_to_public_output_validator/tail_to_public_output_hints.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/tail_to_public_output_validator/tail_to_public_output_hints.nr @@ -1,7 +1,7 @@ use dep::types::{ abis::{kernel_circuit_public_inputs::PrivateKernelCircuitPublicInputs}, constants::{MAX_L2_TO_L1_MSGS_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX}, - utils::arrays::{sort_get_split_order_hints_asc, sort_get_split_order_hints_desc, SplitOrderHints} + utils::arrays::{get_split_order_hints_asc, get_split_order_hints_desc, SplitOrderHints} }; struct TailToPublicOutputHints { @@ -17,13 +17,13 @@ unconstrained pub fn generate_tail_to_public_output_hints(previous_kernel: Priva let split_counter = previous_kernel.min_revertible_side_effect_counter; // l2_to_l1_msgss - let sorted_l2_to_l1_msg_hints = sort_get_split_order_hints_asc(previous_kernel.end.l2_to_l1_msgs, split_counter); + let sorted_l2_to_l1_msg_hints = get_split_order_hints_asc(previous_kernel.end.l2_to_l1_msgs, split_counter); // unencrypted_logs - let sorted_unencrypted_log_hash_hints = sort_get_split_order_hints_asc(previous_kernel.end.unencrypted_logs_hashes, split_counter); + let sorted_unencrypted_log_hash_hints = get_split_order_hints_asc(previous_kernel.end.unencrypted_logs_hashes, split_counter); // public_call_requests - let sorted_public_call_request_hints = sort_get_split_order_hints_desc(previous_kernel.end.public_call_requests, split_counter); + let sorted_public_call_request_hints = get_split_order_hints_desc(previous_kernel.end.public_call_requests, split_counter); TailToPublicOutputHints { sorted_l2_to_l1_msg_hints, sorted_unencrypted_log_hash_hints, sorted_public_call_request_hints } } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/mod.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/mod.nr index 962c5490905..3fad7634f8c 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/mod.nr @@ -2,3 +2,4 @@ mod previous_kernel_validator; mod public_call_data_validator; mod public_kernel_output_composer; mod public_tail_output_composer; +mod public_tail_output_validator; 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 a4212e4d65d..d19f34dbef3 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,6 +1,6 @@ mod combine_data; -use crate::components::public_tail_output_composer::combine_data::{combine_data, CombineHints}; +use crate::components::public_tail_output_composer::combine_data::combine_data; use dep::types::{ abis::{kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}}, partial_state_reference::PartialStateReference @@ -9,23 +9,20 @@ use dep::types::{ struct PublicTailOutputComposer { previous_kernel: PublicKernelCircuitPublicInputs, start_state: PartialStateReference, - combine_hints: CombineHints } impl PublicTailOutputComposer { pub fn new( previous_kernel: PublicKernelCircuitPublicInputs, - start_state: PartialStateReference, - combine_hints: CombineHints + start_state: PartialStateReference ) -> Self { - PublicTailOutputComposer { previous_kernel, start_state, combine_hints } + PublicTailOutputComposer { previous_kernel, start_state } } pub fn finish(self) -> KernelCircuitPublicInputs { let end = combine_data( self.previous_kernel.end_non_revertible, - self.previous_kernel.end, - self.combine_hints + self.previous_kernel.end ); KernelCircuitPublicInputs { 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 8ab94be1acd..778ccbac6ce 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,134 +1,75 @@ use dep::types::{ - hash::{compute_tx_logs_hash, compute_tx_note_logs_hash}, abis::{ accumulated_data::{CombinedAccumulatedData, PublicAccumulatedData}, - log_hash::{LogHash, ScopedLogHash}, note_hash::ScopedNoteHash, nullifier::Nullifier, - public_data_update_request::PublicDataUpdateRequest, side_effect::Ordered + log_hash::{LogHash, ScopedLogHash}, nullifier::Nullifier }, - 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, - utils::arrays::{array_merge, assert_sorted_array, assert_deduped_array, check_permutation} + constants::MAX_NOTE_HASHES_PER_TX, hash::silo_note_hash, + utils::arrays::{array_merge, dedupe_array, sort_by_counter_asc} }; -struct CombineHints { - sorted_note_hashes: [ScopedNoteHash; MAX_NOTE_HASHES_PER_TX], - sorted_note_hashes_indexes: [u32; MAX_NOTE_HASHES_PER_TX], - sorted_note_encrypted_logs_hashes: [LogHash; MAX_NOTE_ENCRYPTED_LOGS_PER_TX], - sorted_note_encrypted_logs_hashes_indexes: [u32; MAX_NOTE_ENCRYPTED_LOGS_PER_TX], - sorted_encrypted_logs_hashes: [ScopedLogHash; MAX_ENCRYPTED_LOGS_PER_TX], - sorted_encrypted_logs_hashes_indexes: [u32; MAX_ENCRYPTED_LOGS_PER_TX], - sorted_unencrypted_logs_hashes: [ScopedLogHash; MAX_UNENCRYPTED_LOGS_PER_TX], - sorted_unencrypted_logs_hashes_indexes: [u32; MAX_UNENCRYPTED_LOGS_PER_TX], - // the public data update requests are sorted by their leaf index AND counter - sorted_public_data_update_requests: [PublicDataUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - sorted_public_data_update_requests_indexes: [u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - // THEN deduplicated based on their leaf slot - deduped_public_data_update_requests: [PublicDataUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - deduped_public_data_update_requests_runs: [u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], -} - -fn asc_sort_by_counters(a: T, b: T) -> bool where T: Ordered { - a.counter() <= b.counter() -} - -pub fn combine_data( +unconstrained pub fn combine_data( non_revertible: PublicAccumulatedData, - revertible: PublicAccumulatedData, - combine_hints: CombineHints + revertible: PublicAccumulatedData ) -> CombinedAccumulatedData { - let merged_note_hashes = array_merge(non_revertible.note_hashes, revertible.note_hashes); - assert_sorted_array( - merged_note_hashes, - combine_hints.sorted_note_hashes, - combine_hints.sorted_note_hashes_indexes, - asc_sort_by_counters - ); - - let mut siloed_note_hashes = [0; MAX_NOTE_HASHES_PER_TX]; - let sorted_note_hashes = combine_hints.sorted_note_hashes; + 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_note_hashes.len() { - let note_hash = sorted_note_hashes[i]; - siloed_note_hashes[i] = if note_hash.counter() == 0 { - // If counter is zero, the note hash was emitted from private and has been siloed in private_kernel_tail_to_public. + 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 merged_public_data_update_requests = array_merge( - non_revertible.public_data_update_requests, - revertible.public_data_update_requests - ); + let nullifiers = sort_by_counter_asc(array_merge(non_revertible.nullifiers, revertible.nullifiers)).map(|n: Nullifier| n.value); - // Just check a permutation here... - check_permutation( - merged_public_data_update_requests, - combine_hints.sorted_public_data_update_requests, - combine_hints.sorted_public_data_update_requests_indexes - ); - // ...because the ordering checks are done here. - assert_deduped_array( - combine_hints.sorted_public_data_update_requests, - combine_hints.deduped_public_data_update_requests, - combine_hints.deduped_public_data_update_requests_runs - ); + let l2_to_l1_msgs = sort_by_counter_asc(array_merge(non_revertible.l2_to_l1_msgs, revertible.l2_to_l1_msgs)); - let merged_note_encrypted_logs_hashes = array_merge( - non_revertible.note_encrypted_logs_hashes, - revertible.note_encrypted_logs_hashes - ); - assert_sorted_array( - merged_note_encrypted_logs_hashes, - combine_hints.sorted_note_encrypted_logs_hashes, - combine_hints.sorted_note_encrypted_logs_hashes_indexes, - asc_sort_by_counters + let public_data_update_requests = dedupe_array( + array_merge( + non_revertible.public_data_update_requests, + revertible.public_data_update_requests + ) ); - let merged_encrypted_logs_hashes = array_merge( - non_revertible.encrypted_logs_hashes, - revertible.encrypted_logs_hashes - ); - assert_sorted_array( - merged_encrypted_logs_hashes, - combine_hints.sorted_encrypted_logs_hashes, - combine_hints.sorted_encrypted_logs_hashes_indexes, - asc_sort_by_counters + let note_encrypted_logs_hashes = sort_by_counter_asc( + array_merge( + non_revertible.note_encrypted_logs_hashes, + revertible.note_encrypted_logs_hashes + ) ); + let note_encrypted_log_preimages_length = note_encrypted_logs_hashes.fold(0, |a, b: LogHash| a + b.length); - let merged_unencrypted_logs_hashes = array_merge( - non_revertible.unencrypted_logs_hashes, - revertible.unencrypted_logs_hashes + let encrypted_logs_hashes = sort_by_counter_asc( + array_merge( + non_revertible.encrypted_logs_hashes, + revertible.encrypted_logs_hashes + ) ); - assert_sorted_array( - merged_unencrypted_logs_hashes, - combine_hints.sorted_unencrypted_logs_hashes, - combine_hints.sorted_unencrypted_logs_hashes_indexes, - asc_sort_by_counters + let encrypted_log_preimages_length = encrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length); + + let unencrypted_logs_hashes = sort_by_counter_asc( + array_merge( + non_revertible.unencrypted_logs_hashes, + revertible.unencrypted_logs_hashes + ) ); + let unencrypted_log_preimages_length = unencrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length); - let note_encrypted_log_preimages_length = non_revertible.note_encrypted_logs_hashes.fold(0, |a, b: LogHash| a + b.length) - + revertible.note_encrypted_logs_hashes.fold(0, |a, b: LogHash| a + b.length); - let encrypted_log_preimages_length = non_revertible.encrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length) - + revertible.encrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length); - let unencrypted_log_preimages_length = non_revertible.unencrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length) - + revertible.unencrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length); CombinedAccumulatedData { - note_hashes: siloed_note_hashes, - nullifiers: array_merge(non_revertible.nullifiers, revertible.nullifiers).map(|n: Nullifier| n.value), - l2_to_l1_msgs: array_merge(non_revertible.l2_to_l1_msgs, revertible.l2_to_l1_msgs), - note_encrypted_logs_hashes: combine_hints.sorted_note_encrypted_logs_hashes, - encrypted_logs_hashes: combine_hints.sorted_encrypted_logs_hashes, - unencrypted_logs_hashes: combine_hints.sorted_unencrypted_logs_hashes, + note_hashes, + nullifiers, + l2_to_l1_msgs, + note_encrypted_logs_hashes, + encrypted_logs_hashes, + unencrypted_logs_hashes, note_encrypted_log_preimages_length, encrypted_log_preimages_length, unencrypted_log_preimages_length, - public_data_update_requests: combine_hints.deduped_public_data_update_requests, + public_data_update_requests, gas_used: revertible.gas_used + non_revertible.gas_used } } 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 new file mode 100644 index 00000000000..2dba886c754 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator.nr @@ -0,0 +1,155 @@ +mod output_hints; + +use crate::components::public_tail_output_validator::output_hints::{generate_output_hints, OutputHints, SiloedNoteHashHint}; +use dep::types::{ + abis::{ + kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}, + log_hash::{LogHash, ScopedLogHash}, note_hash::{NoteHash, ScopedNoteHash}, nullifier::Nullifier +}, + 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} +}; + +struct PublicTailOutputValidator { + output: KernelCircuitPublicInputs, + previous_kernel: PublicKernelCircuitPublicInputs, + start_state: PartialStateReference, + hints: OutputHints +} + +impl PublicTailOutputValidator { + pub fn new( + output: KernelCircuitPublicInputs, + previous_kernel: PublicKernelCircuitPublicInputs, + start_state: PartialStateReference + ) -> Self { + let hints = generate_output_hints(previous_kernel, output); + PublicTailOutputValidator { output, previous_kernel, start_state, hints } + } + + pub fn validate(self) { + self.validate_aggregated_data(); + + // CombinedAccumulatedData + self.validate_combined_siloed_note_hashes(); + self.validate_combined_data(); + self.validate_deduped_public_data_writes(); + self.validate_gas(); + } + + fn validate_aggregated_data(self) { + assert_eq( + self.output.rollup_validation_requests, self.previous_kernel.validation_requests.for_rollup, "mismatch rollup_validation_requests" + ); + assert_eq(self.output.constants, self.previous_kernel.constants, "mismatch constants"); + assert_eq(self.output.start_state, self.start_state, "mismatch start_state"); + assert_eq(self.output.revert_code, self.previous_kernel.revert_code, "mismatch revert_code"); + assert_eq(self.output.fee_payer, self.previous_kernel.fee_payer, "mismatch fee_payer"); + } + + fn validate_combined_siloed_note_hashes(self) { + let non_revertible = self.previous_kernel.end_non_revertible; + let revertible = self.previous_kernel.end; + let tx_hash = self.output.end.nullifiers[0]; + let output_note_hashes = self.output.end.note_hashes; + let siloed_note_hash_hints = self.hints.siloed_note_hash_hints; + for i in 0..output_note_hashes.len() { + let output_note_hash = output_note_hashes[i]; + assert_eq(output_note_hash, siloed_note_hash_hints[i].siloed_note_hash); + if output_note_hash != 0 { + assert_eq(i, siloed_note_hash_hints[i].index); + } + } + + assert_combined_sorted_transformed_value_array_asc( + non_revertible.note_hashes, + revertible.note_hashes, + siloed_note_hash_hints, + |prev: ScopedNoteHash, out: SiloedNoteHashHint| { + let siloed_value = if prev.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. + prev.value() + } else { + silo_note_hash(prev, tx_hash, out.index) + }; + out.siloed_note_hash == siloed_value + }, + self.hints.sorted_note_hash_hints + ); + } + + fn validate_combined_data(self) { + let non_revertible = self.previous_kernel.end_non_revertible; + let revertible = self.previous_kernel.end; + + // nullifiers + assert_combined_sorted_transformed_value_array_asc( + non_revertible.nullifiers, + revertible.nullifiers, + self.output.end.nullifiers, + |prev: Nullifier, out: Field| out == prev.value, + self.hints.sorted_nullifier_hints + ); + + // l2_to_l1_msgs + assert_combined_sorted_transformed_value_array_asc( + non_revertible.l2_to_l1_msgs, + revertible.l2_to_l1_msgs, + self.output.end.l2_to_l1_msgs, + |prev: ScopedL2ToL1Message, out: ScopedL2ToL1Message| out == prev, + self.hints.sorted_l2_to_l1_msg_hints + ); + + // note_encrypted_logs_hashes + assert_combined_sorted_transformed_value_array_asc( + non_revertible.note_encrypted_logs_hashes, + revertible.note_encrypted_logs_hashes, + self.output.end.note_encrypted_logs_hashes, + |prev: LogHash, out: LogHash| out == prev, + self.hints.sorted_note_encrypted_log_hash_hints + ); + + let note_encrypted_log_preimages_length = self.output.end.note_encrypted_logs_hashes.fold(0, |a, b: LogHash| a + b.length); + assert_eq(self.output.end.note_encrypted_log_preimages_length, note_encrypted_log_preimages_length); + + // encrypted_logs_hashes + assert_combined_sorted_transformed_value_array_asc( + non_revertible.encrypted_logs_hashes, + revertible.encrypted_logs_hashes, + self.output.end.encrypted_logs_hashes, + |prev: ScopedLogHash, out: ScopedLogHash| out == prev, + self.hints.sorted_encrypted_log_hash_hints + ); + + let encrypted_log_preimages_length = self.output.end.encrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length); + assert_eq(self.output.end.encrypted_log_preimages_length, encrypted_log_preimages_length); + + // unencrypted_logs_hashes + assert_combined_sorted_transformed_value_array_asc( + non_revertible.unencrypted_logs_hashes, + revertible.unencrypted_logs_hashes, + self.output.end.unencrypted_logs_hashes, + |prev: ScopedLogHash, out: ScopedLogHash| out == prev, + self.hints.sorted_unencrypted_log_hash_hints + ); + + let unencrypted_log_preimages_length = self.output.end.unencrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length); + assert_eq(self.output.end.unencrypted_log_preimages_length, unencrypted_log_preimages_length); + } + + fn validate_deduped_public_data_writes(self) { + assert_combined_deduped_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 + ); + } + + fn validate_gas(self) { + let gas_used = self.previous_kernel.end_non_revertible.gas_used + self.previous_kernel.end.gas_used; + assert_eq(self.output.end.gas_used, gas_used, "incorrect output gas_used"); + } +} 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 new file mode 100644 index 00000000000..95d26ece7e2 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/public_tail_output_validator/output_hints.nr @@ -0,0 +1,89 @@ +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/public_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr index 7cb0cff6bea..3c95ad18bad 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,7 +1,8 @@ use crate::{ components::{ previous_kernel_validator::PreviousKernelValidator, - public_tail_output_composer::{PublicTailOutputComposer, CombineHints} + public_tail_output_composer::PublicTailOutputComposer, + public_tail_output_validator::PublicTailOutputValidator }, public_kernel_phase::PublicKernelPhase }; @@ -31,11 +32,14 @@ struct PublicKernelTailCircuitPrivateInputs { public_data_hints: [PublicDataHint; MAX_PUBLIC_DATA_HINTS], public_data_read_request_hints: PublicDataReadRequestHints, start_state: PartialStateReference, - combine_hints: CombineHints } impl PublicKernelTailCircuitPrivateInputs { - pub fn public_kernel_tail(self) -> KernelCircuitPublicInputs { + unconstrained fn generate_output(self) -> KernelCircuitPublicInputs { + PublicTailOutputComposer::new(self.previous_kernel.public_inputs, self.start_state).finish() + } + + pub fn execute(self) -> KernelCircuitPublicInputs { let previous_kernel_validator = PreviousKernelValidator::new(self.previous_kernel); previous_kernel_validator.validate_phase(PublicKernelPhase.TAIL); previous_kernel_validator.validate_proof(ALLOWED_PREVIOUS_CIRCUITS); @@ -51,15 +55,16 @@ impl PublicKernelTailCircuitPrivateInputs { self.start_state.public_data_tree.root ).validate(); - PublicTailOutputComposer::new(previous_public_inputs, self.start_state, self.combine_hints).finish() + let output = self.generate_output(); + + PublicTailOutputValidator::new(output, previous_public_inputs, self.start_state).validate(); + + output } } mod tests { - use crate::{ - components::public_tail_output_composer::CombineHints, - public_kernel_tail::{ALLOWED_PREVIOUS_CIRCUITS, PublicKernelTailCircuitPrivateInputs} - }; + use crate::{public_kernel_tail::{ALLOWED_PREVIOUS_CIRCUITS, PublicKernelTailCircuitPrivateInputs}}; use dep::reset_kernel_lib::{ tests::{ nullifier_non_existent_read_request_hints_builder::NullifierNonExistentReadRequestHintsBuilder, @@ -71,9 +76,7 @@ mod tests { use dep::types::{ abis::{ kernel_circuit_public_inputs::KernelCircuitPublicInputs, public_kernel_data::PublicKernelData, - nullifier::ScopedNullifier, nullifier_leaf_preimage::NullifierLeafPreimage, - accumulated_data::CombinedAccumulatedData, public_data_update_request::PublicDataUpdateRequest, - note_hash::ScopedNoteHash, log_hash::{ScopedLogHash, LogHash} + nullifier::ScopedNullifier, nullifier_leaf_preimage::NullifierLeafPreimage }, address::AztecAddress, constants::{ @@ -85,12 +88,14 @@ mod tests { PUBLIC_KERNEL_APP_LOGIC_INDEX, BASE_ROLLUP_INDEX, PUBLIC_KERNEL_SETUP_INDEX, PUBLIC_KERNEL_TEARDOWN_INDEX }, - hash::{compute_siloed_nullifier, sha256_to_field}, + hash::{compute_siloed_nullifier, silo_note_hash}, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, - tests::{fixture_builder::FixtureBuilder, merkle_tree_utils::NonEmptyMerkleTree}, + tests::{ + fixture_builder::FixtureBuilder, merkle_tree_utils::NonEmptyMerkleTree, + utils::{assert_array_eq, swap_items} + }, traits::is_empty, partial_state_reference::PartialStateReference, - utils::arrays::{array_length, array_merge, sort_get_sorted_hints}, - merkle_tree::MembershipWitness + utils::arrays::{array_length, array_merge}, merkle_tree::MembershipWitness }; fn build_nullifier_tree() -> NonEmptyMerkleTree { @@ -140,7 +145,7 @@ mod tests { let previous_revertible = FixtureBuilder::new(); let nullifier_non_existent_read_request_hints_builder = NullifierNonExistentReadRequestHintsBuilder::new(); - let mut builder = PublicKernelTailCircuitPrivateInputsBuilder { + PublicKernelTailCircuitPrivateInputsBuilder { previous_kernel, previous_revertible, nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(), @@ -149,9 +154,7 @@ mod tests { public_data_hints: BoundedVec::new(), public_data_tree: NonEmptyMerkleTree::empty(), start_state: PartialStateReference::empty() - }; - builder.set_nullifiers_for_non_existent_read_request_hints(); - builder + } } pub fn with_nullifier_tree(&mut self) -> Self { @@ -270,78 +273,7 @@ mod tests { let mut previous_kernel = self.previous_kernel.to_public_kernel_data(false); previous_kernel.public_inputs.end = self.previous_revertible.to_public_accumulated_data(); - // Note: note hashes are a bit odd here: whereas we'd like to use `combined` and then - // sort the result, we cannot because we lose the side effect counters when we combine. - let merged = array_merge( - previous_kernel.public_inputs.end_non_revertible.note_hashes, - previous_kernel.public_inputs.end.note_hashes - ); - let sorted = sort_get_sorted_hints( - merged, - |a: ScopedNoteHash, b: ScopedNoteHash| a.counter() < b.counter() - ); - let sorted_note_hashes = sorted.sorted_array; - let sorted_note_hashes_indexes = sorted.sorted_index_hints; - - let merged = array_merge( - previous_kernel.public_inputs.end_non_revertible.unencrypted_logs_hashes, - previous_kernel.public_inputs.end.unencrypted_logs_hashes - ); - let sorted = sort_get_sorted_hints( - merged, - |a: ScopedLogHash, b: ScopedLogHash| a.counter() < b.counter() - ); - let sorted_unencrypted_logs_hashes = sorted.sorted_array; - let sorted_unencrypted_logs_hashes_indexes = sorted.sorted_index_hints; - - let merged = array_merge( - previous_kernel.public_inputs.end_non_revertible.encrypted_logs_hashes, - previous_kernel.public_inputs.end.encrypted_logs_hashes - ); - let sorted = sort_get_sorted_hints( - merged, - |a: ScopedLogHash, b: ScopedLogHash| a.counter() < b.counter() - ); - let sorted_encrypted_logs_hashes = sorted.sorted_array; - let sorted_encrypted_logs_hashes_indexes = sorted.sorted_index_hints; - - let merged = array_merge( - previous_kernel.public_inputs.end_non_revertible.note_encrypted_logs_hashes, - previous_kernel.public_inputs.end.note_encrypted_logs_hashes - ); - let sorted = sort_get_sorted_hints(merged, |a: LogHash, b: LogHash| a.counter() < b.counter()); - let sorted_note_encrypted_logs_hashes = sorted.sorted_array; - let sorted_note_encrypted_logs_hashes_indexes = sorted.sorted_index_hints; - - let merged = array_merge( - previous_kernel.public_inputs.end_non_revertible.public_data_update_requests, - previous_kernel.public_inputs.end.public_data_update_requests - ); - - let mut deduped_public_data_update_requests_runs = [0; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - let mut sorted_public_data_update_requests_indexes = [0; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - let array_length = array_length(merged); - for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - if i < array_length { - deduped_public_data_update_requests_runs[i] = 1; - } - sorted_public_data_update_requests_indexes[i] = i; - } - - let combine_hints = CombineHints { - sorted_note_hashes, - sorted_note_hashes_indexes, - sorted_unencrypted_logs_hashes, - sorted_unencrypted_logs_hashes_indexes, - sorted_encrypted_logs_hashes, - sorted_encrypted_logs_hashes_indexes, - sorted_note_encrypted_logs_hashes, - sorted_note_encrypted_logs_hashes_indexes, - sorted_public_data_update_requests: merged, - sorted_public_data_update_requests_indexes, - deduped_public_data_update_requests: merged, - deduped_public_data_update_requests_runs - }; + self.set_nullifiers_for_non_existent_read_request_hints(); let kernel = PublicKernelTailCircuitPrivateInputs { previous_kernel, @@ -349,11 +281,10 @@ mod tests { nullifier_non_existent_read_request_hints: self.nullifier_non_existent_read_request_hints_builder.to_hints(), public_data_hints: self.public_data_hints.storage, public_data_read_request_hints: self.public_data_read_request_hints_builder.to_hints(), - start_state: self.start_state, - combine_hints + start_state: self.start_state }; - kernel.public_kernel_tail() + kernel.execute() } pub fn succeeded(&mut self) { @@ -590,6 +521,68 @@ mod tests { assert_eq(public_inputs.fee_payer, AztecAddress::empty()); } + #[test] + unconstrained fn combine_and_silo_accumulated_note_hashes() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + builder.previous_revertible.value_offset = 999; + builder.previous_revertible.counter = 88; + builder.previous_kernel.set_first_nullifier(); + + builder.previous_kernel.append_note_hashes(3); + // 1 of them is from private. + builder.previous_kernel.note_hashes.storage[0].note_hash.counter = 0; + let non_rev = builder.previous_kernel.note_hashes.storage; + + builder.previous_revertible.append_note_hashes(5); + // 2 of them are from private: + builder.previous_revertible.note_hashes.storage[0].note_hash.counter = 0; + builder.previous_revertible.note_hashes.storage[1].note_hash.counter = 0; + let rev = builder.previous_revertible.note_hashes.storage; + // Swap the items so that they are not ordered by counters. + swap_items(&mut builder.previous_revertible.note_hashes, 2, 3); + + let public_inputs = builder.execute(); + + let tx_hash = builder.previous_kernel.nullifiers.storage[0].value(); + let expected = [ + // Note hashes from private are already siloed. + non_rev[0].value(), + rev[0].value(), + rev[1].value(), + silo_note_hash(non_rev[1], tx_hash, 3), + silo_note_hash(non_rev[2], tx_hash, 4), + silo_note_hash(rev[2], tx_hash, 5), + silo_note_hash(rev[3], tx_hash, 6), + silo_note_hash(rev[4], tx_hash, 7) + ]; + assert_array_eq(public_inputs.end.note_hashes, expected); + } + + #[test] + unconstrained fn combine_accumulated_nullifiers() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); + builder.previous_revertible.value_offset = 999; + builder.previous_revertible.counter = 88; + + builder.previous_kernel.append_nullifiers(3); + // 1 of them is from private: + builder.previous_kernel.nullifiers.storage[0].nullifier.counter = 0; + let non_rev = builder.previous_kernel.nullifiers.storage; + + builder.previous_revertible.append_nullifiers(5); + // 2 of them are from private: + builder.previous_revertible.nullifiers.storage[0].nullifier.counter = 0; + builder.previous_revertible.nullifiers.storage[1].nullifier.counter = 0; + let rev = builder.previous_revertible.nullifiers.storage; + // Swap the items so that they are not ordered by counters. + swap_items(&mut builder.previous_revertible.nullifiers, 2, 3); + + let public_inputs = builder.execute(); + + let expected = [non_rev[0], rev[0], rev[1], non_rev[1], non_rev[2], rev[2], rev[3], rev[4]].map(|n: ScopedNullifier| n.nullifier.value); + assert_array_eq(public_inputs.end.nullifiers, expected); + } + #[test] fn valid_previous_kernel_setup() { let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/src/main.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/src/main.nr index 0a9f18ffd54..d387581d4aa 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/src/main.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail-simulated/src/main.nr @@ -2,5 +2,5 @@ use dep::public_kernel_lib::PublicKernelTailCircuitPrivateInputs; use dep::types::KernelCircuitPublicInputs; unconstrained fn main(input: PublicKernelTailCircuitPrivateInputs) -> pub KernelCircuitPublicInputs { - input.public_kernel_tail() + input.execute() } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/src/main.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/src/main.nr index 487ed3061d0..a777bdad401 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/src/main.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-tail/src/main.nr @@ -3,5 +3,5 @@ use dep::types::KernelCircuitPublicInputs; #[recursive] fn main(input: PublicKernelTailCircuitPrivateInputs) -> pub KernelCircuitPublicInputs { - input.public_kernel_tail() + input.execute() } 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 a7e0d118bef..ea0d8affd55 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, sort_get_sorted_hints}, field::full_field_greater_than} + utils::{arrays::{find_index_hint, get_sorted_hints}, field::full_field_greater_than} }; struct NullifierNonExistentReadRequestHintsBuilder { @@ -50,9 +50,9 @@ impl NullifierNonExistentReadRequestHintsBuilder { } pub fn to_hints(self) -> NullifierNonExistentReadRequestHints { - let sorted_result = sort_get_sorted_hints( + let sorted_result = get_sorted_hints( self.pending_nullifiers, - |a: Nullifier, b: Nullifier| a.value.lt(b.value) + |a: Nullifier, b: Nullifier| (b.value == 0) | ((a.value != 0) & a.value.lt(b.value)) ); let sorted_pending_values = sorted_result.sorted_array; let sorted_pending_value_index_hints = sorted_result.sorted_index_hints; 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 f2c4457942d..0f30eabfed1 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 @@ -489,9 +489,12 @@ mod tests { public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, tests::{ fixtures, fixture_builder::FixtureBuilder, - merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes}, sort::sort_high_to_low + merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes} + }, + utils::{ + arrays::get_sorted_tuple::get_sorted_tuple, + field::{full_field_less_than, field_from_bytes_32_trunc, field_from_bytes}, uint256::U256 }, - utils::{field::{full_field_less_than, field_from_bytes_32_trunc, field_from_bytes}, uint256::U256}, traits::Empty }; @@ -537,14 +540,14 @@ mod tests { final_public_data_writes.extend_from_array(protocol_public_data_writes.storage); } - let mut sorted_write_tuples = sort_high_to_low( + let mut sorted_write_tuples = get_sorted_tuple( final_public_data_writes.storage, - |(_, leaf_a): (u32, PublicDataTreeLeaf),(_,leaf_b):(u32, PublicDataTreeLeaf)| full_field_less_than(leaf_a.slot, leaf_b.slot) + |(_, leaf_a): (u32, PublicDataTreeLeaf), (_, leaf_b): (u32, PublicDataTreeLeaf)| full_field_less_than(leaf_b.slot, leaf_a.slot) ); for i in 0..MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { if i < final_public_data_writes.len() { - let (low_leaf_index, leaf): (u32, PublicDataTreeLeaf) = sorted_write_tuples[i].value; + let (low_leaf_index, leaf): (u32, PublicDataTreeLeaf) = sorted_write_tuples[i].elem; sorted_public_data_writes[i] = leaf; sorted_public_data_writes_indexes[i] = sorted_write_tuples[i].original_index; @@ -670,9 +673,9 @@ mod tests { let mut nullifier_predecessor_preimages = [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX]; let mut low_nullifier_membership_witness = [MembershipWitness::empty(); MAX_NULLIFIERS_PER_TX]; - let sorted_new_nullifier_tuples = sort_high_to_low( + let sorted_new_nullifier_tuples = get_sorted_tuple( self.nullifiers.storage.map(|insertion: NullifierInsertion| insertion.value), - full_field_less_than + |a, b| full_field_less_than(b, a) ); let mut sorted_nullifiers = [0; MAX_NULLIFIERS_PER_TX]; @@ -680,7 +683,7 @@ mod tests { for i in 0..MAX_NULLIFIERS_PER_TX { if (i as u32) < (MAX_nullifiers_PER_TEST as u32) { - sorted_nullifiers[i] = sorted_new_nullifier_tuples[i].value; + sorted_nullifiers[i] = sorted_new_nullifier_tuples[i].elem; sorted_nullifiers_indexes[i] = sorted_new_nullifier_tuples[i].original_index; } else { sorted_nullifiers[i] = 0; @@ -693,7 +696,7 @@ mod tests { for i in 0..MAX_nullifiers_PER_TEST { if i < self.nullifiers.len() { let sorted_tuple = sorted_new_nullifier_tuples[i]; - let new_nullifier = sorted_tuple.value; + let new_nullifier = sorted_tuple.elem; let original_index = sorted_tuple.original_index; let low_index = self.nullifiers.get_unchecked(original_index).existing_index; 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 678a7be1cfe..01e031f90fa 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,5 +1,4 @@ mod fixture_builder; mod fixtures; mod merkle_tree_utils; -mod sort; mod utils; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr deleted file mode 100644 index 7217c8955a2..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr +++ /dev/null @@ -1,17 +0,0 @@ -struct SortedTuple { - value: T, - original_index: u32, -} - -pub fn sort_high_to_low(values: [T; N], is_less_than: fn(T, T) -> bool) -> [SortedTuple; N] where T: Eq { - let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; - - for i in 0..N { - sorted_tuples[i] = SortedTuple { - value: values[i], - original_index: i, - }; - } - - sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| (b.value == a.value) | is_less_than(b.value, a.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 c5187420b29..9890994c051 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 @@ -25,3 +25,11 @@ pub fn swap_items(vec: &mut BoundedVec, from_index: u32, to vec.storage[from_index] = vec.storage[to_index]; vec.storage[to_index] = tmp; } + +pub fn pad_end(items: [T; N], emptyItem: T) -> [T; M] { + let mut output = [emptyItem; M]; + for i in 0..N { + output[i] = items[i]; + } + output +} 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 81e454a0445..d2358e05f5a 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,27 +1,40 @@ +mod assert_combined_deduped_array; +mod assert_combined_sorted_transformed_value_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 sort_by_counters; -mod sort_get_order_hints; -mod sort_get_sorted_hints; -mod sort_get_sorted_tuple; -mod sort_get_split_order_hints; +mod get_sorted_hints; +mod get_sorted_tuple; +mod sort_by; +mod sort_by_counter; // Re-exports. -use assert_exposed_sorted_transformed_value_array::assert_exposed_sorted_transformed_value_array; +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_sorted_transformed_value_array::{ + assert_combined_sorted_transformed_value_array_asc, + get_combined_order_hints::{CombinedOrderHint, get_combined_order_hints_asc} +}; +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_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}; +use assert_split_sorted_transformed_value_arrays::{ + assert_split_sorted_transformed_value_arrays_asc, assert_split_sorted_transformed_value_arrays_desc, + get_split_order_hints::{get_split_order_hints_asc, get_split_order_hints_desc, SplitOrderHints} +}; 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 sort_by_counters::{sort_by_counters_asc, sort_by_counters_desc}; -use sort_get_order_hints::{OrderHint, sort_get_order_hints_asc, sort_get_order_hints_desc}; -use sort_get_sorted_hints::sort_get_sorted_hints; -use sort_get_split_order_hints::{sort_get_split_order_hints_asc, sort_get_split_order_hints_desc, SplitOrderHints}; +use get_sorted_hints::get_sorted_hints; +use sort_by_counter::{sort_by_counter_asc, sort_by_counter_desc}; use crate::traits::{Empty, is_empty}; -use crate::abis::side_effect::{Positioned, Ordered}; pub fn array_to_bounded_vec(array: [T; N]) -> BoundedVec where T: Empty + Eq { let mut len = 0; @@ -87,17 +100,6 @@ pub fn array_length(array: [T; N]) -> u32 where T: Empty + Eq { length } -// Deprecated. Use tests/utils/assert_array_eq instead. -pub fn array_eq(array: [T; N], expected: [T; S]) -> bool where T: Empty + Eq { - let mut eq = array_length(array) == S; - - for i in 0..S { - eq &= array[i].eq(expected[i]); - } - - eq -} - pub fn array_concat(array1: [T; N], array2: [T; M]) -> [T; S] { assert_eq(N + M, S, "combined array length does not match return array length"); let mut result = [array1[0]; S]; @@ -128,11 +130,7 @@ 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]; @@ -143,323 +141,6 @@ pub fn check_permutation( } } -pub fn assert_deduped_array( - original_array: [T; N], - deduped_array: [T; N], - run_lengths: [u32; N] -) where T: Positioned + Ordered + Empty + Eq { - /* - The original_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 original_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 ] - */ - - let deduped_len = validate_array(deduped_array); // This also makes sure that the array is padded with empty items. - let run_lengths_len = array_length(run_lengths); // Don't have to be a "validated" array because non zero padded values don't hurt. - assert_eq(deduped_len, run_lengths_len, "Deduped array length does not match number of run lengths"); - - let mut seen_empty = false; - // container at the start of the current run - let mut start_run_index = 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 = run_lengths[deduped_index]; - for i in 0..N { - let current_container = original_array[i]; - if is_empty(current_container) { - seen_empty = true; - } else { - assert(!seen_empty, "Empty values must be padded to the right"); - assert(run_counter > 0, "Invalid run length"); - assert( - current_container.position().eq(original_array[start_run_index].position()), "The position of the current container must match the start of the run" - ); - run_counter -= 1; - if run_counter == 0 { - assert( - deduped_array[deduped_index].eq(current_container), "The container we are collapsing into must match the current container" - ); - start_run_index = i + 1; - deduped_index += 1; - run_counter = run_lengths[deduped_index]; - } 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() <= original_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::utils::arrays::assert_deduped_array; - use crate::abis::side_effect::{Positioned, Ordered}; - use crate::traits::{Empty, is_empty}; - - 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 original_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(original_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_empty_arrays() { - let original_array = [TestContainer { value: 0, position: 0, counter: 0 }; 12]; - let deduped_array = [TestContainer { value: 0, position: 0, counter: 0 }; 12]; - let run_lengths = [0; 12]; - assert_deduped_array(original_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_no_duplicates() { - let original_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: 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 deduped_array = original_array; - let run_lengths = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]; - assert_deduped_array(original_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_single_run_at_end() { - let original_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(original_array, deduped_array, run_lengths); - } - - #[test] - fn assert_deduped_array_all_duplicates() { - let original_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 = [ - TestContainer { value: 9, position: 1, counter: 9 }, - 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 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let run_lengths = [9, 0, 0, 0, 0, 0, 0, 0, 0]; - assert_deduped_array(original_array, deduped_array, run_lengths); - } - - #[test(should_fail_with = "Empty values must be padded to the right")] - fn test_empty_not_padded_right() { - let original_array = [ - TestContainer { value: 1, position: 1, counter: 1 }, - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 3, position: 2, counter: 3 } - ]; - let deduped_array = [ - TestContainer { value: 2, position: 1, counter: 2 }, - TestContainer { value: 3, position: 2, counter: 3 }, - TestContainer { value: 0, position: 0, counter: 0 }, - TestContainer { value: 0, position: 0, counter: 0 } - ]; - let run_lengths = [2, 1, 0, 0]; - assert_deduped_array(original_array, deduped_array, run_lengths); - } - - #[test(should_fail_with = "The position of the current container must match the start of the run")] - fn test_mismatched_position_in_run() { - let original_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(original_array, deduped_array, run_lengths); - } - - #[test(should_fail_with = "The container we are collapsing into must match the current container")] - fn test_mismatched_deduped_value() { - let original_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(original_array, deduped_array, run_lengths); - } - - #[test(should_fail_with = "Deduped array length does not match number of run lengths")] - fn test_run_lengths_not_zero_padded() { - let original_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 = [2, 1, 1, 1]; // Last element should be 0 - assert_deduped_array(original_array, deduped_array, run_lengths); - } - - #[test(should_fail_with = "Deduped array length does not match number of run lengths")] - fn test_deduped_padding_not_zero_padded() { - let original_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 }// Last element should be 0 - ]; - let run_lengths = [2, 1, 1, 0]; - assert_deduped_array(original_array, deduped_array, run_lengths); - } -} - #[test] fn smoke_validate_array() { let valid_array: [Field; 0] = []; 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 new file mode 100644 index 00000000000..366ec585a2f --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array.nr @@ -0,0 +1,218 @@ +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 new file mode 100644 index 00000000000..bc37a1320aa --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_combined_permuted_array.nr @@ -0,0 +1,131 @@ +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 { + pub fn new() -> TestBuilder<7> { + 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 } + } + + pub fn new_with_padded_zeros() -> TestBuilder<10> { + 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 } + } + + 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 new file mode 100644 index 00000000000..c58e0f66b83 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/assert_deduped_array.nr @@ -0,0 +1,343 @@ +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 new file mode 100644 index 00000000000..3aa86e380c8 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/dedupe_array.nr @@ -0,0 +1,131 @@ +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 new file mode 100644 index 00000000000..2f88b95392e --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/get_deduped_hints.nr @@ -0,0 +1,49 @@ +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 new file mode 100644 index 00000000000..b017e68ec0f --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_deduped_array/sort_by_position_then_counter.nr @@ -0,0 +1,53 @@ +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 new file mode 100644 index 00000000000..5839a4c53d5 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array.nr @@ -0,0 +1,409 @@ +mod get_combined_order_hints; + +use crate::{ + abis::side_effect::Ordered, traits::{Empty, is_empty}, + utils::arrays::{ + array_length, + assert_combined_sorted_transformed_value_array::get_combined_order_hints::{CombinedOrderHint, count_private_items} +} +}; + +fn get_num_private_items(array: [T; N]) -> u32 where T: Ordered + Empty + Eq { + let length = 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)); + } + if length != N { + let first_non_private_item = array[length]; + assert(is_empty(first_non_private_item) | (first_non_private_item.counter() != 0)); + } + length +} + +// original_array(_lt/_gte) must be valid, i.e. validate_array(original_array) == true +// Both arrays come from the previous kernel, so they are guaranteed to be valid, and meet the following conditions: +// - Items with a counter of 0 are placed at the begining of each array. +// - All non-zero counters in original_array_lt must be less than those in original_array_gte. +pub fn assert_combined_sorted_transformed_value_array_asc( + original_array_lt: [T; N], + original_array_gte: [T; N], + sorted_transformed_value_array: [S; N], + is_transformed: fn[Env](T, S) -> bool, + hints: [CombinedOrderHint; N] +) where T: Ordered + Empty + Eq, S: Empty + Eq { + let num_private_lt = get_num_private_items(original_array_lt); + let num_private_gte = get_num_private_items(original_array_gte); + let total_private = num_private_lt + num_private_gte; + let num_lt = array_length(original_array_lt); + let num_gte = array_length(original_array_gte); + let total_non_public_gte = num_lt + num_private_gte; + let total_num = num_lt + num_gte; + + let mut prev_counter = 0; + let mut should_be_private_lt = true; + let mut should_be_private = true; + let mut should_be_public_gte = false; + let mut should_be_empty = false; + for i in 0..N { + should_be_private_lt &= i != num_private_lt; + should_be_private &= i != total_private; + should_be_public_gte |= i == total_non_public_gte; + should_be_empty |= i == total_num; + let hint = hints[i]; + let from_lt = original_array_lt[hint.original_index]; + let from_gte = original_array_gte[hint.original_index]; + let from = if should_be_private_lt { + assert_eq(hint.original_index, i, "items with a counter of 0 should be prepended"); + from_lt + } else if should_be_private { + assert_eq(hint.original_index, i - num_private_lt, "items with a counter of 0 should be prepended"); + from_gte + } else if should_be_public_gte { + from_gte + } else { + from_lt + }; + let dest = sorted_transformed_value_array[i]; + let dest_counter = hint.counter; + assert(is_transformed(from, dest), "incorrect transformed value"); + assert_eq(from.counter(), dest_counter, "mismatch counter"); + if !should_be_empty { + if !should_be_private { + assert( + dest_counter > prev_counter, "value array must be sorted by counter in ascending order" + ); + } + } else { + assert(is_empty(dest), "array must be padded with empty items"); + } + prev_counter = dest_counter; + } +} + +mod tests { + use crate::{ + abis::side_effect::Ordered, tests::utils::pad_end, traits::Empty, + utils::arrays::{ + array_merge, + assert_combined_sorted_transformed_value_array::{ + assert_combined_sorted_transformed_value_array_asc, + get_combined_order_hints::{CombinedOrderHint, get_combined_order_hints_asc} + }, + sort_by_counter::sort_by_counter_asc + } + }; + + 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], + hints: [CombinedOrderHint; N], + } + + impl TestDataBuilder { + pub fn new() -> TestDataBuilder { + 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 } + ], + TestItem::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 } + ], + TestItem::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 } + ], + TestValue::empty() + ); + + let hints = [ + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 11, original_index: 3 }, + CombinedOrderHint { counter: 22, original_index: 2 }, + CombinedOrderHint { counter: 33, original_index: 3 }, + CombinedOrderHint { counter: 44, original_index: 4 }, + CombinedOrderHint { counter: 55, original_index: 2 }, + CombinedOrderHint { counter: 0, original_index: 5 }, + CombinedOrderHint { counter: 0, original_index: 6 }, + CombinedOrderHint { counter: 0, original_index: 7 } + ]; + + TestDataBuilder { original_array_lt, original_array_gte, sorted_transformed_value_array, hints } + } + + pub fn new_without_prepended() -> TestDataBuilder { + let original_array_lt = pad_end( + [ + TestItem { name: 6, price: 80, tax: 1, counter: 22 }, + TestItem { name: 3, price: 30, tax: 4, counter: 11 } + ], + TestItem::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 } + ], + TestItem::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 } + ], + TestValue::empty() + ); + + let hints = [ + CombinedOrderHint { counter: 11, original_index: 1 }, + CombinedOrderHint { counter: 22, original_index: 0 }, + CombinedOrderHint { counter: 33, original_index: 1 }, + CombinedOrderHint { counter: 44, original_index: 2 }, + CombinedOrderHint { counter: 55, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 3 }, + CombinedOrderHint { counter: 0, original_index: 4 }, + CombinedOrderHint { counter: 0, original_index: 5 } + ]; + + TestDataBuilder { original_array_lt, original_array_gte, sorted_transformed_value_array, hints } + } + + 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]; + self.sorted_transformed_value_array[to] = tmp; + } + + pub fn swap_hints(&mut self, from: u32, to: u32) { + let tmp = self.hints[from]; + self.hints[from] = self.hints[to]; + self.hints[to] = tmp; + } + + pub fn execute(self) { + assert_combined_sorted_transformed_value_array_asc( + self.original_array_lt, + self.original_array_gte, + self.sorted_transformed_value_array, + is_transformed, + self.hints + ); + } + } + + #[test] + fn assert_combined_sorted_transformed_value_array_asc_succeeds() { + let builder = TestDataBuilder::new(); + + 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); + assert_eq(sorted, builder.sorted_transformed_value_array); + + builder.execute(); + } + + #[test] + fn assert_combined_sorted_transformed_value_array_asc_without_prepended_counter_zeros_succeeds() { + let builder = TestDataBuilder::new_without_prepended(); + + 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); + assert_eq(sorted, builder.sorted_transformed_value_array); + + builder.execute(); + } + + #[test(should_fail_with="incorrect transformed value")] + fn assert_combined_sorted_transformed_value_array_asc_mismatch_value_fails() { + let mut builder = TestDataBuilder::new(); + + // Tweak the value at index 1. + builder.sorted_transformed_value_array[1].total += 1; + + builder.execute(); + } + + #[test(should_fail_with="incorrect transformed value")] + fn assert_combined_sorted_transformed_value_array_asc_cross_boundary_fails() { + let mut builder = TestDataBuilder::new(); + + builder.swap_items(4, 6); + + builder.execute(); + } + + #[test(should_fail_with="items with a counter of 0 should be prepended")] + fn assert_combined_sorted_transformed_value_array_asc_unordered_prepended_fails() { + let mut builder = TestDataBuilder::new(); + + builder.swap_items(0, 1); + builder.swap_hints(0, 1); + + builder.execute(); + } + + #[test(should_fail_with="items with a counter of 0 should be prepended")] + fn assert_combined_sorted_transformed_value_array_asc_mixed_prepended_fails() { + let mut builder = TestDataBuilder::new(); + + builder.swap_items(1, 4); + builder.swap_hints(1, 4); + + builder.execute(); + } + + #[test(should_fail_with="value array must be sorted by counter in ascending order")] + fn assert_combined_sorted_transformed_value_array_asc_unordered_fails() { + let mut builder = TestDataBuilder::new(); + + builder.swap_items(4, 5); + builder.swap_hints(4, 5); + + builder.execute(); + } + + #[test(should_fail_with="mismatch counter")] + fn assert_combined_sorted_transformed_value_array_asc_unordered_values_with_ordered_counters_fails() { + let mut builder = TestDataBuilder::new(); + + builder.swap_items(4, 5); + + // Update indexes in hints. + let tmp = builder.hints[4].original_index; + builder.hints[4].original_index = builder.hints[5].original_index; + builder.hints[5].original_index = tmp; + + builder.execute(); + } + + #[test(should_fail_with="array must be padded with empty items")] + fn assert_combined_sorted_transformed_value_array_asc_extra_non_empty_fails() { + let mut builder = TestDataBuilder::new(); + + // Add an item. + builder.sorted_transformed_value_array[9] = builder.sorted_transformed_value_array[8]; + builder.hints[9] = builder.hints[8]; + + builder.execute(); + } + + #[test(should_fail_with="value array must be sorted by counter in ascending order")] + fn assert_combined_sorted_transformed_value_array_asc_extra_empty_fails() { + let mut builder = TestDataBuilder::new(); + + // Clear an item. + builder.sorted_transformed_value_array[8] = TestValue::empty(); + builder.hints[8] = CombinedOrderHint { counter: 0, original_index: 6 }; + + builder.execute(); + } + + #[test(should_fail_with="value array must be sorted by counter in ascending order")] + fn assert_combined_sorted_transformed_value_array_asc_duplicate_fails() { + let mut builder = TestDataBuilder::new(); + + // Duplicate an item. + builder.sorted_transformed_value_array[5] = builder.sorted_transformed_value_array[4]; + builder.hints[5] = builder.hints[4]; + + builder.execute(); + } + + #[test(should_fail_with="items with a counter of 0 should be prepended")] + fn assert_combined_sorted_transformed_value_array_asc_duplicate_zero_counter_fails() { + let mut builder = TestDataBuilder::new(); + + // Duplicate an item with 0 counter. + builder.sorted_transformed_value_array[1] = builder.sorted_transformed_value_array[0]; + builder.hints[1] = builder.hints[0]; + + 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 new file mode 100644 index 00000000000..7b7822fa272 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_combined_sorted_transformed_value_array/get_combined_order_hints.nr @@ -0,0 +1,269 @@ +use crate::{ + abis::side_effect::Ordered, traits::{Empty, is_empty}, + utils::arrays::{array_length, sort_by_counter::compare_by_counter_empty_padded_asc, get_sorted_tuple::get_sorted_tuple} +}; + +struct CombinedOrderHint { + counter: u32, + original_index: u32, +} + +impl CombinedOrderHint { + pub fn empty() -> Self { + CombinedOrderHint { counter: 0, original_index: 0 } + } +} + +impl Eq for CombinedOrderHint { + fn eq(self, other: Self) -> bool { + (self.counter == other.counter) & (self.original_index == other.original_index) + } +} + +unconstrained 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) { + length += 1; + } + } + length +} + +pub fn get_combined_order_hints_asc( + array_lt: [T; N], + array_gte: [T; N] +) -> [CombinedOrderHint; N] where T: Ordered + Eq + Empty { + let mut hints = [CombinedOrderHint::empty(); N]; + let sorted_lt = get_sorted_tuple(array_lt, compare_by_counter_empty_padded_asc); + let sorted_gte = get_sorted_tuple(array_gte, compare_by_counter_empty_padded_asc); + let num_private_lt = count_private_items(array_lt); + let num_private_gte = count_private_items(array_gte); + let num_lt = array_length(array_lt); + let total_private = num_private_lt + num_private_gte; + let total_non_public_gte = num_lt + num_private_gte; + for i in 0..N { + let sorted = if i < num_private_lt { + sorted_lt[i] + } else if i < total_private { + sorted_gte[i - num_private_lt] + } else if i < total_non_public_gte { + sorted_lt[i - num_private_gte] + } else { + sorted_gte[i - num_lt] + }; + hints[i].original_index = sorted.original_index; + hints[i].counter = sorted.elem.counter(); + } + hints +} + +mod tests { + use crate::{ + abis::side_effect::Ordered, tests::utils::pad_end, traits::Empty, + 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 } + ], + TestItem::empty() + ); + let array_gte = pad_end( + [ + TestItem { value: 200, counter: 13 }, + TestItem { value: 100, counter: 19 }, + TestItem { value: 300, counter: 16 } + ], + TestItem::empty() + ); + let expected_hints = [ + CombinedOrderHint { counter: 3, original_index: 1 }, + CombinedOrderHint { counter: 6, original_index: 2 }, + CombinedOrderHint { counter: 9, original_index: 0 }, + CombinedOrderHint { counter: 13, original_index: 0 }, + CombinedOrderHint { counter: 16, original_index: 2 }, + CombinedOrderHint { counter: 19, original_index: 1 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } + + #[test] + fn get_combined_order_hints_asc_padded_empty() { + let array_lt = pad_end( + [ + TestItem { value: 500, counter: 6 }, + TestItem { value: 400, counter: 3 } + ], + TestItem::empty() + ); + let array_gte = pad_end([TestItem { value: 100, counter: 19 }], TestItem::empty()); + let expected_hints = [ + CombinedOrderHint { counter: 3, original_index: 1 }, + CombinedOrderHint { counter: 6, original_index: 0 }, + CombinedOrderHint { counter: 19, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 0, original_index: 2 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } + + #[test] + fn get_combined_order_hints_asc_lt_empty() { + let array_lt = [TestItem::empty(); 4]; + let array_gte = pad_end( + [ + TestItem { value: 200, counter: 13 }, + TestItem { value: 100, counter: 19 } + ], + TestItem::empty() + ); + let expected_hints = [ + CombinedOrderHint { counter: 13, original_index: 0 }, + CombinedOrderHint { counter: 19, original_index: 1 }, + CombinedOrderHint { counter: 0, original_index: 2 }, + CombinedOrderHint { counter: 0, original_index: 3 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } + + #[test] + fn get_combined_order_hints_asc_gte_empty() { + let array_lt = pad_end( + [ + TestItem { value: 400, counter: 3 }, + TestItem { value: 500, counter: 6 } + ], + TestItem::empty() + ); + let array_gte = [TestItem::empty(); 4]; + let expected_hints = [ + CombinedOrderHint { counter: 3, original_index: 0 }, + CombinedOrderHint { counter: 6, original_index: 1 }, + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } + + #[test] + fn get_combined_order_hints_asc_all_empty() { + let array_lt = [TestItem::empty(); 3]; + let array_gte = [TestItem::empty(); 3]; + let expected_hints = [ + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 0, original_index: 2 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } + + #[test] + 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 } + ], + TestItem::empty() + ); + let array_gte = pad_end( + [ + TestItem { value: 300, counter: 0 }, + TestItem { value: 600, counter: 0 }, + TestItem { value: 200, counter: 19 } + ], + TestItem::empty() + ); + let expected_hints = [ + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 13, original_index: 3 }, + CombinedOrderHint { counter: 16, original_index: 2 }, + CombinedOrderHint { counter: 19, original_index: 2 }, + CombinedOrderHint { counter: 0, original_index: 3 }, + CombinedOrderHint { counter: 0, original_index: 4 }, + CombinedOrderHint { counter: 0, original_index: 5 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } + + #[test] + fn get_combined_order_hints_asc_prepended_zero_counters_lt_empty() { + let array_lt = [TestItem::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 } + ], + TestItem::empty() + ); + let expected_hints = [ + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 13, original_index: 3 }, + CombinedOrderHint { counter: 19, original_index: 2 }, + CombinedOrderHint { counter: 0, original_index: 4 }, + CombinedOrderHint { counter: 0, original_index: 5 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } + + #[test] + 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 } + ], + TestItem::empty() + ); + let array_gte = [TestItem::empty(); 6]; + let expected_hints = [ + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 }, + CombinedOrderHint { counter: 13, original_index: 3 }, + CombinedOrderHint { counter: 19, original_index: 2 }, + CombinedOrderHint { counter: 0, original_index: 0 }, + CombinedOrderHint { counter: 0, original_index: 1 } + ]; + assert_eq(get_combined_order_hints_asc(array_lt, array_gte), expected_hints); + } +} + 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 0b8a4806c30..dd0260be56a 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 @@ -1,6 +1,8 @@ +mod get_order_hints; + use crate::{ abis::side_effect::Ordered, traits::{Empty, is_empty}, - utils::arrays::{array_length, sort_get_order_hints::OrderHint} + utils::arrays::{array_length, assert_exposed_sorted_transformed_value_array::get_order_hints::OrderHint} }; // original_array must be valid, i.e. validate_array(original_array) == true @@ -37,10 +39,7 @@ pub fn assert_exposed_sorted_transformed_value_array( mod tests { use crate::{ abis::side_effect::Ordered, traits::Empty, - utils::arrays::{ - assert_exposed_sorted_transformed_value_array::assert_exposed_sorted_transformed_value_array, - sort_get_order_hints::OrderHint - } + utils::arrays::{assert_exposed_sorted_transformed_value_array::{assert_exposed_sorted_transformed_value_array, get_order_hints::OrderHint}} }; struct TestItem { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_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 similarity index 75% rename from noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_get_order_hints.nr rename to noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_exposed_sorted_transformed_value_array/get_order_hints.nr index b60e5a50049..68b967efe2c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_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,8 +1,8 @@ use crate::{ abis::side_effect::Ordered, traits::{Empty, is_empty}, utils::arrays::{ - sort_by_counters::{order_by_counters_empty_padded_asc, order_by_counters_empty_padded_desc}, - sort_get_sorted_tuple::sort_get_sorted_tuple + sort_by_counter::{compare_by_counter_empty_padded_asc, compare_by_counter_empty_padded_desc}, + get_sorted_tuple::get_sorted_tuple } }; @@ -23,11 +23,11 @@ impl Eq for OrderHint { } } -pub fn sort_get_order_hints( +pub fn get_order_hints( array: [T; N], ordering: fn(T, T) -> bool ) -> [OrderHint; N] where T: Ordered + Eq + Empty { - let sorted_tuples = sort_get_sorted_tuple(array, ordering); + let sorted_tuples = get_sorted_tuple(array, ordering); let mut hints = [OrderHint::empty(); N]; for i in 0..N { @@ -44,18 +44,18 @@ pub fn sort_get_order_hints( hints } -pub fn sort_get_order_hints_asc(array: [T; N]) -> [OrderHint; N] where T: Ordered + Eq + Empty { - sort_get_order_hints(array, order_by_counters_empty_padded_asc) +pub fn get_order_hints_asc(array: [T; N]) -> [OrderHint; N] where T: Ordered + Eq + Empty { + get_order_hints(array, compare_by_counter_empty_padded_asc) } -pub fn sort_get_order_hints_desc(array: [T; N]) -> [OrderHint; N] where T: Ordered + Eq + Empty { - sort_get_order_hints(array, order_by_counters_empty_padded_desc) +pub fn get_order_hints_desc(array: [T; N]) -> [OrderHint; N] where T: Ordered + Eq + Empty { + get_order_hints(array, compare_by_counter_empty_padded_desc) } mod tests { use crate::{ abis::side_effect::Ordered, traits::Empty, - utils::arrays::sort_get_order_hints::{OrderHint, sort_get_order_hints_asc, sort_get_order_hints_desc} + utils::arrays::assert_exposed_sorted_transformed_value_array::get_order_hints::{get_order_hints_asc, get_order_hints_desc, OrderHint} }; struct TestItem { @@ -82,13 +82,13 @@ mod tests { } #[test] - fn sort_get_order_hints_asc_full_non_empty() { + 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 } ]; - let hints = sort_get_order_hints_asc(array); + let hints = get_order_hints_asc(array); let expected_hints = [ OrderHint { counter: 3, sorted_index: 2 }, OrderHint { counter: 6, sorted_index: 0 }, @@ -98,7 +98,7 @@ mod tests { } #[test] - fn sort_get_order_hints_asc_padded_empty() { + fn get_order_hints_asc_padded_empty() { let array = [ TestItem { value: 100, counter: 9 }, TestItem { value: 200, counter: 3 }, @@ -106,7 +106,7 @@ mod tests { TestItem::empty(), TestItem::empty() ]; - let hints = sort_get_order_hints_asc(array); + let hints = get_order_hints_asc(array); let expected_hints = [ OrderHint { counter: 3, sorted_index: 2 }, OrderHint { counter: 6, sorted_index: 0 }, @@ -118,13 +118,13 @@ mod tests { } #[test] - fn sort_get_order_hints_desc_full_non_empty() { + 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 } ]; - let hints = sort_get_order_hints_desc(array); + let hints = get_order_hints_desc(array); let expected_hints = [ OrderHint { counter: 9, sorted_index: 0 }, OrderHint { counter: 6, sorted_index: 2 }, @@ -134,7 +134,7 @@ mod tests { } #[test] - fn sort_get_order_hints_desc_padded_empty() { + fn get_order_hints_desc_padded_empty() { let array = [ TestItem { value: 100, counter: 9 }, TestItem { value: 200, counter: 3 }, @@ -142,7 +142,7 @@ mod tests { TestItem::empty(), TestItem::empty() ]; - let hints = sort_get_order_hints_desc(array); + let hints = get_order_hints_desc(array); let expected_hints = [ OrderHint { counter: 9, sorted_index: 0 }, OrderHint { counter: 6, sorted_index: 2 }, 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 8908ac21375..5efe0c698d5 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,6 +1,11 @@ +mod get_split_order_hints; + use crate::{ abis::side_effect::Ordered, traits::{Empty, is_empty}, - utils::arrays::{array_length, sort_get_split_order_hints::SplitOrderHints, validate_array} + utils::arrays::{ + array_length, assert_split_sorted_transformed_value_arrays::get_split_order_hints::SplitOrderHints, + validate_array +} }; // original_array must be valid, i.e. validate_array(original_array) == true @@ -112,8 +117,10 @@ mod tests { use crate::{ abis::side_effect::Ordered, traits::Empty, utils::arrays::{ - assert_split_sorted_transformed_value_arrays::{assert_split_sorted_transformed_value_arrays_asc, assert_split_sorted_transformed_value_arrays_desc}, - sort_get_split_order_hints::SplitOrderHints + assert_split_sorted_transformed_value_arrays::{ + assert_split_sorted_transformed_value_arrays_asc, + assert_split_sorted_transformed_value_arrays_desc, get_split_order_hints::SplitOrderHints + } } }; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_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 similarity index 73% rename from noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_get_split_order_hints.nr rename to noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/assert_split_sorted_transformed_value_arrays/get_split_order_hints.nr index 1fa12e193fa..433cfbbb1d4 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_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 @@ -1,8 +1,8 @@ use crate::{ abis::side_effect::Ordered, traits::{Empty, is_empty}, utils::arrays::{ - sort_by_counters::{order_by_counters_empty_padded_asc, order_by_counters_empty_padded_desc}, - sort_get_sorted_tuple::sort_get_sorted_tuple + sort_by_counter::{compare_by_counter_empty_padded_asc, compare_by_counter_empty_padded_desc}, + get_sorted_tuple::get_sorted_tuple } }; @@ -26,17 +26,17 @@ impl Eq for SplitOrderHints { } } -fn sort_get_split_order_hints( +fn get_split_order_hints( array: [T; N], split_counter: u32, ascending: bool ) -> SplitOrderHints where T: Ordered + Eq + Empty { let ordering = if ascending { - order_by_counters_empty_padded_asc + compare_by_counter_empty_padded_asc } else { - order_by_counters_empty_padded_desc + compare_by_counter_empty_padded_desc }; - let sorted_tuples = sort_get_sorted_tuple(array, ordering); + let sorted_tuples = get_sorted_tuple(array, ordering); let mut sorted_counters_lt = [0; N]; let mut sorted_counters_gte = [0; N]; @@ -67,24 +67,24 @@ fn sort_get_split_order_hints( SplitOrderHints { sorted_counters_lt, sorted_counters_gte, sorted_indexes } } -pub fn sort_get_split_order_hints_asc( +pub fn get_split_order_hints_asc( array: [T; N], split_counter: u32 ) -> SplitOrderHints where T: Ordered + Eq + Empty { - sort_get_split_order_hints(array, split_counter, true) + get_split_order_hints(array, split_counter, true) } -pub fn sort_get_split_order_hints_desc( +pub fn get_split_order_hints_desc( array: [T; N], split_counter: u32 ) -> SplitOrderHints where T: Ordered + Eq + Empty { - sort_get_split_order_hints(array, split_counter, false) + get_split_order_hints(array, split_counter, false) } mod tests { use crate::{ abis::side_effect::Ordered, traits::Empty, - utils::arrays::sort_get_split_order_hints::{sort_get_split_order_hints_asc, sort_get_split_order_hints_desc, SplitOrderHints} + 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 { @@ -131,9 +131,9 @@ mod tests { // asc #[test] - fn sort_get_split_order_hints_asc_zero_split_counter_full() { + fn get_split_order_hints_asc_zero_split_counter_full() { let split_counter = 0; - let hints = sort_get_split_order_hints_asc(full_array, split_counter); + let hints = get_split_order_hints_asc(full_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [0, 0, 0, 0, 0], sorted_counters_gte: [5, 7, 11, 13, 17], @@ -143,9 +143,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_asc_non_zero_split_counter_full() { + fn get_split_order_hints_asc_non_zero_split_counter_full() { let split_counter = 9; - let hints = sort_get_split_order_hints_asc(full_array, split_counter); + let hints = get_split_order_hints_asc(full_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [5, 7, 0, 0, 0], sorted_counters_gte: [11, 13, 17, 0, 0], @@ -155,9 +155,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_asc_non_zero_split_counter_equal_full() { + fn get_split_order_hints_asc_non_zero_split_counter_equal_full() { let split_counter = 11; // Equal one of the item's counter. - let hints = sort_get_split_order_hints_asc(full_array, split_counter); + let hints = get_split_order_hints_asc(full_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [5, 7, 0, 0, 0], sorted_counters_gte: [11, 13, 17, 0, 0], @@ -167,9 +167,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_asc_zero_split_counter_padded_empty() { + fn get_split_order_hints_asc_zero_split_counter_padded_empty() { let split_counter = 0; - let hints = sort_get_split_order_hints_asc(padded_array, split_counter); + let hints = get_split_order_hints_asc(padded_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [0, 0, 0, 0, 0, 0, 0], sorted_counters_gte: [5, 7, 11, 13, 17, 0, 0], @@ -179,9 +179,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_asc_non_zero_split_counter_padded_empty() { + fn get_split_order_hints_asc_non_zero_split_counter_padded_empty() { let split_counter = 9; - let hints = sort_get_split_order_hints_asc(padded_array, split_counter); + let hints = get_split_order_hints_asc(padded_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [5, 7, 0, 0, 0, 0, 0], sorted_counters_gte: [11, 13, 17, 0, 0, 0, 0], @@ -191,9 +191,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_asc_non_zero_split_counter_equal_padded_empty() { + fn get_split_order_hints_asc_non_zero_split_counter_equal_padded_empty() { let split_counter = 11; - let hints = sort_get_split_order_hints_asc(padded_array, split_counter); + let hints = get_split_order_hints_asc(padded_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [5, 7, 0, 0, 0, 0, 0], sorted_counters_gte: [11, 13, 17, 0, 0, 0, 0], @@ -205,9 +205,9 @@ mod tests { // desc #[test] - fn sort_get_split_order_hints_desc_zero_split_counter_empty() { + fn get_split_order_hints_desc_zero_split_counter_empty() { let split_counter = 0; - let hints = sort_get_split_order_hints_desc(full_array, split_counter); + let hints = get_split_order_hints_desc(full_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [0, 0, 0, 0, 0], sorted_counters_gte: [17, 13, 11, 7, 5], @@ -217,9 +217,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_desc_non_zero_split_counter_empty() { + fn get_split_order_hints_desc_non_zero_split_counter_empty() { let split_counter = 9; - let hints = sort_get_split_order_hints_desc(full_array, split_counter); + let hints = get_split_order_hints_desc(full_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [7, 5, 0, 0, 0], sorted_counters_gte: [17, 13, 11, 0, 0], @@ -229,9 +229,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_desc_non_zero_split_counter_equal_empty() { + fn get_split_order_hints_desc_non_zero_split_counter_equal_empty() { let split_counter = 11; - let hints = sort_get_split_order_hints_desc(full_array, split_counter); + let hints = get_split_order_hints_desc(full_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [7, 5, 0, 0, 0], sorted_counters_gte: [17, 13, 11, 0, 0], @@ -241,9 +241,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_desc_zero_split_counter_padded_empty() { + fn get_split_order_hints_desc_zero_split_counter_padded_empty() { let split_counter = 0; - let hints = sort_get_split_order_hints_desc(padded_array, split_counter); + let hints = get_split_order_hints_desc(padded_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [0, 0, 0, 0, 0, 0, 0], sorted_counters_gte: [17, 13, 11, 7, 5, 0, 0], @@ -253,9 +253,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_desc_non_zero_split_counter_padded_empty() { + fn get_split_order_hints_desc_non_zero_split_counter_padded_empty() { let split_counter = 9; - let hints = sort_get_split_order_hints_desc(padded_array, split_counter); + let hints = get_split_order_hints_desc(padded_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [7, 5, 0, 0, 0, 0, 0], sorted_counters_gte: [17, 13, 11, 0, 0, 0, 0], @@ -265,9 +265,9 @@ mod tests { } #[test] - fn sort_get_split_order_hints_desc_non_zero_split_counter_equal_padded_empty() { + fn get_split_order_hints_desc_non_zero_split_counter_equal_padded_empty() { let split_counter = 11; - let hints = sort_get_split_order_hints_desc(padded_array, split_counter); + let hints = get_split_order_hints_desc(padded_array, split_counter); let expected_hints = SplitOrderHints { sorted_counters_lt: [7, 5, 0, 0, 0, 0, 0], sorted_counters_gte: [17, 13, 11, 0, 0, 0, 0], 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 8e68c32e580..c1927035aae 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,7 +70,7 @@ pub fn assert_split_transformed_value_arrays( mod tests { use crate::{ - abis::side_effect::Ordered, traits::Empty, + abis::side_effect::Ordered, tests::utils::pad_end, traits::Empty, utils::arrays::assert_split_transformed_value_arrays::{assert_split_transformed_value_arrays, assert_split_transformed_value_arrays_with_hint} }; @@ -103,14 +103,6 @@ mod tests { from.value == to } - fn pad_end(items: [T; N], emptyItem: T) -> [T; NUM_TEST_ITEMS] { - let mut output = [emptyItem; NUM_TEST_ITEMS]; - for i in 0..N { - output[i] = items[i]; - } - output - } - struct TestBuilder { sorted_array: [TestItem; NUM_TEST_ITEMS], transformed_value_array_lt: [Field; NUM_TEST_ITEMS], diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_get_sorted_hints.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_hints.nr similarity index 60% rename from noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_get_sorted_hints.nr rename to noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_hints.nr index cbbf8c5c76e..9e6a490a008 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_get_sorted_hints.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_hints.nr @@ -1,21 +1,15 @@ -use crate::{ - traits::{Empty, is_empty}, - utils::arrays::{sort_get_sorted_tuple::{sort_get_sorted_tuple, SortedTuple}} -}; +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 sort_get_sorted_hints( +pub fn get_sorted_hints( values: [T; N], ordering: fn(T, T) -> bool ) -> SortedResult where T: Eq + Empty { - let sorted = sort_get_sorted_tuple( - values, - |a: T, b: T| is_empty(b) | (!is_empty(a) & !is_empty(b) & ordering(a, b)) - ); + let sorted = get_sorted_tuple(values, ordering); let sorted_array = sorted.map(|t: SortedTuple| t.elem); let mut sorted_index_hints = [0; N]; @@ -30,33 +24,33 @@ pub fn sort_get_sorted_hints( } #[test] -fn sort_get_sorted_hints_asc_non_padded() { +fn get_sorted_hints_asc_non_padded() { let values = [40, 60, 20, 50]; - let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b)); + 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 sort_get_sorted_hints_desc_non_padded() { +fn get_sorted_hints_desc_non_padded() { let values = [40, 20, 60, 50]; - let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a)); + 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 sort_get_sorted_hints_asc_padded() { +fn get_sorted_hints_asc_padded() { let values = [40, 60, 20, 50, 0, 0]; - let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b)); + 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 sort_get_sorted_hints_desc_padded() { +fn get_sorted_hints_desc_padded() { let values = [40, 20, 60, 50, 0, 0]; - let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a)); + 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/sort_get_sorted_tuple.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_tuple.nr similarity index 65% rename from noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_get_sorted_tuple.nr rename to noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_tuple.nr index 6460a883fed..f7d48a6aea9 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_get_sorted_tuple.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/get_sorted_tuple.nr @@ -1,9 +1,14 @@ +use crate::utils::arrays::sort_by::sort_by; + struct SortedTuple { elem: T, original_index: u32, } -pub fn sort_get_sorted_tuple(array: [T; N], ordering: fn[Env](T, T) -> bool) -> [SortedTuple; N] { +pub fn get_sorted_tuple( + array: [T; N], + ordering: fn[Env](T, T) -> bool +) -> [SortedTuple; N] { let mut tuples = [SortedTuple { elem: array[0], original_index: 0 }; N]; for i in 0..N { tuples[i] = SortedTuple { @@ -11,11 +16,14 @@ pub fn sort_get_sorted_tuple(array: [T; N], ordering: fn[Env original_index: i, }; } - tuples.sort_via(|a: SortedTuple, b: SortedTuple| ordering(a.elem, b.elem)) + sort_by( + tuples, + |a: SortedTuple, b: SortedTuple| ordering(a.elem, b.elem) + ) } #[test] -fn sort_get_sorted_tuple_asc() { +fn get_sorted_tuple_asc() { let original = [3, 2, 9, 5]; let expected = [ SortedTuple { elem: 2, original_index: 1 }, @@ -23,7 +31,7 @@ fn sort_get_sorted_tuple_asc() { SortedTuple { elem: 5, original_index: 3 }, SortedTuple { elem: 9, original_index: 2 } ]; - let sorted = sort_get_sorted_tuple(original, |a: u64, b: u64| a < b); + let sorted = get_sorted_tuple(original, |a: u64, b: u64| a < b); for i in 0..4 { assert_eq(sorted[i].elem, expected[i].elem); assert_eq(sorted[i].original_index, expected[i].original_index); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by.nr new file mode 100644 index 00000000000..cb4052431d7 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by.nr @@ -0,0 +1,23 @@ +use crate::utils::arrays::find_index_hint; + +// Copied from the stdlib Array.sort_via, but this one doesn't use `ordering` to check that the array is sorted. +pub fn sort_by(array: [T; N], ordering: fn[Env](T, T) -> bool) -> [T; N] { + let mut result = array; + let sorted_index = array.get_sorting_index(ordering); + // Ensure the indexes are correct + for i in 0..N { + let pos = find_index_hint(sorted_index, |index: u32| index == i); + assert(sorted_index[pos] == i); + } + // Sort the array using the indexes + for i in 0..N { + result[i] = array[sorted_index[i]]; + } + + // Ensure the array is sorted + // for i in 0..N - 1 { + // assert(ordering(result[i], result[i + 1])); + // } + + result +} 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 new file mode 100644 index 00000000000..b23a317e59a --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counter.nr @@ -0,0 +1,160 @@ +use crate::{abis::side_effect::Ordered, traits::{Empty, is_empty}, utils::arrays::sort_by::sort_by}; + +pub fn compare_by_counter_empty_padded_asc(a: T, b: T) -> bool where T: Ordered + Empty + Eq { + !is_empty(a) & (is_empty(b) | (a.counter() < b.counter())) +} + +pub fn compare_by_counter_empty_padded_desc(a: T, b: T) -> bool where T: Ordered + Empty + Eq { + !is_empty(a) & (is_empty(b) | (a.counter() >= b.counter())) +} + +pub fn sort_by_counter_asc(array: [T; N]) -> [T; N] where T: Ordered + Empty + Eq { + sort_by(array, compare_by_counter_empty_padded_asc) +} + +pub fn sort_by_counter_desc(array: [T; N]) -> [T; N] where T: Ordered + Empty + Eq { + sort_by(array, compare_by_counter_empty_padded_desc) +} + +mod tests { + use crate::{ + abis::side_effect::Ordered, traits::Empty, + 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) + } + + 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) + } + + 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 } + ) + } + + 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 } + ) + } + + #[test] + fn compare_by_counter_empty_padded_asc_expected() { + assert_eq(compare_test_items_asc(1, 2), true); + assert_eq(compare_test_items_asc(1, 1), false); + assert_eq(compare_test_items_asc(2, 1), false); + assert_eq(compare_test_items_asc(0, 0), false); + } + + #[test] + fn compare_by_counter_empty_padded_desc_expected() { + assert_eq(compare_test_items_desc(1, 2), false); + assert_eq(compare_test_items_desc(1, 1), true); + assert_eq(compare_test_items_desc(2, 1), true); + assert_eq(compare_test_items_desc(0, 0), false); + } + + #[test] + fn sort_by_counter_asc_full_non_empty() { + let sorted = sort_by_values_asc([4, 2, 1, 3, 5]); + assert_eq(sorted, [1, 2, 3, 4, 5]); + } + + #[test] + fn sort_by_counter_desc_full_non_empty() { + let sorted = sort_by_values_desc([4, 2, 1, 3, 5]); + assert_eq(sorted, [5, 4, 3, 2, 1]); + } + + #[test] + fn sort_by_counter_asc_padded_empty() { + let sorted = sort_by_values_asc([4, 2, 0, 0, 1, 0, 3, 5]); + assert_eq(sorted, [1, 2, 3, 4, 5, 0, 0, 0]); + } + + #[test] + fn sort_by_counter_desc_padded_empty() { + let sorted = sort_by_values_desc([4, 2, 0, 0, 1, 0, 3, 5]); + assert_eq(sorted, [5, 4, 3, 2, 1, 0, 0, 0]); + } + + #[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() + ]; + 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() + ]; + assert_eq(sort_by_counter_asc(original), expected); + } + + #[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() + ]; + 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() + ]; + assert_eq(sort_by_counter_desc(original), expected); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counters.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counters.nr deleted file mode 100644 index 771879bbc60..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays/sort_by_counters.nr +++ /dev/null @@ -1,21 +0,0 @@ -use crate::{abis::side_effect::Ordered, traits::{Empty, is_empty}}; - -pub fn order_by_counters_empty_padded_asc(a: T, b: T) -> bool where T: Ordered + Eq + Empty { - is_empty(b) | (!is_empty(a) & !is_empty(b) & a.counter() < b.counter()) -} - -pub fn order_by_counters_empty_padded_desc(a: T, b: T) -> bool where T: Ordered + Eq + Empty { - is_empty(b) | (!is_empty(a) & !is_empty(b) & a.counter() > b.counter()) -} - -fn sort_by(array: [T; N], ordering: fn(T, T) -> bool) -> [T; N] { - array.sort_via(|a, b| ordering(a, b)) -} - -pub fn sort_by_counters_asc(array: [T; N]) -> [T; N] where T: Ordered + Eq + Empty { - sort_by(array, order_by_counters_empty_padded_asc) -} - -pub fn sort_by_counters_desc(array: [T; N]) -> [T; N] where T: Ordered + Eq + Empty { - sort_by(array, order_by_counters_empty_padded_desc) -} diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 68b6261a0dc..e58f824e9f8 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -13,7 +13,6 @@ export * from './gas_fees.js'; export * from './gas_settings.js'; export * from './global_variables.js'; export * from './header.js'; -export * from './kernel/combine_hints.js'; export * from './kernel/combined_accumulated_data.js'; export * from './kernel/combined_constant_data.js'; export * from './kernel/private_kernel_empty_inputs.js'; diff --git a/yarn-project/circuits.js/src/structs/kernel/combine_hints.ts b/yarn-project/circuits.js/src/structs/kernel/combine_hints.ts deleted file mode 100644 index f3e9e039fc5..00000000000 --- a/yarn-project/circuits.js/src/structs/kernel/combine_hints.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { type FieldsOf } from '@aztec/foundation/array'; -import { removeArrayPaddingEnd } from '@aztec/foundation/collection'; -import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; - -import { inspect } from 'util'; - -import { - MAX_ENCRYPTED_LOGS_PER_TX, - MAX_NOTE_ENCRYPTED_LOGS_PER_TX, - MAX_NOTE_HASHES_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - MAX_UNENCRYPTED_LOGS_PER_TX, -} from '../../constants.gen.js'; -import { - deduplicateSortedArray, - getNonEmptyItems, - mergeAccumulatedData, - sortByCounterGetSortedHints, - sortByPositionThenCounterGetSortedHints, -} from '../../utils/index.js'; -import { LogHash, ScopedLogHash } from '../log_hash.js'; -import { ScopedNoteHash } from '../note_hash.js'; -import { PublicDataUpdateRequest } from '../public_data_update_request.js'; -import { type PublicAccumulatedData } from './public_accumulated_data.js'; - -export class CombineHints { - constructor( - public readonly sortedNoteHashes: Tuple, - public readonly sortedNoteHashesIndexes: Tuple, - public readonly sortedNoteEncryptedLogsHashes: Tuple, - public readonly sortedNoteEncryptedLogsHashesIndexes: Tuple, - public readonly sortedEncryptedLogsHashes: Tuple, - public readonly sortedEncryptedLogsHashesIndexes: Tuple, - public readonly sortedUnencryptedLogsHashes: Tuple, - public readonly sortedUnencryptedLogsHashesIndexes: Tuple, - public readonly sortedPublicDataUpdateRequests: Tuple< - PublicDataUpdateRequest, - typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - >, - public readonly sortedPublicDataUpdateRequestsIndexes: Tuple, - public readonly dedupedPublicDataUpdateRequests: Tuple< - PublicDataUpdateRequest, - typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - >, - public readonly dedupedPublicDataUpdateRequestsRuns: Tuple, - ) {} - - static getFields(fields: FieldsOf) { - return [ - fields.sortedNoteHashes, - fields.sortedNoteHashesIndexes, - fields.sortedNoteEncryptedLogsHashes, - fields.sortedNoteEncryptedLogsHashesIndexes, - fields.sortedEncryptedLogsHashes, - fields.sortedEncryptedLogsHashesIndexes, - fields.sortedUnencryptedLogsHashes, - fields.sortedUnencryptedLogsHashesIndexes, - fields.sortedPublicDataUpdateRequests, - fields.sortedPublicDataUpdateRequestsIndexes, - fields.dedupedPublicDataUpdateRequests, - fields.dedupedPublicDataUpdateRequestsRuns, - ] as const; - } - - static from(fields: FieldsOf): CombineHints { - return new CombineHints(...CombineHints.getFields(fields)); - } - - toBuffer() { - return serializeToBuffer(...CombineHints.getFields(this)); - } - - static fromBuffer(buffer: Buffer | BufferReader) { - const reader = BufferReader.asReader(buffer); - return new CombineHints( - reader.readArray(MAX_NOTE_HASHES_PER_TX, ScopedNoteHash), - reader.readNumbers(MAX_NOTE_HASHES_PER_TX), - reader.readArray(MAX_NOTE_ENCRYPTED_LOGS_PER_TX, LogHash), - reader.readNumbers(MAX_NOTE_ENCRYPTED_LOGS_PER_TX), - reader.readArray(MAX_ENCRYPTED_LOGS_PER_TX, ScopedLogHash), - reader.readNumbers(MAX_ENCRYPTED_LOGS_PER_TX), - reader.readArray(MAX_UNENCRYPTED_LOGS_PER_TX, ScopedLogHash), - reader.readNumbers(MAX_UNENCRYPTED_LOGS_PER_TX), - reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataUpdateRequest), - reader.readNumbers(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX), - reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataUpdateRequest), - reader.readNumbers(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX), - ); - } - - static fromPublicData({ - revertibleData, - nonRevertibleData, - }: { - revertibleData: PublicAccumulatedData; - nonRevertibleData: PublicAccumulatedData; - }): CombineHints { - const mergedNoteHashes = mergeAccumulatedData( - nonRevertibleData.noteHashes, - revertibleData.noteHashes, - MAX_NOTE_HASHES_PER_TX, - ); - - const [sortedNoteHashes, sortedNoteHashesIndexes] = sortByCounterGetSortedHints( - mergedNoteHashes, - MAX_NOTE_HASHES_PER_TX, - ); - - const mergedNoteEncryptedLogsHashes = mergeAccumulatedData( - nonRevertibleData.noteEncryptedLogsHashes, - revertibleData.noteEncryptedLogsHashes, - MAX_NOTE_ENCRYPTED_LOGS_PER_TX, - ); - const [sortedNoteEncryptedLogsHashes, sortedNoteEncryptedLogsHashesIndexes] = sortByCounterGetSortedHints( - mergedNoteEncryptedLogsHashes, - MAX_NOTE_ENCRYPTED_LOGS_PER_TX, - ); - - const mergedEncryptedLogsHashes = mergeAccumulatedData( - nonRevertibleData.encryptedLogsHashes, - revertibleData.encryptedLogsHashes, - MAX_ENCRYPTED_LOGS_PER_TX, - ); - const [sortedEncryptedLogsHashes, sortedEncryptedLogsHashesIndexes] = sortByCounterGetSortedHints( - mergedEncryptedLogsHashes, - MAX_ENCRYPTED_LOGS_PER_TX, - ); - - const unencryptedLogHashes = mergeAccumulatedData( - nonRevertibleData.unencryptedLogsHashes, - revertibleData.unencryptedLogsHashes, - MAX_ENCRYPTED_LOGS_PER_TX, - ); - - const [sortedUnencryptedLogsHashes, sortedUnencryptedLogsHashesIndexes] = sortByCounterGetSortedHints( - unencryptedLogHashes, - MAX_ENCRYPTED_LOGS_PER_TX, - ); - - const publicDataUpdateRequests = mergeAccumulatedData( - nonRevertibleData.publicDataUpdateRequests, - revertibleData.publicDataUpdateRequests, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ); - - // Since we're using `check_permutation` in the circuit, we need index hints based on the original array. - const [sortedPublicDataUpdateRequests, sortedPublicDataUpdateRequestsIndexes] = - sortByPositionThenCounterGetSortedHints(publicDataUpdateRequests, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, { - ascending: true, - hintIndexesBy: 'original', - }); - - // further, we need to fill in the rest of the hints with an identity mapping - for (let i = 0; i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; i++) { - if (publicDataUpdateRequests[i].isEmpty()) { - sortedPublicDataUpdateRequestsIndexes[i] = i; - } - } - - const [dedupedPublicDataUpdateRequests, dedupedPublicDataUpdateRequestsRuns] = deduplicateSortedArray( - sortedPublicDataUpdateRequests, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - () => PublicDataUpdateRequest.empty(), - ); - - return CombineHints.from({ - sortedNoteHashes, - sortedNoteHashesIndexes, - sortedNoteEncryptedLogsHashes, - sortedNoteEncryptedLogsHashesIndexes, - sortedEncryptedLogsHashes, - sortedEncryptedLogsHashesIndexes, - sortedUnencryptedLogsHashes, - sortedUnencryptedLogsHashesIndexes, - sortedPublicDataUpdateRequests, - sortedPublicDataUpdateRequestsIndexes, - dedupedPublicDataUpdateRequests, - dedupedPublicDataUpdateRequestsRuns, - }); - } - - [inspect.custom](): string { - return `CombineHints { - sortedNoteHashes: ${getNonEmptyItems(this.sortedNoteHashes) - .map(h => inspect(h)) - .join(', ')}, - sortedNoteHashesIndexes: ${removeArrayPaddingEnd(this.sortedNoteHashesIndexes, n => n === 0)}, - sortedUnencryptedLogsHashes: ${getNonEmptyItems(this.sortedUnencryptedLogsHashes) - .map(h => inspect(h)) - .join(', ')}, - sortedUnencryptedLogsHashesIndexes: ${removeArrayPaddingEnd(this.sortedUnencryptedLogsHashesIndexes, n => n === 0)}, - sortedPublicDataUpdateRequests: ${getNonEmptyItems(this.sortedPublicDataUpdateRequests) - .map(h => inspect(h)) - .join(', ')}, - sortedPublicDataUpdateRequestsIndexes: ${this.sortedPublicDataUpdateRequestsIndexes}, - dedupedPublicDataUpdateRequests: ${getNonEmptyItems(this.dedupedPublicDataUpdateRequests) - .map(h => inspect(h)) - .join(', ')}, - dedupedPublicDataUpdateRequestsRuns: ${this.dedupedPublicDataUpdateRequestsRuns}, -}`; - } -} 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 639296898f1..c6f22d4b536 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 @@ -9,7 +9,6 @@ import { PartialStateReference } from '../partial_state_reference.js'; import { PublicDataHint } from '../public_data_hint.js'; import { PublicDataReadRequestHints } from '../public_data_read_request_hints.js'; import { type NullifierReadRequestHints, nullifierReadRequestHintsFromBuffer } from '../read_request_hints/index.js'; -import { CombineHints } from './combine_hints.js'; import { PublicKernelData } from './public_kernel_data.js'; export class PublicKernelTailCircuitPrivateInputs { @@ -32,7 +31,6 @@ export class PublicKernelTailCircuitPrivateInputs { public readonly publicDataHints: Tuple, public readonly publicDataReadRequestHints: PublicDataReadRequestHints, public readonly startState: PartialStateReference, - public readonly combineHints: CombineHints, ) {} toBuffer() { @@ -43,7 +41,6 @@ export class PublicKernelTailCircuitPrivateInputs { this.publicDataHints, this.publicDataReadRequestHints, this.startState, - this.combineHints, ); } @@ -68,7 +65,6 @@ export class PublicKernelTailCircuitPrivateInputs { reader.readArray(MAX_PUBLIC_DATA_HINTS, PublicDataHint), reader.readObject(PublicDataReadRequestHints), reader.readObject(PartialStateReference), - reader.readObject(CombineHints), ); } diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index cd35c218f41..a9e06ab980e 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -24,7 +24,6 @@ import { BaseParityInputs, BaseRollupInputs, CallContext, - CombineHints, CombinedAccumulatedData, CombinedConstantData, ConstantRollupData, @@ -669,31 +668,6 @@ export function makePublicKernelCircuitPrivateInputs(seed = 1): PublicKernelCirc return new PublicKernelCircuitPrivateInputs(makePublicKernelData(seed), makePublicCallData(seed + 0x1000)); } -export function makeCombineHints(seed = 1): CombineHints { - return CombineHints.from({ - sortedNoteHashes: makeTuple(MAX_NOTE_HASHES_PER_TX, makeScopedNoteHash, seed + 0x100), - sortedNoteHashesIndexes: makeTuple(MAX_NOTE_HASHES_PER_TX, i => i, seed + 0x200), - sortedNoteEncryptedLogsHashes: makeTuple(MAX_NOTE_ENCRYPTED_LOGS_PER_TX, makeLogHash, seed + 0x300), - sortedNoteEncryptedLogsHashesIndexes: makeTuple(MAX_NOTE_ENCRYPTED_LOGS_PER_TX, i => i, seed + 0x400), - sortedEncryptedLogsHashes: makeTuple(MAX_ENCRYPTED_LOGS_PER_TX, makeScopedLogHash, seed + 0x500), - sortedEncryptedLogsHashesIndexes: makeTuple(MAX_ENCRYPTED_LOGS_PER_TX, i => i, seed + 0x600), - sortedUnencryptedLogsHashes: makeTuple(MAX_UNENCRYPTED_LOGS_PER_TX, makeScopedLogHash, seed + 0x700), - sortedUnencryptedLogsHashesIndexes: makeTuple(MAX_UNENCRYPTED_LOGS_PER_TX, i => i, seed + 0x800), - sortedPublicDataUpdateRequests: makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - makePublicDataUpdateRequest, - seed + 0x900, - ), - sortedPublicDataUpdateRequestsIndexes: makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => i, seed + 0x1000), - dedupedPublicDataUpdateRequests: makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - makePublicDataUpdateRequest, - seed + 0x1100, - ), - dedupedPublicDataUpdateRequestsRuns: makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => i, seed + 0x1200), - }); -} - /** * Makes arbitrary public kernel tail inputs. * @param seed - The seed to use for generating the public kernel inputs. @@ -707,7 +681,6 @@ export function makePublicKernelTailCircuitPrivateInputs(seed = 1): PublicKernel makeTuple(MAX_PUBLIC_DATA_HINTS, PublicDataHint.empty, seed + 0x100), PublicDataReadRequestHintsBuilder.empty(), makePartialStateReference(seed + 0x200), - makeCombineHints(seed + 0x300), ); } diff --git a/yarn-project/circuits.js/src/utils/index.ts b/yarn-project/circuits.js/src/utils/index.ts index 237d704329f..4ebd321ce2e 100644 --- a/yarn-project/circuits.js/src/utils/index.ts +++ b/yarn-project/circuits.js/src/utils/index.ts @@ -1,7 +1,6 @@ -import { makeTuple } from '@aztec/foundation/array'; import { type Tuple } from '@aztec/foundation/serialize'; -import type { IsEmpty, Ordered, Positioned } from '../interfaces/index.js'; +import type { IsEmpty, Ordered } from '../interfaces/index.js'; // Define these utils here as their design is very specific to kernel's accumulated data and not general enough to be put in foundation. @@ -36,7 +35,7 @@ export function mergeAccumulatedData( } // Sort items by a provided compare function. All empty items are padded to the right. -export function genericSort( +function genericSort( arr: Tuple, compareFn: (a: T, b: T) => number, ascending: boolean = true, @@ -52,7 +51,7 @@ export function genericSort( }) as Tuple; } -export function compareByCounter(a: T, b: T): number { +function compareByCounter(a: T, b: T): number { return a.counter - b.counter; } @@ -63,134 +62,6 @@ export function sortByCounter( return genericSort(arr, compareByCounter, ascending); } -export function compareByPositionThenCounter(a: T, b: T): number { - const positionComp = a.position.cmp(b.position); - if (positionComp !== 0) { - return positionComp; - } - return a.counter - b.counter; -} - -export function sortByPositionThenCounter( - arr: Tuple, - ascending: boolean = true, -): Tuple { - return genericSort(arr, compareByPositionThenCounter, ascending); -} - -export interface SortOptions { - ascending: boolean; - // If you're using this in the circuits, and checking via `assert_sorted`, then you should use 'sorted'. - // If you're using this in the circuits, and checking via `check_permutation`, then you should use 'original'. - hintIndexesBy: 'original' | 'sorted'; -} - -const defaultSortOptions: SortOptions = { - ascending: true, - hintIndexesBy: 'sorted', -}; - -export function sortAndGetSortedHints( - arr: Tuple, - compareFn: (a: T, b: T) => number, - length: N = arr.length as N, // Need this for ts to infer the return Tuple length. - { ascending = true, hintIndexesBy = 'sorted' }: SortOptions, -): [Tuple, Tuple] { - const itemsWithIndexes = arr.map((item, i) => ({ - item, - originalIndex: i, - isEmpty: () => item.isEmpty(), - })); - - const sorted = genericSort(itemsWithIndexes, (a, b) => compareFn(a.item, b.item), ascending); - const items = sorted.map(({ item }) => item) as Tuple; - - const indexHints = makeTuple(length, () => 0); - if (hintIndexesBy === 'sorted') { - sorted.forEach(({ originalIndex }, i) => { - if (!items[i].isEmpty()) { - indexHints[originalIndex] = i; - } - }); - } else { - sorted.forEach(({ originalIndex }, i) => { - if (!items[i].isEmpty()) { - indexHints[i] = originalIndex; - } - }); - } - - return [items, indexHints]; -} - -export function sortByCounterGetSortedHints( - arr: Tuple, - length: N = arr.length as N, // Need this for ts to infer the return Tuple length. - options: SortOptions = defaultSortOptions, -): [Tuple, Tuple] { - return sortAndGetSortedHints(arr, compareByCounter, length, options); -} - -export function sortByPositionThenCounterGetSortedHints( - arr: Tuple, - length: N = arr.length as N, // Need this for ts to infer the return Tuple length. - options: SortOptions = defaultSortOptions, -): [Tuple, Tuple] { - return sortAndGetSortedHints(arr, compareByPositionThenCounter, length, options); -} - -/** - * @param arr An array sorted on position then counter. - * @param length for type inference. - * @param getEmptyItem helper function to get an empty item. - * @returns the array deduplicated by position, and the original run lengths of each position. - */ -export function deduplicateSortedArray( - arr: Tuple, - length: N = arr.length as N, - getEmptyItem: () => T, -): [Tuple, Tuple] { - const dedupedArray = makeTuple(length, getEmptyItem) as Tuple; - const runLengths = makeTuple(length, () => 0); - - let dedupedIndex = 0; - let runCounter = 0; - let currentPosition = arr[0].position; - - let i = 0; - for (; i < length; i++) { - const item = arr[i]; - - if (item.isEmpty()) { - break; // Stop processing when encountering the first empty item. - } - - if (item.position.equals(currentPosition)) { - runCounter++; - } else { - dedupedArray[dedupedIndex] = arr[i - 1]; - runLengths[dedupedIndex] = runCounter; - dedupedIndex++; - runCounter = 1; - currentPosition = item.position; - } - } - - if (runCounter > 0) { - dedupedArray[dedupedIndex] = arr[i - 1]; - runLengths[dedupedIndex] = runCounter; - dedupedIndex++; - } - - // Fill the remaining part of the deduped array and run lengths with empty items and zeros. - for (let i = dedupedIndex; i < length; i++) { - dedupedArray[i] = getEmptyItem(); - runLengths[i] = 0; - } - - return [dedupedArray, runLengths]; -} - export function isEmptyArray(arr: T[]): boolean { return arr.every(item => item.isEmpty()); } diff --git a/yarn-project/circuits.js/src/utils/utils.test.ts b/yarn-project/circuits.js/src/utils/utils.test.ts index edfd1b11db5..bb313e99ce4 100644 --- a/yarn-project/circuits.js/src/utils/utils.test.ts +++ b/yarn-project/circuits.js/src/utils/utils.test.ts @@ -4,16 +4,7 @@ import { type Tuple } from '@aztec/foundation/serialize'; import { MAX_FIELD_VALUE } from '../constants.gen.js'; import { type IsEmpty } from '../interfaces/index.js'; -import { - countAccumulatedItems, - deduplicateSortedArray, - getNonEmptyItems, - isEmptyArray, - mergeAccumulatedData, - sortByCounter, - sortByCounterGetSortedHints, - sortByPositionThenCounter, -} from './index.js'; +import { countAccumulatedItems, getNonEmptyItems, isEmptyArray, mergeAccumulatedData, sortByCounter } from './index.js'; class TestItem { constructor(public value: number, public counter = 0, public position = Fr.ZERO) {} @@ -211,268 +202,6 @@ describe('utils', () => { }); }); - describe('sortByCounterGetSortedHints', () => { - it('sorts descending items in ascending order', () => { - // Original array is in descending order. - const arr: TestItem[] = []; - for (let i = 0; i < 6; ++i) { - arr[i] = new TestItem(i, 100 - i); - } - - const [sorted, hints] = sortByCounterGetSortedHints(arr); - - for (let i = 1; i < arr.length; ++i) { - expect(sorted[i].counter).toBeGreaterThan(sorted[i - 1].counter); - expect(hints[i]).toBe(arr.length - i - 1); // Index is reversed. - } - expect(sorted).toEqual(arr.slice().reverse()); - }); - - it('sorts ascending items in ascending order', () => { - const arr: TestItem[] = []; - for (let i = 0; i < 6; ++i) { - arr[i] = new TestItem(i, i + 1); - } - - const [sorted, hints] = sortByCounterGetSortedHints(arr); - - for (let i = 1; i < arr.length; ++i) { - expect(sorted[i].counter).toBeGreaterThan(sorted[i - 1].counter); - expect(hints[i]).toBe(i); // Index is preserved. - } - expect(sorted).toEqual(arr); - }); - - it('sorts random items in ascending order', () => { - const arr: TestItem[] = [ - new TestItem(2, 13), - new TestItem(3, 328), - new TestItem(4, 4), - new TestItem(5, 59), - new TestItem(6, 1), - ]; - - const [sorted, hints] = sortByCounterGetSortedHints(arr); - - expect(sorted).toEqual([ - new TestItem(6, 1), - new TestItem(4, 4), - new TestItem(2, 13), - new TestItem(5, 59), - new TestItem(3, 328), - ]); - - expect(hints).toEqual([2, 4, 1, 3, 0]); - }); - - it('sorts random items and keep empty items to the right', () => { - const arr: TestItem[] = [ - new TestItem(2, 13), - new TestItem(3, 328), - new TestItem(4, 4), - new TestItem(5, 59), - new TestItem(6, 27), - TestItem.empty(), - TestItem.empty(), - ]; - - const [sorted, hints] = sortByCounterGetSortedHints(arr); - - expect(sorted).toEqual([ - new TestItem(4, 4), - new TestItem(2, 13), - new TestItem(6, 27), - new TestItem(5, 59), - new TestItem(3, 328), - TestItem.empty(), - TestItem.empty(), - ]); - - expect(hints).toEqual([1, 4, 0, 3, 2, 0, 0]); - }); - - it('does not mix 0 counter with empty items', () => { - const arr: TestItem[] = [ - new TestItem(3, 328), - new TestItem(2, 0), - new TestItem(6, 27), - TestItem.empty(), - TestItem.empty(), - ]; - - const [sorted, hints] = sortByCounterGetSortedHints(arr); - - expect(sorted).toEqual([ - new TestItem(2, 0), - new TestItem(6, 27), - new TestItem(3, 328), - TestItem.empty(), - TestItem.empty(), - ]); - - expect(hints).toEqual([2, 0, 1, 0, 0]); - }); - }); - - describe('sortByPositionThenCounter', () => { - it('sorts items by position and then by counter in ascending order', () => { - const arr: TestItem[] = [ - new TestItem(4, 2, new Fr(3)), - new TestItem(1, 1, new Fr(1)), - new TestItem(3, 3, new Fr(2)), - new TestItem(2, 4, new Fr(1)), - new TestItem(5, 1, new Fr(2)), - ]; - - const sorted = sortByPositionThenCounter(arr); - - expect(sorted).toEqual([ - new TestItem(1, 1, new Fr(1)), - new TestItem(2, 4, new Fr(1)), - new TestItem(5, 1, new Fr(2)), - new TestItem(3, 3, new Fr(2)), - new TestItem(4, 2, new Fr(3)), - ]); - }); - - it('sorts items by position and then by counter in descending order', () => { - const arr: TestItem[] = [ - new TestItem(4, 2, new Fr(3)), - new TestItem(1, 1, new Fr(1)), - new TestItem(3, 3, new Fr(2)), - new TestItem(2, 4, new Fr(1)), - new TestItem(5, 1, new Fr(2)), - ]; - - const sorted = sortByPositionThenCounter(arr, false); - - expect(sorted).toEqual([ - new TestItem(4, 2, new Fr(3)), - new TestItem(3, 3, new Fr(2)), - new TestItem(5, 1, new Fr(2)), - new TestItem(2, 4, new Fr(1)), - new TestItem(1, 1, new Fr(1)), - ]); - }); - - it('handles arrays with empty items correctly', () => { - const arr: TestItem[] = [ - new TestItem(4, 2, new Fr(3)), - new TestItem(1, 1, new Fr(1)), - new TestItem(0, 0, new Fr(0)), - new TestItem(5, 1, new Fr(2)), - new TestItem(0, 0, new Fr(0)), - ]; - - const sorted = sortByPositionThenCounter(arr); - - expect(sorted).toEqual([ - new TestItem(1, 1, new Fr(1)), - new TestItem(5, 1, new Fr(2)), - new TestItem(4, 2, new Fr(3)), - new TestItem(0, 0, new Fr(0)), - new TestItem(0, 0, new Fr(0)), - ]); - }); - - it('sorts items with same position by counter in ascending order', () => { - const arr: TestItem[] = [ - new TestItem(4, 2, new Fr(1)), - new TestItem(1, 1, new Fr(1)), - new TestItem(3, 3, new Fr(1)), - new TestItem(2, 4, new Fr(1)), - new TestItem(5, 1, new Fr(1)), - ]; - - const sorted = sortByPositionThenCounter(arr); - - expect(sorted).toEqual([ - new TestItem(1, 1, new Fr(1)), - new TestItem(5, 1, new Fr(1)), - new TestItem(4, 2, new Fr(1)), - new TestItem(3, 3, new Fr(1)), - new TestItem(2, 4, new Fr(1)), - ]); - }); - }); - - describe('deduplicateArray', () => { - it('deduplicates and returns run lengths correctly', () => { - const arr: Tuple = [ - new TestItem(1, 1, new Fr(1)), - new TestItem(2, 4, new Fr(1)), - new TestItem(3, 3, new Fr(2)), - new TestItem(4, 2, new Fr(3)), - new TestItem(5, 5, new Fr(3)), - new TestItem(6, 6, new Fr(3)), - new TestItem(7, 8, new Fr(4)), - new TestItem(8, 9, new Fr(4)), - new TestItem(9, 7, new Fr(5)), - new TestItem(0, 0, new Fr(0)), - ]; - - const [dedupedArray, runLengths] = deduplicateSortedArray(arr, 10, TestItem.empty); - - const expectedDedupedArray: Tuple = [ - new TestItem(2, 4, new Fr(1)), - new TestItem(3, 3, new Fr(2)), - new TestItem(6, 6, new Fr(3)), - new TestItem(8, 9, new Fr(4)), - new TestItem(9, 7, new Fr(5)), - new TestItem(0, 0, new Fr(0)), - new TestItem(0, 0, new Fr(0)), - new TestItem(0, 0, new Fr(0)), - new TestItem(0, 0, new Fr(0)), - new TestItem(0, 0, new Fr(0)), - ]; - - const expectedRunLengths = [2, 1, 3, 2, 1, 0, 0, 0, 0, 0]; - - expect(dedupedArray).toEqual(expectedDedupedArray); - expect(runLengths).toEqual(expectedRunLengths); - }); - - it('handles arrays with all empty items', () => { - const arr: Tuple = Array(10).fill(new TestItem(0, 0, new Fr(0))) as Tuple; - - const [dedupedArray, runLengths] = deduplicateSortedArray(arr, 10, TestItem.empty); - - const expectedDedupedArray: Tuple = Array(10).fill(new TestItem(0, 0, new Fr(0))) as Tuple< - TestItem, - 10 - >; - const expectedRunLengths = Array(10).fill(0); - - expect(dedupedArray).toEqual(expectedDedupedArray); - expect(runLengths).toEqual(expectedRunLengths); - }); - - it('handles arrays with no duplicates', () => { - const arr: Tuple = [ - new TestItem(1, 1, new Fr(1)), - new TestItem(2, 2, new Fr(2)), - new TestItem(3, 3, new Fr(3)), - new TestItem(4, 4, new Fr(4)), - new TestItem(5, 5, new Fr(5)), - ]; - - const [dedupedArray, runLengths] = deduplicateSortedArray(arr, 5, TestItem.empty); - - const expectedDedupedArray: Tuple = [ - new TestItem(1, 1, new Fr(1)), - new TestItem(2, 2, new Fr(2)), - new TestItem(3, 3, new Fr(3)), - new TestItem(4, 4, new Fr(4)), - new TestItem(5, 5, new Fr(5)), - ]; - - const expectedRunLengths = [1, 1, 1, 1, 1]; - - expect(dedupedArray).toEqual(expectedDedupedArray); - expect(runLengths).toEqual(expectedRunLengths); - }); - }); - describe('isEmptyArray', () => { it('returns true if all items in an array are empty', () => { const arr = [TestItem.empty(), TestItem.empty(), TestItem.empty()]; 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 af6edc5a838..150396a60dd 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -8,7 +8,6 @@ import { BlockRootOrBlockMergePublicInputs, type BlockRootRollupInputs, CallContext, - type CombineHints, CombinedAccumulatedData, CombinedConstantData, ConstantRollupData, @@ -144,7 +143,6 @@ import type { BlockRootOrBlockMergePublicInputs as BlockRootOrBlockMergePublicInputsNoir, BlockRootRollupInputs as BlockRootRollupInputsNoir, CallContext as CallContextNoir, - CombineHints as CombineHintsNoir, CombinedAccumulatedData as CombinedAccumulatedDataNoir, CombinedConstantData as CombinedConstantDataNoir, ConstantRollupData as ConstantRollupDataNoir, @@ -1747,38 +1745,6 @@ export function mapPublicKernelCircuitPrivateInputsToNoir( }; } -export function mapCombineHintsToNoir(combineHints: CombineHints): CombineHintsNoir { - return { - sorted_note_hashes: mapTuple(combineHints.sortedNoteHashes, mapScopedNoteHashToNoir), - sorted_note_hashes_indexes: mapTuple(combineHints.sortedNoteHashesIndexes, mapNumberToNoir), - sorted_note_encrypted_logs_hashes: mapTuple(combineHints.sortedNoteEncryptedLogsHashes, mapLogHashToNoir), - sorted_note_encrypted_logs_hashes_indexes: mapTuple( - combineHints.sortedNoteEncryptedLogsHashesIndexes, - mapNumberToNoir, - ), - sorted_encrypted_logs_hashes: mapTuple(combineHints.sortedEncryptedLogsHashes, mapScopedLogHashToNoir), - sorted_encrypted_logs_hashes_indexes: mapTuple(combineHints.sortedEncryptedLogsHashesIndexes, mapNumberToNoir), - sorted_unencrypted_logs_hashes: mapTuple(combineHints.sortedUnencryptedLogsHashes, mapScopedLogHashToNoir), - sorted_unencrypted_logs_hashes_indexes: mapTuple(combineHints.sortedUnencryptedLogsHashesIndexes, mapNumberToNoir), - sorted_public_data_update_requests: mapTuple( - combineHints.sortedPublicDataUpdateRequests, - mapPublicDataUpdateRequestToNoir, - ), - sorted_public_data_update_requests_indexes: mapTuple( - combineHints.sortedPublicDataUpdateRequestsIndexes, - mapNumberToNoir, - ), - deduped_public_data_update_requests: mapTuple( - combineHints.dedupedPublicDataUpdateRequests, - mapPublicDataUpdateRequestToNoir, - ), - deduped_public_data_update_requests_runs: mapTuple( - combineHints.dedupedPublicDataUpdateRequestsRuns, - mapNumberToNoir, - ), - }; -} - export function mapPublicKernelTailCircuitPrivateInputsToNoir( inputs: PublicKernelTailCircuitPrivateInputs, ): PublicKernelTailCircuitPrivateInputsNoir { @@ -1791,7 +1757,6 @@ export function mapPublicKernelTailCircuitPrivateInputsToNoir( public_data_hints: mapTuple(inputs.publicDataHints, mapPublicDataHintToNoir), public_data_read_request_hints: mapPublicDataReadRequestHintsToNoir(inputs.publicDataReadRequestHints), start_state: mapPartialStateReferenceToNoir(inputs.startState), - combine_hints: mapCombineHintsToNoir(inputs.combineHints), }; } diff --git a/yarn-project/simulator/src/public/tail_phase_manager.ts b/yarn-project/simulator/src/public/tail_phase_manager.ts index d33b0826c88..5b55af3e250 100644 --- a/yarn-project/simulator/src/public/tail_phase_manager.ts +++ b/yarn-project/simulator/src/public/tail_phase_manager.ts @@ -1,6 +1,5 @@ import { type PublicKernelRequest, PublicKernelType, type Tx } from '@aztec/circuit-types'; import { - CombineHints, type GlobalVariables, type Header, type KernelCircuitPublicInputs, @@ -113,8 +112,6 @@ export class TailPhaseManager extends AbstractPhaseManager { const currentState = await this.db.getStateReference(); - const hints = CombineHints.fromPublicData({ nonRevertibleData, revertibleData }); - return new PublicKernelTailCircuitPrivateInputs( previousKernel, nullifierReadRequestHints, @@ -122,7 +119,6 @@ export class TailPhaseManager extends AbstractPhaseManager { publicDataHints, publicDataReadRequestHints, currentState.partial, - hints, ); } }