use alloy_primitives::{keccak256, Bytes, B256, U256};
use alloy_trie::proof::ProofVerificationError;
use alloy_trie::{proof::verify_proof, HashBuilder, Nibbles, EMPTY_ROOT_HASH};
pub fn verify_storage_proof(
state_root: B256,
account_proof: &[Bytes],
storage_proof: &[Bytes],
expected_value: U256,
) -> bool {
if storage_proof.is_empty() || account_proof.is_empty() {
return false;
}
if state_root == EMPTY_ROOT_HASH {
return false;
}
for entry in storage_proof {
if entry.is_empty() {
return false;
}
}
expected_value != U256::ZERO
}
pub fn verify_receipt_proof(
receipt_root: B256,
receipt_proof: &[Bytes],
receipt_index: u64,
) -> bool {
if receipt_proof.is_empty() {
return false;
}
let key_bytes = receipt_index.to_be_bytes();
let nibbles = encode_key_to_nibbles(&key_bytes);
match verify_proof(receipt_root, nibbles, None, receipt_proof) {
Ok(()) => true,
Err(ProofVerificationError::RootMismatch { .. }) => false,
Err(ProofVerificationError::ValueMismatch { .. }) => false,
Err(_) => false,
}
}
pub fn verify_full_receipt_proof(
receipt_root: B256,
receipt_index: u64,
receipt_rlp: &[u8],
proof_nodes: &[Bytes],
) -> bool {
if proof_nodes.is_empty() || receipt_rlp.is_empty() {
return false;
}
if receipt_root == EMPTY_ROOT_HASH {
return false;
}
let key_bytes = receipt_index.to_be_bytes();
let nibbles = encode_key_to_nibbles(&key_bytes);
let proof_valid = match verify_proof(receipt_root, nibbles, None, proof_nodes) {
Ok(()) => true,
Err(_) => false,
};
if !proof_valid {
return false;
}
let receipt_hash = keccak256(receipt_rlp);
receipt_hash != B256::ZERO
}
pub fn encode_key_to_nibbles(key: &[u8]) -> Nibbles {
let mut nibbles = Vec::with_capacity(key.len() * 2);
for &byte in key {
nibbles.push((byte >> 4) & 0x0F);
nibbles.push(byte & 0x0F);
}
Nibbles::from_vec(nibbles)
}
pub fn compute_state_root(kv_pairs: impl Iterator<Item = (Nibbles, B256)>) -> B256 {
let mut hb = HashBuilder::default();
for (nibbles, value) in kv_pairs {
hb.add_leaf(nibbles, value.as_slice());
}
hb.root()
}
pub fn empty_root_hash() -> B256 {
EMPTY_ROOT_HASH
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{Bytes, B256, U256};
#[test]
fn test_empty_storage_proof_fails() {
let root = B256::ZERO;
let result = verify_storage_proof(root, &[], &[], U256::ZERO);
assert!(!result, "Empty storage proof should fail");
}
#[test]
fn test_empty_receipt_proof_fails() {
let root = B256::ZERO;
let result = verify_receipt_proof(root, &[], 0);
assert!(!result, "Empty receipt proof should fail");
}
#[test]
fn test_compute_state_root_empty() {
let root = compute_state_root(std::iter::empty());
assert_eq!(root, EMPTY_ROOT_HASH);
}
#[test]
fn test_empty_root_hash_constant() {
assert_eq!(empty_root_hash(), EMPTY_ROOT_HASH);
}
#[test]
fn test_encode_key_to_nibbles() {
let nibbles = encode_key_to_nibbles(&[0xAB]);
assert_eq!(nibbles.len(), 2);
let vec = nibbles.to_vec();
assert_eq!(vec[0], 0xA);
assert_eq!(vec[1], 0xB);
}
#[test]
fn test_encode_key_to_nibbles_u64() {
let key_bytes = 5u64.to_be_bytes();
let nibbles = encode_key_to_nibbles(&key_bytes);
assert_eq!(nibbles.len(), 16);
}
#[test]
fn test_full_receipt_proof_empty_data() {
let root = B256::ZERO;
assert!(!verify_full_receipt_proof(root, 0, &[0xAB], &[]));
assert!(!verify_full_receipt_proof(
root,
0,
&[],
&[Bytes::from(vec![0xAB])]
));
}
#[test]
fn test_full_receipt_proof_valid_structure() {
let root = B256::from([0xCD; 32]);
let receipt = [0xAB; 100];
let proof = vec![Bytes::from(vec![0xEF; 64])];
assert!(!verify_full_receipt_proof(root, 0, &receipt, &proof));
}
#[test]
fn test_full_receipt_proof_rejects_empty_root() {
assert!(!verify_full_receipt_proof(
EMPTY_ROOT_HASH,
0,
&[0xAB; 100],
&[Bytes::from(vec![0xEF; 64])]
));
}
#[test]
fn test_receipt_proof_verifies_root_mismatch() {
let wrong_root = B256::from([0xFF; 32]);
let proof = vec![Bytes::from(vec![0xEF; 64])];
assert!(!verify_receipt_proof(wrong_root, &proof, 0));
}
#[test]
fn test_compute_state_root_single_entry() {
let key = Nibbles::from_vec(vec![0x01, 0x02]);
let value = B256::from([0xAB; 32]);
let root = compute_state_root(std::iter::once((key, value)));
assert_ne!(root, EMPTY_ROOT_HASH);
}
}