1use crate::AccountProof;
7use alloy_primitives::{keccak256, B256, U256};
8use alloy_trie::{proof::verify_proof, Nibbles, TrieAccount, EMPTY_ROOT_HASH};
9
10#[derive(Debug, thiserror::Error)]
12pub enum ProofError {
13 #[error("account proof invalid against state_root {root}: {reason}")]
15 Account {
16 root: B256,
18 reason: String,
20 },
21 #[error("storage proof for slot {slot} invalid against storage_root {root}: {reason}")]
23 Storage {
24 slot: B256,
26 root: B256,
28 reason: String,
30 },
31 #[error("proof did not include slot {0}")]
33 MissingSlot(B256),
34 #[error("proof included unrequested slot {0}")]
36 UnexpectedSlot(B256),
37}
38
39pub fn check_requested(requested: &[B256], proof: &AccountProof) -> Result<(), ProofError> {
43 let got: std::collections::HashSet<B256> = proof.storage_proofs.iter().map(|p| p.key).collect();
44 for r in requested {
45 if !got.contains(r) {
46 return Err(ProofError::MissingSlot(*r));
47 }
48 }
49 let want: std::collections::HashSet<B256> = requested.iter().copied().collect();
50 for g in &got {
51 if !want.contains(g) {
52 return Err(ProofError::UnexpectedSlot(*g));
53 }
54 }
55 Ok(())
56}
57
58pub fn verify_account_proof(
63 state_root: B256,
64 proof: &AccountProof,
65) -> Result<Vec<(B256, U256)>, ProofError> {
66 let account = TrieAccount {
67 nonce: proof.nonce,
68 balance: proof.balance,
69 storage_root: proof.storage_hash,
70 code_hash: proof.code_hash,
71 };
72 let account_exists = !(proof.nonce == 0
74 && proof.balance.is_zero()
75 && proof.storage_hash == EMPTY_ROOT_HASH
76 && proof.code_hash == alloy_primitives::KECCAK256_EMPTY);
77 let expected_account = account_exists.then(|| alloy_rlp::encode(account));
78 verify_proof(
79 state_root,
80 Nibbles::unpack(keccak256(proof.address)),
81 expected_account,
82 proof.account_proof.iter(),
83 )
84 .map_err(|e| ProofError::Account {
85 root: state_root,
86 reason: e.to_string(),
87 })?;
88
89 let mut out = Vec::with_capacity(proof.storage_proofs.len());
90 for sp in &proof.storage_proofs {
91 let expected = (!sp.value.is_zero()).then(|| alloy_rlp::encode(sp.value));
92 verify_proof(
93 proof.storage_hash,
94 Nibbles::unpack(keccak256(sp.key)),
95 expected,
96 sp.proof.iter(),
97 )
98 .map_err(|e| ProofError::Storage {
99 slot: sp.key,
100 root: proof.storage_hash,
101 reason: e.to_string(),
102 })?;
103 out.push((sp.key, sp.value));
104 }
105 Ok(out)
106}