miden_objects/block/
note_tree.rs1use alloc::string::ToString;
2
3use miden_crypto::merkle::SparseMerklePath;
4
5use crate::batch::BatchNoteTree;
6use crate::crypto::merkle::{LeafIndex, MerkleError, SimpleSmt};
7use crate::note::{NoteId, NoteMetadata, compute_note_commitment};
8use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
9use crate::{
10 BLOCK_NOTE_TREE_DEPTH,
11 MAX_BATCHES_PER_BLOCK,
12 MAX_OUTPUT_NOTES_PER_BATCH,
13 MAX_OUTPUT_NOTES_PER_BLOCK,
14 Word,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct BlockNoteTree(SimpleSmt<BLOCK_NOTE_TREE_DEPTH>);
24
25impl BlockNoteTree {
26 pub fn with_entries(
39 entries: impl IntoIterator<Item = (BlockNoteIndex, NoteId, NoteMetadata)>,
40 ) -> Result<Self, MerkleError> {
41 let leaves = entries.into_iter().map(|(index, note_id, metadata)| {
42 (index.leaf_index_value() as u64, compute_note_commitment(note_id, &metadata))
43 });
44
45 SimpleSmt::with_leaves(leaves).map(Self)
46 }
47
48 pub fn empty() -> Self {
50 Self(SimpleSmt::new().expect("depth should be 16 and thus > 0 and <= 64"))
51 }
52
53 pub fn insert_batch_note_subtree(
61 &mut self,
62 batch_idx: u64,
63 batch_note_tree: BatchNoteTree,
64 ) -> Result<(), MerkleError> {
65 self.0.set_subtree(batch_idx, batch_note_tree.into_smt()).map(|_| ())
69 }
70
71 pub fn root(&self) -> Word {
73 self.0.root()
74 }
75
76 pub fn open(&self, index: BlockNoteIndex) -> SparseMerklePath {
78 self.0.open(&index.leaf_index()).path
80 }
81
82 pub fn num_notes(&self) -> usize {
84 self.0.num_leaves()
85 }
86
87 pub fn is_empty(&self) -> bool {
89 self.0.is_empty()
90 }
91}
92
93impl Default for BlockNoteTree {
94 fn default() -> Self {
95 Self(SimpleSmt::new().expect("Unreachable"))
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct BlockNoteIndex {
105 batch_idx: usize,
106 note_idx_in_batch: usize,
107}
108
109impl BlockNoteIndex {
110 pub fn new(batch_idx: usize, note_idx_in_batch: usize) -> Option<Self> {
117 if batch_idx >= MAX_BATCHES_PER_BLOCK || note_idx_in_batch >= MAX_OUTPUT_NOTES_PER_BATCH {
118 return None;
119 }
120
121 Some(Self { batch_idx, note_idx_in_batch })
122 }
123
124 pub fn batch_idx(&self) -> usize {
126 self.batch_idx
127 }
128
129 pub fn note_idx_in_batch(&self) -> usize {
131 self.note_idx_in_batch
132 }
133
134 pub fn leaf_index(&self) -> LeafIndex<BLOCK_NOTE_TREE_DEPTH> {
136 LeafIndex::new(
137 (self.batch_idx() * MAX_OUTPUT_NOTES_PER_BATCH + self.note_idx_in_batch()) as u64,
138 )
139 .expect("Unreachable: Input values must be valid at this point")
140 }
141
142 pub fn leaf_index_value(&self) -> u16 {
144 const _: () = assert!(
145 MAX_OUTPUT_NOTES_PER_BLOCK <= u16::MAX as usize + 1,
146 "Any note index is expected to fit in `u16`"
147 );
148
149 self.leaf_index()
150 .value()
151 .try_into()
152 .expect("Unreachable: Input values must be valid at this point")
153 }
154}
155
156impl Serializable for BlockNoteTree {
160 fn write_into<W: ByteWriter>(&self, target: &mut W) {
161 target.write_u32(self.0.num_leaves() as u32);
162 target.write_many(self.0.leaves());
163 }
164}
165
166impl Deserializable for BlockNoteTree {
167 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
168 let count = source.read_u32()?;
169 let leaves = source.read_many(count as usize)?;
170
171 SimpleSmt::with_leaves(leaves)
172 .map(Self)
173 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use miden_crypto::merkle::SimpleSmt;
180 use miden_crypto::utils::{Deserializable, Serializable};
181
182 use super::BlockNoteTree;
183 use crate::Word;
184
185 #[test]
186 fn test_serialization() {
187 let data = core::iter::repeat(())
188 .enumerate()
189 .map(|(idx, ())| (idx as u64, Word::from([1, 0, 1, idx as u32])))
190 .take(100);
191 let initial_tree = BlockNoteTree(SimpleSmt::with_leaves(data).unwrap());
192
193 let serialized = initial_tree.to_bytes();
194 let deserialized_tree = BlockNoteTree::read_from_bytes(&serialized).unwrap();
195
196 assert_eq!(deserialized_tree, initial_tree);
197 }
198}