bankai_verify/evm/
execution.rs

1extern crate alloc;
2use alloc::vec::Vec;
3
4use alloy_rlp::{Decodable, Encodable};
5use bankai_types::fetch::evm::execution::{AccountProof, ExecutionHeaderProof, TxProof};
6use bankai_types::verify::evm::execution::{Account, ExecutionHeader, TxEnvelope};
7
8use alloy_primitives::{keccak256, FixedBytes};
9use alloy_rlp::encode as rlp_encode;
10use alloy_trie::{proof::verify_proof as mpt_verify, Nibbles};
11
12use crate::bankai::mmr::MmrVerifier;
13use crate::VerifyError;
14
15/// Verifier for EVM execution layer proofs
16///
17/// Provides methods to verify execution headers, account states, and transactions
18/// against trusted MMR roots and header state roots. All verifications are cryptographically
19/// sound and establish trust through the STWO proof → MMR proof → Merkle proof chain.
20pub struct ExecutionVerifier;
21
22impl ExecutionVerifier {
23    /// Verifies an execution layer header using an MMR inclusion proof
24    ///
25    /// This method establishes trust in an execution header by:
26    /// 1. Verifying the MMR root matches the expected root from the STWO proof
27    /// 2. Verifying the MMR inclusion proof
28    /// 3. Verifying the header hash matches the value committed in the MMR
29    ///
30    /// Once verified, the header can be trusted and used to verify accounts and transactions.
31    ///
32    /// # Arguments
33    ///
34    /// * `proof` - The execution header proof containing the header and MMR inclusion proof
35    /// * `root` - The trusted MMR root from the verified STWO proof
36    ///
37    /// # Returns
38    ///
39    /// Returns the verified `ExecutionHeader` containing all block data (number, timestamp,
40    /// state root, transactions root, etc.)
41    ///
42    /// # Errors
43    ///
44    /// Returns an error if:
45    /// - `InvalidMmrRoot`: The MMR root in the proof doesn't match the expected root
46    /// - `InvalidMmrProof`: The MMR inclusion proof is invalid
47    /// - `InvalidHeaderHash`: The header hash doesn't match the MMR commitment
48    ///
49    /// # Example
50    ///
51    /// ```no_run
52    /// use bankai_verify::evm::ExecutionVerifier;
53    /// use bankai_types::fetch::evm::execution::ExecutionHeaderProof;
54    /// use alloy_primitives::FixedBytes;
55    ///
56    /// # fn example(proof: ExecutionHeaderProof, mmr_root: FixedBytes<32>) -> Result<(), Box<dyn std::error::Error>> {
57    /// let verified_header = ExecutionVerifier::verify_header_proof(&proof, mmr_root)?;
58    /// println!("Verified block {}", verified_header.number);
59    /// println!("State root: {:?}", verified_header.state_root);
60    /// # Ok(())
61    /// # }
62    /// ```
63    pub fn verify_header_proof(
64        proof: &ExecutionHeaderProof,
65        root: FixedBytes<32>,
66    ) -> Result<ExecutionHeader, VerifyError> {
67        if proof.mmr_proof.root != root {
68            return Err(VerifyError::InvalidMmrRoot);
69        }
70
71        MmrVerifier::verify_mmr_proof(&proof.mmr_proof.clone())
72            .map_err(|_| VerifyError::InvalidMmrProof)?;
73
74        let hash = proof.header.inner.hash_slow();
75        if hash != proof.mmr_proof.header_hash {
76            return Err(VerifyError::InvalidHeaderHash);
77        }
78
79        Ok(proof.header.clone().inner)
80    }
81
82    /// Verifies an account's state using a Merkle Patricia Trie proof
83    ///
84    /// This method verifies an account's state (balance, nonce, code hash, storage root)
85    /// against a previously verified execution header. The verification uses a Merkle Patricia
86    /// Trie proof to establish that the account state is included in the header's state root.
87    ///
88    /// # Arguments
89    ///
90    /// * `account_proof` - The account proof containing the account state and MPT proof
91    /// * `headers` - List of previously verified execution headers. Must contain the header
92    ///   for the block number referenced in the account proof
93    ///
94    /// # Returns
95    ///
96    /// Returns the verified `Account` containing:
97    /// - Balance (in wei)
98    /// - Nonce (transaction count)
99    /// - Code hash (contract code hash, or empty for EOAs)
100    /// - Storage root (Merkle root of contract storage)
101    ///
102    /// # Errors
103    ///
104    /// Returns an error if:
105    /// - `InvalidExecutionHeaderProof`: The referenced header is not in the verified headers list
106    /// - `InvalidStateRoot`: The state root in the proof doesn't match the header's state root
107    /// - `InvalidAccountProof`: The MPT proof verification failed
108    ///
109    /// # Example
110    ///
111    /// ```no_run
112    /// use bankai_verify::evm::ExecutionVerifier;
113    /// use bankai_types::fetch::evm::execution::AccountProof;
114    /// use bankai_types::verify::evm::execution::ExecutionHeader;
115    ///
116    /// # fn example(
117    /// #     account_proof: AccountProof,
118    /// #     verified_headers: Vec<ExecutionHeader>
119    /// # ) -> Result<(), Box<dyn std::error::Error>> {
120    /// let account = ExecutionVerifier::verify_account_proof(&account_proof, &verified_headers)?;
121    /// println!("Account balance: {} wei", account.balance);
122    /// println!("Account nonce: {}", account.nonce);
123    /// # Ok(())
124    /// # }
125    /// ```
126    pub fn verify_account_proof(
127        account_proof: &AccountProof,
128        headers: &[ExecutionHeader],
129    ) -> Result<Account, VerifyError> {
130        let header = headers
131            .iter()
132            .find(|h| h.number == account_proof.block_number)
133            .ok_or(VerifyError::InvalidExecutionHeaderProof)?;
134
135        if header.state_root != account_proof.state_root {
136            return Err(VerifyError::InvalidStateRoot);
137        }
138
139        let expected_value = rlp_encode(account_proof.account).to_vec();
140        let key = Nibbles::unpack(keccak256(account_proof.address));
141
142        mpt_verify(
143            header.state_root,
144            key,
145            Some(expected_value),
146            account_proof.mpt_proof.iter(),
147        )
148        .map_err(|_| VerifyError::InvalidAccountProof)?;
149
150        Ok(account_proof.account)
151    }
152
153    /// Verifies one or more storage slots from the same contract using Merkle Patricia Trie proofs.
154    ///
155    /// This method establishes that storage slot values are committed in the state of a given
156    /// block by:
157    /// 1. Verifying the contract account is included in the block's state trie (against the
158    ///    verified header's `state_root`)
159    /// 2. Verifying each storage slot is included in the contract's storage trie (against the
160    ///    account's `storage_root`)
161    ///
162    /// # Arguments
163    ///
164    /// * `slot_proof` - The storage slot proof containing the account proof and individual
165    ///   storage slot proofs
166    /// * `headers` - List of previously verified execution headers. Must contain the header
167    ///   for the block number referenced in the storage slot proof
168    ///
169    /// # Returns
170    ///
171    /// Returns a vector of verified (slot_key, slot_value) pairs in the same order as the input.
172    ///
173    /// # Errors
174    ///
175    /// Returns an error if:
176    /// - `InvalidExecutionHeaderProof`: The referenced header is not in the verified headers list
177    /// - `InvalidStateRoot`: The state root in the proof doesn't match the header's state root
178    /// - `InvalidAccountProof`: The account MPT proof verification failed
179    /// - `InvalidStorageProof`: Any storage slot MPT proof verification failed
180    ///
181    /// # Example
182    ///
183    /// ```no_run
184    /// use bankai_verify::evm::ExecutionVerifier;
185    /// use bankai_types::fetch::evm::execution::StorageSlotProof;
186    /// use bankai_types::verify::evm::execution::ExecutionHeader;
187    ///
188    /// # fn example(
189    /// #     slot_proof: StorageSlotProof,
190    /// #     verified_headers: Vec<ExecutionHeader>
191    /// # ) -> Result<(), Box<dyn std::error::Error>> {
192    /// let values = ExecutionVerifier::verify_storage_slot_proof(&slot_proof, &verified_headers)?;
193    /// for (key, value) in values {
194    ///     println!("Slot {:?} = {}", key, value);
195    /// }
196    /// # Ok(())
197    /// # }
198    /// ```
199    pub fn verify_storage_slot_proof(
200        slot_proof: &bankai_types::fetch::evm::execution::StorageSlotProof,
201        headers: &[ExecutionHeader],
202    ) -> Result<Vec<(alloy_primitives::U256, alloy_primitives::U256)>, VerifyError> {
203        let header = headers
204            .iter()
205            .find(|h| h.number == slot_proof.block_number)
206            .ok_or(VerifyError::InvalidExecutionHeaderProof)?;
207
208        if header.state_root != slot_proof.state_root {
209            return Err(VerifyError::InvalidStateRoot);
210        }
211
212        let expected_account = rlp_encode(slot_proof.account).to_vec();
213        let account_key = Nibbles::unpack(keccak256(slot_proof.address));
214        mpt_verify(
215            header.state_root,
216            account_key,
217            Some(expected_account),
218            slot_proof.account_mpt_proof.iter(),
219        )
220        .map_err(|_| VerifyError::InvalidAccountProof)?;
221
222        let mut results = Vec::with_capacity(slot_proof.slots.len());
223        for slot in &slot_proof.slots {
224            Self::verify_storage_slot_entry(
225                slot_proof.account.storage_root,
226                slot.slot_key,
227                slot.slot_value,
228                &slot.storage_mpt_proof,
229            )?;
230            results.push((slot.slot_key, slot.slot_value));
231        }
232
233        Ok(results)
234    }
235
236    /// Internal helper to verify a single storage slot entry against a storage root
237    fn verify_storage_slot_entry(
238        storage_root: FixedBytes<32>,
239        slot_key: alloy_primitives::U256,
240        slot_value: alloy_primitives::U256,
241        storage_mpt_proof: &[alloy_primitives::Bytes],
242    ) -> Result<(), VerifyError> {
243        let slot_key_bytes = slot_key.to_be_bytes::<32>();
244        let storage_key = Nibbles::unpack(keccak256(slot_key_bytes));
245        let expected_storage_value = if slot_value.is_zero() {
246            None
247        } else {
248            Some(rlp_encode(slot_value).to_vec())
249        };
250
251        mpt_verify(
252            storage_root,
253            storage_key,
254            expected_storage_value,
255            storage_mpt_proof.iter(),
256        )
257        .map_err(|_| VerifyError::InvalidStorageProof)?;
258
259        Ok(())
260    }
261
262    /// Verifies a transaction using a Merkle Patricia Trie proof
263    ///
264    /// This method verifies that a transaction was included in a specific block by validating
265    /// an MPT proof against a previously verified execution header. The proof establishes that
266    /// the transaction exists at a specific index in the block's transaction list.
267    ///
268    /// # Arguments
269    ///
270    /// * `proof` - The transaction proof containing the encoded transaction and MPT proof
271    /// * `headers` - List of previously verified execution headers. Must contain the header
272    ///   for the block number referenced in the transaction proof
273    ///
274    /// # Returns
275    ///
276    /// Returns the verified `TxEnvelope` containing the full transaction data including:
277    /// - Transaction type (Legacy, EIP-1559, EIP-2930, etc.)
278    /// - From/to addresses
279    /// - Value transferred
280    /// - Gas limit and gas price
281    /// - Input data
282    /// - Signature (v, r, s)
283    ///
284    /// # Errors
285    ///
286    /// Returns an error if:
287    /// - `InvalidExecutionHeaderProof`: The referenced header is not in the verified headers list
288    /// - `InvalidTxProof`: The MPT proof verification failed
289    /// - `InvalidRlpDecode`: The transaction data could not be decoded
290    ///
291    /// # Example
292    ///
293    /// ```no_run
294    /// use bankai_verify::evm::ExecutionVerifier;
295    /// use bankai_types::fetch::evm::execution::TxProof;
296    /// use bankai_types::verify::evm::execution::ExecutionHeader;
297    ///
298    /// # fn example(
299    /// #     tx_proof: TxProof,
300    /// #     verified_headers: Vec<ExecutionHeader>
301    /// # ) -> Result<(), Box<dyn std::error::Error>> {
302    /// let tx = ExecutionVerifier::verify_tx_proof(&tx_proof, &verified_headers)?;
303    /// println!("Verified transaction in block {}", tx_proof.block_number);
304    /// # Ok(())
305    /// # }
306    /// ```
307    pub fn verify_tx_proof(
308        proof: &TxProof,
309        headers: &[ExecutionHeader],
310    ) -> Result<TxEnvelope, VerifyError> {
311        let header = headers
312            .iter()
313            .find(|h| h.number == proof.block_number)
314            .ok_or(VerifyError::InvalidExecutionHeaderProof)?;
315
316        let mut rlp_tx_index = Vec::new();
317        proof.tx_index.encode(&mut rlp_tx_index);
318        let key = Nibbles::unpack(&rlp_tx_index);
319
320        mpt_verify(
321            header.transactions_root,
322            key,
323            Some(proof.encoded_tx.clone()),
324            proof.proof.iter(),
325        )
326        .map_err(|_| VerifyError::InvalidTxProof)?;
327
328        let tx = TxEnvelope::decode(&mut proof.encoded_tx.as_slice())
329            .map_err(|_| VerifyError::InvalidRlpDecode)?;
330
331        Ok(tx)
332    }
333}