miden_objects/block/
note_tree.rs

1use alloc::string::ToString;
2
3use crate::{
4    BLOCK_NOTE_TREE_DEPTH, MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH,
5    MAX_OUTPUT_NOTES_PER_BLOCK,
6    batch::BatchNoteTree,
7    crypto::{
8        hash::rpo::RpoDigest,
9        merkle::{LeafIndex, MerkleError, MerklePath, SimpleSmt},
10    },
11    note::{NoteId, NoteMetadata, compute_note_commitment},
12    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
13};
14
15/// Wrapper over [SimpleSmt<BLOCK_NOTE_TREE_DEPTH>] for notes tree.
16///
17/// Each note is stored as two adjacent leaves: odd leaf for id, even leaf for metadata hash.
18/// ID's leaf index is calculated as [(batch_idx * MAX_NOTES_PER_BATCH + note_idx_in_batch) * 2].
19/// Metadata hash leaf is stored the next after id leaf: [id_index + 1].
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct BlockNoteTree(SimpleSmt<BLOCK_NOTE_TREE_DEPTH>);
22
23impl BlockNoteTree {
24    /// Returns a new [`BlockNoteTree`] instantiated with entries set as specified by the provided
25    /// entries.
26    ///
27    /// Entry format: (note_index, note_id, note_metadata).
28    ///
29    /// Value of each leaf is computed as: `hash(note_id || note_metadata)`.
30    /// All leaves omitted from the entries list are set to [crate::EMPTY_WORD].
31    ///
32    /// # Errors
33    /// Returns an error if:
34    /// - The number of entries exceeds the maximum notes tree capacity, that is 2^16.
35    /// - The provided entries contain multiple values for the same key.
36    pub fn with_entries(
37        entries: impl IntoIterator<Item = (BlockNoteIndex, NoteId, NoteMetadata)>,
38    ) -> Result<Self, MerkleError> {
39        let leaves = entries.into_iter().map(|(index, note_id, metadata)| {
40            (
41                index.leaf_index_value() as u64,
42                compute_note_commitment(note_id, &metadata).into(),
43            )
44        });
45
46        SimpleSmt::with_leaves(leaves).map(Self)
47    }
48
49    /// Returns a new, empty [`BlockNoteTree`].
50    pub fn empty() -> Self {
51        Self(SimpleSmt::new().expect("depth should be 16 and thus > 0 and <= 64"))
52    }
53
54    /// Inserts the given [`BatchNoteTree`] as a subtree into the block note tree at the specified
55    /// index.
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if:
60    /// - the given batch index is greater or equal to [`MAX_BATCHES_PER_BLOCK`]
61    pub fn insert_batch_note_subtree(
62        &mut self,
63        batch_idx: u64,
64        batch_note_tree: BatchNoteTree,
65    ) -> Result<(), MerkleError> {
66        // Note that the subtree depth > depth error cannot occur, as the batch note tree's depth is
67        // smaller than the block note tree's depth.
68        // This is guaranteed through the definition of MAX_BATCHES_PER_BLOCK.
69        self.0.set_subtree(batch_idx, batch_note_tree.into_smt()).map(|_| ())
70    }
71
72    /// Returns the root of the tree
73    pub fn root(&self) -> RpoDigest {
74        self.0.root()
75    }
76
77    /// Returns merkle path for the note with specified batch/note indexes.
78    pub fn get_note_path(&self, index: BlockNoteIndex) -> MerklePath {
79        // get the path to the leaf containing the note (path len = 16)
80        self.0.open(&index.leaf_index()).path
81    }
82
83    /// Returns the number of notes in this block note tree.
84    pub fn num_notes(&self) -> usize {
85        self.0.num_leaves()
86    }
87
88    /// Returns a boolean value indicating whether the block note tree is empty.
89    pub fn is_empty(&self) -> bool {
90        self.0.is_empty()
91    }
92}
93
94impl Default for BlockNoteTree {
95    fn default() -> Self {
96        Self(SimpleSmt::new().expect("Unreachable"))
97    }
98}
99
100// BLOCK NOTE INDEX
101// ================================================================================================
102
103/// Index of a block note.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct BlockNoteIndex {
106    batch_idx: usize,
107    note_idx_in_batch: usize,
108}
109
110impl BlockNoteIndex {
111    /// Creates a new [BlockNoteIndex].
112    ///
113    /// # Errors
114    ///
115    /// Returns `None` if the batch index is equal to or greater than [`MAX_BATCHES_PER_BLOCK`] or
116    /// if the note index is equal to or greater than [`MAX_OUTPUT_NOTES_PER_BATCH`].
117    pub fn new(batch_idx: usize, note_idx_in_batch: usize) -> Option<Self> {
118        if batch_idx >= MAX_BATCHES_PER_BLOCK || note_idx_in_batch >= MAX_OUTPUT_NOTES_PER_BATCH {
119            return None;
120        }
121
122        Some(Self { batch_idx, note_idx_in_batch })
123    }
124
125    /// Returns the batch index.
126    pub fn batch_idx(&self) -> usize {
127        self.batch_idx
128    }
129
130    /// Returns the note index in the batch.
131    pub fn note_idx_in_batch(&self) -> usize {
132        self.note_idx_in_batch
133    }
134
135    /// Returns the leaf index of the note in the note tree.
136    pub fn leaf_index(&self) -> LeafIndex<BLOCK_NOTE_TREE_DEPTH> {
137        LeafIndex::new(
138            (self.batch_idx() * MAX_OUTPUT_NOTES_PER_BATCH + self.note_idx_in_batch()) as u64,
139        )
140        .expect("Unreachable: Input values must be valid at this point")
141    }
142
143    /// Returns the leaf index value of the note in the note tree.
144    pub fn leaf_index_value(&self) -> u16 {
145        const _: () = assert!(
146            MAX_OUTPUT_NOTES_PER_BLOCK <= u16::MAX as usize + 1,
147            "Any note index is expected to fit in `u16`"
148        );
149
150        self.leaf_index()
151            .value()
152            .try_into()
153            .expect("Unreachable: Input values must be valid at this point")
154    }
155}
156
157// SERIALIZATION
158// ================================================================================================
159
160impl Serializable for BlockNoteTree {
161    fn write_into<W: ByteWriter>(&self, target: &mut W) {
162        target.write_u32(self.0.num_leaves() as u32);
163        target.write_many(self.0.leaves());
164    }
165}
166
167impl Deserializable for BlockNoteTree {
168    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
169        let count = source.read_u32()?;
170        let leaves = source.read_many(count as usize)?;
171
172        SimpleSmt::with_leaves(leaves)
173            .map(Self)
174            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use miden_crypto::{
181        Felt, ONE, ZERO,
182        merkle::SimpleSmt,
183        utils::{Deserializable, Serializable},
184    };
185
186    use super::BlockNoteTree;
187
188    #[test]
189    fn test_serialization() {
190        let data = core::iter::repeat(())
191            .enumerate()
192            .map(|(idx, ())| (idx as u64, [ONE, ZERO, ONE, Felt::new(idx as u64)]))
193            .take(100);
194        let initial_tree = BlockNoteTree(SimpleSmt::with_leaves(data).unwrap());
195
196        let serialized = initial_tree.to_bytes();
197        let deserialized_tree = BlockNoteTree::read_from_bytes(&serialized).unwrap();
198
199        assert_eq!(deserialized_tree, initial_tree);
200    }
201}