reth_trie_common/
proofs.rs

1//! Merkle trie proofs.
2
3use crate::{Nibbles, TrieAccount};
4use alloy_consensus::constants::KECCAK_EMPTY;
5use alloy_primitives::{
6    keccak256,
7    map::{hash_map, B256HashMap, B256HashSet, HashMap},
8    Address, Bytes, B256, U256,
9};
10use alloy_rlp::{encode_fixed_size, Decodable, EMPTY_STRING_CODE};
11use alloy_trie::{
12    nodes::TrieNode,
13    proof::{verify_proof, ProofNodes, ProofVerificationError},
14    TrieMask, EMPTY_ROOT_HASH,
15};
16use derive_more::derive::{Deref, DerefMut, From, Into, IntoIterator};
17use itertools::Itertools;
18use reth_primitives_traits::Account;
19
20/// Proof targets map.
21#[derive(Debug, Default, Clone, Deref, DerefMut, From, Into, IntoIterator)]
22pub struct MultiProofTargets(B256HashMap<B256HashSet>);
23
24impl MultiProofTargets {
25    /// Extends the proof targets map with another one.
26    pub fn extend(&mut self, other: Self) {
27        for (address, slots) in other.0 {
28            self.0.entry(address).or_default().extend(slots);
29        }
30    }
31
32    /// Extends the proof targets map with another one by reference.
33    pub fn extend_ref(&mut self, other: &Self) {
34        for (address, slots) in &other.0 {
35            self.0.entry(*address).or_default().extend(slots);
36        }
37    }
38}
39
40/// The state multiproof of target accounts and multiproofs of their storage tries.
41/// Multiproof is effectively a state subtrie that only contains the nodes
42/// in the paths of target accounts.
43#[derive(Clone, Default, Debug, PartialEq, Eq)]
44pub struct MultiProof {
45    /// State trie multiproof for requested accounts.
46    pub account_subtree: ProofNodes,
47    /// The hash masks of the branch nodes in the account proof.
48    pub branch_node_hash_masks: HashMap<Nibbles, TrieMask>,
49    /// Storage trie multiproofs.
50    pub storages: B256HashMap<StorageMultiProof>,
51}
52
53impl MultiProof {
54    /// Return the account proof nodes for the given account path.
55    pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, Bytes)> {
56        self.account_subtree.matching_nodes_sorted(path)
57    }
58
59    /// Return the storage proof nodes for the given storage slots of the account path.
60    pub fn storage_proof_nodes(
61        &self,
62        hashed_address: B256,
63        slots: impl IntoIterator<Item = B256>,
64    ) -> Vec<(B256, Vec<(Nibbles, Bytes)>)> {
65        self.storages
66            .get(&hashed_address)
67            .map(|storage_mp| {
68                slots
69                    .into_iter()
70                    .map(|slot| {
71                        let nibbles = Nibbles::unpack(slot);
72                        (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
73                    })
74                    .collect()
75            })
76            .unwrap_or_default()
77    }
78
79    /// Construct the account proof from the multiproof.
80    pub fn account_proof(
81        &self,
82        address: Address,
83        slots: &[B256],
84    ) -> Result<AccountProof, alloy_rlp::Error> {
85        let hashed_address = keccak256(address);
86        let nibbles = Nibbles::unpack(hashed_address);
87
88        // Retrieve the account proof.
89        let proof = self
90            .account_proof_nodes(&nibbles)
91            .into_iter()
92            .map(|(_, node)| node)
93            .collect::<Vec<_>>();
94
95        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
96        // then the node contains the encoded trie account.
97        let info = 'info: {
98            if let Some(last) = proof.last() {
99                if let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? {
100                    if nibbles.ends_with(&leaf.key) {
101                        let account = TrieAccount::decode(&mut &leaf.value[..])?;
102                        break 'info Some(Account {
103                            balance: account.balance,
104                            nonce: account.nonce,
105                            bytecode_hash: (account.code_hash != KECCAK_EMPTY)
106                                .then_some(account.code_hash),
107                        })
108                    }
109                }
110            }
111            None
112        };
113
114        // Retrieve proofs for requested storage slots.
115        let storage_multiproof = self.storages.get(&hashed_address);
116        let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
117        let mut storage_proofs = Vec::with_capacity(slots.len());
118        for slot in slots {
119            let proof = if let Some(multiproof) = &storage_multiproof {
120                multiproof.storage_proof(*slot)?
121            } else {
122                StorageProof::new(*slot)
123            };
124            storage_proofs.push(proof);
125        }
126        Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
127    }
128
129    /// Extends this multiproof with another one, merging both account and storage
130    /// proofs.
131    pub fn extend(&mut self, other: Self) {
132        self.account_subtree.extend_from(other.account_subtree);
133
134        self.branch_node_hash_masks.extend(other.branch_node_hash_masks);
135
136        for (hashed_address, storage) in other.storages {
137            match self.storages.entry(hashed_address) {
138                hash_map::Entry::Occupied(mut entry) => {
139                    debug_assert_eq!(entry.get().root, storage.root);
140                    let entry = entry.get_mut();
141                    entry.subtree.extend_from(storage.subtree);
142                    entry.branch_node_hash_masks.extend(storage.branch_node_hash_masks);
143                }
144                hash_map::Entry::Vacant(entry) => {
145                    entry.insert(storage);
146                }
147            }
148        }
149    }
150}
151
152/// The merkle multiproof of storage trie.
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct StorageMultiProof {
155    /// Storage trie root.
156    pub root: B256,
157    /// Storage multiproof for requested slots.
158    pub subtree: ProofNodes,
159    /// The hash masks of the branch nodes in the storage proof.
160    pub branch_node_hash_masks: HashMap<Nibbles, TrieMask>,
161}
162
163impl StorageMultiProof {
164    /// Create new storage multiproof for empty trie.
165    pub fn empty() -> Self {
166        Self {
167            root: EMPTY_ROOT_HASH,
168            subtree: ProofNodes::from_iter([(
169                Nibbles::default(),
170                Bytes::from([EMPTY_STRING_CODE]),
171            )]),
172            branch_node_hash_masks: HashMap::default(),
173        }
174    }
175
176    /// Return storage proofs for the target storage slot (unhashed).
177    pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
178        let nibbles = Nibbles::unpack(keccak256(slot));
179
180        // Retrieve the storage proof.
181        let proof = self
182            .subtree
183            .matching_nodes_iter(&nibbles)
184            .sorted_by(|a, b| a.0.cmp(b.0))
185            .map(|(_, node)| node.clone())
186            .collect::<Vec<_>>();
187
188        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
189        // then the node contains the encoded slot value.
190        let value = 'value: {
191            if let Some(last) = proof.last() {
192                if let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? {
193                    if nibbles.ends_with(&leaf.key) {
194                        break 'value U256::decode(&mut &leaf.value[..])?
195                    }
196                }
197            }
198            U256::ZERO
199        };
200
201        Ok(StorageProof { key: slot, nibbles, value, proof })
202    }
203}
204
205/// The merkle proof with the relevant account info.
206#[derive(Clone, PartialEq, Eq, Debug)]
207#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
208#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))]
209pub struct AccountProof {
210    /// The address associated with the account.
211    pub address: Address,
212    /// Account info.
213    pub info: Option<Account>,
214    /// Array of rlp-serialized merkle trie nodes which starting from the root node and
215    /// following the path of the hashed address as key.
216    pub proof: Vec<Bytes>,
217    /// The storage trie root.
218    pub storage_root: B256,
219    /// Array of storage proofs as requested.
220    pub storage_proofs: Vec<StorageProof>,
221}
222
223#[cfg(feature = "eip1186")]
224impl AccountProof {
225    /// Convert into an EIP-1186 account proof response
226    pub fn into_eip1186_response(
227        self,
228        slots: Vec<alloy_serde::JsonStorageKey>,
229    ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
230        let info = self.info.unwrap_or_default();
231        alloy_rpc_types_eth::EIP1186AccountProofResponse {
232            address: self.address,
233            balance: info.balance,
234            code_hash: info.get_bytecode_hash(),
235            nonce: info.nonce,
236            storage_hash: self.storage_root,
237            account_proof: self.proof,
238            storage_proof: self
239                .storage_proofs
240                .into_iter()
241                .filter_map(|proof| {
242                    let input_slot = slots.iter().find(|s| s.as_b256() == proof.key)?;
243                    Some(proof.into_eip1186_proof(*input_slot))
244                })
245                .collect(),
246        }
247    }
248}
249
250impl Default for AccountProof {
251    fn default() -> Self {
252        Self::new(Address::default())
253    }
254}
255
256impl AccountProof {
257    /// Create new account proof entity.
258    pub const fn new(address: Address) -> Self {
259        Self {
260            address,
261            info: None,
262            proof: Vec::new(),
263            storage_root: EMPTY_ROOT_HASH,
264            storage_proofs: Vec::new(),
265        }
266    }
267
268    /// Verify the storage proofs and account proof against the provided state root.
269    pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
270        // Verify storage proofs.
271        for storage_proof in &self.storage_proofs {
272            storage_proof.verify(self.storage_root)?;
273        }
274
275        // Verify the account proof.
276        let expected = if self.info.is_none() && self.storage_root == EMPTY_ROOT_HASH {
277            None
278        } else {
279            Some(alloy_rlp::encode(TrieAccount::from((
280                self.info.unwrap_or_default(),
281                self.storage_root,
282            ))))
283        };
284        let nibbles = Nibbles::unpack(keccak256(self.address));
285        verify_proof(root, nibbles, expected, &self.proof)
286    }
287}
288
289/// The merkle proof of the storage entry.
290#[derive(Clone, PartialEq, Eq, Default, Debug)]
291#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
292pub struct StorageProof {
293    /// The raw storage key.
294    pub key: B256,
295    /// The hashed storage key nibbles.
296    pub nibbles: Nibbles,
297    /// The storage value.
298    pub value: U256,
299    /// Array of rlp-serialized merkle trie nodes which starting from the storage root node and
300    /// following the path of the hashed storage slot as key.
301    pub proof: Vec<Bytes>,
302}
303
304impl StorageProof {
305    /// Convert into an EIP-1186 storage proof
306    #[cfg(feature = "eip1186")]
307    pub fn into_eip1186_proof(
308        self,
309        slot: alloy_serde::JsonStorageKey,
310    ) -> alloy_rpc_types_eth::EIP1186StorageProof {
311        alloy_rpc_types_eth::EIP1186StorageProof { key: slot, value: self.value, proof: self.proof }
312    }
313}
314
315impl StorageProof {
316    /// Create new storage proof from the storage slot.
317    pub fn new(key: B256) -> Self {
318        let nibbles = Nibbles::unpack(keccak256(key));
319        Self { key, nibbles, ..Default::default() }
320    }
321
322    /// Create new storage proof from the storage slot and its pre-hashed image.
323    pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
324        Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
325    }
326
327    /// Create new storage proof from the storage slot and its pre-hashed image.
328    pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
329        Self { key, nibbles, ..Default::default() }
330    }
331
332    /// Set proof nodes on storage proof.
333    pub fn with_proof(mut self, proof: Vec<Bytes>) -> Self {
334        self.proof = proof;
335        self
336    }
337
338    /// Verify the proof against the provided storage root.
339    pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
340        let expected =
341            if self.value.is_zero() { None } else { Some(encode_fixed_size(&self.value).to_vec()) };
342        verify_proof(root, self.nibbles.clone(), expected, &self.proof)
343    }
344}
345
346/// Implementation of hasher using our keccak256 hashing function
347/// for compatibility with `triehash` crate.
348#[cfg(any(test, feature = "test-utils"))]
349pub mod triehash {
350    use alloy_primitives::{keccak256, B256};
351    use alloy_rlp::RlpEncodable;
352    use hash_db::Hasher;
353    use plain_hasher::PlainHasher;
354
355    /// A [Hasher] that calculates a keccak256 hash of the given data.
356    #[derive(Default, Debug, Clone, PartialEq, Eq, RlpEncodable)]
357    #[non_exhaustive]
358    pub struct KeccakHasher;
359
360    #[cfg(any(test, feature = "test-utils"))]
361    impl Hasher for KeccakHasher {
362        type Out = B256;
363        type StdHasher = PlainHasher;
364
365        const LENGTH: usize = 32;
366
367        fn hash(x: &[u8]) -> Self::Out {
368            keccak256(x)
369        }
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[test]
378    fn test_multiproof_extend_account_proofs() {
379        let mut proof1 = MultiProof::default();
380        let mut proof2 = MultiProof::default();
381
382        let addr1 = B256::random();
383        let addr2 = B256::random();
384
385        proof1.account_subtree.insert(
386            Nibbles::unpack(addr1),
387            alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
388        );
389        proof2.account_subtree.insert(
390            Nibbles::unpack(addr2),
391            alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
392        );
393
394        proof1.extend(proof2);
395
396        assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr1)));
397        assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr2)));
398    }
399
400    #[test]
401    fn test_multiproof_extend_storage_proofs() {
402        let mut proof1 = MultiProof::default();
403        let mut proof2 = MultiProof::default();
404
405        let addr = B256::random();
406        let root = B256::random();
407
408        let mut subtree1 = ProofNodes::default();
409        subtree1.insert(
410            Nibbles::from_nibbles(vec![0]),
411            alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
412        );
413        proof1.storages.insert(
414            addr,
415            StorageMultiProof {
416                root,
417                subtree: subtree1,
418                branch_node_hash_masks: HashMap::default(),
419            },
420        );
421
422        let mut subtree2 = ProofNodes::default();
423        subtree2.insert(
424            Nibbles::from_nibbles(vec![1]),
425            alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
426        );
427        proof2.storages.insert(
428            addr,
429            StorageMultiProof {
430                root,
431                subtree: subtree2,
432                branch_node_hash_masks: HashMap::default(),
433            },
434        );
435
436        proof1.extend(proof2);
437
438        let storage = proof1.storages.get(&addr).unwrap();
439        assert_eq!(storage.root, root);
440        assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![0])));
441        assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![1])));
442    }
443}