use std::collections::{BTreeMap, VecDeque};
use ethereum_types::H256;
use ethrex_crypto::{NativeCrypto, keccak::keccak_hash};
use ethrex_rlp::decode::RLPDecode;
use crate::{
ProofTrie, Trie, TrieError, ValueRLP,
nibbles::Nibbles,
node::{Node, NodeRef},
node_hash::NodeHash,
};
pub fn verify_range(
root: H256,
left_bound: &H256,
keys: &[H256],
values: &[ValueRLP],
proof: &[Vec<u8>],
) -> Result<bool, TrieError> {
if keys.len() != values.len() {
return Err(TrieError::Verify(format!(
"inconsistent proof data, got {} keys and {} values",
keys.len(),
values.len()
)));
}
for keys in keys.windows(2) {
if keys[0] >= keys[1] {
return Err(TrieError::Verify(String::from(
"key range is not monotonically increasing",
)));
}
}
if values.iter().any(|value| value.is_empty()) {
return Err(TrieError::Verify(String::from(
"value range contains empty value",
)));
}
let mut trie = Trie::stateless();
if proof.is_empty() {
for (key, value) in keys.iter().zip(values.iter()) {
trie.insert(key.0.to_vec(), value.clone())?;
}
let hash = trie.hash(&NativeCrypto)?;
if hash != root {
return Err(TrieError::Verify(format!(
"invalid proof, expected root hash {root}, got {hash}",
)));
}
return Ok(false);
}
if keys.is_empty() {
let result = process_proof_nodes(proof, root.into(), (*left_bound, None), None)?;
if result.num_right_references > 0 || !result.left_value.is_empty() {
return Err(TrieError::Verify(
"no keys returned but more are available on the trie".to_string(),
));
} else {
return Ok(false);
};
}
let last_key = keys.last().unwrap();
if keys.len() == 1 && left_bound == last_key {
if left_bound != &keys[0] {
return Err(TrieError::Verify(
"correct proof but invalid key".to_string(),
));
}
let result = process_proof_nodes(
proof,
root.into(),
(*left_bound, Some(*last_key)),
Some(*keys.first().unwrap()),
)?;
if result.left_value != values[0] {
return Err(TrieError::Verify(
"correct proof but invalid data".to_string(),
));
}
return Ok(result.num_right_references > 0);
}
if left_bound >= last_key {
return Err(TrieError::Verify("invalid edge keys".to_string()));
}
let result = process_proof_nodes(
proof,
root.into(),
(*left_bound, Some(*last_key)),
Some(*keys.first().unwrap()),
)?;
for (key, value) in keys.iter().zip(values.iter()) {
trie.insert(key.0.to_vec(), value.clone())?;
}
let mut trie = ProofTrie::from(trie);
for (partial_path, external_ref) in result.external_references {
trie.insert(partial_path, external_ref)?;
}
let hash = trie.hash(&NativeCrypto);
if hash != root {
return Err(TrieError::Verify(format!(
"invalid proof, expected root hash {root}, got {hash}",
)));
}
Ok(result.num_right_references > 0)
}
struct RangeProof<'a> {
node_refs: BTreeMap<H256, &'a [u8]>,
}
impl<'a> From<&'a [Vec<u8>]> for RangeProof<'a> {
fn from(proof: &'a [Vec<u8>]) -> Self {
let node_refs = proof
.iter()
.map(|node| {
let hash = H256(keccak_hash(node));
let encoded_data = node.as_slice();
(hash, encoded_data)
})
.collect();
RangeProof { node_refs }
}
}
impl RangeProof<'_> {
fn get_node(&self, hash: NodeHash) -> Result<Option<Node>, TrieError> {
let encoded_node = match hash {
NodeHash::Hashed(hash) => self.node_refs.get(&hash).copied(),
NodeHash::Inline(_) => Some(hash.as_ref()),
};
Ok(encoded_node.map(Node::decode).transpose()?)
}
}
struct ProofProcessingResult {
external_references: Vec<(Nibbles, NodeHash)>,
left_value: Vec<u8>,
num_right_references: usize,
}
fn process_proof_nodes(
raw_proof: &[Vec<u8>],
root: NodeHash,
bounds: (H256, Option<H256>),
first_key: Option<H256>,
) -> Result<ProofProcessingResult, TrieError> {
let bounds = (
Nibbles::from_bytes(&bounds.0.0),
Nibbles::from_bytes(&bounds.1.unwrap_or(bounds.0).0),
);
let first_key = first_key.map(|first_key| Nibbles::from_bytes(&first_key.0));
let proof = RangeProof::from(raw_proof);
let mut external_references = Vec::new();
let mut left_value = Vec::new();
let mut num_right_references = 0;
let mut stack = VecDeque::from_iter([(
Nibbles::default(),
proof.get_node(root)?.ok_or(TrieError::Verify(format!(
"root node missing from proof: {root:?}"
)))?,
)]);
while let Some((mut current_path, current_node)) = stack.pop_front() {
let value = match current_node {
Node::Branch(node) => {
for (index, choice) in node.choices.into_iter().enumerate() {
if !choice.is_valid() {
continue;
}
num_right_references += visit_child_node(
&mut stack,
&mut external_references,
&proof,
&bounds,
first_key.as_ref(),
current_path.append_new(index as u8),
choice,
)?;
}
node.value
}
Node::Extension(node) => {
current_path.extend(&node.prefix);
num_right_references += visit_child_node(
&mut stack,
&mut external_references,
&proof,
&bounds,
first_key.as_ref(),
current_path.clone(),
node.child,
)?;
Vec::new()
}
Node::Leaf(node) => node.value,
};
if !value.is_empty() && current_path == bounds.0 {
left_value = value.clone();
}
}
let result = ProofProcessingResult {
external_references,
left_value,
num_right_references,
};
Ok(result)
}
fn visit_child_node(
stack: &mut VecDeque<(Nibbles, Node)>,
external_refs: &mut Vec<(Nibbles, NodeHash)>,
proof: &RangeProof,
(left_bound, right_bound): &(Nibbles, Nibbles),
first_key: Option<&Nibbles>,
mut partial_path: Nibbles,
child: NodeRef,
) -> Result<usize, TrieError> {
let cmp_l = left_bound.compare_prefix(&partial_path);
let cmp_r = right_bound.compare_prefix(&partial_path);
if cmp_l.is_lt() && cmp_r.is_gt() {
return Ok(0);
}
let NodeRef::Hash(hash) = child else {
unreachable!()
};
match proof.get_node(hash)? {
Some(node) => {
if first_key.is_some_and(|fk| fk.compare_prefix(&partial_path).is_gt()) {
external_refs.push((partial_path, hash));
} else {
if let Node::Leaf(node) = &node {
partial_path.extend(&node.partial);
}
if right_bound.compare_prefix(&partial_path).is_lt() {
external_refs.push((partial_path.clone(), hash));
}
stack.push_back((partial_path, node));
}
}
None => {
if cmp_l.is_eq() || cmp_r.is_eq() {
return Err(TrieError::Verify(format!("proof node missing: {hash:?}")));
}
external_refs.push((partial_path, hash));
}
}
let n_right_references = if cmp_l.is_lt() && cmp_r.is_lt() { 1 } else { 0 };
Ok(n_right_references)
}