diff --git a/CHANGELOG.md b/CHANGELOG.md index 32426bd63..3a7f2dc66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Added `CHANGELOG.md` warning message on CI (#799). - Account deltas can now be merged (#797). - Changed `AccountCode` procedures from merkle tree to sequential hash + added storage_offset support (#763). +- [BREAKING] Refactored and simplified `NoteOrigin` and `NoteInclusionProof` structs (#810). ## 0.4.0 (2024-07-03) diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index 59be5709f..9c7db604e 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use miden_objects::{ accounts::{Account, AccountProcedureInfo}, transaction::{ - ChainMmr, ExecutedTransaction, InputNote, InputNotes, PreparedTransaction, TransactionArgs, + ChainMmr, ExecutedTransaction, InputNote, PreparedTransaction, TransactionArgs, TransactionInputs, TransactionScript, TransactionWitness, }, vm::{AdviceInputs, StackInputs}, @@ -92,7 +92,7 @@ fn extend_advice_inputs( // build the advice map and Merkle store for relevant components add_chain_mmr_to_advice_inputs(tx_inputs.block_chain(), advice_inputs); add_account_to_advice_inputs(tx_inputs.account(), tx_inputs.account_seed(), advice_inputs); - add_input_notes_to_advice_inputs(tx_inputs.input_notes(), tx_args, advice_inputs); + add_input_notes_to_advice_inputs(tx_inputs, tx_args, advice_inputs); advice_inputs.extend(tx_args.advice_inputs().clone()); } @@ -267,29 +267,29 @@ fn add_account_to_advice_inputs( /// The advice provider is populated with: /// /// - For each note: -/// - The note's details (serial number, script root, and its' input / assets hash). +/// - The note's details (serial number, script root, and its input / assets hash). /// - The note's private arguments. /// - The note's public metadata. /// - The note's public inputs data. Prefixed by its length and padded to an even word length. /// - The note's asset padded. Prefixed by its length and padded to an even word length. -/// - For autheticated notes (determined by the `is_authenticated` flag): +/// - For authenticated notes (determined by the `is_authenticated` flag): /// - The note's authentication path against its block's note tree. /// - The block number, sub hash, note root. /// - The note's position in the note tree /// /// The data above is processed by `prologue::process_input_notes_data`. fn add_input_notes_to_advice_inputs( - notes: &InputNotes, + tx_inputs: &TransactionInputs, tx_args: &TransactionArgs, inputs: &mut AdviceInputs, ) { // if there are no input notes, nothing is added to the advice inputs - if notes.is_empty() { + if tx_inputs.input_notes().is_empty() { return; } let mut note_data = Vec::new(); - for input_note in notes.iter() { + for input_note in tx_inputs.input_notes().iter() { let note = input_note.note(); let assets = note.assets(); let recipient = note.recipient(); @@ -320,6 +320,16 @@ fn add_input_notes_to_advice_inputs( // insert note authentication path nodes into the Merkle store match input_note { InputNote::Authenticated { note, proof } => { + let block_num = proof.location().block_num(); + let note_block_header = if block_num == tx_inputs.block_header().block_num() { + tx_inputs.block_header() + } else { + tx_inputs + .block_chain() + .get_block(block_num) + .expect("block not found in chain MMR") + }; + // NOTE: keep in sync with the `prologue::process_input_note` kernel procedure // Push the `is_authenticated` flag note_data.push(Felt::ONE); @@ -328,17 +338,16 @@ fn add_input_notes_to_advice_inputs( inputs.extend_merkle_store( proof .note_path() - .inner_nodes(proof.origin().node_index.value(), note.hash()) + .inner_nodes(proof.location().node_index_in_block(), note.hash()) .unwrap(), ); - note_data.push(proof.origin().block_num.into()); - note_data.extend(*proof.sub_hash()); - note_data.extend(*proof.note_root()); + note_data.push(proof.location().block_num().into()); + note_data.extend(note_block_header.sub_hash()); + note_data.extend(note_block_header.note_root()); note_data.push( proof - .origin() - .node_index - .value() + .location() + .node_index_in_block() .try_into() .expect("value is greater than or equal to the field modulus"), ); @@ -352,5 +361,5 @@ fn add_input_notes_to_advice_inputs( } // NOTE: keep map in sync with the `prologue::process_input_notes_data` kernel procedure - inputs.extend_map([(notes.commitment(), note_data)]); + inputs.extend_map([(tx_inputs.input_notes().commitment(), note_data)]); } diff --git a/miden-tx/src/compiler/tests.rs b/miden-tx/src/compiler/tests.rs index 87280a444..83e9e0825 100644 --- a/miden-tx/src/compiler/tests.rs +++ b/miden-tx/src/compiler/tests.rs @@ -182,14 +182,7 @@ fn test_transaction_compilation_succeeds() { let _account_code = tx_compiler.load_account(account_id, account_code_ast).unwrap(); let notes = mock_input_notes(&mut tx_compiler, account_id); - let mock_inclusion_proof = NoteInclusionProof::new( - Default::default(), - Default::default(), - Default::default(), - 0, - Default::default(), - ) - .unwrap(); + let mock_inclusion_proof = NoteInclusionProof::new(0, 0, Default::default()).unwrap(); let notes = notes .into_iter() .map(|note| InputNote::authenticated(note, mock_inclusion_proof.clone())) diff --git a/objects/src/block/note_tree.rs b/objects/src/block/note_tree.rs index b97053c06..96447e3db 100644 --- a/objects/src/block/note_tree.rs +++ b/objects/src/block/note_tree.rs @@ -8,7 +8,7 @@ use miden_crypto::{ use crate::{ notes::NoteMetadata, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, - BLOCK_OUTPUT_NOTES_TREE_DEPTH, MAX_NOTES_PER_BATCH, + BLOCK_OUTPUT_NOTES_TREE_DEPTH, MAX_NOTES_PER_BATCH, MAX_OUTPUT_NOTES_PER_BLOCK, }; /// Wrapper over [SimpleSmt] for notes tree. @@ -35,7 +35,7 @@ impl BlockNoteTree { entries: impl IntoIterator, ) -> Result { let interleaved = entries.into_iter().flat_map(|(index, note_id, metadata)| { - let id_index = index.leaf_index(); + let id_index = index.leaf_index().into(); [(id_index, note_id.into()), (id_index + 1, metadata.into())] }); @@ -52,7 +52,7 @@ impl BlockNoteTree { /// The returned path is to the node which is the parent of both note and note metadata node. pub fn get_note_path(&self, index: BlockNoteIndex) -> Result { // get the path to the leaf containing the note (path len = 21) - let leaf_index = LeafIndex::new(index.leaf_index())?; + let leaf_index = LeafIndex::new(index.leaf_index().into())?; // move up the path by removing the first node, this path now points to the parent of the // note path @@ -92,11 +92,14 @@ impl BlockNoteIndex { } /// Returns an index to the node which the parent of both the note and note metadata. - pub fn to_absolute_index(&self) -> u64 { - (self.batch_idx() * MAX_NOTES_PER_BATCH + self.note_idx_in_batch()) as u64 + pub fn to_absolute_index(&self) -> u32 { + const _: () = assert!(MAX_OUTPUT_NOTES_PER_BLOCK <= u32::MAX as usize); + (self.batch_idx() * MAX_NOTES_PER_BATCH + self.note_idx_in_batch()) as u32 } - fn leaf_index(&self) -> u64 { + /// Returns an index of the leaf containing the note. + fn leaf_index(&self) -> u32 { + const _: () = assert!(MAX_OUTPUT_NOTES_PER_BLOCK * 2 <= u32::MAX as usize); self.to_absolute_index() * 2 } } diff --git a/objects/src/constants.rs b/objects/src/constants.rs index 84bab520a..81e8b614d 100644 --- a/objects/src/constants.rs +++ b/objects/src/constants.rs @@ -37,9 +37,6 @@ pub const BATCH_OUTPUT_NOTES_TREE_DEPTH: u8 = 13; /// number of notes is the number of leaves in the tree. pub const MAX_NOTES_PER_BATCH: usize = 2_usize.pow((BATCH_OUTPUT_NOTES_TREE_DEPTH - 1) as u32); -/// The maximum number of transaction in a single batch. -pub const MAX_TRANSACTIONS_PER_BATCH: usize = MAX_NOTES_PER_BATCH / MAX_OUTPUT_NOTES_PER_TX; - // BLOCK // ================================================================================================ @@ -48,8 +45,8 @@ pub const MAX_TRANSACTIONS_PER_BATCH: usize = MAX_NOTES_PER_BATCH / MAX_OUTPUT_N /// This value can be interpreted as: /// /// - The depth of a tree with the leaves set to a batch output note tree root. -/// - The level at which the batches create note trees are merged, creating a new tree with this many -/// additional new levels. +/// - The level at which the batches create note trees are merged, creating a new tree with this +/// many additional new levels. pub const BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH: u8 = 8; /// The final depth of the Sparse Merkle Tree used to store all notes created in a block. @@ -59,5 +56,8 @@ pub const BLOCK_OUTPUT_NOTES_TREE_DEPTH: u8 = /// Maximum number of batches that can be inserted into a single block. pub const MAX_BATCHES_PER_BLOCK: usize = 2_usize.pow(BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH as u32); +/// Maximum number of output notes that can be created in a single block. +pub const MAX_OUTPUT_NOTES_PER_BLOCK: usize = MAX_NOTES_PER_BATCH * MAX_BATCHES_PER_BLOCK; + /// The block height of the genesis block pub const GENESIS_BLOCK: u32 = 0; diff --git a/objects/src/errors.rs b/objects/src/errors.rs index 15fb20871..fcc138f7f 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -146,7 +146,7 @@ pub enum NoteError { InvalidNoteTagUseCase(u16), InvalidNoteType(NoteType), InvalidNoteTypeValue(u64), - InvalidOriginIndex(String), + InvalidLocationIndex(String), InvalidStubDataLen(usize), NetworkExecutionRequiresOnChainAccount, NetworkExecutionRequiresPublicNote(NoteType), @@ -166,8 +166,8 @@ impl NoteError { Self::DuplicateNonFungibleAsset(asset) } - pub fn invalid_origin_index(msg: String) -> Self { - Self::InvalidOriginIndex(msg) + pub fn invalid_location_index(msg: String) -> Self { + Self::InvalidLocationIndex(msg) } pub fn too_many_assets(num_assets: usize) -> Self { diff --git a/objects/src/notes/file.rs b/objects/src/notes/file.rs index c34d879a4..af7f3d5e1 100644 --- a/objects/src/notes/file.rs +++ b/objects/src/notes/file.rs @@ -179,14 +179,7 @@ mod tests { #[test] fn serialize_with_proof() { let note = create_example_note(); - let mock_inclusion_proof = NoteInclusionProof::new( - Default::default(), - Default::default(), - Default::default(), - 0, - Default::default(), - ) - .unwrap(); + let mock_inclusion_proof = NoteInclusionProof::new(0, 0, Default::default()).unwrap(); let file = NoteFile::NoteWithProof(note.clone(), mock_inclusion_proof.clone()); let mut buffer = Vec::new(); file.write_into(&mut buffer); diff --git a/objects/src/notes/origin.rs b/objects/src/notes/location.rs similarity index 50% rename from objects/src/notes/origin.rs rename to objects/src/notes/location.rs index a23b7c10d..b1982d87e 100644 --- a/objects/src/notes/origin.rs +++ b/objects/src/notes/location.rs @@ -1,34 +1,42 @@ -use alloc::string::ToString; - use super::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, NoteError, Serializable, - NOTE_TREE_DEPTH, + ByteReader, ByteWriter, Deserializable, DeserializationError, NoteError, Serializable, }; -use crate::crypto::merkle::{MerklePath, NodeIndex}; +use crate::{crypto::merkle::MerklePath, MAX_BATCHES_PER_BLOCK, MAX_NOTES_PER_BATCH}; -/// Contains information about the origin of a note. +/// Contains information about the location of a note. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct NoteOrigin { +pub struct NoteLocation { /// The block number the note was created in. - pub block_num: u32, + block_num: u32, /// The index of the note in the note Merkle tree of the block the note was created in. - pub node_index: NodeIndex, // TODO: should be a u32 because the depth is always the same + node_index_in_block: u32, +} + +impl NoteLocation { + /// Returns the block number the note was created in. + pub fn block_num(&self) -> u32 { + self.block_num + } + + /// Returns the index of the note in the note Merkle tree of the block the note was created in. + /// + /// # Note + /// + /// The height of the Merkle tree is `BLOCK_OUTPUT_NOTES_TREE_DEPTH`. Thus, the maximum index + /// is `2 ^ BLOCK_OUTPUT_NOTES_TREE_DEPTH - 1`. + pub fn node_index_in_block(&self) -> u64 { + self.node_index_in_block as u64 + } } /// Contains the data required to prove inclusion of a note in the canonical chain. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct NoteInclusionProof { - /// Details about the note's origin. - origin: NoteOrigin, - - /// The sub hash of the block the note was created in. - sub_hash: Digest, - - /// The note root of the block the note was created in. - note_root: Digest, + /// Details about the note's location. + location: NoteLocation, /// The note's authentication Merkle path its block's the note root. note_path: MerklePath, @@ -38,37 +46,26 @@ impl NoteInclusionProof { /// Returns a new [NoteInclusionProof]. pub fn new( block_num: u32, - sub_hash: Digest, - note_root: Digest, - index: u64, + node_index_in_block: u32, note_path: MerklePath, ) -> Result { - let node_index = NodeIndex::new(NOTE_TREE_DEPTH, index) - .map_err(|e| NoteError::invalid_origin_index(e.to_string()))?; - Ok(Self { - origin: NoteOrigin { block_num, node_index }, - sub_hash, - note_root, - note_path, - }) + const HIGHEST_INDEX: usize = MAX_BATCHES_PER_BLOCK * MAX_NOTES_PER_BATCH - 1; + if node_index_in_block as usize > HIGHEST_INDEX { + return Err(NoteError::InvalidLocationIndex(format!( + "Node index ({node_index_in_block}) is out of bounds (0..={HIGHEST_INDEX})." + ))); + } + let location = NoteLocation { block_num, node_index_in_block }; + + Ok(Self { location, note_path }) } // ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the sub hash of the block header the note was created in. - pub fn sub_hash(&self) -> Digest { - self.sub_hash - } - - /// Returns the note root of the block header the note was created in. - pub fn note_root(&self) -> Digest { - self.note_root - } - - /// Returns the origin of the note. - pub fn origin(&self) -> &NoteOrigin { - &self.origin + /// Returns the location of the note. + pub fn location(&self) -> &NoteLocation { + &self.location } /// Returns the Merkle path to the note in the note Merkle tree of the block the note was @@ -81,38 +78,34 @@ impl NoteInclusionProof { // SERIALIZATION // ================================================================================================ -impl Serializable for NoteOrigin { +impl Serializable for NoteLocation { fn write_into(&self, target: &mut W) { target.write_u32(self.block_num); - self.node_index.write_into(target); + target.write_u32(self.node_index_in_block); } } -impl Deserializable for NoteOrigin { +impl Deserializable for NoteLocation { fn read_from(source: &mut R) -> Result { let block_num = source.read_u32()?; - let node_index = NodeIndex::read_from(source)?; + let node_index_in_block = source.read_u32()?; - Ok(Self { block_num, node_index }) + Ok(Self { block_num, node_index_in_block }) } } impl Serializable for NoteInclusionProof { fn write_into(&self, target: &mut W) { - self.origin.write_into(target); - self.sub_hash.write_into(target); - self.note_root.write_into(target); + self.location.write_into(target); self.note_path.write_into(target); } } impl Deserializable for NoteInclusionProof { fn read_from(source: &mut R) -> Result { - let origin = NoteOrigin::read_from(source)?; - let sub_hash = Digest::read_from(source)?; - let note_root = Digest::read_from(source)?; + let location = NoteLocation::read_from(source)?; let note_path = MerklePath::read_from(source)?; - Ok(Self { origin, sub_hash, note_root, note_path }) + Ok(Self { location, note_path }) } } diff --git a/objects/src/notes/mod.rs b/objects/src/notes/mod.rs index e7dd6aca6..0e312fafe 100644 --- a/objects/src/notes/mod.rs +++ b/objects/src/notes/mod.rs @@ -41,8 +41,8 @@ pub use note_type::NoteType; mod nullifier; pub use nullifier::Nullifier; -mod origin; -pub use origin::{NoteInclusionProof, NoteOrigin}; +mod location; +pub use location::{NoteInclusionProof, NoteLocation}; mod partial; pub use partial::PartialNote; diff --git a/objects/src/testing/block.rs b/objects/src/testing/block.rs index d82de7c21..f078cdb2b 100644 --- a/objects/src/testing/block.rs +++ b/objects/src/testing/block.rs @@ -200,9 +200,9 @@ impl MockChain { for note in notes { let input_note = self.available_notes.get(note).unwrap().clone(); block_headers_map.insert( - input_note.origin().unwrap().block_num, + input_note.location().unwrap().block_num(), self.blocks - .get(input_note.origin().unwrap().block_num as usize) + .get(input_note.location().unwrap().block_num() as usize) .unwrap() .header(), ); @@ -291,8 +291,6 @@ impl MockChain { let note_path = notes_tree.get_note_path(block_note_index).unwrap(); let note_inclusion_proof = NoteInclusionProof::new( block.header().block_num(), - block.header().sub_hash(), - block.header().note_root(), block_note_index.to_absolute_index(), note_path, ) diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index b50e5e4ae..8378efb93 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -4,7 +4,7 @@ use core::fmt::Debug; use super::{BlockHeader, ChainMmr, Digest, Felt, Hasher, Word}; use crate::{ accounts::{Account, AccountId}, - notes::{Note, NoteId, NoteInclusionProof, NoteOrigin, Nullifier}, + notes::{Note, NoteId, NoteInclusionProof, NoteLocation, Nullifier}, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, TransactionInputError, MAX_INPUT_NOTES_PER_TX, }; @@ -60,7 +60,7 @@ impl TransactionInputs { // check the authentication paths of the input notes. for note in input_notes.iter() { if let InputNote::Authenticated { note, proof } = note { - let note_block_num = proof.origin().block_num; + let note_block_num = proof.location().block_num(); let block_header = if note_block_num == block_num { &block_header @@ -369,15 +369,15 @@ impl InputNote { } } - /// Returns a reference to the origin of the note. - pub fn origin(&self) -> Option<&NoteOrigin> { - self.proof().map(|proof| proof.origin()) + /// Returns a reference to the location of the note. + pub fn location(&self) -> Option<&NoteLocation> { + self.proof().map(|proof| proof.location()) } } /// Returns true if this note belongs to the note tree of the specified block. fn is_in_block(note: &Note, proof: &NoteInclusionProof, block_header: &BlockHeader) -> bool { - let note_index = proof.origin().node_index.value(); + let note_index = proof.location().node_index_in_block(); let note_hash = note.hash(); proof.note_path().verify(note_index, note_hash, &block_header.note_root()) }