1use std::collections::{BTreeMap, VecDeque};
2
3use ethereum_types::H256;
4use ethrex_crypto::{NativeCrypto, keccak::keccak_hash};
5use ethrex_rlp::decode::RLPDecode;
6
7use crate::{
8 ProofTrie, Trie, TrieError, ValueRLP,
9 nibbles::Nibbles,
10 node::{Node, NodeRef},
11 node_hash::NodeHash,
12};
13
14pub fn verify_range(
17 root: H256,
18 left_bound: &H256,
19 keys: &[H256],
20 values: &[ValueRLP],
21 proof: &[Vec<u8>],
22) -> Result<bool, TrieError> {
23 if keys.len() != values.len() {
25 return Err(TrieError::Verify(format!(
26 "inconsistent proof data, got {} keys and {} values",
27 keys.len(),
28 values.len()
29 )));
30 }
31 for keys in keys.windows(2) {
33 if keys[0] >= keys[1] {
34 return Err(TrieError::Verify(String::from(
35 "key range is not monotonically increasing",
36 )));
37 }
38 }
39 if values.iter().any(|value| value.is_empty()) {
41 return Err(TrieError::Verify(String::from(
42 "value range contains empty value",
43 )));
44 }
45
46 let mut trie = Trie::stateless();
47
48 if proof.is_empty() {
50 for (key, value) in keys.iter().zip(values.iter()) {
52 trie.insert(key.0.to_vec(), value.clone())?;
53 }
54 let hash = trie.hash(&NativeCrypto)?;
55 if hash != root {
56 return Err(TrieError::Verify(format!(
57 "invalid proof, expected root hash {root}, got {hash}",
58 )));
59 }
60 return Ok(false);
61 }
62
63 if keys.is_empty() {
65 let result = process_proof_nodes(proof, root.into(), (*left_bound, None), None)?;
68 if result.num_right_references > 0 || !result.left_value.is_empty() {
69 return Err(TrieError::Verify(
70 "no keys returned but more are available on the trie".to_string(),
71 ));
72 } else {
73 return Ok(false);
74 };
75 }
76
77 let last_key = keys.last().unwrap();
78
79 if keys.len() == 1 && left_bound == last_key {
81 if left_bound != &keys[0] {
83 return Err(TrieError::Verify(
84 "correct proof but invalid key".to_string(),
85 ));
86 }
87 let result = process_proof_nodes(
88 proof,
89 root.into(),
90 (*left_bound, Some(*last_key)),
91 Some(*keys.first().unwrap()),
92 )?;
93 if result.left_value != values[0] {
94 return Err(TrieError::Verify(
95 "correct proof but invalid data".to_string(),
96 ));
97 }
98 return Ok(result.num_right_references > 0);
99 }
100
101 if left_bound >= last_key {
103 return Err(TrieError::Verify("invalid edge keys".to_string()));
104 }
105
106 let result = process_proof_nodes(
108 proof,
109 root.into(),
110 (*left_bound, Some(*last_key)),
111 Some(*keys.first().unwrap()),
112 )?;
113
114 for (key, value) in keys.iter().zip(values.iter()) {
116 trie.insert(key.0.to_vec(), value.clone())?;
117 }
118
119 let mut trie = ProofTrie::from(trie);
121 for (partial_path, external_ref) in result.external_references {
122 trie.insert(partial_path, external_ref)?;
123 }
124
125 let hash = trie.hash(&NativeCrypto);
127 if hash != root {
128 return Err(TrieError::Verify(format!(
129 "invalid proof, expected root hash {root}, got {hash}",
130 )));
131 }
132 Ok(result.num_right_references > 0)
133}
134
135struct RangeProof<'a> {
138 node_refs: BTreeMap<H256, &'a [u8]>,
139}
140
141impl<'a> From<&'a [Vec<u8>]> for RangeProof<'a> {
142 fn from(proof: &'a [Vec<u8>]) -> Self {
143 let node_refs = proof
144 .iter()
145 .map(|node| {
146 let hash = H256(keccak_hash(node));
147 let encoded_data = node.as_slice();
148 (hash, encoded_data)
149 })
150 .collect();
151 RangeProof { node_refs }
152 }
153}
154
155impl RangeProof<'_> {
156 fn get_node(&self, hash: NodeHash) -> Result<Option<Node>, TrieError> {
159 let encoded_node = match hash {
160 NodeHash::Hashed(hash) => self.node_refs.get(&hash).copied(),
161 NodeHash::Inline(_) => Some(hash.as_ref()),
162 };
163 Ok(encoded_node.map(Node::decode).transpose()?)
164 }
165}
166
167struct ProofProcessingResult {
178 external_references: Vec<(Nibbles, NodeHash)>,
179 left_value: Vec<u8>,
180 num_right_references: usize,
181}
182
183fn process_proof_nodes(
184 raw_proof: &[Vec<u8>],
185 root: NodeHash,
186 bounds: (H256, Option<H256>),
187 first_key: Option<H256>,
188) -> Result<ProofProcessingResult, TrieError> {
189 let bounds = (
191 Nibbles::from_bytes(&bounds.0.0),
192 Nibbles::from_bytes(&bounds.1.unwrap_or(bounds.0).0),
194 );
195 let first_key = first_key.map(|first_key| Nibbles::from_bytes(&first_key.0));
196
197 let proof = RangeProof::from(raw_proof);
199
200 let mut external_references = Vec::new();
202 let mut left_value = Vec::new();
203 let mut num_right_references = 0;
204
205 let mut stack = VecDeque::from_iter([(
212 Nibbles::default(),
213 proof.get_node(root)?.ok_or(TrieError::Verify(format!(
214 "root node missing from proof: {root:?}"
215 )))?,
216 )]);
217 while let Some((mut current_path, current_node)) = stack.pop_front() {
218 let value = match current_node {
219 Node::Branch(node) => {
220 for (index, choice) in node.choices.into_iter().enumerate() {
221 if !choice.is_valid() {
222 continue;
223 }
224 num_right_references += visit_child_node(
225 &mut stack,
226 &mut external_references,
227 &proof,
228 &bounds,
229 first_key.as_ref(),
230 current_path.append_new(index as u8),
231 choice,
232 )?;
233 }
234 node.value
235 }
236 Node::Extension(node) => {
237 current_path.extend(&node.prefix);
238 num_right_references += visit_child_node(
239 &mut stack,
240 &mut external_references,
241 &proof,
242 &bounds,
243 first_key.as_ref(),
244 current_path.clone(),
245 node.child,
246 )?;
247 Vec::new()
248 }
249 Node::Leaf(node) => node.value,
250 };
251
252 if !value.is_empty() && current_path == bounds.0 {
253 left_value = value.clone();
254 }
255 }
256
257 let result = ProofProcessingResult {
258 external_references,
259 left_value,
260 num_right_references,
261 };
262 Ok(result)
263}
264
265fn visit_child_node(
266 stack: &mut VecDeque<(Nibbles, Node)>,
267 external_refs: &mut Vec<(Nibbles, NodeHash)>,
268 proof: &RangeProof,
269 (left_bound, right_bound): &(Nibbles, Nibbles),
270 first_key: Option<&Nibbles>,
271 mut partial_path: Nibbles,
272 child: NodeRef,
273) -> Result<usize, TrieError> {
274 let cmp_l = left_bound.compare_prefix(&partial_path);
275 let cmp_r = right_bound.compare_prefix(&partial_path);
276
277 if cmp_l.is_lt() && cmp_r.is_gt() {
280 return Ok(0);
281 }
282 let NodeRef::Hash(hash) = child else {
283 unreachable!()
286 };
287
288 match proof.get_node(hash)? {
289 Some(node) => {
290 if first_key.is_some_and(|fk| fk.compare_prefix(&partial_path).is_gt()) {
308 external_refs.push((partial_path, hash));
311 } else {
312 if let Node::Leaf(node) = &node {
314 partial_path.extend(&node.partial);
315 }
316 if right_bound.compare_prefix(&partial_path).is_lt() {
317 external_refs.push((partial_path.clone(), hash));
318 }
319
320 stack.push_back((partial_path, node));
321 }
322 }
323 None => {
324 if cmp_l.is_eq() || cmp_r.is_eq() {
325 return Err(TrieError::Verify(format!("proof node missing: {hash:?}")));
326 }
327
328 external_refs.push((partial_path, hash));
329 }
330 }
331
332 let n_right_references = if cmp_l.is_lt() && cmp_r.is_lt() { 1 } else { 0 };
334
335 Ok(n_right_references)
336}