use crate::AccountProof;
use alloy_primitives::{keccak256, B256, U256};
use alloy_trie::{proof::verify_proof, Nibbles, TrieAccount, EMPTY_ROOT_HASH};
#[derive(Debug, thiserror::Error)]
pub enum ProofError {
#[error("account proof invalid against state_root {root}: {reason}")]
Account {
root: B256,
reason: String,
},
#[error("storage proof for slot {slot} invalid against storage_root {root}: {reason}")]
Storage {
slot: B256,
root: B256,
reason: String,
},
#[error("proof did not include slot {0}")]
MissingSlot(B256),
#[error("proof included unrequested slot {0}")]
UnexpectedSlot(B256),
}
pub fn check_requested(requested: &[B256], proof: &AccountProof) -> Result<(), ProofError> {
let got: std::collections::HashSet<B256> = proof.storage_proofs.iter().map(|p| p.key).collect();
for r in requested {
if !got.contains(r) {
return Err(ProofError::MissingSlot(*r));
}
}
let want: std::collections::HashSet<B256> = requested.iter().copied().collect();
for g in &got {
if !want.contains(g) {
return Err(ProofError::UnexpectedSlot(*g));
}
}
Ok(())
}
pub fn verify_account_proof(
state_root: B256,
proof: &AccountProof,
) -> Result<Vec<(B256, U256)>, ProofError> {
let account = TrieAccount {
nonce: proof.nonce,
balance: proof.balance,
storage_root: proof.storage_hash,
code_hash: proof.code_hash,
};
let account_exists = !(proof.nonce == 0
&& proof.balance.is_zero()
&& proof.storage_hash == EMPTY_ROOT_HASH
&& proof.code_hash == alloy_primitives::KECCAK256_EMPTY);
let expected_account = account_exists.then(|| alloy_rlp::encode(account));
guarded_verify(
state_root,
Nibbles::unpack(keccak256(proof.address)),
expected_account,
&proof.account_proof,
)
.map_err(|reason| ProofError::Account {
root: state_root,
reason,
})?;
let mut out = Vec::with_capacity(proof.storage_proofs.len());
for sp in &proof.storage_proofs {
let expected = (!sp.value.is_zero()).then(|| alloy_rlp::encode(sp.value));
guarded_verify(
proof.storage_hash,
Nibbles::unpack(keccak256(sp.key)),
expected,
&sp.proof,
)
.map_err(|reason| ProofError::Storage {
slot: sp.key,
root: proof.storage_hash,
reason,
})?;
out.push((sp.key, sp.value));
}
Ok(out)
}
fn guarded_verify(
root: B256,
key: Nibbles,
expected: Option<Vec<u8>>,
nodes: &[alloy_primitives::Bytes],
) -> Result<(), String> {
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
verify_proof(root, key, expected, nodes.iter())
}));
match r {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => Err(e.to_string()),
Err(_) => Err("malformed proof node (verifier panicked)".into()),
}
}