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