diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index cd7e26908bd5..346ac3b059dc 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -288,7 +288,8 @@ void AvmTraceBuilder::insert_private_revertible_state(const std::vector& sil for (size_t i = 0; i < siloed_note_hashes.size(); i++) { size_t note_index_in_tx = i + get_inserted_note_hashes_count(); - FF nonce = AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_tx_hash(), note_index_in_tx); + FF nonce = + AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_first_nullifier(), note_index_in_tx); unique_note_hashes.push_back( AvmMerkleTreeTraceBuilder::unconstrained_compute_unique_note_hash(nonce, siloed_note_hashes.at(i))); } @@ -3101,8 +3102,8 @@ AvmError AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash AppendTreeHint note_hash_write_hint = execution_hints.note_hash_write_hints.at(note_hash_write_counter++); FF siloed_note_hash = AvmMerkleTreeTraceBuilder::unconstrained_silo_note_hash( current_public_call_request.contract_address, row.main_ia); - FF nonce = - AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_tx_hash(), inserted_note_hashes_count); + FF nonce = AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_first_nullifier(), + inserted_note_hashes_count); FF unique_note_hash = AvmMerkleTreeTraceBuilder::unconstrained_compute_unique_note_hash(nonce, siloed_note_hash); ASSERT(unique_note_hash == note_hash_write_hint.leaf_value); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp index 0ae4e06d904f..a2c750d633e6 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp @@ -395,7 +395,7 @@ class AvmTraceBuilder { uint32_t get_inserted_note_hashes_count(); uint32_t get_inserted_nullifiers_count(); uint32_t get_public_data_writes_count(); - FF get_tx_hash() const { return public_inputs.previous_non_revertible_accumulated_data.nullifiers[0]; } + FF get_first_nullifier() const { return public_inputs.previous_non_revertible_accumulated_data.nullifiers[0]; } // TODO: remove these once everything is constrained. AvmMemoryTag unconstrained_get_memory_tag(AddressWithMode addr); diff --git a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr index c7f51654c76c..d293a2c01719 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr @@ -1,8 +1,10 @@ +// docs:start:declaration mod test; use dep::aztec::macros::aztec; #[aztec] contract EasyPrivateVoting { + // docs:end:declaration // docs:start:imports use dep::aztec::{ keys::getters::get_public_keys, diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr index 54fcf722f766..fe5f3cab4371 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr @@ -9,6 +9,7 @@ use types::{ }; pub(crate) struct TxEffect { + pub(crate) tx_hash: Field, pub(crate) revert_code: u8, pub(crate) transaction_fee: Field, pub(crate) note_hashes: [Field; MAX_NOTE_HASHES_PER_TX], @@ -25,6 +26,7 @@ pub(crate) struct TxEffect { impl Empty for TxEffect { fn empty() -> Self { TxEffect { + tx_hash: 0, revert_code: 0, transaction_fee: 0, note_hashes: [0; MAX_NOTE_HASHES_PER_TX], diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr index 903b6d37e798..eabcb3a59024 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr @@ -111,6 +111,7 @@ impl PrivateBaseRollupInputs { let out_hash = compute_kernel_out_hash(siloed_l2_to_l1_msgs); let tx_effect = TxEffect { + tx_hash: self.tube_data.public_inputs.hash(), revert_code: 0, transaction_fee, note_hashes: self.tube_data.public_inputs.end.note_hashes, @@ -241,7 +242,7 @@ mod tests { NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, NULLIFIERS_PREFIX, PRIVATE_LOG_SIZE_IN_FIELDS, PRIVATE_LOGS_PREFIX, PUBLIC_DATA_TREE_HEIGHT, REVERT_CODE_PREFIX, TUBE_VK_INDEX, TX_FEE_PREFIX, - TX_START_PREFIX, UNENCRYPTED_LOGS_PREFIX, + TX_START_PREFIX, }, data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, hash::silo_l2_to_l1_message, @@ -816,12 +817,13 @@ mod tests { unconstrained fn non_empty_tx_effects_sponge() { let mut builder = PrivateBaseRollupInputsBuilder::new(); builder.tube_data.append_note_hashes(50); - let outputs = builder.execute(); - let mut tx_effects = [0; 53]; - // TODO(#8954): This test uses 50 notes and 3 extra absorbed fields + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); + let mut tx_effects = [0; 54]; + // TODO(#8954): This test uses 50 notes and 4 extra absorbed fields // This may change when logs are deconstructed // Initial field = TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code - // The first 3 are: i=0 init field, i=1: tx fee, i=2: note prefix + // The first 4 are: i=0 init field, i=1: tx hash, i=2: tx fee, i=3: note prefix tx_effects[0] = field_from_bytes( array_concat( TX_START_PREFIX.to_be_bytes::<8>(), @@ -829,13 +831,15 @@ mod tests { ), true, ); - tx_effects[1] = field_from_bytes( + // TX hash + tx_effects[1] = inputs.tube_data.public_inputs.hash(); + tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], (0).to_be_bytes::<29>()), true, ); - tx_effects[2] = encode_blob_prefix(NOTES_PREFIX, 50); + tx_effects[3] = encode_blob_prefix(NOTES_PREFIX, 50); for i in 0..50 { - tx_effects[i + 3] = builder.tube_data.note_hashes.storage()[i].value(); + tx_effects[i + 4] = builder.tube_data.note_hashes.storage()[i].value(); } let mut expected_sponge = outputs.start_sponge_blob; @@ -849,21 +853,8 @@ mod tests { let NUM_NULLIFIERS = 3; let NUM_MSGS = 5; let NUM_PRIV_EVENT_LOGS = 4; - let NUM_UNENC_LOGS = 6; let NUM_CC_LOGS = 1; - let TOTAL_BLOB_FIELDS = 2 // revert code and tx fee - + NUM_NOTES - + 1 // notes and prefix - + NUM_NULLIFIERS - + 1 // nullifiers and prefix - + NUM_MSGS - + 1 // L2 to L1 msgs and prefix - + NUM_UNENC_LOGS - + 1 // unenc. logs and prefix - + NUM_CC_LOGS - + 1 // contract class logs and prefix - + (NUM_NOTES + NUM_PRIV_EVENT_LOGS) * PRIVATE_LOG_SIZE_IN_FIELDS - + 1; // private logs and prefix + let mut builder = PrivateBaseRollupInputsBuilder::new(); builder.tube_data.set_gas_used(100, 200); builder.constants.global_variables.gas_fees.fee_per_da_gas = 1; @@ -887,17 +878,19 @@ mod tests { builder.tube_data.append_private_logs(NUM_PRIV_EVENT_LOGS); // Below will only work with NUM_CC_LOGS=1 builder.tube_data.add_contract_class_log_hash(1, 2); - let outputs = builder.execute(); + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); let mut reconstructed_tx_effects = [0; TX_EFFECTS_BLOB_HASH_INPUT_FIELDS]; - + // tx hash + reconstructed_tx_effects[1] = inputs.tube_data.public_inputs.hash(); // tx fee - reconstructed_tx_effects[1] = field_from_bytes( + reconstructed_tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], tx_fee.to_be_bytes::<29>()), true, ); // notes - let mut offset = 2; + let mut offset = 3; let notes_prefix = encode_blob_prefix(NOTES_PREFIX, NUM_NOTES); reconstructed_tx_effects[offset] = notes_prefix; offset += 1; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr index ae48ee652453..ef0263b5a9e1 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr @@ -87,6 +87,7 @@ impl PublicBaseRollupInputs { .fold(0, |len, l: ScopedLogHash| len + l.log_hash.length); TxEffect { + tx_hash: self.tube_data.public_inputs.hash(), revert_code, transaction_fee: from_public.transaction_fee, note_hashes: from_public.accumulated_data.note_hashes, @@ -916,12 +917,13 @@ mod tests { unconstrained fn non_empty_tx_effects_sponge() { let mut builder = PublicBaseRollupInputsBuilder::new(); builder.avm_data.append_note_hashes(50); - let outputs = builder.execute(); - let mut tx_effects = [0; 53]; - // TODO(#8954): This test uses 50 notes and 3 extra absorbed fields + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); + let mut tx_effects = [0; 54]; + // TODO(#8954): This test uses 50 notes and 4 extra absorbed fields // This may change when logs are deconstructed // Initial field = TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code - // The first 3 are: i=0 init field, i=1: tx fee, i=2: note prefix + // The first 4 are: i=0 init field, i=1: tx hash, i=2: tx fee, i=3: note prefix tx_effects[0] = field_from_bytes( array_concat( TX_START_PREFIX.to_be_bytes::<8>(), @@ -929,13 +931,15 @@ mod tests { ), true, ); - tx_effects[1] = field_from_bytes( + // TX hash + tx_effects[1] = inputs.tube_data.public_inputs.hash(); + tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], (0).to_be_bytes::<29>()), true, ); - tx_effects[2] = encode_blob_prefix(NOTES_PREFIX, 50); + tx_effects[3] = encode_blob_prefix(NOTES_PREFIX, 50); for i in 0..50 { - tx_effects[i + 3] = builder.avm_data.note_hashes.storage()[i].value(); + tx_effects[i + 4] = builder.avm_data.note_hashes.storage()[i].value(); } let mut expected_sponge = outputs.start_sponge_blob; @@ -953,7 +957,7 @@ mod tests { let NUM_CC_LOGS = 1; let PUB_DATA_SLOT = 25; let PUB_DATA_VALUE = 60; - let TOTAL_BLOB_FIELDS = 2 // revert code and tx fee + let TOTAL_BLOB_FIELDS = 3 // revert code, tx hash and tx fee + NUM_NOTES + 1 // notes and prefix + NUM_NULLIFIERS @@ -993,7 +997,8 @@ mod tests { builder.avm_data.append_unencrypted_log_hashes(NUM_UNENC_LOGS); // Below will only work with NUM_CC_LOGS=1 builder.tube_data.add_contract_class_log_hash(1, 2); - let outputs = builder.execute(); + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); let mut reconstructed_tx_effects = [0; TX_EFFECTS_BLOB_HASH_INPUT_FIELDS]; // Initial field = TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code @@ -1014,13 +1019,15 @@ mod tests { ), true, ); + // tx hash + reconstructed_tx_effects[1] = inputs.tube_data.public_inputs.hash(); // tx fee - reconstructed_tx_effects[1] = field_from_bytes( + reconstructed_tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], tx_fee.to_be_bytes::<29>()), true, ); // notes - let mut offset = 2; + let mut offset = 3; let notes_prefix = encode_blob_prefix(NOTES_PREFIX, NUM_NOTES); reconstructed_tx_effects[offset] = notes_prefix; offset += 1; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr index ebc342151a8c..45bf221e1ef3 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr @@ -202,13 +202,15 @@ pub(crate) mod tests { // The below values are generated from block_building_helper.test.ts. let blob_fields_0 = [ - 0x000000000000000000000000000000000074785f737461727400000400010000, + 0x000000000000000000000000000000000074785f737461727400000500010000, + 0x000000000000000000000000000000000000000000000000000000000000002a, 0x0002000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000004000001, 0x0000000000000000000000000000000000000000000000000000000000000123, ]; let blob_fields_1 = [ - 0x000000000000000000000000000000000074785f737461727400000600010000, + 0x000000000000000000000000000000000074785f737461727400000700010000, + 0x000000000000000000000000000000000000000000000000000000000000002b, 0x0002000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000003000001, 0x0000000000000000000000000000000000000000000000000000000000006789, @@ -216,12 +218,12 @@ pub(crate) mod tests { 0x0000000000000000000000000000000000000000000000000000000000000045, ]; let expected_blob_commitment = [ - 0x00ad8be66e7276942652627bb00fe1e65dc1c3c6701ab27cc05eff662950071d, - 0x00000000000000000000000000000071baf7a9af9757f1d3878b37b438797213, + 0x008c32fe581c8fdba12c0d7597911dead2d937d68525bae655508412bb53bb98, + 0x0000000000000000000000000000006aaa0680f21270e7d8de4e19da5164f95c, ]; let expected_blobs_hash = - 0x00dc577f5c94c82b847693b76ee69cd33d4e5eee3adb6f37d8d7ab662c84725d; - let expected_z = 0x1582b354f32263abde313d597582ebceafe17d4e2a68dd47533383e85b4cb780; + 0x00a965619c8668b834755678b32d023b9c5e8588ce449f44f7fa9335455b5cc5; + let expected_z = 0x1f92b871671f27a378d23f1cef10fbd8f0d90dd7172da9e3c3fc1aa745a072c3; let mut builder = TestBuilder::new_with_blobs_fields(blob_fields_0, blob_fields_1); builder.data.blob_commitments[0].inner = expected_blob_commitment; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr index 46c9eebfafff..42a2f485bfea 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr @@ -140,6 +140,7 @@ pub fn encode_blob_prefix(input_type: u8, array_len: u32) -> Field { // Tx effects consist of // 1 field for revert code +// 1 field for tx hash // 1 field for transaction fee // MAX_NOTE_HASHES_PER_TX fields for note hashes // MAX_NULLIFIERS_PER_TX fields for nullifiers @@ -151,6 +152,7 @@ pub fn encode_blob_prefix(input_type: u8, array_len: u32) -> Field { // MAX_CONTRACT_CLASS_LOGS_PER_TX fields for contract class logs // 7 fields for prefixes for each of the above categories pub(crate) global TX_EFFECTS_BLOB_HASH_INPUT_FIELDS: u32 = 1 + + 1 + 1 + MAX_NOTE_HASHES_PER_TX + MAX_NULLIFIERS_PER_TX @@ -172,8 +174,8 @@ pub(crate) fn append_tx_effects_for_blob( let mut out_sponge = start_sponge_blob; // If we have an empty tx (usually a padding tx), we don't want to absorb anything - // An empty tx will only have 2 effects - revert code and fee - hence offset = 2 - if offset != 2 { + // An empty tx will only have 3 effects - revert code, tx hash and fee - hence offset = 3 + if offset != 3 { out_sponge.absorb(tx_effects_hash_input, offset); } @@ -208,6 +210,9 @@ fn get_tx_effects_hash_input( // We only know the value once the appending is complete, hence we overwrite input[0] below offset += 1; + assert_eq(tx_effects_hash_input[offset], tx_effect.tx_hash); + offset += 1; + // TX FEE // Using 29 bytes to encompass all reasonable fee lengths assert_eq( @@ -400,6 +405,9 @@ unconstrained fn get_tx_effects_hash_input_helper( tx_effects_hash_input[offset] = 0; offset += 1; + tx_effects_hash_input[offset] = tx_effect.tx_hash; + offset += 1; + // TX FEE // Using 29 bytes to encompass all reasonable fee lengths tx_effects_hash_input[offset] = field_from_bytes( diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr index 517f68705904..265fc2e1ba27 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr @@ -5,8 +5,11 @@ use crate::{ validation_requests::RollupValidationRequests, }, address::AztecAddress, - constants::PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, - traits::{Deserialize, Empty, Serialize}, + constants::{ + GENERATOR_INDEX__PUBLIC_TX_HASH, PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, + }, + hash::poseidon2_hash_with_separator, + traits::{Deserialize, Empty, Hash, Serialize}, utils::reader::Reader, }; @@ -65,6 +68,12 @@ impl Serialize for Privat } } +impl Hash for PrivateToPublicKernelCircuitPublicInputs { + fn hash(self) -> Field { + poseidon2_hash_with_separator(self.serialize(), GENERATOR_INDEX__PUBLIC_TX_HASH) + } +} + impl Deserialize for PrivateToPublicKernelCircuitPublicInputs { fn deserialize( fields: [Field; PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH], diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr index 9f00f0320e56..887be0af24e4 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr @@ -4,8 +4,11 @@ use crate::{ tx_constant_data::TxConstantData, validation_requests::RollupValidationRequests, }, address::AztecAddress, - constants::PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, - traits::{Deserialize, Empty, Serialize}, + constants::{ + GENERATOR_INDEX__PRIVATE_TX_HASH, PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, + }, + hash::poseidon2_hash_with_separator, + traits::{Deserialize, Empty, Hash, Serialize}, utils::reader::Reader, }; use std::meta::derive; @@ -48,6 +51,12 @@ impl Serialize for Privat } } +impl Hash for PrivateToRollupKernelCircuitPublicInputs { + fn hash(self) -> Field { + poseidon2_hash_with_separator(self.serialize(), GENERATOR_INDEX__PRIVATE_TX_HASH) + } +} + impl Deserialize for PrivateToRollupKernelCircuitPublicInputs { fn deserialize( fields: [Field; PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH], diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 4f00c01f79ff..921a1fb6405b 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -489,20 +489,7 @@ pub global AVM_PROOF_LENGTH_IN_FIELDS: u32 = 4155; pub global AVM_PUBLIC_COLUMN_MAX_SIZE: u32 = 1024; pub global AVM_PUBLIC_INPUTS_FLATTENED_SIZE: u32 = 2 * AVM_PUBLIC_COLUMN_MAX_SIZE + PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH; -/** - * Enumerate the hash_indices which are used for pedersen hashing. - * We start from 1 to avoid the default generators. The generator indices are listed - * based on the number of elements each index hashes. The following conditions must be met: - * - * +-----------+-------------------------------+----------------------+ - * | Hash size | Number of elements hashed (n) | Condition to use | - * |-----------+-------------------------------+----------------------| - * | LOW | n <= 8 | 0 < hash_index <= 32 | - * | MID | 8 < n <= 16 | 32 < hash_index <= 40 | - * | HIGH | 16 < n <= 48 | 40 < hash_index <= 48 | - * +-----------+-------------------------------+----------------------+ - */ -// Indices with size <= 8 + pub global GENERATOR_INDEX__NOTE_HASH: u32 = 1; pub global GENERATOR_INDEX__NOTE_HASH_NONCE: u32 = 2; pub global GENERATOR_INDEX__UNIQUE_NOTE_HASH: u32 = 3; @@ -535,14 +522,11 @@ pub global GENERATOR_INDEX__SIDE_EFFECT: u32 = 29; pub global GENERATOR_INDEX__FEE_PAYLOAD: u32 = 30; pub global GENERATOR_INDEX__COMBINED_PAYLOAD: u32 = 31; pub global GENERATOR_INDEX__TX_NULLIFIER: u32 = 32; -// Indices with size <= 16 pub global GENERATOR_INDEX__TX_REQUEST: u32 = 33; pub global GENERATOR_INDEX__SIGNATURE_PAYLOAD: u32 = 34; -// Indices with size <= 44 pub global GENERATOR_INDEX__VK: u32 = 41; pub global GENERATOR_INDEX__PRIVATE_CIRCUIT_PUBLIC_INPUTS: u32 = 42; pub global GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS: u32 = 43; -// TODO: Function args generator index is being used to hash 64 items pub global GENERATOR_INDEX__FUNCTION_ARGS: u32 = 44; pub global GENERATOR_INDEX__AUTHWIT_INNER: u32 = 45; pub global GENERATOR_INDEX__AUTHWIT_OUTER: u32 = 46; @@ -557,6 +541,9 @@ pub global GENERATOR_INDEX__NOTE_NULLIFIER: u32 = 53; pub global GENERATOR_INDEX__NOTE_HIDING_POINT: u32 = 54; pub global GENERATOR_INDEX__SYMMETRIC_KEY: u8 = 55; +pub global GENERATOR_INDEX__PUBLIC_TX_HASH: u32 = 56; +pub global GENERATOR_INDEX__PRIVATE_TX_HASH: u32 = 57; + // AVM memory tags pub global MEM_TAG_FF: Field = 0; pub global MEM_TAG_U1: Field = 1; diff --git a/yarn-project/aztec-node/src/aztec-node/server.test.ts b/yarn-project/aztec-node/src/aztec-node/server.test.ts index 7ba1860158b6..c2a290c8d500 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.test.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.test.ts @@ -10,7 +10,14 @@ import { type WorldStateSynchronizer, mockTxForRollup, } from '@aztec/circuit-types'; -import { type ContractDataSource, EthAddress, Fr, GasFees, MaxBlockNumber } from '@aztec/circuits.js'; +import { + type ContractDataSource, + EthAddress, + Fr, + GasFees, + MaxBlockNumber, + RollupValidationRequests, +} from '@aztec/circuits.js'; import { type P2P } from '@aztec/p2p'; import { type GlobalVariableBuilder } from '@aztec/sequencer-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; @@ -107,7 +114,7 @@ describe('aztec node', () => { expect(await node.isValidTx(doubleSpendTx)).toEqual({ result: 'valid' }); // We push a duplicate nullifier that was created in the same transaction - doubleSpendTx.data.forRollup!.end.nullifiers.push(doubleSpendTx.data.forRollup!.end.nullifiers[0]); + doubleSpendTx.data.forRollup!.end.nullifiers[1] = doubleSpendTx.data.forRollup!.end.nullifiers[0]; expect(await node.isValidTx(doubleSpendTx)).toEqual({ result: 'invalid', reason: ['Duplicate nullifier in tx'] }); @@ -154,19 +161,13 @@ describe('aztec node', () => { const invalidMaxBlockNumberMetadata = txs[1]; const validMaxBlockNumberMetadata = txs[2]; - invalidMaxBlockNumberMetadata.data.rollupValidationRequests = { - maxBlockNumber: new MaxBlockNumber(true, new Fr(1)), - getSize: () => 1, - toBuffer: () => Fr.ZERO.toBuffer(), - toString: () => Fr.ZERO.toString(), - }; - - validMaxBlockNumberMetadata.data.rollupValidationRequests = { - maxBlockNumber: new MaxBlockNumber(true, new Fr(5)), - getSize: () => 1, - toBuffer: () => Fr.ZERO.toBuffer(), - toString: () => Fr.ZERO.toString(), - }; + invalidMaxBlockNumberMetadata.data.rollupValidationRequests = new RollupValidationRequests( + new MaxBlockNumber(true, new Fr(1)), + ); + + validMaxBlockNumberMetadata.data.rollupValidationRequests = new RollupValidationRequests( + new MaxBlockNumber(true, new Fr(5)), + ); lastBlockNumber = 3; diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 969f618b58c6..67e96b8e77c7 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -834,7 +834,7 @@ export class AztecNodeService implements AztecNode, Traceable { * @param tx - The transaction to simulate. **/ @trackSpan('AztecNodeService.simulatePublicCalls', (tx: Tx) => ({ - [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString(), + [Attributes.TX_HASH]: tx.getTxHash().toString(), })) public async simulatePublicCalls(tx: Tx, enforceFeePayment = true): Promise { const txHash = tx.getTxHash(); diff --git a/yarn-project/circuit-types/src/tx/processed_tx.ts b/yarn-project/circuit-types/src/tx/processed_tx.ts index 877cca9ecff7..feacb5de6e18 100644 --- a/yarn-project/circuit-types/src/tx/processed_tx.ts +++ b/yarn-project/circuit-types/src/tx/processed_tx.ts @@ -93,6 +93,7 @@ export function makeProcessedTxFromPrivateOnlyTx( const data = tx.data.forRollup!; const txEffect = new TxEffect( RevertCode.OK, + tx.getTxHash(), transactionFee, data.end.noteHashes.filter(h => !h.isZero()), data.end.nullifiers.filter(h => !h.isZero()), @@ -114,7 +115,7 @@ export function makeProcessedTxFromPrivateOnlyTx( } satisfies GasUsed; return { - hash: tx.getTxHash(), + hash: txEffect.txHash, data: tx.data, clientIvcProof: tx.clientIvcProof, avmProvingRequest: undefined, @@ -156,6 +157,7 @@ export function makeProcessedTxFromTxWithPublicCalls( const txEffect = new TxEffect( revertCode, + tx.getTxHash(), avmOutput.transactionFee, avmOutput.accumulatedData.noteHashes.filter(h => !h.isZero()), avmOutput.accumulatedData.nullifiers.filter(h => !h.isZero()), @@ -171,7 +173,7 @@ export function makeProcessedTxFromTxWithPublicCalls( ); return { - hash: tx.getTxHash(), + hash: txEffect.txHash, data: tx.data, clientIvcProof: tx.clientIvcProof, avmProvingRequest, diff --git a/yarn-project/circuit-types/src/tx/tx.ts b/yarn-project/circuit-types/src/tx/tx.ts index 969a069e4f71..87768204e1a9 100644 --- a/yarn-project/circuit-types/src/tx/tx.ts +++ b/yarn-project/circuit-types/src/tx/tx.ts @@ -28,6 +28,8 @@ import { TxHash } from './tx_hash.js'; */ export class Tx extends Gossipable { static override p2pTopic: string; + // For memoization + private txHash: TxHash | undefined; constructor( /** @@ -174,25 +176,27 @@ export class Tx extends Gossipable { } /** - * Construct & return transaction hash. - * @returns The transaction's hash. + * Computes (if necessary) & return transaction hash. + * @returns The hash of the public inputs of the private kernel tail circuit. */ - getTxHash(): TxHash { - // Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier - const firstNullifier = this.data.getNonEmptyNullifiers()[0]; - if (!firstNullifier || firstNullifier.isZero()) { - throw new Error(`Cannot get tx hash since first nullifier is missing`); + getTxHash(forceRecompute = false): TxHash { + if (!this.txHash || forceRecompute) { + const hash = this.data.forPublic + ? this.data.toPrivateToPublicKernelCircuitPublicInputs().hash() + : this.data.toPrivateToRollupKernelCircuitPublicInputs().hash(); + this.txHash = new TxHash(hash); } - return new TxHash(firstNullifier); + return this.txHash!; } - /** Returns the tx hash, or undefined if none is set. */ - tryGetTxHash(): TxHash | undefined { - try { - return this.getTxHash(); - } catch { - return undefined; - } + /** + * Allows setting the hash of the Tx. + * Use this when you want to skip computing it from the original data. + * Don't set a Tx hash received from an untrusted source. + * @param hash - The hash to set. + */ + setTxHash(hash: TxHash) { + this.txHash = hash; } /** Returns stats about this tx. */ @@ -283,7 +287,7 @@ export class Tx extends Gossipable { PublicExecutionRequest.fromBuffer(x.toBuffer()), ); const publicTeardownFunctionCall = PublicExecutionRequest.fromBuffer(tx.publicTeardownFunctionCall.toBuffer()); - return new Tx( + const clonedTx = new Tx( publicInputs, clientIvcProof, unencryptedLogs, @@ -291,6 +295,11 @@ export class Tx extends Gossipable { enqueuedPublicFunctionCalls, publicTeardownFunctionCall, ); + if (tx.txHash) { + clonedTx.setTxHash(TxHash.fromBuffer(tx.txHash.toBuffer())); + } + + return clonedTx; } static random() { diff --git a/yarn-project/circuit-types/src/tx_effect.ts b/yarn-project/circuit-types/src/tx_effect.ts index 507827c44941..a59959157364 100644 --- a/yarn-project/circuit-types/src/tx_effect.ts +++ b/yarn-project/circuit-types/src/tx_effect.ts @@ -53,6 +53,10 @@ export class TxEffect { * Whether the transaction reverted during public app logic. */ public revertCode: RevertCode, + /** + * The identifier of the transaction. + */ + public txHash: TxHash, /** * The transaction fee, denominated in FPA. */ @@ -139,6 +143,7 @@ export class TxEffect { toBuffer(): Buffer { return serializeToBuffer([ this.revertCode, + this.txHash, this.transactionFee, serializeArrayOfBufferableToVector(this.noteHashes, 1), serializeArrayOfBufferableToVector(this.nullifiers, 1), @@ -167,6 +172,7 @@ export class TxEffect { return new TxEffect( RevertCode.fromBuffer(reader), + TxHash.fromBuffer(reader), Fr.fromBuffer(reader), reader.readVectorUint8Prefix(Fr), reader.readVectorUint8Prefix(Fr), @@ -213,6 +219,7 @@ export class TxEffect { const contractClassLogs = ContractClassTxL2Logs.random(1, 1); return new TxEffect( RevertCode.random(), + TxHash.random(), new Fr(Math.floor(Math.random() * 100_000)), makeTuple(MAX_NOTE_HASHES_PER_TX, Fr.random), makeTuple(MAX_NULLIFIERS_PER_TX, Fr.random), @@ -229,6 +236,7 @@ export class TxEffect { static empty(): TxEffect { return new TxEffect( RevertCode.OK, + TxHash.zero(), Fr.ZERO, [], [], @@ -339,6 +347,8 @@ export class TxEffect { const flattened: Fr[] = []; // We reassign the first field when we know the length of all effects - see below flattened.push(Fr.ZERO); + + flattened.push(this.txHash.hash); // TODO: how long should tx fee be? For now, not using toPrefix() flattened.push( new Fr( @@ -409,6 +419,8 @@ export class TxEffect { } const { length: _, revertCode } = this.decodeFirstField(firstField); effect.revertCode = RevertCode.fromField(new Fr(revertCode)); + + effect.txHash = new TxHash(reader.readField()); // TODO: how long should tx fee be? For now, not using fromPrefix() const prefixedFee = reader.readField(); // NB: Fr.fromBuffer hangs here if you provide a buffer less than 32 in len @@ -495,9 +507,10 @@ export class TxEffect { }); } - static from(fields: Omit, 'txHash'>) { + static from(fields: FieldsOf) { return new TxEffect( fields.revertCode, + fields.txHash, fields.transactionFee, fields.noteHashes, fields.nullifiers, @@ -515,6 +528,7 @@ export class TxEffect { return z .object({ revertCode: RevertCode.schema, + txHash: TxHash.schema, transactionFee: schemas.Fr, noteHashes: z.array(schemas.Fr), nullifiers: z.array(schemas.Fr), @@ -532,6 +546,7 @@ export class TxEffect { [inspect.custom]() { return `TxEffect { revertCode: ${this.revertCode}, + txHash: ${this.txHash}, transactionFee: ${this.transactionFee}, note hashes: [${this.noteHashes.map(h => h.toString()).join(', ')}], nullifiers: [${this.nullifiers.map(h => h.toString()).join(', ')}], @@ -553,8 +568,4 @@ export class TxEffect { static fromString(str: string) { return TxEffect.fromBuffer(hexToBuffer(str)); } - - get txHash(): TxHash { - return new TxHash(this.nullifiers[0]); - } } diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index afa72979d9e4..e6e00cd15514 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -395,4 +395,6 @@ export enum GeneratorIndex { NOTE_NULLIFIER = 53, NOTE_HIDING_POINT = 54, SYMMETRIC_KEY = 55, + PUBLIC_TX_HASH = 56, + PRIVATE_TX_HASH = 57, } diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 0059acf4213f..8da2960cd5e8 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -18,7 +18,7 @@ export * from './block_header.js'; export * from './indexed_tagging_secret.js'; export * from './kernel/private_to_rollup_accumulated_data.js'; export * from './kernel/combined_constant_data.js'; -export * from './kernel/kernel_circuit_public_inputs.js'; +export * from './kernel/private_to_rollup_kernel_circuit_public_inputs.js'; export * from './kernel/private_accumulated_data.js'; export * from './kernel/private_call_data.js'; export * from './kernel/private_kernel_circuit_public_inputs.js'; diff --git a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts index 14d20e21a11d..9b389874bde4 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts @@ -7,10 +7,10 @@ import { countAccumulatedItems, mergeAccumulatedData } from '../../utils/index.j import { Gas } from '../gas.js'; import { PublicCallRequest } from '../public_call_request.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; -import { PrivateToRollupKernelCircuitPublicInputs } from './kernel_circuit_public_inputs.js'; import { PrivateToPublicAccumulatedData } from './private_to_public_accumulated_data.js'; import { PrivateToPublicKernelCircuitPublicInputs } from './private_to_public_kernel_circuit_public_inputs.js'; import { PrivateToRollupAccumulatedData } from './private_to_rollup_accumulated_data.js'; +import { PrivateToRollupKernelCircuitPublicInputs } from './private_to_rollup_kernel_circuit_public_inputs.js'; import { TxConstantData } from './tx_constant_data.js'; export class PartialPrivateTailPublicInputsForPublic { @@ -144,7 +144,7 @@ export class PrivateKernelTailCircuitPublicInputs { ); } - toPublicKernelCircuitPublicInputs() { + toPrivateToPublicKernelCircuitPublicInputs() { if (!this.forPublic) { throw new Error('Private tail public inputs is not for public circuit.'); } @@ -170,9 +170,9 @@ export class PrivateKernelTailCircuitPublicInputs { this.constants.protocolContractTreeRoot, ); return new PrivateToRollupKernelCircuitPublicInputs( + constants, this.rollupValidationRequests, this.forRollup.end, - constants, this.gasUsed, this.feePayer, ); diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts index 75026cd53713..66eaee844eaa 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts @@ -1,11 +1,22 @@ import { type FieldsOf, makeTuple } from '@aztec/foundation/array'; import { arraySerializedSizeOfNonEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { + BufferReader, + FieldReader, + type Tuple, + serializeToBuffer, + serializeToFields, +} from '@aztec/foundation/serialize'; import { inspect } from 'util'; -import { MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX } from '../../constants.gen.js'; +import { + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH, +} from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; import { type UInt32 } from '../shared.js'; @@ -37,6 +48,16 @@ export class PrivateToAvmAccumulatedData { ); } + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToAvmAccumulatedData.getFields(this)); + if (fields.length !== PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToAvmAccumulatedData. Expected ${PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + static from(fields: FieldsOf) { return new PrivateToAvmAccumulatedData(...PrivateToAvmAccumulatedData.getFields(fields)); } diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts index 99a3c48fa223..b9df967a756e 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts @@ -1,7 +1,13 @@ import { type FieldsOf, makeTuple } from '@aztec/foundation/array'; import { arraySerializedSizeOfNonEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { + BufferReader, + FieldReader, + type Tuple, + serializeToBuffer, + serializeToFields, +} from '@aztec/foundation/serialize'; import { inspect } from 'util'; @@ -12,6 +18,7 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, + PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH, } from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; import { ScopedLogHash } from '../log_hash.js'; @@ -82,6 +89,16 @@ export class PrivateToPublicAccumulatedData { return serializeToBuffer(...PrivateToPublicAccumulatedData.getFields(this)); } + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToPublicAccumulatedData.getFields(this)); + if (fields.length !== PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToPublicAccumulatedData. Expected ${PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + static empty() { return new PrivateToPublicAccumulatedData( makeTuple(MAX_NOTE_HASHES_PER_TX, Fr.zero), diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts index ed837be80848..0c5a5247e5d7 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts @@ -1,7 +1,11 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; +import { type Fr } from '@aztec/foundation/fields'; +import { BufferReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; +import { type FieldsOf } from '@aztec/foundation/types'; +import { GeneratorIndex, PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH } from '../../constants.gen.js'; import { Gas } from '../gas.js'; import { PublicCallRequest } from '../public_call_request.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; @@ -31,6 +35,18 @@ export class PrivateToPublicKernelCircuitPublicInputs { ); } + static getFields(fields: FieldsOf) { + return [ + fields.constants, + fields.rollupValidationRequests, + fields.nonRevertibleAccumulatedData, + fields.revertibleAccumulatedData, + fields.publicTeardownCallRequest, + fields.gasUsed, + fields.feePayer, + ] as const; + } + static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new PrivateToPublicKernelCircuitPublicInputs( @@ -63,4 +79,18 @@ export class PrivateToPublicKernelCircuitPublicInputs { toString() { return bufferToHex(this.toBuffer()); } + + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToPublicKernelCircuitPublicInputs.getFields(this)); + if (fields.length !== PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToPublicKernelCircuitPublicInputs. Expected ${PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + + hash() { + return poseidon2HashWithSeparator(this.toFields(), GeneratorIndex.PUBLIC_TX_HASH); + } } diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts index fc3fcc0b9cb0..7d511c61ca91 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts @@ -2,7 +2,7 @@ import { type FieldsOf, makeTuple } from '@aztec/foundation/array'; import { arraySerializedSizeOfNonEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { bufferSchemaFor } from '@aztec/foundation/schemas'; -import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, type Tuple, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; import { inspect } from 'util'; @@ -13,6 +13,7 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, + PRIVATE_TO_ROLLUP_ACCUMULATED_DATA_LENGTH, } from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; import { ScopedLogHash } from '../log_hash.js'; @@ -129,6 +130,16 @@ export class PrivateToRollupAccumulatedData { ); } + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToRollupAccumulatedData.getFields(this)); + if (fields.length !== PRIVATE_TO_ROLLUP_ACCUMULATED_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToRollupAccumulatedData. Expected ${PRIVATE_TO_ROLLUP_ACCUMULATED_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + [inspect.custom]() { return `PrivateToRollupAccumulatedData { noteHashes: [${this.noteHashes diff --git a/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_kernel_circuit_public_inputs.ts similarity index 68% rename from yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts rename to yarn-project/circuits.js/src/structs/kernel/private_to_rollup_kernel_circuit_public_inputs.ts index c0fecd8b9992..f822a51ac7ed 100644 --- a/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_kernel_circuit_public_inputs.ts @@ -1,8 +1,12 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; +import { type Fr } from '@aztec/foundation/fields'; import { bufferSchemaFor } from '@aztec/foundation/schemas'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; +import { type FieldsOf } from '@aztec/foundation/types'; +import { GeneratorIndex, PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH } from '../../constants.gen.js'; import { Gas } from '../gas.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; import { PrivateToRollupAccumulatedData } from './private_to_rollup_accumulated_data.js'; @@ -14,6 +18,10 @@ import { TxConstantData } from './tx_constant_data.js'; */ export class PrivateToRollupKernelCircuitPublicInputs { constructor( + /** + * Data which is not modified by the circuits. + */ + public constants: TxConstantData, /** * Validation requests accumulated from private and public execution to be completed by the rollup. */ @@ -22,10 +30,6 @@ export class PrivateToRollupKernelCircuitPublicInputs { * Data accumulated from both public and private circuits. */ public end: PrivateToRollupAccumulatedData, - /** - * Data which is not modified by the circuits. - */ - public constants: TxConstantData, /** * Gas used during this transaction */ @@ -41,7 +45,7 @@ export class PrivateToRollupKernelCircuitPublicInputs { } toBuffer() { - return serializeToBuffer(this.rollupValidationRequests, this.end, this.constants, this.gasUsed, this.feePayer); + return serializeToBuffer(this.constants, this.rollupValidationRequests, this.end, this.gasUsed, this.feePayer); } /** @@ -52,9 +56,9 @@ export class PrivateToRollupKernelCircuitPublicInputs { static fromBuffer(buffer: Buffer | BufferReader): PrivateToRollupKernelCircuitPublicInputs { const reader = BufferReader.asReader(buffer); return new PrivateToRollupKernelCircuitPublicInputs( + reader.readObject(TxConstantData), reader.readObject(RollupValidationRequests), reader.readObject(PrivateToRollupAccumulatedData), - reader.readObject(TxConstantData), reader.readObject(Gas), reader.readObject(AztecAddress), ); @@ -62,9 +66,9 @@ export class PrivateToRollupKernelCircuitPublicInputs { static empty() { return new PrivateToRollupKernelCircuitPublicInputs( + TxConstantData.empty(), RollupValidationRequests.empty(), PrivateToRollupAccumulatedData.empty(), - TxConstantData.empty(), Gas.empty(), AztecAddress.ZERO, ); @@ -83,8 +87,26 @@ export class PrivateToRollupKernelCircuitPublicInputs { return this.toBuffer(); } + static getFields(fields: FieldsOf) { + return [fields.constants, fields.rollupValidationRequests, fields.end, fields.gasUsed, fields.feePayer] as const; + } + /** Creates an instance from a hex string. */ static get schema() { return bufferSchemaFor(PrivateToRollupKernelCircuitPublicInputs); } + + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToRollupKernelCircuitPublicInputs.getFields(this)); + if (fields.length !== PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToRollupKernelCircuitPublicInputs. Expected ${PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + + hash() { + return poseidon2HashWithSeparator(this.toFields(), GeneratorIndex.PRIVATE_TX_HASH); + } } diff --git a/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts b/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts index 67549544e4a2..361852982adf 100644 --- a/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts @@ -1,7 +1,8 @@ import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; +import { TX_CONSTANT_DATA_LENGTH } from '../../constants.gen.js'; import { BlockHeader } from '../block_header.js'; import { TxContext } from '../tx_context.js'; @@ -49,6 +50,16 @@ export class TxConstantData { ); } + toFields(): Fr[] { + const fields = serializeToFields(...TxConstantData.getFields(this)); + if (fields.length !== TX_CONSTANT_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for TxConstantData. Expected ${TX_CONSTANT_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + static fromBuffer(buffer: Buffer | BufferReader): TxConstantData { const reader = BufferReader.asReader(buffer); return new TxConstantData( diff --git a/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts b/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts index 37d1ae3c8115..d69541886003 100644 --- a/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts +++ b/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts @@ -1,9 +1,10 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; +import { type FieldsOf } from '@aztec/foundation/types'; -import { L2_TO_L1_MESSAGE_LENGTH } from '../constants.gen.js'; +import { L2_TO_L1_MESSAGE_LENGTH, SCOPED_L2_TO_L1_MESSAGE_LENGTH } from '../constants.gen.js'; export class L2ToL1Message { constructor(public recipient: EthAddress, public content: Fr, public counter: number) {} @@ -85,6 +86,10 @@ export class L2ToL1Message { export class ScopedL2ToL1Message { constructor(public message: L2ToL1Message, public contractAddress: AztecAddress) {} + static getFields(fields: FieldsOf) { + return [fields.message, fields.contractAddress] as const; + } + static empty() { return new ScopedL2ToL1Message(L2ToL1Message.empty(), AztecAddress.ZERO); } @@ -107,6 +112,16 @@ export class ScopedL2ToL1Message { return new ScopedL2ToL1Message(reader.readObject(L2ToL1Message), reader.readObject(AztecAddress)); } + toFields(): Fr[] { + const fields = serializeToFields(...ScopedL2ToL1Message.getFields(this)); + if (fields.length !== SCOPED_L2_TO_L1_MESSAGE_LENGTH) { + throw new Error( + `Invalid number of fields for ScopedL2ToL1Message. Expected ${SCOPED_L2_TO_L1_MESSAGE_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + isEmpty(): boolean { return this.message.isEmpty() && this.contractAddress.isZero(); } diff --git a/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts b/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts index b574dbc49b20..a6eac678493e 100644 --- a/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts +++ b/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts @@ -1,7 +1,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH } from '../../constants.gen.js'; -import { PrivateToRollupKernelCircuitPublicInputs } from '../kernel/kernel_circuit_public_inputs.js'; +import { PrivateToRollupKernelCircuitPublicInputs } from '../kernel/private_to_rollup_kernel_circuit_public_inputs.js'; import { RecursiveProof, makeEmptyRecursiveProof } from '../recursive_proof.js'; import { VkWitnessData } from '../vk_witness_data.js'; diff --git a/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts b/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts index 2d2279b8c880..c415c9817824 100644 --- a/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts +++ b/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts @@ -1,7 +1,9 @@ import { type Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; +import { type FieldsOf } from '@aztec/foundation/types'; +import { ROLLUP_VALIDATION_REQUESTS_LENGTH } from '../constants.gen.js'; import { MaxBlockNumber } from './max_block_number.js'; /** @@ -27,11 +29,25 @@ export class RollupValidationRequests { return bufferToHex(this.toBuffer()); } + static getFields(fields: FieldsOf) { + return [fields.maxBlockNumber] as const; + } + static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); return new RollupValidationRequests(MaxBlockNumber.fromFields(reader)); } + toFields(): Fr[] { + const fields = serializeToFields(...RollupValidationRequests.getFields(this)); + if (fields.length !== ROLLUP_VALIDATION_REQUESTS_LENGTH) { + throw new Error( + `Invalid number of fields for RollupValidationRequests. Expected ${ROLLUP_VALIDATION_REQUESTS_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + /** * Deserializes from a buffer or reader, corresponding to a write in cpp. * @param buffer - Buffer or reader to read from. diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 91b4a613eef6..0c99e91f8c05 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -139,7 +139,7 @@ import { TxConstantData, VkWitnessData, } from '../structs/index.js'; -import { PrivateToRollupKernelCircuitPublicInputs } from '../structs/kernel/kernel_circuit_public_inputs.js'; +import { PrivateToRollupKernelCircuitPublicInputs } from '../structs/kernel/private_to_rollup_kernel_circuit_public_inputs.js'; import { AvmProofData } from '../structs/rollup/avm_proof_data.js'; import { BaseOrMergeRollupPublicInputs } from '../structs/rollup/base_or_merge_rollup_public_inputs.js'; import { PrivateBaseRollupHints, PublicBaseRollupHints } from '../structs/rollup/base_rollup_hints.js'; @@ -422,9 +422,9 @@ export function makePrivateToRollupKernelCircuitPublicInputs( fullAccumulatedData = true, ): PrivateToRollupKernelCircuitPublicInputs { return new PrivateToRollupKernelCircuitPublicInputs( + makeTxConstantData(seed + 0x100), makeRollupValidationRequests(seed), makePrivateToRollupAccumulatedData(seed, fullAccumulatedData), - makeTxConstantData(seed + 0x100), makeGas(seed + 0x600), makeAztecAddress(seed + 0x700), ); diff --git a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts index 3e4175b75364..b0ae555c1e8c 100644 --- a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts +++ b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts @@ -943,9 +943,9 @@ export function mapPrivateToRollupKernelCircuitPublicInputsFromNoir( inputs: PrivateToRollupKernelCircuitPublicInputsNoir, ) { return new PrivateToRollupKernelCircuitPublicInputs( + mapTxConstantDataFromNoir(inputs.constants), mapRollupValidationRequestsFromNoir(inputs.rollup_validation_requests), mapPrivateToRollupAccumulatedDataFromNoir(inputs.end), - mapTxConstantDataFromNoir(inputs.constants), mapGasFromNoir(inputs.gas_used), mapAztecAddressFromNoir(inputs.fee_payer), ); diff --git a/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts b/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts index 3247c88355c6..58a924399573 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts @@ -117,7 +117,12 @@ export class AztecKVTxPool implements TxPool { */ public getTxByHash(txHash: TxHash): Tx | undefined { const buffer = this.#txs.get(txHash.toString()); - return buffer ? Tx.fromBuffer(buffer) : undefined; + if (buffer) { + const tx = Tx.fromBuffer(buffer); + tx.setTxHash(txHash); + return tx; + } + return undefined; } /** @@ -190,7 +195,11 @@ export class AztecKVTxPool implements TxPool { * @returns Array of tx objects in the order they were added to the pool. */ public getAllTxs(): Tx[] { - return Array.from(this.#txs.values()).map(buffer => Tx.fromBuffer(buffer)); + return Array.from(this.#txs.entries()).map(([hash, buffer]) => { + const tx = Tx.fromBuffer(buffer); + tx.setTxHash(TxHash.fromString(hash)); + return tx; + }); } /** diff --git a/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts b/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts index f3c92e688b84..056d12d6e4a9 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts @@ -69,10 +69,12 @@ export function describeTxPool(getTxPool: () => TxPool) { await pool.addTxs([tx1]); // this peer knows that tx2 was mined, but it does not have the tx object await pool.markAsMined([tx1.getTxHash(), someTxHashThatThisPeerDidNotSee], 1); - expect(pool.getMinedTxHashes()).toEqual([ - [tx1.getTxHash(), 1], - [someTxHashThatThisPeerDidNotSee, 1], - ]); + expect(new Set(pool.getMinedTxHashes())).toEqual( + new Set([ + [tx1.getTxHash(), 1], + [someTxHashThatThisPeerDidNotSee, 1], + ]), + ); // reorg: both txs should now become available again await pool.markMinedAsPending([tx1.getTxHash(), someTxHashThatThisPeerDidNotSee]); diff --git a/yarn-project/p2p/src/services/reqresp/reqresp.test.ts b/yarn-project/p2p/src/services/reqresp/reqresp.test.ts index 11a951d9e338..e99e2f4cb551 100644 --- a/yarn-project/p2p/src/services/reqresp/reqresp.test.ts +++ b/yarn-project/p2p/src/services/reqresp/reqresp.test.ts @@ -153,6 +153,8 @@ describe('ReqResp', () => { await sleep(500); const res = await nodes[0].req.sendRequest(TX_REQ_PROTOCOL, txHash); + // Set tx hash since expect will compare private properties + res.getTxHash(); expect(res).toEqual(tx); }); diff --git a/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts b/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts index 20043b613ca5..97f67d24ce01 100644 --- a/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts +++ b/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts @@ -1,4 +1,4 @@ -import { TxEffect } from '@aztec/circuit-types'; +import { TxEffect, TxHash } from '@aztec/circuit-types'; import { Fr } from '@aztec/circuits.js'; import { BlobPublicInputs } from '@aztec/circuits.js/blobs'; import { updateInlineTestData } from '@aztec/foundation/testing/files'; @@ -49,29 +49,33 @@ describe('buildBlobHints', () => { it('correctly builds hints for non-empty blob fields', () => { const txEffect0 = TxEffect.empty(); + txEffect0.txHash = new TxHash(new Fr(42)); txEffect0.nullifiers[0] = new Fr(0x123); const txEffect1 = TxEffect.empty(); + txEffect1.txHash = new TxHash(new Fr(43)); txEffect1.noteHashes[0] = new Fr(0x6789); txEffect1.nullifiers[0] = new Fr(0x45); const { blobFields, blobCommitments, blobsHash, blobs } = buildBlobHints([txEffect0, txEffect1]); - const blobFields0Str = fieldArrToStr(blobFields.slice(0, 4)); - const blobFields1Str = fieldArrToStr(blobFields.slice(4)); - expect(blobFields.length).toBe(4 + 6); + const blobFields0Str = fieldArrToStr(blobFields.slice(0, 5)); + const blobFields1Str = fieldArrToStr(blobFields.slice(5)); + expect(blobFields.length).toBe(5 + 7); expect(blobCommitments.length).toBe(1); const blobCommitmentStr = fieldArrToStr(blobCommitments[0]); expect(blobCommitmentStr).toMatchInlineSnapshot( - `"[0x00ad8be66e7276942652627bb00fe1e65dc1c3c6701ab27cc05eff662950071d, 0x00000000000000000000000000000071baf7a9af9757f1d3878b37b438797213]"`, + `"[0x008c32fe581c8fdba12c0d7597911dead2d937d68525bae655508412bb53bb98, 0x0000000000000000000000000000006aaa0680f21270e7d8de4e19da5164f95c]"`, ); const blobsHashStr = blobsHash.toString(); - expect(blobsHashStr).toMatchInlineSnapshot(`"0x00dc577f5c94c82b847693b76ee69cd33d4e5eee3adb6f37d8d7ab662c84725d"`); + expect(blobsHashStr).toMatchInlineSnapshot(`"0x00a965619c8668b834755678b32d023b9c5e8588ce449f44f7fa9335455b5cc5"`); const publicInputs = BlobPublicInputs.fromBlob(blobs[0]); - expect(publicInputs.y).toBe(11463660905914812112228400842008710735611240877901286242511876802170210355245n); + expect(publicInputs.y).toMatchInlineSnapshot( + `17179655213294173540446545222866729565951946174336496855332549059993428157821n`, + ); const zStr = publicInputs.z.toString(); - expect(zStr).toMatchInlineSnapshot(`"0x1582b354f32263abde313d597582ebceafe17d4e2a68dd47533383e85b4cb780"`); + expect(zStr).toMatchInlineSnapshot(`"0x1f92b871671f27a378d23f1cef10fbd8f0d90dd7172da9e3c3fc1aa745a072c3"`); // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data. updateInlineTestData( diff --git a/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts b/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts index 71d3a8017fcd..457a6b3e4b6c 100644 --- a/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts @@ -104,7 +104,7 @@ export class TxProvingState { } const tubeData = new PublicTubeData( - this.processedTx.data.toPublicKernelCircuitPublicInputs(), + this.processedTx.data.toPrivateToPublicKernelCircuitPublicInputs(), this.tube.proof, this.#getTubeVkData(), ); diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index 8d3359149b2b..d784fbea92db 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -154,9 +154,7 @@ describe('prover-node', () => { l2BlockSource.getL1Constants.mockResolvedValue(EmptyL1RollupConstants); // Coordination plays along and returns a tx whenever requested - mockCoordination.getTxByHash.mockImplementation(hash => - Promise.resolve(mock({ getTxHash: () => hash, tryGetTxHash: () => hash })), - ); + mockCoordination.getTxByHash.mockImplementation(hash => Promise.resolve(mock({ getTxHash: () => hash }))); // A sample claim claim = { epochToProve: 10n, bondProvider: address } as EpochProofClaim; diff --git a/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts b/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts index 7ce0ade4c4a1..abbae919f825 100644 --- a/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts +++ b/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts @@ -34,6 +34,7 @@ export async function bruteForceNoteInfo( simulator: AcirSimulator, uniqueNoteHashes: Fr[], txHash: TxHash, + firstNullifier: Fr, contractAddress: AztecAddress, storageSlot: Fr, noteTypeId: NoteSelector, @@ -46,7 +47,6 @@ export async function bruteForceNoteInfo( let noteHash: Fr | undefined; let uniqueNoteHash: Fr | undefined; let innerNullifier: Fr | undefined; - const firstNullifier = Fr.fromBuffer(txHash.toBuffer()); for (; noteHashIndex < uniqueNoteHashes.length; ++noteHashIndex) { if (excludedIndices.has(noteHashIndex)) { diff --git a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts index 7e1f94abe03d..242b34b39fff 100644 --- a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts +++ b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts @@ -31,6 +31,7 @@ export async function produceNoteDaos( addressPoint: PublicKey | undefined, payload: L1NotePayload, txHash: TxHash, + firstNullifier: Fr, l2BlockNumber: number, l2BlockHash: string, noteHashes: Fr[], @@ -51,6 +52,7 @@ export async function produceNoteDaos( addressPoint, payload, txHash, + firstNullifier, l2BlockNumber, l2BlockHash, noteHashes, diff --git a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts index 291d9efd80d9..ef78cf7a76b6 100644 --- a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts +++ b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts @@ -13,6 +13,7 @@ export async function produceNoteDaosForKey( pkM: PublicKey, payload: L1NotePayload, txHash: TxHash, + firstNullifier: Fr, l2BlockNumber: number, l2BlockHash: string, noteHashes: Fr[], @@ -39,6 +40,7 @@ export async function produceNoteDaosForKey( simulator, noteHashes, txHash, + firstNullifier, payload.contractAddress, payload.storageSlot, payload.noteTypeId, diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 6082a3cc1eb9..b6b4eba6cd61 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -517,8 +517,8 @@ export class PXEService implements PXE { } } - this.log.info(`Simulation completed for ${simulatedTx.tryGetTxHash()} in ${timer.ms()}ms`, { - txHash: simulatedTx.tryGetTxHash(), + this.log.info(`Simulation completed for ${simulatedTx.getTxHash()} in ${timer.ms()}ms`, { + txHash: simulatedTx.getTxHash(), ...txInfo, ...(profileResult ? { gateCounts: profileResult.gateCounts } : {}), ...(publicOutput diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 7b3d332738e8..019ca15c7fce 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -610,6 +610,7 @@ export class SimulatorOracle implements DBOracle { notePayload ? recipient.toAddressPoint() : undefined, payload!, txEffect.data.txHash, + txEffect.data.nullifiers[0], txEffect.l2BlockNumber, txEffect.l2BlockHash, txEffect.data.noteHashes, diff --git a/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts index e4e95f0ada55..11e9a6aaaf41 100644 --- a/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts +++ b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts @@ -490,7 +490,7 @@ describe('Simulator oracle', () => { }); function mockTaggedLogs(requests: MockNoteRequest[], nullifiers: number = 0) { - const txEffectsMap: { [k: string]: { noteHashes: Fr[]; txHash: TxHash } } = {}; + const txEffectsMap: { [k: string]: { noteHashes: Fr[]; txHash: TxHash; nullifiers: Fr[] } } = {}; const taggedLogs: TxScopedL2Log[] = []; const groupedByTx = requests.reduce<{ [i: number]: { [j: number]: MockNoteRequest[] } }>((acc, request) => { if (!acc[request.blockNumber]) { @@ -513,6 +513,7 @@ describe('Simulator oracle', () => { if (!txEffectsMap[txHash.toString()]) { txEffectsMap[txHash.toString()] = { txHash, + nullifiers: [new Fr(txHash.hash.toBigInt() + 27n)], noteHashes: Array(maxNoteIndex + 1) .fill(0) .map(() => Fr.random()), diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 7ee18457dbfa..d6fff38670fc 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -642,7 +642,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1); const siloedNotehash = siloNoteHash(address, value0); - const nonce = computeNoteHashNonce(Fr.fromBuffer(context.persistableState.txHash.toBuffer()), 0); + const nonce = computeNoteHashNonce(context.persistableState.firstNullifier, 0); const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash); expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash); }); diff --git a/yarn-project/simulator/src/avm/fixtures/index.ts b/yarn-project/simulator/src/avm/fixtures/index.ts index 7990a0b2c3ba..494a500f4128 100644 --- a/yarn-project/simulator/src/avm/fixtures/index.ts +++ b/yarn-project/simulator/src/avm/fixtures/index.ts @@ -1,4 +1,4 @@ -import { TxHash, isNoirCallStackUnresolved } from '@aztec/circuit-types'; +import { isNoirCallStackUnresolved } from '@aztec/circuit-types'; import { GasFees, GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js'; import { type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; @@ -45,7 +45,7 @@ export function initPersistableStateManager(overrides?: { nullifiers?: NullifierManager; doMerkleOperations?: boolean; merkleTrees?: AvmEphemeralForest; - txHash?: TxHash; + firstNullifier?: Fr; }): AvmPersistableStateManager { const worldStateDB = overrides?.worldStateDB || mock(); return new AvmPersistableStateManager( @@ -55,7 +55,7 @@ export function initPersistableStateManager(overrides?: { overrides?.nullifiers || new NullifierManager(worldStateDB), overrides?.doMerkleOperations || false, overrides?.merkleTrees || mock(), - overrides?.txHash || new TxHash(new Fr(27)), + overrides?.firstNullifier || new Fr(27), ); } diff --git a/yarn-project/simulator/src/avm/journal/journal.test.ts b/yarn-project/simulator/src/avm/journal/journal.test.ts index 9aae7f88889d..afd936960e4e 100644 --- a/yarn-project/simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/simulator/src/avm/journal/journal.test.ts @@ -85,7 +85,7 @@ describe('journal', () => { persistableState.writeNoteHash(address, utxo); expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1); const siloedNotehash = siloNoteHash(address, utxo); - const nonce = computeNoteHashNonce(Fr.fromBuffer(persistableState.txHash.toBuffer()), 1); + const nonce = computeNoteHashNonce(persistableState.firstNullifier, 1); const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash); expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash); }); diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 87f93d97b451..1c0182ad74df 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -1,4 +1,4 @@ -import { MerkleTreeId, type TxHash } from '@aztec/circuit-types'; +import { MerkleTreeId } from '@aztec/circuit-types'; import { AztecAddress, CANONICAL_AUTH_REGISTRY_ADDRESS, @@ -61,7 +61,7 @@ export class AvmPersistableStateManager { private readonly doMerkleOperations: boolean = false, /** Ephmeral forest for merkle tree operations */ public merkleTrees: AvmEphemeralForest, - public readonly txHash: TxHash, + public readonly firstNullifier: Fr, ) {} /** @@ -71,7 +71,7 @@ export class AvmPersistableStateManager { worldStateDB: WorldStateDB, trace: PublicSideEffectTraceInterface, doMerkleOperations: boolean = false, - txHash: TxHash, + firstNullifier: Fr, ) { const ephemeralForest = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface()); return new AvmPersistableStateManager( @@ -81,7 +81,7 @@ export class AvmPersistableStateManager { /*nullifiers=*/ new NullifierManager(worldStateDB), /*doMerkleOperations=*/ doMerkleOperations, ephemeralForest, - txHash, + firstNullifier, ); } @@ -96,7 +96,7 @@ export class AvmPersistableStateManager { this.nullifiers.fork(), this.doMerkleOperations, this.merkleTrees.fork(), - this.txHash, + this.firstNullifier, ); } @@ -294,8 +294,7 @@ export class AvmPersistableStateManager { * @param noteHash - the non unique note hash to write */ public writeSiloedNoteHash(noteHash: Fr): void { - const txHash = Fr.fromBuffer(this.txHash.toBuffer()); - const nonce = computeNoteHashNonce(txHash, this.trace.getNoteHashCount()); + const nonce = computeNoteHashNonce(this.firstNullifier, this.trace.getNoteHashCount()); const uniqueNoteHash = computeUniqueNoteHash(nonce, noteHash); this.writeUniqueNoteHash(uniqueNoteHash); diff --git a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts index af4fd0957863..2fd3d84edba7 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts @@ -125,7 +125,7 @@ describe('Accrued Substate', () => { await new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ value0Offset).execute(context); expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1); const siloedNotehash = siloNoteHash(address, value0); - const nonce = computeNoteHashNonce(Fr.fromBuffer(context.persistableState.txHash.toBuffer()), 0); + const nonce = computeNoteHashNonce(persistableState.firstNullifier, 0); const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash); expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash); }); diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index eb3f743d963a..b67d187cdaa8 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -290,7 +290,7 @@ export class PublicProcessor implements Traceable { return [result, failed, returns]; } - @trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString() })) + @trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.getTxHash().toString() })) private async processTx(tx: Tx, deadline?: Date): Promise<[ProcessedTx, NestedProcessReturnValues[]]> { const [time, [processedTx, returnValues]] = await elapsed(() => this.processTxWithinDeadline(tx, deadline)); diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 0c376934a20a..194838dd0188 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -7,7 +7,7 @@ import { type SimulationError, type Tx, TxExecutionPhase, - TxHash, + type TxHash, } from '@aztec/circuit-types'; import { AvmCircuitInputs, @@ -61,6 +61,7 @@ export class PublicTxContext { public avmProvingRequest: AvmProvingRequest | undefined; // FIXME(dbanks12): remove constructor( + public readonly txHash: TxHash, public readonly state: PhaseStateManager, private readonly globalVariables: GlobalVariables, private readonly startStateReference: StateReference, @@ -103,12 +104,14 @@ export class PublicTxContext { previousAccumulatedDataArrayLengths, ); + const firstNullifier = nonRevertibleAccumulatedDataFromPrivate.nullifiers[0]; + // Transaction level state manager that will be forked for revertible phases. const txStateManager = await AvmPersistableStateManager.create( worldStateDB, enqueuedCallTrace, doMerkleOperations, - fetchTxHash(nonRevertibleAccumulatedDataFromPrivate), + firstNullifier, ); const gasSettings = tx.data.constants.txContext.gasSettings; @@ -117,6 +120,7 @@ export class PublicTxContext { const gasAllocatedToPublic = applyMaxToAvailableGas(gasSettings.gasLimits.sub(gasUsedByPrivate)); return new PublicTxContext( + tx.getTxHash(), new PhaseStateManager(txStateManager), globalVariables, await db.getStateReference(), @@ -188,14 +192,6 @@ export class PublicTxContext { return this.revertCode; } - /** - * Construct & return transaction hash. - * @returns The transaction's hash. - */ - getTxHash(): TxHash { - return fetchTxHash(this.nonRevertibleAccumulatedDataFromPrivate); - } - /** * Are there any call requests for the speciiied phase? */ @@ -452,12 +448,3 @@ function applyMaxToAvailableGas(availableGas: Gas) { /*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_TX_PUBLIC_PORTION), ); } - -function fetchTxHash(nonRevertibleAccumulatedData: PrivateToPublicAccumulatedData): TxHash { - // Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier - const firstNullifier = nonRevertibleAccumulatedData.nullifiers[0]; - if (!firstNullifier || firstNullifier.isZero()) { - throw new Error(`Cannot get tx hash since first nullifier is missing`); - } - return new TxHash(firstNullifier); -} diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index b4c91484e275..e973d28f654e 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -219,8 +219,8 @@ export class PublicTxSimulator { const callRequests = context.getCallRequestsForPhase(phase); const executionRequests = context.getExecutionRequestsForPhase(phase); - this.log.debug(`Processing phase ${TxExecutionPhase[phase]} for tx ${context.getTxHash()}`, { - txHash: context.getTxHash().toString(), + this.log.debug(`Processing phase ${TxExecutionPhase[phase]} for tx ${context.txHash}`, { + txHash: context.txHash.toString(), phase: TxExecutionPhase[phase], callRequests: callRequests.length, executionRequests: executionRequests.length, @@ -266,7 +266,7 @@ export class PublicTxSimulator { * @returns The result of execution. */ @trackSpan('PublicTxSimulator.simulateEnqueuedCall', (phase, context, _callRequest, executionRequest) => ({ - [Attributes.TX_HASH]: context.getTxHash().toString(), + [Attributes.TX_HASH]: context.txHash.toString(), [Attributes.TARGET_ADDRESS]: executionRequest.callContext.contractAddress.toString(), [Attributes.SENDER_ADDRESS]: executionRequest.callContext.msgSender.toString(), [Attributes.SIMULATOR_PHASE]: TxExecutionPhase[phase].toString(),