1use crate::{HashBuilder, Nibbles, RlpNode, TrieNode};
9use alloc::vec::Vec;
10use alloy_primitives::{keccak256, map::B256Map, Bytes, B256};
11use alloy_rlp::Decodable;
12
13const KEY_NIBBLES: usize = B256::len_bytes() * 2;
14
15struct RangeProofVerifier<'a> {
17 range: ProofRange,
19 nodes: ProofNodes<'a>,
21 frontier: ProofFrontier,
23 next: Option<Nibbles>,
25}
26
27impl<'a> RangeProofVerifier<'a> {
28 fn new(left: B256, right: B256, proof: &'a [Bytes], frontier: ProofFrontier) -> Self {
30 Self {
31 range: ProofRange::new(left, right),
32 nodes: ProofNodes::new(proof),
33 frontier,
34 next: None,
35 }
36 }
37
38 fn verify(mut self, root: B256) -> Result<Option<B256>, RangeProofError> {
41 self.visit_reference(Nibbles::new(), &RlpNode::word_rlp(&root))?;
42
43 let got = self.frontier.root()?;
44 if got != root {
45 return Err(RangeProofError::RootMismatch { expected: root, got })
46 }
47 Ok(self.next.as_ref().map(TriePath::lowest_key))
48 }
49
50 fn visit_reference(
53 &mut self,
54 prefix: Nibbles,
55 reference: &RlpNode,
56 ) -> Result<(), RangeProofError> {
57 match self.range.subtree_relation(&prefix)? {
58 SubtreeRelation::OutsideLeft => self.add_outside_reference(prefix, reference),
59 SubtreeRelation::OutsideRight => {
60 self.note_next(prefix);
61 self.add_outside_reference(prefix, reference)
62 }
63 SubtreeRelation::Inside => Ok(()),
64 SubtreeRelation::Boundary => {
65 let node = self.nodes.resolve(prefix, reference)?;
66 self.visit_node(node, prefix)
67 }
68 }
69 }
70
71 fn visit_node(&mut self, node: TrieNode, prefix: Nibbles) -> Result<(), RangeProofError> {
73 match node {
74 TrieNode::EmptyRoot => Ok(()),
75 TrieNode::Leaf(leaf) => {
76 let path = prefix.descend_leaf(&leaf.key)?;
77 match self.range.key_relation(&path) {
78 KeyRelation::Before => self.frontier.push_leaf(path, leaf.value),
79 KeyRelation::Inside => {}
80 KeyRelation::After => {
81 self.note_next(path);
82 self.frontier.push_leaf(path, leaf.value);
83 }
84 }
85 Ok(())
86 }
87 TrieNode::Extension(extension) => {
88 self.visit_reference(prefix.descend_extension(&extension.key)?, &extension.child)
89 }
90 TrieNode::Branch(branch) => {
91 for (nibble, child) in branch
92 .as_ref()
93 .children()
94 .filter_map(|(nibble, child)| child.map(|child| (nibble, child)))
95 {
96 self.visit_reference(prefix.descend_child(nibble)?, child)?;
97 }
98 Ok(())
99 }
100 }
101 }
102
103 fn add_outside_reference(
105 &mut self,
106 prefix: Nibbles,
107 reference: &RlpNode,
108 ) -> Result<(), RangeProofError> {
109 if let Some(hash) = reference.as_hash() {
110 self.frontier.push_subtree(prefix, hash);
111 return Ok(())
112 }
113 self.add_outside_node(TrieNode::decode(&mut reference.as_slice())?, prefix)
114 }
115
116 fn add_outside_node(&mut self, node: TrieNode, prefix: Nibbles) -> Result<(), RangeProofError> {
118 match node {
119 TrieNode::EmptyRoot => Ok(()),
120 TrieNode::Leaf(leaf) => {
121 let path = prefix.descend_leaf(&leaf.key)?;
122 self.frontier.push_leaf(path, leaf.value);
123 Ok(())
124 }
125 TrieNode::Extension(extension) => self
126 .add_outside_reference(prefix.descend_extension(&extension.key)?, &extension.child),
127 TrieNode::Branch(branch) => {
128 for (nibble, child) in branch
129 .as_ref()
130 .children()
131 .filter_map(|(nibble, child)| child.map(|child| (nibble, child)))
132 {
133 self.add_outside_reference(prefix.descend_child(nibble)?, child)?;
134 }
135 Ok(())
136 }
137 }
138 }
139
140 fn note_next(&mut self, path: Nibbles) {
142 if self.next.is_none_or(|next| path < next) {
143 self.next = Some(path);
144 }
145 }
146}
147
148struct ProofRange {
150 left: Nibbles,
152 right: Nibbles,
154}
155
156impl ProofRange {
157 fn new(left: B256, right: B256) -> Self {
159 Self { left: Nibbles::unpack(left), right: Nibbles::unpack(right) }
160 }
161
162 fn subtree_relation(&self, prefix: &Nibbles) -> Result<SubtreeRelation, RangeProofError> {
164 if prefix.len() > KEY_NIBBLES {
165 return Err(RangeProofError::PathTooLong { path: *prefix })
166 }
167 let left = self.left.slice(..prefix.len());
168 let right = self.right.slice(..prefix.len());
169
170 Ok(if *prefix < left {
171 SubtreeRelation::OutsideLeft
172 } else if *prefix > right {
173 SubtreeRelation::OutsideRight
174 } else if *prefix > left && *prefix < right {
175 SubtreeRelation::Inside
176 } else {
177 SubtreeRelation::Boundary
178 })
179 }
180
181 fn key_relation(&self, path: &Nibbles) -> KeyRelation {
183 if path < &self.left {
184 KeyRelation::Before
185 } else if path > &self.right {
186 KeyRelation::After
187 } else {
188 KeyRelation::Inside
189 }
190 }
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196enum SubtreeRelation {
197 OutsideLeft,
199 OutsideRight,
201 Boundary,
203 Inside,
205}
206
207#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209enum KeyRelation {
210 Before,
212 Inside,
214 After,
216}
217
218struct ProofNodes<'a>(B256Map<&'a [u8]>);
220
221impl<'a> ProofNodes<'a> {
222 fn new(proof: &'a [Bytes]) -> Self {
224 Self(proof.iter().map(|node| (keccak256(node), node.as_ref())).collect())
225 }
226
227 fn resolve(&self, path: Nibbles, reference: &RlpNode) -> Result<TrieNode, RangeProofError> {
229 let Some(hash) = reference.as_hash() else {
230 return Ok(TrieNode::decode(&mut reference.as_slice())?)
231 };
232 let node = self.0.get(&hash).ok_or(RangeProofError::MissingProofNode { path })?;
233 Ok(TrieNode::decode(&mut &node[..])?)
234 }
235}
236
237#[derive(Default)]
239struct ProofFrontier(Vec<FrontierEntry>);
240
241impl ProofFrontier {
242 fn from_leaves<I, V>(origin: B256, leaves: I) -> Result<(Self, Option<B256>), RangeProofError>
244 where
245 I: IntoIterator<Item = (B256, V)>,
246 V: Into<Vec<u8>>,
247 {
248 let mut frontier = Self::default();
249 let mut previous = None;
250
251 for (key, value) in leaves {
252 let value = value.into();
253 if key < origin {
254 return Err(RangeProofError::LeafBeforeOrigin { key, origin })
255 }
256 if previous.is_some_and(|previous| key <= previous) {
257 return Err(RangeProofError::NonMonotonicLeaves)
258 }
259 if value.is_empty() {
260 return Err(RangeProofError::EmptyLeafValue { key })
261 }
262 previous = Some(key);
263 frontier.push_leaf(Nibbles::unpack(key), value);
264 }
265
266 Ok((frontier, previous))
267 }
268
269 fn push_leaf(&mut self, path: Nibbles, value: Vec<u8>) {
271 debug_assert_eq!(path.len(), KEY_NIBBLES);
272 self.0.push(FrontierEntry::Leaf { path, value });
273 }
274
275 fn push_subtree(&mut self, path: Nibbles, hash: B256) {
277 debug_assert!(path.len() <= KEY_NIBBLES);
278 self.0.push(FrontierEntry::Subtree { path, hash });
279 }
280
281 fn root(mut self) -> Result<B256, RangeProofError> {
283 self.0.sort_unstable_by_key(FrontierEntry::path);
286 let mut builder = HashBuilder::default();
287 let mut previous = None;
288
289 for entry in self.0 {
290 let path = entry.path();
291 if previous.is_some_and(|previous| path <= previous) {
292 return Err(RangeProofError::DuplicateFrontierPath { path })
293 }
294 previous = Some(path);
295 match entry {
296 FrontierEntry::Leaf { path, value } => builder.add_leaf(path, &value),
297 FrontierEntry::Subtree { path, hash } => builder.add_branch(path, hash, false),
298 }
299 }
300 Ok(builder.root())
301 }
302}
303
304#[derive(Clone, Debug)]
307enum FrontierEntry {
308 Leaf { path: Nibbles, value: Vec<u8> },
310 Subtree { path: Nibbles, hash: B256 },
312}
313
314impl FrontierEntry {
315 const fn path(&self) -> Nibbles {
317 match self {
318 Self::Leaf { path, .. } | Self::Subtree { path, .. } => *path,
319 }
320 }
321}
322
323#[derive(Debug, thiserror::Error, PartialEq, Eq)]
325pub enum RangeProofError {
326 #[error("range leaves are not strictly increasing")]
328 NonMonotonicLeaves,
329 #[error("range leaf {key} precedes origin {origin}")]
331 LeafBeforeOrigin {
332 key: B256,
334 origin: B256,
336 },
337 #[error("range leaf {key} has an empty value")]
339 EmptyLeafValue {
340 key: B256,
342 },
343 #[error("missing proof node at path {path:?}")]
345 MissingProofNode {
346 path: Nibbles,
348 },
349 #[error("extension node at path {path:?} has an empty key")]
351 EmptyExtensionKey {
352 path: Nibbles,
354 },
355 #[error("proof path {path:?} exceeds hashed key length")]
357 PathTooLong {
358 path: Nibbles,
360 },
361 #[error("leaf path {path:?} does not resolve to a hashed key")]
363 InvalidLeafPath {
364 path: Nibbles,
366 },
367 #[error("duplicate range-proof frontier path {path:?}")]
369 DuplicateFrontierPath {
370 path: Nibbles,
372 },
373 #[error("range proof root mismatch: expected {expected}, got {got}")]
375 RootMismatch {
376 expected: B256,
378 got: B256,
380 },
381 #[error(transparent)]
383 Rlp(#[from] alloy_rlp::Error),
384}
385
386pub fn verify_range_proof<I, V>(
393 root: B256,
394 origin: B256,
395 limit: B256,
396 leaves: I,
397 proof: &[Bytes],
398) -> Result<Option<B256>, RangeProofError>
399where
400 I: IntoIterator<Item = (B256, V)>,
401 V: Into<Vec<u8>>,
402{
403 let (frontier, last_key) = ProofFrontier::from_leaves(origin, leaves)?;
404
405 if proof.is_empty() {
407 let got = frontier.root()?;
408 if got != root {
409 return Err(RangeProofError::RootMismatch { expected: root, got })
410 }
411 return Ok(None)
412 }
413
414 RangeProofVerifier::new(origin, last_key.unwrap_or(limit), proof, frontier).verify(root)
415}
416
417trait TriePath: Sized {
420 fn lowest_key(&self) -> B256;
422
423 fn descend_extension(self, key: &Nibbles) -> Result<Self, RangeProofError>;
425
426 fn descend_child(self, nibble: u8) -> Result<Self, RangeProofError>;
428
429 fn descend_leaf(self, key: &Nibbles) -> Result<Self, RangeProofError>;
431
432 fn join_checked(self, key: &Nibbles) -> Result<Self, RangeProofError>;
434}
435
436impl TriePath for Nibbles {
437 fn lowest_key(&self) -> B256 {
439 B256::right_padding_from(&self.pack())
440 }
441
442 fn descend_extension(self, key: &Nibbles) -> Result<Self, RangeProofError> {
444 if key.is_empty() {
445 return Err(RangeProofError::EmptyExtensionKey { path: self })
446 }
447 self.join_checked(key)
448 }
449
450 fn descend_child(self, nibble: u8) -> Result<Self, RangeProofError> {
452 if self.len() >= KEY_NIBBLES {
453 return Err(RangeProofError::PathTooLong { path: self })
454 }
455 let mut path = self;
456 path.push(nibble);
457 Ok(path)
458 }
459
460 fn descend_leaf(self, key: &Nibbles) -> Result<Self, RangeProofError> {
462 let path = self.join_checked(key)?;
463 if path.len() != KEY_NIBBLES {
464 return Err(RangeProofError::InvalidLeafPath { path })
465 }
466 Ok(path)
467 }
468
469 fn join_checked(self, key: &Nibbles) -> Result<Self, RangeProofError> {
471 if self.len() + key.len() > KEY_NIBBLES {
472 return Err(RangeProofError::PathTooLong { path: self })
473 }
474 Ok(self.join(key))
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::{proof::ProofRetainer, BranchNode, ExtensionNode, TrieMask, EMPTY_ROOT_HASH};
482 use alloc::{vec, vec::Vec};
483
484 const MAX_HASH: B256 = B256::new([0xff; B256::len_bytes()]);
485
486 fn verify_range_proof<I, V>(
487 root: B256,
488 origin: B256,
489 leaves: I,
490 proof: &[Bytes],
491 ) -> Result<Option<B256>, RangeProofError>
492 where
493 I: IntoIterator<Item = (B256, V)>,
494 V: Into<Vec<u8>>,
495 {
496 super::verify_range_proof(root, origin, MAX_HASH, leaves, proof)
497 }
498
499 fn key(value: u64) -> B256 {
500 B256::left_padding_from(&value.to_be_bytes())
501 }
502
503 fn encode_node(node: &TrieNode) -> Bytes {
504 alloy_rlp::encode(node).into()
505 }
506
507 fn no_leaves() -> Vec<(B256, Vec<u8>)> {
508 Vec::new()
509 }
510
511 fn value(byte: u8) -> Vec<u8> {
512 vec![byte; 64]
513 }
514
515 fn build_proof(leaves: &[(B256, Vec<u8>)], targets: &[B256]) -> (B256, Vec<Bytes>) {
516 let targets = targets.iter().copied().map(Nibbles::unpack).collect();
517 let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(targets));
518 for (key, value) in leaves {
519 builder.add_leaf(Nibbles::unpack(*key), value);
520 }
521 let root = builder.root();
522 let proof = builder
523 .take_proof_nodes()
524 .into_nodes_sorted()
525 .into_iter()
526 .map(|(_, node)| node)
527 .collect();
528 (root, proof)
529 }
530
531 #[test]
532 fn partial_range_authenticates_and_reports_the_next_key() {
533 let leaves =
534 vec![(key(1), value(1)), (key(2), value(2)), (key(3), value(3)), (key(4), value(4))];
535 let (root, proof) = build_proof(&leaves, &[key(2), key(3)]);
536
537 assert_eq!(
538 verify_range_proof(root, key(2), leaves[1..3].to_vec(), &proof).unwrap(),
539 Some(key(4))
540 );
541 }
542
543 #[test]
545 fn unexpanded_right_subtree_reports_a_prefix_bound() {
546 let right = |tail: u8| {
547 let mut key = B256::ZERO;
548 key.0[0] = 0x40;
549 key.0[31] = tail;
550 key
551 };
552 let leaves = vec![
553 (key(1), value(1)),
554 (key(2), value(2)),
555 (right(1), value(3)),
556 (right(2), value(4)),
557 ];
558 let (root, proof) = build_proof(&leaves, &[key(1), key(2)]);
559
560 assert_eq!(
561 verify_range_proof(root, key(1), leaves[..2].to_vec(), &proof).unwrap(),
562 Some(B256::right_padding_from(&[0x40]))
563 );
564 }
565
566 #[test]
567 fn inline_outside_nodes_are_reconstructed() {
568 let leaves =
569 vec![(key(1), vec![1]), (key(2), vec![2]), (key(3), vec![3]), (key(4), vec![4])];
570 let (root, proof) = build_proof(&leaves, &[key(2), key(3)]);
571
572 assert_eq!(
573 verify_range_proof(root, key(2), leaves[1..3].to_vec(), &proof).unwrap(),
574 Some(key(4))
575 );
576 }
577
578 #[test]
579 fn unused_proof_nodes_are_accepted() {
580 let leaves =
581 vec![(key(1), value(1)), (key(2), value(2)), (key(3), value(3)), (key(4), value(4))];
582 let (root, mut proof) = build_proof(&leaves, &[key(2), key(3)]);
583 proof.push(Bytes::from_static(&[alloy_rlp::EMPTY_STRING_CODE]));
584
585 assert_eq!(
586 verify_range_proof(root, key(2), leaves[1..3].to_vec(), &proof).unwrap(),
587 Some(key(4))
588 );
589 }
590
591 #[test]
593 fn node_paths_are_bounded_before_they_overflow_a_key() {
594 let dangling = RlpNode::word_rlp(&B256::repeat_byte(0xaa));
595
596 let overlong = encode_node(&TrieNode::Extension(ExtensionNode::new(
598 Nibbles::unpack(key(1)),
599 dangling.clone(),
600 )));
601 let branch = encode_node(&TrieNode::Branch(BranchNode::new(
602 vec![RlpNode::word_rlp(&keccak256(&overlong))],
603 TrieMask::new(1),
604 )));
605 let proof = vec![branch.clone(), overlong];
606
607 assert!(matches!(
608 verify_range_proof(keccak256(&branch), key(1), no_leaves(), &proof),
609 Err(RangeProofError::PathTooLong { .. })
610 ));
611
612 let deep_branch =
614 encode_node(&TrieNode::Branch(BranchNode::new(vec![dangling], TrieMask::new(1))));
615 let reach = encode_node(&TrieNode::Extension(ExtensionNode::new(
616 Nibbles::unpack(key(1)),
617 RlpNode::word_rlp(&keccak256(&deep_branch)),
618 )));
619 let proof = vec![reach.clone(), deep_branch];
620
621 assert!(matches!(
622 verify_range_proof(keccak256(&reach), key(1), no_leaves(), &proof),
623 Err(RangeProofError::PathTooLong { .. })
624 ));
625 }
626
627 #[test]
629 fn empty_extension_keys_are_rejected() {
630 let empty_key = Nibbles::new();
631 let mut proof = Vec::new();
632 let mut child = RlpNode::word_rlp(&B256::repeat_byte(0xaa));
633
634 for _ in 0..64 {
635 let node = encode_node(&TrieNode::Extension(ExtensionNode::new(empty_key, child)));
636 child = RlpNode::word_rlp(&keccak256(&node));
637 proof.push(node);
638 }
639 let root = keccak256(proof.last().unwrap());
640
641 assert!(matches!(
642 verify_range_proof(root, key(1), no_leaves(), &proof),
643 Err(RangeProofError::EmptyExtensionKey { .. })
644 ));
645 }
646
647 #[test]
648 fn missing_boundary_node_is_rejected() {
649 let leaves =
650 vec![(key(1), value(1)), (key(2), value(2)), (key(3), value(3)), (key(4), value(4))];
651 let (root, _) = build_proof(&leaves, &[key(2), key(3)]);
652 let unrelated = [Bytes::from_static(&[alloy_rlp::EMPTY_STRING_CODE])];
653
654 assert!(matches!(
655 verify_range_proof(root, key(2), leaves[1..3].to_vec(), &unrelated),
656 Err(RangeProofError::MissingProofNode { .. })
657 ));
658 }
659
660 #[test]
661 fn boundary_proof_can_authenticate_an_exhausted_range() {
662 let leaves =
663 vec![(key(1), value(1)), (key(2), value(2)), (key(3), value(3)), (key(4), value(4))];
664 let (root, proof) = build_proof(&leaves, &[key(2), key(4)]);
665
666 assert_eq!(verify_range_proof(root, key(2), leaves[1..].to_vec(), &proof).unwrap(), None);
667 }
668
669 #[test]
670 fn proof_free_full_trie_is_exhausted() {
671 let leaves = vec![(key(1), value(1)), (key(2), value(2)), (key(3), value(3))];
672 let (root, _) = build_proof(&leaves, &[]);
673
674 assert_eq!(verify_range_proof(root, B256::ZERO, leaves, &[]).unwrap(), None);
675 }
676
677 #[test]
678 fn omitted_interior_leaf_changes_root() {
679 let leaves =
680 vec![(key(1), value(1)), (key(2), value(2)), (key(3), value(3)), (key(4), value(4))];
681 let (root, proof) = build_proof(&leaves, &[key(2), key(4)]);
682 let returned = vec![(key(2), value(2)), (key(4), value(4))];
683
684 assert!(matches!(
685 verify_range_proof(root, key(2), returned, &proof),
686 Err(RangeProofError::RootMismatch { .. })
687 ));
688 }
689
690 #[test]
691 fn mutated_leaf_changes_root() {
692 let leaves = vec![(key(1), value(1)), (key(2), value(2)), (key(3), value(3))];
693 let (root, proof) = build_proof(&leaves, &[key(2), key(3)]);
694 let returned = vec![(key(2), value(9)), (key(3), value(3))];
695
696 assert!(matches!(
697 verify_range_proof(root, key(2), returned, &proof),
698 Err(RangeProofError::RootMismatch { .. })
699 ));
700 }
701
702 #[test]
703 fn empty_tail_is_authenticated() {
704 let leaves = vec![(key(1), value(1)), (key(2), value(2))];
705 let (root, proof) = build_proof(&leaves, &[key(3)]);
706
707 assert_eq!(
708 verify_range_proof(root, key(3), core::iter::empty::<(B256, Vec<u8>)>(), &proof)
709 .unwrap(),
710 None
711 );
712 }
713
714 #[test]
715 fn empty_range_cannot_hide_a_right_leaf() {
716 let leaves = vec![(key(1), value(1)), (key(3), value(3))];
717 let (root, proof) = build_proof(&leaves, &[key(2)]);
718
719 assert!(matches!(
720 verify_range_proof(root, key(2), core::iter::empty::<(B256, Vec<u8>)>(), &proof,),
721 Err(RangeProofError::RootMismatch { .. })
722 ));
723 }
724
725 #[test]
726 fn empty_interval_is_authenticated_through_its_limit() {
727 let leaves = vec![(key(1), value(1)), (key(3), value(3))];
728 let (root, proof) = build_proof(&leaves, &[key(2)]);
729
730 assert_eq!(
731 super::verify_range_proof(root, key(2), key(2), no_leaves(), &proof).unwrap(),
732 Some(key(3))
733 );
734 }
735
736 #[test]
737 fn rejects_non_monotonic_leaves_and_leaves_before_origin() {
738 let leaves = vec![(key(2), value(2)), (key(1), value(1))];
739
740 assert_eq!(
741 verify_range_proof(B256::ZERO, B256::ZERO, leaves, &[]),
742 Err(RangeProofError::NonMonotonicLeaves)
743 );
744 assert!(matches!(
745 verify_range_proof(B256::ZERO, key(2), [(key(1), value(1))], &[],),
746 Err(RangeProofError::LeafBeforeOrigin { .. })
747 ));
748 assert_eq!(
749 verify_range_proof(B256::ZERO, B256::ZERO, [(key(1), Vec::new())], &[]),
750 Err(RangeProofError::EmptyLeafValue { key: key(1) })
751 );
752 }
753
754 #[test]
755 fn empty_root_accepts_only_an_empty_range() {
756 assert_eq!(
757 verify_range_proof(
758 EMPTY_ROOT_HASH,
759 B256::ZERO,
760 core::iter::empty::<(B256, Vec<u8>)>(),
761 &[]
762 )
763 .unwrap(),
764 None
765 );
766 assert!(matches!(
767 verify_range_proof(EMPTY_ROOT_HASH, B256::ZERO, [(key(1), value(1))], &[],),
768 Err(RangeProofError::RootMismatch { .. })
769 ));
770 assert!(
771 verify_range_proof(EMPTY_ROOT_HASH, B256::ZERO, no_leaves(), &[Bytes::new()]).is_err()
772 );
773 }
774}