miden_crypto/merkle/smt/partial/serialization/mod.rs
1//! This module contains a system for producing compact serialized representations of the
2//! `PartialSmt` data structure, intended to reduce data sent over the wire through de-duplication.
3
4pub mod property_tests;
5mod tests;
6
7use alloc::{string::ToString, vec::Vec};
8use core::mem::size_of;
9
10use miden_field::{Felt, FeltFromIntError, Word};
11use miden_serde_utils::{
12 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
13};
14
15use crate::{
16 Map,
17 merkle::{
18 EmptySubtreeRoots,
19 smt::{SMT_DEPTH, SmtLeaf},
20 },
21};
22
23// UNIQUE NODES
24// ================================================================================================
25
26/// A representation of a partial SMT that contains only the unique nodes in the tree, designed for
27/// better efficiency when sending data across the wire.
28///
29/// It _explicitly_ does not need to contain a fully-realized SMT, and instead may contain some
30/// subset of a full tree. It contains the minimal set of data necessary to reconstruct its input.
31///
32/// # Versioning
33///
34/// Note that this structure is explicitly **not intended to be versioned**. This structure should
35/// be used as part of a broader serialization solution that does include this if necessary.
36///
37/// # Serialization
38///
39/// The serialization and deserialization process does not validate that node or leaf indices are
40/// valid for their level. This is the responsibility of the client of this type.
41#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct UniqueNodes {
43 /// The expected root of the tree after reconstruction.
44 ///
45 /// This primarily exists as a sanity check, taking little extra space but ensuring that we can
46 /// detect more possible cases of corruption.
47 pub root: Word,
48
49 /// The nodes that make up the tree itself.
50 ///
51 /// It maps the node depth to a vector containing all the nodes at that depth, ensuring that no
52 /// data that can be reasonably reconstructed is stored.
53 pub nodes: Map<u8, Vec<(u64, NodeValue)>>,
54
55 /// The leaves of the tree.
56 ///
57 /// It only stores the populated leaves, keyed on their index.
58 pub leaves: Vec<(u64, SmtLeaf)>,
59
60 /// The leaves for which we only have the hash value, and not the actual leaf value.
61 ///
62 /// We keep these separately to the `leaves` as storing them this way is more compact.
63 pub value_only_leaves: Vec<(u64, Word)>,
64}
65
66impl UniqueNodes {
67 /// Creates an empty `UniqueNodes` with no nodes or leaves in it.
68 pub fn empty() -> Self {
69 Self {
70 root: *EmptySubtreeRoots::entry(SMT_DEPTH, 0),
71 nodes: Map::default(),
72 leaves: Vec::default(),
73 value_only_leaves: Vec::default(),
74 }
75 }
76}
77
78impl Default for UniqueNodes {
79 fn default() -> Self {
80 Self::empty()
81 }
82}
83
84impl Serializable for UniqueNodes {
85 fn write_into<W: ByteWriter>(&self, target: &mut W) {
86 // First we write the expected root into the buffer.
87 self.root.write_into(target);
88
89 // We write the length as u64 to ensure portability.
90 let node_count = self.nodes.len() as u64;
91 target.write(node_count);
92
93 // We then write each of the pairs of (u8, Vec<...>) independently.
94 for (depth, nodes) in self.nodes.iter() {
95 target.write(depth);
96 let node_count = nodes.len() as u64;
97 target.write(node_count);
98 target.write_many(nodes.iter());
99 }
100
101 // The leaves themselves come next.
102 let leaf_count = self.leaves.len() as u64;
103 target.write(leaf_count);
104 target.write_many(self.leaves.iter());
105
106 // And the value-only leaves bring up the rear.
107 let value_only_leaf_count = self.value_only_leaves.len() as u64;
108 target.write(value_only_leaf_count);
109 target.write_many(self.value_only_leaves.iter());
110 }
111}
112
113impl Deserializable for UniqueNodes {
114 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
115 // The first item is the 32 bytes containing the expected root of the tree after
116 // reconstruction.
117 let root = Word::read_from(source)?;
118
119 // We first have to read the count of levels.
120 let level_count = source.read_u64()?;
121 let mut nodes = Map::new();
122
123 // Next we have that many levels to read, but each is of a variable size.
124 for _ in 0..level_count {
125 let depth = source.read_u8()?;
126 let node_count = source.read_u64()?;
127 let level_nodes = source
128 .read_many_iter(node_count.try_into().map_err(|_| {
129 DeserializationError::InvalidValue(format!("Node count {node_count} overflow"))
130 })?)?
131 .collect::<Result<Vec<_>, _>>()?;
132 nodes.insert(depth, level_nodes);
133 }
134
135 // Next we need the number of leaves.
136 let leaf_count = source.read_u64()?;
137 let mut leaves = Vec::new();
138
139 // And then we have to read that many leaves.
140 for _ in 0..leaf_count {
141 leaves.push(source.read()?);
142 }
143
144 // Finally we read the number of value-only leaves...
145 let value_only_leaf_count = source.read_u64()?;
146 let mut value_only_leaves = Vec::new();
147
148 // ... and read that many.
149 for _ in 0..value_only_leaf_count {
150 value_only_leaves.push(source.read()?);
151 }
152
153 Ok(Self { root, nodes, leaves, value_only_leaves })
154 }
155}
156
157// NODE VALUE
158// ================================================================================================
159
160/// The value of a node in the serialized representation.
161///
162/// # Serialization
163///
164/// This enum can be in one of two cases: empty, or containing a Word. The naïve serialization would
165/// use a flag to indicate the variant, costing at least a byte to avoid the need for potentially
166/// expensive unaligned accesses.
167///
168/// [`Word`], however, consists of four `Felt`s, each of which occupies the Goldilocks field. This
169/// provides a niche in each of those `Felt`s that allows us to not require the extra byte when
170/// serializing a true value. As we assume that real values are more common than empty subtree roots
171/// by their very nature, making an empty root take 8 bytes instead of 1 is a smaller cost to pay
172/// than an extra byte for each populated node.
173///
174/// To that end, we encode the type as follows:
175///
176/// - For `Self::EmptySubtreeRoot` we encode the LE bytes for [`u64::MAX`], which exceeds the field
177/// order and hence serves as a sentinel value using the niche.
178/// - For `Self::Present` we simply encode the word as its four component felts. As none of the
179/// felts can take a value exceeding [`Felt::ORDER`], we can immediately disambiguate between this
180/// case and the one above.
181#[derive(Clone, Debug, Eq, PartialEq)]
182pub enum NodeValue {
183 /// The node is the head of an empty subtree at the depth given by the outer map in
184 /// [`UniqueNodes`].
185 EmptySubtreeRoot,
186
187 /// The node's value is the provided hash.
188 Present(Word),
189}
190
191impl Serializable for NodeValue {
192 fn write_into<W: ByteWriter>(&self, target: &mut W) {
193 match self {
194 NodeValue::EmptySubtreeRoot => target.write_u64(u64::MAX),
195 NodeValue::Present(w) => w.write_into(target),
196 }
197 }
198}
199
200impl Deserializable for NodeValue {
201 fn min_serialized_size() -> usize {
202 size_of::<u64>()
203 }
204
205 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
206 let first_value = source.read_u64()?;
207
208 let to_e = |e: FeltFromIntError| DeserializationError::InvalidValue(e.to_string());
209
210 if first_value == u64::MAX {
211 Ok(Self::EmptySubtreeRoot)
212 } else {
213 // We start by reading the rest of the bytes here to make sure that we have enough data
214 // before actually deserializing.
215 let remaining_values: [u64; Word::NUM_ELEMENTS - 1] = source.read()?;
216
217 let felts = [
218 Felt::new(first_value).map_err(to_e)?,
219 Felt::new(remaining_values[0]).map_err(to_e)?,
220 Felt::new(remaining_values[1]).map_err(to_e)?,
221 Felt::new(remaining_values[2]).map_err(to_e)?,
222 ];
223
224 Ok(Self::Present(Word::new(felts)))
225 }
226 }
227}