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 guarded_verify(
79 state_root,
80 Nibbles::unpack(keccak256(proof.address)),
81 expected_account,
82 &proof.account_proof,
83 )
84 .map_err(|reason| ProofError::Account {
85 root: state_root,
86 reason,
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 guarded_verify(
93 proof.storage_hash,
94 Nibbles::unpack(keccak256(sp.key)),
95 expected,
96 &sp.proof,
97 )
98 .map_err(|reason| ProofError::Storage {
99 slot: sp.key,
100 root: proof.storage_hash,
101 reason,
102 })?;
103 out.push((sp.key, sp.value));
104 }
105 Ok(out)
106}
107
108fn guarded_verify(
113 root: B256,
114 key: Nibbles,
115 expected: Option<Vec<u8>>,
116 nodes: &[alloy_primitives::Bytes],
117) -> Result<(), String> {
118 let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
119 verify_proof(root, key, expected, nodes.iter())
120 }));
121 match r {
122 Ok(Ok(())) => Ok(()),
123 Ok(Err(e)) => Err(e.to_string()),
124 Err(_) => Err("malformed proof node (verifier panicked)".into()),
125 }
126}