nomt_core/proof/
path_proof.rs

1//! Proving and verifying inclusion, non-inclusion, and updates to the trie.
2
3use crate::hasher::NodeHasher;
4use crate::trie::{self, InternalData, KeyPath, LeafData, Node, NodeKind, TERMINATOR};
5use crate::trie_pos::TriePosition;
6
7use bitvec::prelude::*;
8use core::fmt;
9
10#[cfg(not(feature = "std"))]
11use alloc::vec::Vec;
12
13/// Wrapper for a terminal node, it will store the LeafData if it is a leaf node,
14/// and just the KeyPath to that terminal if it is a terminator node
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(
17    feature = "borsh",
18    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
19)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub enum PathProofTerminal {
22    Leaf(LeafData),
23    Terminator(TriePosition),
24}
25
26impl PathProofTerminal {
27    /// Return the bit-path to the Terminal Node
28    pub fn path(&self) -> &BitSlice<u8, Msb0> {
29        match self {
30            Self::Leaf(leaf_data) => &leaf_data.key_path.view_bits(),
31            Self::Terminator(key_path) => key_path.path(),
32        }
33    }
34
35    pub fn node<H: NodeHasher>(&self) -> Node {
36        match self {
37            Self::Leaf(leaf_data) => H::hash_leaf(leaf_data),
38            Self::Terminator(_key_path) => TERMINATOR,
39        }
40    }
41
42    /// Transform this into an optional LeafData.
43    pub fn as_leaf_option(&self) -> Option<LeafData> {
44        match self {
45            Self::Leaf(leaf_data) => Some(leaf_data.clone()),
46            Self::Terminator(_) => None,
47        }
48    }
49}
50
51/// A proof of some particular path through the trie.
52#[derive(Debug, Clone)]
53#[cfg_attr(
54    feature = "borsh",
55    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
56)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58pub struct PathProof {
59    /// The terminal node encountered when looking up a key. This is always either a terminator or
60    /// leaf.
61    pub terminal: PathProofTerminal,
62    /// Sibling nodes encountered during lookup, in descending order by depth.
63    pub siblings: Vec<Node>,
64}
65
66impl PathProof {
67    /// Verify this path proof.
68    /// This ONLY verifies the path proof. It does not verify the key path or value of the terminal
69    /// node.
70    ///
71    /// You MUST use this in conjunction with `confirm_value` or `confirm_nonexistence`.
72    ///
73    /// Provide the root node and a key path. The key path can be any key that results in the
74    /// lookup of the terminal node and must be at least as long as the siblings vector.
75    pub fn verify<H: NodeHasher>(
76        &self,
77        key_path: &BitSlice<u8, Msb0>,
78        root: Node,
79    ) -> Result<VerifiedPathProof, PathProofVerificationError> {
80        if self.siblings.len() > core::cmp::min(key_path.len(), 256) {
81            return Err(PathProofVerificationError::TooManySiblings);
82        }
83        let relevant_path = &key_path[..self.siblings.len()];
84
85        let cur_node = self.terminal.node::<H>();
86
87        let new_root = hash_path::<H>(cur_node, relevant_path, self.siblings.iter().rev().cloned());
88
89        if new_root == root {
90            Ok(VerifiedPathProof {
91                key_path: relevant_path.into(),
92                terminal: match &self.terminal {
93                    PathProofTerminal::Leaf(leaf_data) => Some(leaf_data.clone()),
94                    PathProofTerminal::Terminator(_) => None,
95                },
96                siblings: self.siblings.clone(),
97                root,
98            })
99        } else {
100            Err(PathProofVerificationError::RootMismatch)
101        }
102    }
103}
104
105/// Given a node, a path, and a set of siblings, hash up to the root and return it.
106/// This only consumes the last `siblings.len()` bits of the path, or the whole path.
107/// Siblings are in ascending order from the last bit of `path`.
108pub fn hash_path<H: NodeHasher>(
109    mut node: Node,
110    path: &BitSlice<u8, Msb0>,
111    siblings: impl IntoIterator<Item = Node>,
112) -> Node {
113    for (bit, sibling) in path.iter().by_vals().rev().zip(siblings) {
114        let (left, right) = if bit {
115            (sibling, node)
116        } else {
117            (node, sibling)
118        };
119
120        let next = InternalData {
121            left: left.clone(),
122            right: right.clone(),
123        };
124        node = H::hash_internal(&next);
125    }
126
127    node
128}
129
130/// An error type indicating that a key is out of scope of a path proof.
131#[derive(Debug, Clone, Copy)]
132pub struct KeyOutOfScope;
133
134/// Errors in path proof verification.
135#[derive(Debug, Clone, Copy)]
136pub enum PathProofVerificationError {
137    /// Amount of provided siblings is impossible for the expected trie depth.
138    TooManySiblings,
139    /// Root hash mismatched at the end of the verification.
140    RootMismatch,
141}
142
143/// A verified path through the trie.
144///
145/// Each verified path can be used to check up to two kinds of statements:
146///   1. That a single key has a specific value.
147///   2. That a single or multiple keys do not have a value.
148///
149/// Statement (1) is true when the path leads to a leaf node and the leaf has the provided key and
150/// value.
151///
152/// Statement (2) is true for any key which begins with the proven path, where the terminal node is
153/// either not a leaf or contains a value for a different key.
154#[derive(Clone)]
155#[must_use = "VerifiedPathProof only checks the trie path, not whether it actually looks up to your expected value."]
156pub struct VerifiedPathProof {
157    key_path: BitVec<u8, Msb0>,
158    terminal: Option<LeafData>,
159    siblings: Vec<Node>,
160    root: Node,
161}
162
163impl VerifiedPathProof {
164    /// Get the terminal node. `None` signifies that this path concludes with a [`TERMINATOR`].
165    pub fn terminal(&self) -> Option<&LeafData> {
166        self.terminal.as_ref()
167    }
168
169    /// Get the proven path.
170    pub fn path(&self) -> &BitSlice<u8, Msb0> {
171        &self.key_path[..]
172    }
173
174    /// Get the proven root.
175    pub fn root(&self) -> Node {
176        self.root
177    }
178
179    /// Check whether this path resolves to the given leaf.
180    ///
181    /// A return value of `Ok(true)` confirms that the key indeed has this value in the trie.
182    /// `Ok(false)` confirms that this key has a different value or does not exist.
183    ///
184    /// Fails if the key is out of the scope of this path.
185    pub fn confirm_value(&self, expected_leaf: &LeafData) -> Result<bool, KeyOutOfScope> {
186        self.in_scope(&expected_leaf.key_path)
187            .map(|_| self.terminal() == Some(expected_leaf))
188    }
189
190    /// Check whether this proves that a key has no value in the trie.
191    ///
192    /// A return value of `Ok(true)` confirms that the key indeed has no value in the trie.
193    /// A return value of `Ok(false)` means that the key definitely exists within the trie.
194    ///
195    /// Fails if the key is out of the scope of this path.
196    pub fn confirm_nonexistence(&self, key_path: &KeyPath) -> Result<bool, KeyOutOfScope> {
197        self.in_scope(key_path).map(|_| {
198            self.terminal()
199                .as_ref()
200                .map_or(true, |d| &d.key_path != key_path)
201        })
202    }
203
204    fn in_scope(&self, key_path: &KeyPath) -> Result<(), KeyOutOfScope> {
205        let this_path = self.path();
206        let other_path = &key_path.view_bits::<Msb0>()[..self.key_path.len()];
207        if this_path == other_path {
208            Ok(())
209        } else {
210            Err(KeyOutOfScope)
211        }
212    }
213}
214
215impl fmt::Debug for VerifiedPathProof {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        f.debug_struct("VerifiedPathProof")
218            .field("path", &self.path())
219            .field("terminal", &self.terminal())
220            .field("root", &self.root())
221            .finish()
222    }
223}
224
225/// Errors that can occur when verifying an update proof.
226#[derive(Debug, Clone, Copy)]
227pub enum VerifyUpdateError {
228    /// The paths through the trie were provided out-of-order.
229    PathsOutOfOrder,
230    /// The operations on the trie were provided out-of-order.
231    OpsOutOfOrder,
232    /// An operation was out of scope for the path it was provided with.
233    OpOutOfScope,
234    /// A path was provided without any operations.
235    PathWithoutOps,
236    /// Paths were verified against different state-roots.
237    RootMismatch,
238}
239
240/// An update to the node at some path.
241pub struct PathUpdate {
242    /// The proven path.
243    pub inner: VerifiedPathProof,
244    /// Update operations to perform on keys that all start with the path.
245    pub ops: Vec<(KeyPath, Option<trie::ValueHash>)>,
246}
247
248impl fmt::Debug for PathUpdate {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        f.debug_struct("PathUpdate")
251            .field("inner", &self.inner)
252            .field("ops", &self.ops)
253            .finish()
254    }
255}
256
257/// Verify an update operation against the root node. This follows a similar algorithm to the
258/// multi-item update, but without altering any backing storage.
259///
260/// Paths should be ascending, ops should be ascending, all ops should look up to one of the
261/// paths in `paths`.
262///
263/// All paths should share the same root.
264///
265/// Returns the root of the trie obtained after application of the given updates in the `paths`
266/// vector. In case the `paths` is empty, `prev_root` is returned.
267pub fn verify_update<H: NodeHasher>(
268    prev_root: Node,
269    paths: &[PathUpdate],
270) -> Result<Node, VerifyUpdateError> {
271    if paths.is_empty() {
272        return Ok(prev_root);
273    }
274
275    // Verify important properties about the paths.
276    for (i, path) in paths.iter().enumerate() {
277        // All paths must stem from the same starting root.
278        if path.inner.root() != prev_root {
279            return Err(VerifyUpdateError::RootMismatch);
280        }
281
282        // All paths must be ascending.
283        if i != 0 && paths[i - 1].inner.path() >= path.inner.path() {
284            return Err(VerifyUpdateError::PathsOutOfOrder);
285        }
286
287        // Path's operations must be non-empty.
288        if path.ops.is_empty() {
289            return Err(VerifyUpdateError::PathWithoutOps);
290        }
291
292        for (j, (key, _value)) in path.ops.iter().enumerate() {
293            if j != 0 && &path.ops[j - 1].0 >= key {
294                return Err(VerifyUpdateError::OpsOutOfOrder);
295            }
296
297            if !key.view_bits::<Msb0>().starts_with(path.inner.path()) {
298                return Err(VerifyUpdateError::OpOutOfScope);
299            }
300        }
301    }
302
303    // left frontier
304    let mut pending_siblings: Vec<(Node, usize)> = Vec::new();
305    for (i, path) in paths.iter().enumerate() {
306        let leaf = path.inner.terminal().map(|x| x.clone());
307        let ops = &path.ops;
308        let skip = path.inner.path().len();
309
310        let up_layers = match paths.get(i + 1) {
311            None => skip, // go to root
312            Some(p) => {
313                let n = shared_bits(p.inner.path(), path.inner.path());
314                // n always < skip
315                // we want to end at layer n + 1
316                skip - (n + 1)
317            }
318        };
319
320        let ops = crate::update::leaf_ops_spliced(leaf, ops);
321        let sub_root = crate::update::build_trie::<H>(skip, ops, |_| {});
322
323        let mut cur_node = sub_root;
324        let mut cur_layer = skip;
325        let end_layer = skip - up_layers;
326        // iterate siblings up to the point of collision with next path, replacing with pending
327        // siblings, and compacting where possible.
328        // push (node, end_layer) to pending siblings when done.
329        for (bit, sibling) in path
330            .inner
331            .path()
332            .iter()
333            .by_vals()
334            .rev()
335            .take(up_layers)
336            .zip(path.inner.siblings.iter().rev())
337        {
338            let sibling = if pending_siblings.last().map_or(false, |p| p.1 == cur_layer) {
339                // unwrap: checked above
340                pending_siblings.pop().unwrap().0
341            } else {
342                *sibling
343            };
344
345            match (NodeKind::of::<H>(&cur_node), NodeKind::of::<H>(&sibling)) {
346                (NodeKind::Terminator, NodeKind::Terminator) => {}
347                (NodeKind::Leaf, NodeKind::Terminator) => {}
348                (NodeKind::Terminator, NodeKind::Leaf) => {
349                    // relocate sibling upwards.
350                    cur_node = sibling;
351                }
352                _ => {
353                    // otherwise, internal
354                    let node_data = if bit {
355                        trie::InternalData {
356                            left: sibling,
357                            right: cur_node,
358                        }
359                    } else {
360                        trie::InternalData {
361                            left: cur_node,
362                            right: sibling,
363                        }
364                    };
365                    cur_node = H::hash_internal(&node_data);
366                }
367            }
368            cur_layer -= 1;
369        }
370        pending_siblings.push((cur_node, end_layer));
371    }
372
373    // UNWRAP: If `paths` is not empty this can never be `None` since `pending_siblings` is
374    // unconditionally appended to.
375    Ok(pending_siblings.pop().map(|n| n.0).unwrap())
376}
377
378// TODO: dedup, this appears in `update` as well.
379pub fn shared_bits(a: &BitSlice<u8, Msb0>, b: &BitSlice<u8, Msb0>) -> usize {
380    a.iter().zip(b.iter()).take_while(|(a, b)| a == b).count()
381}