Skip to main content

miden_client/rpc/domain/
smt.rs

1use alloc::collections::BTreeSet;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4
5use miden_protocol::Word;
6use miden_protocol::crypto::merkle::NodeIndex;
7use miden_protocol::crypto::merkle::smt::{
8    LeafIndex,
9    NodeValue,
10    PartialSmt,
11    SMT_DEPTH,
12    SmtLeaf,
13    SmtProof,
14    UniqueNodes,
15};
16
17use crate::rpc::domain::MissingFieldHelper;
18use crate::rpc::errors::RpcConversionError;
19use crate::rpc::generated as proto;
20
21// SMT LEAF ENTRY
22// ================================================================================================
23
24impl From<&(Word, Word)> for proto::primitives::SmtLeafEntry {
25    fn from(value: &(Word, Word)) -> Self {
26        proto::primitives::SmtLeafEntry {
27            key: Some(value.0.into()),
28            value: Some(value.1.into()),
29        }
30    }
31}
32
33impl TryFrom<&proto::primitives::SmtLeafEntry> for (Word, Word) {
34    type Error = RpcConversionError;
35
36    fn try_from(value: &proto::primitives::SmtLeafEntry) -> Result<Self, Self::Error> {
37        let key = match value.key {
38            Some(key) => key.try_into()?,
39            None => return Err(proto::primitives::SmtLeafEntry::missing_field(stringify!(key))),
40        };
41
42        let value: Word = match value.value {
43            Some(value) => value.try_into()?,
44            None => return Err(proto::primitives::SmtLeafEntry::missing_field(stringify!(value))),
45        };
46
47        Ok((key, value))
48    }
49}
50
51// SMT LEAF
52// ================================================================================================
53
54impl From<SmtLeaf> for proto::primitives::SmtLeaf {
55    fn from(value: SmtLeaf) -> Self {
56        (&value).into()
57    }
58}
59
60impl From<&SmtLeaf> for proto::primitives::SmtLeaf {
61    fn from(value: &SmtLeaf) -> Self {
62        match value {
63            SmtLeaf::Empty(index) => proto::primitives::SmtLeaf {
64                leaf: Some(proto::primitives::smt_leaf::Leaf::EmptyLeafIndex(index.position())),
65            },
66            SmtLeaf::Single(entry) => proto::primitives::SmtLeaf {
67                leaf: Some(proto::primitives::smt_leaf::Leaf::Single(entry.into())),
68            },
69            SmtLeaf::Multiple(entries) => proto::primitives::SmtLeaf {
70                leaf: Some(proto::primitives::smt_leaf::Leaf::Multiple(
71                    proto::primitives::SmtLeafEntryList {
72                        entries: entries.iter().map(Into::into).collect(),
73                    },
74                )),
75            },
76        }
77    }
78}
79
80impl TryFrom<&proto::primitives::SmtLeaf> for SmtLeaf {
81    type Error = RpcConversionError;
82
83    fn try_from(value: &proto::primitives::SmtLeaf) -> Result<Self, Self::Error> {
84        match &value.leaf {
85            Some(proto::primitives::smt_leaf::Leaf::EmptyLeafIndex(index)) => Ok(SmtLeaf::Empty(
86                LeafIndex::<SMT_DEPTH>::new(*index)
87                    .map_err(|err| RpcConversionError::InvalidField(err.to_string()))?,
88            )),
89            Some(proto::primitives::smt_leaf::Leaf::Single(entry)) => {
90                Ok(SmtLeaf::Single(entry.try_into()?))
91            },
92            Some(proto::primitives::smt_leaf::Leaf::Multiple(entries)) => {
93                let entries =
94                    entries.entries.iter().map(TryInto::try_into).collect::<Result<_, _>>()?;
95                Ok(SmtLeaf::Multiple(entries))
96            },
97            None => Err(proto::primitives::SmtLeaf::missing_field(stringify!(leaf))),
98        }
99    }
100}
101
102// SMT PROOF
103// ================================================================================================
104
105impl From<SmtProof> for proto::primitives::SmtOpening {
106    fn from(value: SmtProof) -> Self {
107        let (path, leaf) = value.into_parts();
108
109        proto::primitives::SmtOpening {
110            leaf: Some(leaf.into()),
111            path: Some(path.into()),
112        }
113    }
114}
115
116// PARTIAL SMT
117// ================================================================================================
118
119impl TryFrom<proto::primitives::PartialSmt> for UniqueNodes {
120    type Error = RpcConversionError;
121
122    /// Decodes the compact partial SMT representation.
123    ///
124    /// The structural invariants that [`PartialSmt::from_unique_nodes`] relies on are checked here
125    /// rather than left to it, so a malformed response is reported as a specific invalid field
126    /// instead of an opaque reconstruction failure.
127    fn try_from(value: proto::primitives::PartialSmt) -> Result<Self, Self::Error> {
128        use proto::primitives::partial_smt_node::Value;
129
130        let proto::primitives::PartialSmt {
131            root,
132            node_levels,
133            leaves,
134            value_only_leaves,
135        } = value;
136
137        let root: Word = root
138            .ok_or(proto::primitives::PartialSmt::missing_field(stringify!(root)))?
139            .try_into()?;
140
141        let mut seen_depths = BTreeSet::new();
142        let mut decoded_levels = Vec::with_capacity(node_levels.len());
143        for level in node_levels {
144            let depth = u8::try_from(level.depth)?;
145            // Depth 0 is the root, which is carried separately, and `SMT_DEPTH` is the leaf level.
146            // Only the strictly intermediate depths are boundary nodes.
147            if depth == 0 || depth >= SMT_DEPTH {
148                return Err(RpcConversionError::InvalidField(format!(
149                    "partial SMT node depth {depth} must be in the range 1..{SMT_DEPTH}"
150                )));
151            }
152            if !seen_depths.insert(depth) {
153                return Err(RpcConversionError::InvalidField(format!(
154                    "partial SMT contains duplicate node depth {depth}"
155                )));
156            }
157
158            let mut seen_indices = BTreeSet::new();
159            let mut decoded_nodes = Vec::with_capacity(level.nodes.len());
160            for node in level.nodes {
161                NodeIndex::new(depth, node.index)?;
162                if !seen_indices.insert(node.index) {
163                    return Err(RpcConversionError::InvalidField(format!(
164                        "partial SMT contains duplicate node index {} at depth {depth}",
165                        node.index
166                    )));
167                }
168
169                let node_value = match node
170                    .value
171                    .ok_or(proto::primitives::PartialSmtNode::missing_field(stringify!(value)))?
172                {
173                    Value::Digest(digest) => NodeValue::Present(digest.try_into()?),
174                    Value::EmptySubtreeRoot(true) => NodeValue::EmptySubtreeRoot,
175                    Value::EmptySubtreeRoot(false) => {
176                        return Err(RpcConversionError::InvalidField(
177                            "partial SMT empty_subtree_root marker must be true".into(),
178                        ));
179                    },
180                };
181                decoded_nodes.push((node.index, node_value));
182            }
183            decoded_levels.push((depth, decoded_nodes));
184        }
185
186        let mut seen_leaf_indices = BTreeSet::new();
187        let mut decoded_leaves = Vec::with_capacity(leaves.len());
188        for indexed_leaf in leaves {
189            if !seen_leaf_indices.insert(indexed_leaf.index) {
190                return Err(RpcConversionError::InvalidField(format!(
191                    "partial SMT contains duplicate leaf index {}",
192                    indexed_leaf.index
193                )));
194            }
195            let leaf: SmtLeaf = indexed_leaf
196                .leaf
197                .as_ref()
198                .ok_or(proto::primitives::IndexedSmtLeaf::missing_field(stringify!(leaf)))?
199                .try_into()?;
200            decoded_leaves.push((indexed_leaf.index, leaf));
201        }
202
203        let mut seen_value_only_indices = BTreeSet::new();
204        let mut decoded_value_only_leaves = Vec::with_capacity(value_only_leaves.len());
205        for indexed_digest in value_only_leaves {
206            if !seen_value_only_indices.insert(indexed_digest.index) {
207                return Err(RpcConversionError::InvalidField(format!(
208                    "partial SMT contains duplicate value-only leaf index {}",
209                    indexed_digest.index
210                )));
211            }
212            if seen_leaf_indices.contains(&indexed_digest.index) {
213                return Err(RpcConversionError::InvalidField(format!(
214                    "partial SMT leaf index {} has both a leaf and a value-only leaf",
215                    indexed_digest.index
216                )));
217            }
218            let digest: Word = indexed_digest
219                .value
220                .ok_or(proto::primitives::IndexedDigest::missing_field(stringify!(value)))?
221                .try_into()?;
222            decoded_value_only_leaves.push((indexed_digest.index, digest));
223        }
224
225        Ok(UniqueNodes {
226            root,
227            nodes: decoded_levels.into_iter().collect(),
228            leaves: decoded_leaves,
229            value_only_leaves: decoded_value_only_leaves,
230        })
231    }
232}
233
234impl TryFrom<proto::primitives::PartialSmt> for PartialSmt {
235    type Error = RpcConversionError;
236
237    fn try_from(value: proto::primitives::PartialSmt) -> Result<Self, Self::Error> {
238        let unique_nodes = UniqueNodes::try_from(value)?;
239        Ok(PartialSmt::from_unique_nodes(unique_nodes)?)
240    }
241}