alloy_trie/proof/
verify.rs

1//! Proof verification logic.
2
3use crate::{
4    nodes::{BranchNode, RlpNode, TrieNode, CHILD_INDEX_RANGE},
5    proof::ProofVerificationError,
6    EMPTY_ROOT_HASH,
7};
8use alloc::vec::Vec;
9use alloy_primitives::{Bytes, B256};
10use alloy_rlp::{Decodable, EMPTY_STRING_CODE};
11use core::ops::Deref;
12use nybbles::Nibbles;
13
14/// Verify the proof for given key value pair against the provided state root.
15///
16/// The expected node value can be either [Some] if it's expected to be present
17/// in the tree or [None] if this is an exclusion proof.
18pub fn verify_proof<'a, I>(
19    root: B256,
20    key: Nibbles,
21    expected_value: Option<Vec<u8>>,
22    proof: I,
23) -> Result<(), ProofVerificationError>
24where
25    I: IntoIterator<Item = &'a Bytes>,
26{
27    let mut proof = proof.into_iter().peekable();
28
29    // If the proof is empty or contains only an empty node, the expected value must be None.
30    if proof.peek().map_or(true, |node| node.as_ref() == [EMPTY_STRING_CODE]) {
31        return if root == EMPTY_ROOT_HASH {
32            if expected_value.is_none() {
33                Ok(())
34            } else {
35                Err(ProofVerificationError::ValueMismatch {
36                    path: key,
37                    got: None,
38                    expected: expected_value.map(Bytes::from),
39                })
40            }
41        } else {
42            Err(ProofVerificationError::RootMismatch { got: EMPTY_ROOT_HASH, expected: root })
43        };
44    }
45
46    let mut walked_path = Nibbles::with_capacity(key.len());
47    let mut last_decoded_node = Some(NodeDecodingResult::Node(RlpNode::word_rlp(&root)));
48    for node in proof {
49        // Check if the node that we just decoded (or root node, if we just started) matches
50        // the expected node from the proof.
51        if Some(RlpNode::from_rlp(node).as_slice()) != last_decoded_node.as_deref() {
52            let got = Some(Bytes::copy_from_slice(node));
53            let expected = last_decoded_node.as_deref().map(Bytes::copy_from_slice);
54            return Err(ProofVerificationError::ValueMismatch { path: walked_path, got, expected });
55        }
56
57        // Decode the next node from the proof.
58        last_decoded_node =
59            process_trie_node(TrieNode::decode(&mut &node[..])?, &mut walked_path, &key)?;
60    }
61
62    // Last decoded node should have the key that we are looking for.
63    last_decoded_node = last_decoded_node.filter(|_| walked_path == key);
64    if last_decoded_node.as_deref() == expected_value.as_deref() {
65        Ok(())
66    } else {
67        Err(ProofVerificationError::ValueMismatch {
68            path: key,
69            got: last_decoded_node.as_deref().map(Bytes::copy_from_slice),
70            expected: expected_value.map(Bytes::from),
71        })
72    }
73}
74
75/// The result of decoding a node from the proof.
76///
77/// - [`TrieNode::Branch`] is decoded into a [`NodeDecodingResult::Value`] if the node at the
78///   specified nibble was decoded into an in-place encoded [`TrieNode::Leaf`], or into a
79///   [`NodeDecodingResult::Node`] otherwise.
80/// - [`TrieNode::Extension`] is always decoded into a [`NodeDecodingResult::Node`].
81/// - [`TrieNode::Leaf`] is always decoded into a [`NodeDecodingResult::Value`].
82#[derive(Debug, PartialEq, Eq)]
83enum NodeDecodingResult {
84    Node(RlpNode),
85    Value(Vec<u8>),
86}
87
88impl Deref for NodeDecodingResult {
89    type Target = [u8];
90
91    fn deref(&self) -> &Self::Target {
92        match self {
93            Self::Node(node) => node.as_slice(),
94            Self::Value(value) => value,
95        }
96    }
97}
98
99#[inline]
100fn process_trie_node(
101    node: TrieNode,
102    walked_path: &mut Nibbles,
103    key: &Nibbles,
104) -> Result<Option<NodeDecodingResult>, ProofVerificationError> {
105    let node = match node {
106        TrieNode::Branch(branch) => process_branch(branch, walked_path, key)?,
107        TrieNode::Extension(extension) => {
108            walked_path.extend_from_slice(&extension.key);
109            if extension.child.is_hash() {
110                Some(NodeDecodingResult::Node(extension.child))
111            } else {
112                process_trie_node(TrieNode::decode(&mut &extension.child[..])?, walked_path, key)?
113            }
114        }
115        TrieNode::Leaf(leaf) => {
116            walked_path.extend_from_slice(&leaf.key);
117            Some(NodeDecodingResult::Value(leaf.value))
118        }
119        TrieNode::EmptyRoot => return Err(ProofVerificationError::UnexpectedEmptyRoot),
120    };
121    Ok(node)
122}
123
124#[inline]
125fn process_branch(
126    mut branch: BranchNode,
127    walked_path: &mut Nibbles,
128    key: &Nibbles,
129) -> Result<Option<NodeDecodingResult>, ProofVerificationError> {
130    if let Some(next) = key.get(walked_path.len()) {
131        let mut stack_ptr = branch.as_ref().first_child_index();
132        for index in CHILD_INDEX_RANGE {
133            if branch.state_mask.is_bit_set(index) {
134                if index == *next {
135                    walked_path.push(*next);
136
137                    let child = branch.stack.remove(stack_ptr);
138                    if child.len() == B256::len_bytes() + 1 {
139                        return Ok(Some(NodeDecodingResult::Node(child)));
140                    } else {
141                        // This node is encoded in-place.
142                        match TrieNode::decode(&mut &child[..])? {
143                            TrieNode::Branch(child_branch) => {
144                                // An in-place branch node can only have direct, also in-place
145                                // encoded, leaf children, as anything else overflows this branch
146                                // node, making it impossible to be encoded in-place in the first
147                                // place.
148                                return process_branch(child_branch, walked_path, key);
149                            }
150                            TrieNode::Extension(child_extension) => {
151                                walked_path.extend_from_slice(&child_extension.key);
152
153                                // If the extension node's child is a hash, the encoded extension
154                                // node itself wouldn't fit for encoding in-place. So this extension
155                                // node must have a child that is also encoded in-place.
156                                //
157                                // Since the child cannot be a leaf node (otherwise this node itself
158                                // would be a leaf node, not an extension node), the child must be a
159                                // branch node encoded in-place.
160                                match TrieNode::decode(&mut &child_extension.child[..])? {
161                                    TrieNode::Branch(extension_child_branch) => {
162                                        return process_branch(
163                                            extension_child_branch,
164                                            walked_path,
165                                            key,
166                                        );
167                                    }
168                                    node @ (TrieNode::EmptyRoot
169                                    | TrieNode::Extension(_)
170                                    | TrieNode::Leaf(_)) => {
171                                        unreachable!("unexpected extension node child: {node:?}")
172                                    }
173                                }
174                            }
175                            TrieNode::Leaf(child_leaf) => {
176                                walked_path.extend_from_slice(&child_leaf.key);
177                                return Ok(Some(NodeDecodingResult::Value(child_leaf.value)));
178                            }
179                            TrieNode::EmptyRoot => {
180                                return Err(ProofVerificationError::UnexpectedEmptyRoot)
181                            }
182                        }
183                    };
184                }
185                stack_ptr += 1;
186            }
187        }
188    }
189
190    Ok(None)
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use crate::{
197        nodes::{BranchNode, ExtensionNode, LeafNode},
198        proof::{ProofNodes, ProofRetainer},
199        triehash_trie_root, HashBuilder, TrieMask,
200    };
201    use alloy_primitives::hex;
202    use alloy_rlp::{Encodable, EMPTY_STRING_CODE};
203    use core::str::FromStr;
204
205    #[test]
206    fn empty_trie() {
207        let key = Nibbles::unpack(B256::repeat_byte(42));
208        let mut hash_builder = HashBuilder::default().with_proof_retainer(ProofRetainer::default());
209        let root = hash_builder.root();
210        let proof = hash_builder.take_proof_nodes();
211        assert_eq!(
212            proof,
213            ProofNodes::from_iter([(Nibbles::default(), Bytes::from([EMPTY_STRING_CODE]))])
214        );
215        assert_eq!(
216            verify_proof(
217                root,
218                key.clone(),
219                None,
220                proof.into_nodes_sorted().iter().map(|(_, node)| node)
221            ),
222            Ok(())
223        );
224
225        let mut dummy_proof = vec![];
226        BranchNode::default().encode(&mut dummy_proof);
227        assert_eq!(
228            verify_proof(root, key, None, [&Bytes::from(dummy_proof.clone())]),
229            Err(ProofVerificationError::ValueMismatch {
230                path: Nibbles::default(),
231                got: Some(Bytes::from(dummy_proof)),
232                expected: Some(Bytes::from(RlpNode::word_rlp(&EMPTY_ROOT_HASH)[..].to_vec()))
233            })
234        );
235    }
236
237    #[test]
238    fn inlined_trie_leaves() {
239        // root: ext(a7)
240        // a7: branch(children: 1, 7, f)
241        // a77: ext(d3)
242        // a77d3: branch(children: 3 (key: 70, value: 0x31), 9 (key: 70, value: 0x312e32))
243        let root =
244            B256::from_str("8523a13fdb0aa86480a61e34443a951e85e618b5c9b23b9e74cf2754941ce061")
245                .unwrap();
246        let proof = [
247            Bytes::from_str("e48200a7a080389e2b58154f1b8756223ec9ac277b6a166417b4279f016cb86582afb5ae6c").unwrap(),
248            Bytes::from_str("f84080c7833135508234358080808080a0d03438e4f6601da47dab30f52e4325509012ebc1a1c8901fd10d37e05db48bf180808080808080c88339365083312e3180").unwrap(),
249            Bytes::from_str("e08200d3dc808080c4822070318080808080c782207083312e3280808080808080").unwrap()
250        ];
251
252        let first_key = Nibbles::unpack(hex!("a77d3370"));
253        let first_value = vec![0x31];
254        let second_key = Nibbles::unpack(hex!("a77d3970"));
255        let second_value = hex!("0x312e32").to_vec();
256
257        assert_eq!(
258            verify_proof(root, first_key.clone(), Some(first_value.clone()), &proof),
259            Ok(())
260        );
261        assert_eq!(
262            verify_proof(root, first_key.clone(), None, &proof),
263            Err(ProofVerificationError::ValueMismatch {
264                path: first_key,
265                got: Some(first_value.into()),
266                expected: None,
267            })
268        );
269
270        assert_eq!(
271            verify_proof(root, second_key.clone(), Some(second_value.clone()), &proof),
272            Ok(())
273        );
274        assert_eq!(
275            verify_proof(root, second_key.clone(), None, &proof),
276            Err(ProofVerificationError::ValueMismatch {
277                path: second_key,
278                got: Some(second_value.into()),
279                expected: None,
280            })
281        );
282    }
283
284    #[test]
285    fn single_leaf_trie_proof_verification() {
286        let target = Nibbles::unpack(B256::with_last_byte(0x2));
287        let target_value = B256::with_last_byte(0x2);
288        let non_existent_target = Nibbles::unpack(B256::with_last_byte(0x3));
289
290        let retainer = ProofRetainer::from_iter([target.clone(), non_existent_target]);
291        let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
292        hash_builder.add_leaf(target.clone(), &target_value[..]);
293        let root = hash_builder.root();
294        assert_eq!(root, triehash_trie_root([(target.pack(), target.pack())]));
295
296        let proof = hash_builder.take_proof_nodes().into_nodes_sorted();
297        assert_eq!(
298            verify_proof(
299                root,
300                target,
301                Some(target_value.to_vec()),
302                proof.iter().map(|(_, node)| node)
303            ),
304            Ok(())
305        );
306    }
307
308    #[test]
309    fn non_existent_proof_verification() {
310        let range = 0..=0xf;
311        let target = Nibbles::unpack(B256::with_last_byte(0xff));
312
313        let retainer = ProofRetainer::from_iter([target.clone()]);
314        let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
315        for key in range.clone() {
316            let hash = B256::with_last_byte(key);
317            hash_builder.add_leaf(Nibbles::unpack(hash), &hash[..]);
318        }
319        let root = hash_builder.root();
320        assert_eq!(
321            root,
322            triehash_trie_root(range.map(|b| (B256::with_last_byte(b), B256::with_last_byte(b))))
323        );
324
325        let proof = hash_builder.take_proof_nodes().into_nodes_sorted();
326        assert_eq!(verify_proof(root, target, None, proof.iter().map(|(_, node)| node)), Ok(()));
327    }
328
329    #[test]
330    fn proof_verification_with_divergent_node() {
331        let existing_keys = [
332            hex!("0000000000000000000000000000000000000000000000000000000000000000"),
333            hex!("3a00000000000000000000000000000000000000000000000000000000000000"),
334            hex!("3c15000000000000000000000000000000000000000000000000000000000000"),
335        ];
336        let target = Nibbles::unpack(
337            B256::from_str("0x3c19000000000000000000000000000000000000000000000000000000000000")
338                .unwrap(),
339        );
340        let value = B256::with_last_byte(1);
341
342        // Build trie without a target and retain proof first.
343        let retainer = ProofRetainer::from_iter([target.clone()]);
344        let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
345        for key in &existing_keys {
346            hash_builder.add_leaf(Nibbles::unpack(B256::from_slice(key)), &value[..]);
347        }
348        let root = hash_builder.root();
349        assert_eq!(
350            root,
351            triehash_trie_root(existing_keys.map(|key| (B256::from_slice(&key), value)))
352        );
353        let proof = hash_builder.take_proof_nodes();
354        assert_eq!(proof, ProofNodes::from_iter([
355            (Nibbles::default(), Bytes::from_str("f851a0c530c099d779362b6bd0be05039b51ccd0a8ed39e0b2abacab8fe0e3441251878080a07d4ee4f073ae7ce32a6cbcdb015eb73dd2616f33ed2e9fb6ba51c1f9ad5b697b80808080808080808080808080").unwrap()),
356            (Nibbles::from_vec(vec![0x3]), Bytes::from_str("f85180808080808080808080a057fcbd3f97b1093cd39d0f58dafd5058e2d9f79a419e88c2498ff3952cb11a8480a07520d69a83a2bdad373a68b2c9c8c0e1e1c99b6ec80b4b933084da76d644081980808080").unwrap()),
357            (Nibbles::from_vec(vec![0x3, 0xc]), Bytes::from_str("f842a02015000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001").unwrap())
358        ]));
359        assert_eq!(
360            verify_proof(
361                root,
362                target.clone(),
363                None,
364                proof.into_nodes_sorted().iter().map(|(_, node)| node)
365            ),
366            Ok(())
367        );
368
369        let retainer = ProofRetainer::from_iter([target.clone()]);
370        let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
371        for key in &existing_keys {
372            hash_builder.add_leaf(Nibbles::unpack(B256::from_slice(key)), &value[..]);
373        }
374        hash_builder.add_leaf(target.clone(), &value[..]);
375        let root = hash_builder.root();
376        assert_eq!(
377            root,
378            triehash_trie_root(
379                existing_keys
380                    .into_iter()
381                    .map(|key| (B256::from_slice(&key), value))
382                    .chain([(B256::from_slice(&target.pack()), value)])
383            )
384        );
385        let proof = hash_builder.take_proof_nodes();
386        assert_eq!(proof, ProofNodes::from_iter([
387            (Nibbles::default(), Bytes::from_str("f851a0c530c099d779362b6bd0be05039b51ccd0a8ed39e0b2abacab8fe0e3441251878080a0abd80d939392f6d222f8becc15f8c6f0dbbc6833dd7e54bfbbee0c589b7fd40380808080808080808080808080").unwrap()),
388            (Nibbles::from_vec(vec![0x3]), Bytes::from_str("f85180808080808080808080a057fcbd3f97b1093cd39d0f58dafd5058e2d9f79a419e88c2498ff3952cb11a8480a09e7b3788773773f15e26ad07b72a2c25a6374bce256d9aab6cea48fbc77d698180808080").unwrap()),
389            (Nibbles::from_vec(vec![0x3, 0xc]), Bytes::from_str("e211a0338ac0a453edb0e40a23a70aee59e02a6c11597c34d79a5ba94da8eb20dd4d52").unwrap()),
390            (Nibbles::from_vec(vec![0x3, 0xc, 0x1]), Bytes::from_str("f8518080808080a020dc5b33292bfad9013bf123f7faf1efcc5c8e00c894177fc0bfb447daef522f808080a020dc5b33292bfad9013bf123f7faf1efcc5c8e00c894177fc0bfb447daef522f80808080808080").unwrap()),
391            (Nibbles::from_vec(vec![0x3, 0xc, 0x1, 0x9]), Bytes::from_str("f8419f20000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001").unwrap()),
392        ]));
393        assert_eq!(
394            verify_proof(
395                root,
396                target,
397                Some(value.to_vec()),
398                proof.into_nodes_sorted().iter().map(|(_, node)| node)
399            ),
400            Ok(())
401        );
402    }
403
404    #[test]
405    fn extension_root_trie_proof_verification() {
406        let range = 0..=0xff;
407        let target = Nibbles::unpack(B256::with_last_byte(0x42));
408        let target_value = B256::with_last_byte(0x42);
409
410        let retainer = ProofRetainer::from_iter([target.clone()]);
411        let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
412        for key in range.clone() {
413            let hash = B256::with_last_byte(key);
414            hash_builder.add_leaf(Nibbles::unpack(hash), &hash[..]);
415        }
416        let root = hash_builder.root();
417        assert_eq!(
418            root,
419            triehash_trie_root(range.map(|b| (B256::with_last_byte(b), B256::with_last_byte(b))))
420        );
421
422        let proof = hash_builder.take_proof_nodes().into_nodes_sorted();
423        assert_eq!(
424            verify_proof(
425                root,
426                target,
427                Some(target_value.to_vec()),
428                proof.iter().map(|(_, node)| node)
429            ),
430            Ok(())
431        );
432    }
433
434    #[test]
435    fn wide_trie_proof_verification() {
436        let range = 0..=0xff;
437        let target1 = Nibbles::unpack(B256::repeat_byte(0x42));
438        let target1_value = B256::repeat_byte(0x42);
439        let target2 = Nibbles::unpack(B256::repeat_byte(0xff));
440        let target2_value = B256::repeat_byte(0xff);
441
442        let retainer = ProofRetainer::from_iter([target1.clone(), target2.clone()]);
443        let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
444        for key in range.clone() {
445            let hash = B256::repeat_byte(key);
446            hash_builder.add_leaf(Nibbles::unpack(hash), &hash[..]);
447        }
448        let root = hash_builder.root();
449        assert_eq!(
450            root,
451            triehash_trie_root(range.map(|b| (B256::repeat_byte(b), B256::repeat_byte(b))))
452        );
453
454        let proof = hash_builder.take_proof_nodes();
455
456        assert_eq!(
457            verify_proof(
458                root,
459                target1.clone(),
460                Some(target1_value.to_vec()),
461                proof.matching_nodes_sorted(&target1).iter().map(|(_, node)| node)
462            ),
463            Ok(())
464        );
465
466        assert_eq!(
467            verify_proof(
468                root,
469                target2.clone(),
470                Some(target2_value.to_vec()),
471                proof.matching_nodes_sorted(&target2).iter().map(|(_, node)| node)
472            ),
473            Ok(())
474        );
475    }
476
477    #[test]
478    fn proof_verification_with_node_encoded_in_place() {
479        // Building a trie with a leaf, branch, and extension encoded in place:
480        //
481        // - node `2a`: 0x64
482        // - node `32a`: 0x64
483        // - node `33b`: 0x64
484        // - node `412a`: 0x64
485        // - node `413b`: 0x64
486        //
487        // This trie looks like:
488        //
489        // f83f => list len = 63
490        //    80
491        //    80
492        //    c2 => list len = 2 (leaf encoded in-place)
493        //       3a => odd leaf
494        //       64 => leaf node value
495        //    d5 => list len = 21 (branch encoded in-place)
496        //       80
497        //       80
498        //       c2 => list len = 2 (leaf node encoded in-place)
499        //          3a => odd leaf
500        //          64 leaf node value
501        //       c2 => list len = 2 (leaf node encoded in-place)
502        //          3b => odd leaf
503        //          64 leaf node value
504        //       80
505        //       80
506        //       80
507        //       80
508        //       80
509        //       80
510        //       80
511        //       80
512        //       80
513        //       80
514        //       80
515        //       80
516        //       80
517        //    d7 => list len = 23 (extension encoded in-place)
518        //       11 => odd extension
519        //       d5 => list len = 21 (branch encoded in-place)
520        //          80
521        //          80
522        //          c2 => list len = 2 (leaf node encoded in-place)
523        //             3a => odd leaf
524        //             64 leaf node value
525        //          c2 => list len = 2 (leaf node encoded in-place)
526        //             3b => odd leaf
527        //             64 leaf node value
528        //          80
529        //          80
530        //          80
531        //          80
532        //          80
533        //          80
534        //          80
535        //          80
536        //          80
537        //          80
538        //          80
539        //          80
540        //          80
541        //    80
542        //    80
543        //    80
544        //    80
545        //    80
546        //    80
547        //    80
548        //    80
549        //    80
550        //    80
551        //    80
552        //    80
553        //
554        // Flattened:
555        // f83f8080c23a64d58080c23a64c23b6480808080808080808080808080d711d58080c23a64c23b6480808080808080808080808080808080808080808080808080
556        //
557        // Root hash:
558        // 67dbae3a9cc1f4292b0739fa1bcb7f9e6603a6a138444656ec674e273417c918
559
560        let mut buffer = vec![];
561
562        let value = vec![0x64];
563        let child_leaf = TrieNode::Leaf(LeafNode::new(Nibbles::from_nibbles([0xa]), value.clone()));
564
565        let child_branch = TrieNode::Branch(BranchNode::new(
566            vec![
567                {
568                    buffer.clear();
569                    TrieNode::Leaf(LeafNode::new(Nibbles::from_nibbles([0xa]), value.clone()))
570                        .rlp(&mut buffer)
571                },
572                {
573                    buffer.clear();
574                    TrieNode::Leaf(LeafNode::new(Nibbles::from_nibbles([0xb]), value))
575                        .rlp(&mut buffer)
576                },
577            ],
578            TrieMask::new(0b0000000000001100_u16),
579        ));
580
581        let child_extension =
582            TrieNode::Extension(ExtensionNode::new(Nibbles::from_nibbles([0x1]), {
583                buffer.clear();
584                child_branch.rlp(&mut buffer)
585            }));
586
587        let root_branch = TrieNode::Branch(BranchNode::new(
588            vec![
589                {
590                    buffer.clear();
591                    child_leaf.rlp(&mut buffer)
592                },
593                {
594                    buffer.clear();
595                    child_branch.rlp(&mut buffer)
596                },
597                {
598                    buffer.clear();
599                    child_extension.rlp(&mut buffer)
600                },
601            ],
602            TrieMask::new(0b0000000000011100_u16),
603        ));
604
605        let mut root_encoded = vec![];
606        root_branch.encode(&mut root_encoded);
607
608        // Just to make sure our manual encoding above is correct
609        assert_eq!(
610            root_encoded,
611            hex!(
612                "f83f8080c23a64d58080c23a64c23b6480808080808080808080808080d711d58080c23a64c23b6480808080808080808080808080808080808080808080808080"
613            )
614        );
615
616        let root_hash = B256::from_slice(&hex!(
617            "67dbae3a9cc1f4292b0739fa1bcb7f9e6603a6a138444656ec674e273417c918"
618        ));
619        let root_encoded = Bytes::from(root_encoded);
620        let proof = vec![&root_encoded];
621
622        // Node `2a`: 0x64
623        verify_proof(root_hash, Nibbles::from_nibbles([0x2, 0xa]), Some(vec![0x64]), proof.clone())
624            .unwrap();
625
626        // Node `32a`: 0x64
627        verify_proof(
628            root_hash,
629            Nibbles::from_nibbles([0x3, 0x2, 0xa]),
630            Some(vec![0x64]),
631            proof.clone(),
632        )
633        .unwrap();
634
635        // Node `33b`: 0x64
636        verify_proof(
637            root_hash,
638            Nibbles::from_nibbles([0x3, 0x3, 0xb]),
639            Some(vec![0x64]),
640            proof.clone(),
641        )
642        .unwrap();
643
644        // Node `412a`: 0x64
645        verify_proof(
646            root_hash,
647            Nibbles::from_nibbles([0x4, 0x1, 0x2, 0xa]),
648            Some(vec![0x64]),
649            proof.clone(),
650        )
651        .unwrap();
652
653        // Node `413b`: 0x64
654        verify_proof(
655            root_hash,
656            Nibbles::from_nibbles([0x4, 0x1, 0x3, 0xb]),
657            Some(vec![0x64]),
658            proof.clone(),
659        )
660        .unwrap();
661    }
662
663    #[test]
664    #[cfg(feature = "arbitrary")]
665    #[cfg_attr(miri, ignore = "no proptest")]
666    fn arbitrary_proof_verification() {
667        use proptest::prelude::*;
668
669        proptest!(|(state: std::collections::BTreeMap<B256, alloy_primitives::U256>)| {
670            let hashed = state.into_iter()
671                .map(|(k, v)| (k, alloy_rlp::encode(v).to_vec()))
672                // Collect into a btree map to sort the data
673                .collect::<std::collections::BTreeMap<_, _>>();
674
675            let retainer = ProofRetainer::from_iter(hashed.clone().into_keys().map(Nibbles::unpack));
676            let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
677            for (key, value) in hashed.clone() {
678                hash_builder.add_leaf(Nibbles::unpack(key), &value);
679            }
680
681            let root = hash_builder.root();
682            assert_eq!(root, triehash_trie_root(&hashed));
683
684            let proofs = hash_builder.take_proof_nodes();
685            for (key, value) in hashed {
686                let nibbles = Nibbles::unpack(key);
687                assert_eq!(verify_proof(root, nibbles.clone(), Some(value), proofs.matching_nodes_sorted(&nibbles).iter().map(|(_, node)| node)), Ok(()));
688            }
689        });
690    }
691}