miden_protocol/block/
note_tree.rs1use alloc::string::ToString;
2
3use miden_crypto::merkle::SparseMerklePath;
4
5use crate::batch::BatchNoteTree;
6use crate::crypto::merkle::MerkleError;
7use crate::crypto::merkle::smt::{LeafIndex, SimpleSmt};
8use crate::note::{NoteId, NoteMetadata, compute_note_commitment};
9use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
10use crate::{
11 BLOCK_NOTE_TREE_DEPTH,
12 MAX_BATCHES_PER_BLOCK,
13 MAX_OUTPUT_NOTES_PER_BATCH,
14 MAX_OUTPUT_NOTES_PER_BLOCK,
15 Word,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct BlockNoteTree(SimpleSmt<BLOCK_NOTE_TREE_DEPTH>);
25
26impl BlockNoteTree {
27 pub fn with_entries<'metadata>(
40 entries: impl IntoIterator<Item = (BlockNoteIndex, NoteId, &'metadata NoteMetadata)>,
41 ) -> Result<Self, MerkleError> {
42 let leaves = entries.into_iter().map(|(index, note_id, metadata)| {
43 (index.leaf_index_value() as u64, compute_note_commitment(note_id, metadata))
44 });
45
46 SimpleSmt::with_leaves(leaves).map(Self)
47 }
48
49 pub fn empty() -> Self {
51 Self(SimpleSmt::new().expect("depth should be 16 and thus > 0 and <= 64"))
52 }
53
54 pub fn insert_batch_note_subtree(
62 &mut self,
63 batch_idx: u64,
64 batch_note_tree: BatchNoteTree,
65 ) -> Result<(), MerkleError> {
66 self.0.set_subtree(batch_idx, batch_note_tree.into_smt()).map(|_| ())
70 }
71
72 pub fn root(&self) -> Word {
74 self.0.root()
75 }
76
77 pub fn open(&self, index: BlockNoteIndex) -> SparseMerklePath {
79 self.0.open(&index.leaf_index()).path
81 }
82
83 pub fn num_notes(&self) -> usize {
85 self.0.num_leaves()
86 }
87
88 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct BlockNoteIndex {
106 batch_idx: usize,
107 note_idx_in_batch: usize,
108}
109
110impl BlockNoteIndex {
111 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 pub fn batch_idx(&self) -> usize {
127 self.batch_idx
128 }
129
130 pub fn note_idx_in_batch(&self) -> usize {
132 self.note_idx_in_batch
133 }
134
135 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 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
157impl 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::merkle::smt::SimpleSmt;
181 use miden_crypto::utils::{Deserializable, Serializable};
182
183 use super::BlockNoteTree;
184 use crate::Word;
185
186 #[test]
187 fn test_serialization() {
188 let data = core::iter::repeat(())
189 .enumerate()
190 .map(|(idx, ())| (idx as u64, Word::from([1, 0, 1, idx as u32])))
191 .take(100);
192 let initial_tree = BlockNoteTree(SimpleSmt::with_leaves(data).unwrap());
193
194 let serialized = initial_tree.to_bytes();
195 let deserialized_tree = BlockNoteTree::read_from_bytes(&serialized).unwrap();
196
197 assert_eq!(deserialized_tree, initial_tree);
198 }
199}