diff --git a/Cargo.lock b/Cargo.lock index 77929f6..610188f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1676,6 +1676,7 @@ dependencies = [ "eth_trie 0.5.0 (git+https://github.com/jonas089/eth-trie.rs)", "serde", "serde_json", + "tiny-keccak 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 2.0.2 (git+https://github.com/sp1-patches/tiny-keccak)", ] diff --git a/README.md b/README.md index e1db4c5..5a0d430 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,81 @@ -# Prove merkle paths for EVM transactions in SP1 +# 🔐 Complete Library to prove EVM state in ZK, to cryptographically verify storage, transactions, receipts and accounts! +This library exposes functions and ZK circuits (SP1, Risc0) to obtain, verify and prove query infromation from `Ethereum` clients. -> [!WARNING] -> Not production ready, under heavy development +# Overview of provided functions -## Prove a real Ethereum mainnet Transaction in SP1 -`cargo test --bin prover test_sp1_merkle_proof_circuit --release`: +| account | storage | receipt | transaction | +| --- | --- | --- | --- | +| Verify that an account exists in the Ethereum Trie | Verify a value stored under an account or smart contract | Verify a receipt or the entire receipt trie of a block | Verify native Ethereum transactions | + +- `accounts`: any Ethereum address with a Balance > 0 +- `receipts`: data related to events (for example ERC20 transfer information) + +# Obtain a Merkle Proof for a value in Ethereum State +For each of these values in storage a function is provided that helps obtain a `merkle proof` from the Ethereum client using `alloy rpc`: + +`trie-utils/src/proofs/*` +- account.rs +- receipt.rs +- storage.rs +- transaction.rs + +For example `transaction.rs` returns a `merkle proof` for an individual native Ethereum transaction: ```rust -#[tokio::test] -async fn test_sp1_merkle_proof_circuit() { - sp1_sdk::utils::setup_logger(); - let client = ProverClient::new(); - let mut stdin = SP1Stdin::new(); - let proof_input = serde_json::to_vec( - &get_ethereum_transaction_proof_inputs( - 0u32, - "0x8230bd00f36e52e68dd4a46bfcddeceacbb689d808327f4c76dbdf8d33d58ca8", +pub async fn get_ethereum_transaction_proof_inputs( + target_index: u32, + block_hash: &str, +) -> MerkleProofInput { + let key = load_infura_key_from_env(); + println!("Key: {}", key); + let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block = provider + .get_block_by_hash( + B256::from_str(block_hash).unwrap(), + alloy::rpc::types::BlockTransactionsKind::Full, ) - .await, - ) - .unwrap(); - stdin.write(&proof_input); - let (pk, vk) = client.setup(MERKLE_ELF); - let proof = client - .prove(&pk, stdin) - .run() - .expect("failed to generate proof"); - let transaction_hash = proof.public_values.to_vec(); - println!( - "Successfully generated proof for Transaction: {:?}", - transaction_hash - ); - client.verify(&proof, &vk).expect("failed to verify proof"); - println!( - "Successfully verified proof for Transaction: {:?}", - transaction_hash - ); + .await + .expect("Failed to get Block!") + .expect("Block not found!"); + let memdb = Arc::new(MemoryDB::new(true)); + let mut trie = EthTrie::new(memdb.clone()); + + for (index, tx) in block.transactions.txns().enumerate() { + let path = alloy_rlp::encode(index); + let mut encoded_tx = vec![]; + match &tx.inner { + TxEnvelope::Legacy(tx) => tx.eip2718_encode(&mut encoded_tx), + TxEnvelope::Eip2930(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + TxEnvelope::Eip1559(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + TxEnvelope::Eip4844(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + TxEnvelope::Eip7702(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + _ => panic!("Unsupported transaction type"), + } + trie.insert(&path, &encoded_tx).expect("Failed to insert"); + } + + trie.root_hash().unwrap(); + let tx_key: Vec = alloy_rlp::encode(target_index); + let proof: Vec> = trie.get_proof(&tx_key).unwrap(); + MerkleProofInput { + proof, + root_hash: block.header.transactions_root.to_vec(), + key: tx_key, + } } ``` -## Deep dive 1: Circuit -The circuit calls the simple merkle proof verification function that depends on the `keccak` precompile: +# Verify a Merkle Proof against a trusted State Root +The `merkle proof` is then be verified using the `verify_merkle_proof` function found in `crypto-ops/lib.rs`: ```rust pub fn verify_merkle_proof(root_hash: B256, proof: Vec>, key: &[u8]) -> Vec { @@ -49,29 +84,40 @@ pub fn verify_merkle_proof(root_hash: B256, proof: Vec>, key: &[u8]) -> let hash: B256 = digest_keccak(&node_encoded).into(); proof_db.insert(hash.as_slice(), node_encoded).unwrap(); } - let trie = EthTrie::from(proof_db, root_hash).expect("Invalid merkle proof"); + let mut trie = EthTrie::from(proof_db, root_hash).expect("Invalid merkle proof"); + assert_eq!(root_hash, trie.root_hash().unwrap()); trie.verify_proof(root_hash, key, proof) .expect("Failed to verify Merkle Proof") .expect("Key does not exist!") } ``` -If the merkle proof is invalid for the given root hash the circuit will revert and there will be no valid -proof. - -## Deep dive 2: Ethereum Merkle Trie -This implementation depends on the [eth_trie](https://crates.io/crates/eth_trie) crate. -`eth_trie` is a reference implementation of the merkle patricia trie. -In the `rpc` crate full integration tests for constructing the trie can be found. -Click [here](https://github.com/jonas089/sp1-eth-tx/blob/master/rpc/src/lib.rs) to review the code. +This function checks that the trie root matches the `trusted root` obtained from the [Light Client](https://github.com/jonas089/spectre-rad). +And that the data we claim exists in the Trie is actually present at the specified path (=`key`). +# Generate a ZK proof for the validity of a Merkle Proof +In order to prove our Merkle verification in ZK, we can use the circuit located in `circuits/merkle-proof/src/main.rs`: +```rust +#![no_main] +sp1_zkvm::entrypoint!(main); +use crypto_ops::{types::MerkleProofInput, verify_merkle_proof}; +pub fn main() { + let merkle_proof: MerkleProofInput = + serde_json::from_slice(&sp1_zkvm::io::read::>()).unwrap(); + let output = verify_merkle_proof( + merkle_proof.root_hash.as_slice().try_into().unwrap(), + merkle_proof.proof.clone(), + &merkle_proof.key, + ); + sp1_zkvm::io::commit_slice(&output); +} +``` -## Benchmarks on M3 Macbook Pro +To try this against a real Ethereum Transaction for testing purposes, run: -### Eth-trie (not perfectly optimized) using Keccak precompile +`cargo test --bin prover test_generate_transaction_zk_proof -F sp1` -| 10 Transactions | 20 Transactions | 30 Transactions | -| ------------- | ------------- | ------------- | -| - | - | - | \ No newline at end of file +> [!NOTE] +> The feature flag `sp1` tells the compiler to leverage the `keccak` precompile for hash acceleration in the ZK circuit. \ No newline at end of file diff --git a/circuits/Cargo.lock b/circuits/Cargo.lock index e6adb40..36b86f8 100644 --- a/circuits/Cargo.lock +++ b/circuits/Cargo.lock @@ -378,7 +378,7 @@ dependencies = [ "eth_trie", "serde", "serde_json", - "tiny-keccak 2.0.2 (git+https://github.com/sp1-patches/tiny-keccak)", + "tiny-keccak 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/circuits/elf/riscv32im-succinct-zkvm-elf b/circuits/elf/riscv32im-succinct-zkvm-elf index 56e6427..deb7e86 100755 Binary files a/circuits/elf/riscv32im-succinct-zkvm-elf and b/circuits/elf/riscv32im-succinct-zkvm-elf differ diff --git a/circuits/merkle-proof/src/main.rs b/circuits/merkle-proof/src/main.rs index 7900efe..ba3caf3 100644 --- a/circuits/merkle-proof/src/main.rs +++ b/circuits/merkle-proof/src/main.rs @@ -1,6 +1,6 @@ #![no_main] sp1_zkvm::entrypoint!(main); -use crypto_ops::{verify_merkle_proof, MerkleProofInput}; +use crypto_ops::{types::MerkleProofInput, verify_merkle_proof}; pub fn main() { let merkle_proof: MerkleProofInput = serde_json::from_slice(&sp1_zkvm::io::read::>()).unwrap(); diff --git a/crypto-ops/Cargo.toml b/crypto-ops/Cargo.toml index abbb44d..2ea926a 100644 --- a/crypto-ops/Cargo.toml +++ b/crypto-ops/Cargo.toml @@ -4,10 +4,14 @@ version = "0.1.0" edition = "2021" [dependencies] -tiny-keccak = { git = "https://github.com/sp1-patches/tiny-keccak", features = [ +tiny-keccak = "2.0.2" +tiny-keccak-sp1 = { package = "tiny-keccak", git = "https://github.com/sp1-patches/tiny-keccak", features = [ "keccak", -] } +], optional = true } alloy-primitives = "0.8.12" eth_trie = { git = "https://github.com/jonas089/eth-trie.rs" } serde = { version = "1", features = ["derive"] } serde_json = "1" + +[features] +sp1 = ["tiny-keccak-sp1"] diff --git a/crypto-ops/src/keccak.rs b/crypto-ops/src/keccak.rs index 46b67ed..6dfa214 100644 --- a/crypto-ops/src/keccak.rs +++ b/crypto-ops/src/keccak.rs @@ -1,11 +1,12 @@ +#[cfg(not(feature = "sp1"))] use tiny_keccak::{Hasher, Keccak}; +#[cfg(feature = "sp1")] +use tiny_keccak_sp1::{Hasher, Keccak}; pub fn digest_keccak(bytes: &[u8]) -> [u8; 32] { let mut hasher = Keccak::v256(); let mut output = [0u8; 32]; - hasher.update(bytes); hasher.finalize(&mut output); - output } diff --git a/crypto-ops/src/lib.rs b/crypto-ops/src/lib.rs index 3d7a9a7..bcf444c 100644 --- a/crypto-ops/src/lib.rs +++ b/crypto-ops/src/lib.rs @@ -1,13 +1,10 @@ -use std::sync::Arc; - use alloy_primitives::B256; use eth_trie::{EthTrie, MemoryDB, Trie, DB}; use keccak::digest_keccak; -use serde::{Deserialize, Serialize}; +use std::sync::Arc; pub mod keccak; +pub mod types; -// verify single transaction proof -// utilizes keccak precompile for SP1 pub fn verify_merkle_proof(root_hash: B256, proof: Vec>, key: &[u8]) -> Vec { let proof_db = Arc::new(MemoryDB::new(true)); for node_encoded in proof.clone().into_iter() { @@ -24,10 +21,3 @@ pub fn verify_merkle_proof(root_hash: B256, proof: Vec>, key: &[u8]) -> .expect("Failed to verify Merkle Proof") .expect("Key does not exist!") } - -#[derive(Serialize, Deserialize)] -pub struct MerkleProofInput { - pub proof: Vec>, - pub root_hash: Vec, - pub key: Vec, -} diff --git a/crypto-ops/src/types.rs b/crypto-ops/src/types.rs new file mode 100644 index 0000000..149a2d2 --- /dev/null +++ b/crypto-ops/src/types.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct MerkleProofInput { + pub proof: Vec>, + pub root_hash: Vec, + pub key: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct MerkleProofListInput { + pub account_proof: Vec>, + pub storage_proofs: Vec>>, + pub root_hash: Vec, + pub account_key: Vec, + pub storage_keys: Vec>, +} diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 6d27163..2583f78 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -21,3 +21,6 @@ tokio = { version = "1.4.11", features = ["full"] } [build-dependencies] sp1-helper = "3.0.0" + +[features] +sp1 = ["crypto-ops/sp1"] diff --git a/prover/src/bin/main.rs b/prover/src/bin/main.rs index 69b1d97..e3410d4 100644 --- a/prover/src/bin/main.rs +++ b/prover/src/bin/main.rs @@ -1,7 +1,5 @@ use sp1_sdk::include_elf; -/// The ELF (executable and linkable format) file for the Succinct RISC-V zkVM. pub const MERKLE_ELF: &[u8] = include_elf!("merkle-proof"); - fn main() { todo!("implement as client or lib") } @@ -12,12 +10,10 @@ mod tests { use sp1_sdk::{ProverClient, SP1Stdin}; use trie_utils::get_ethereum_transaction_proof_inputs; #[tokio::test] - async fn test_sp1_merkle_proof_circuit() { + async fn test_generate_transaction_zk_proof() { sp1_sdk::utils::setup_logger(); let client = ProverClient::new(); let mut stdin = SP1Stdin::new(); - // alternative block hash - // 0xfa2459292cc258e554940516cd4dc12beb058a5640d0c4f865aa106db0354dfa let proof_input = serde_json::to_vec( &get_ethereum_transaction_proof_inputs( 0u32, diff --git a/trie-utils/src/lib.rs b/trie-utils/src/lib.rs index 99a62ab..188f5e8 100644 --- a/trie-utils/src/lib.rs +++ b/trie-utils/src/lib.rs @@ -1,478 +1,15 @@ pub use alloy::eips::eip2718::{Eip2718Envelope, Encodable2718}; -use alloy::{ - consensus::{ReceiptEnvelope, ReceiptWithBloom, TxEnvelope, TxReceipt}, - primitives::B256, - providers::{Provider, ProviderBuilder}, - rpc::types::{BlockTransactionsKind, TransactionReceipt}, -}; -use alloy_rlp::{BufMut, Encodable}; -use crypto_ops::MerkleProofInput; use dotenv::dotenv; -use eth_trie::{EthTrie, MemoryDB, Trie}; -use std::{env, str::FromStr, sync::Arc}; -use url::Url; +use std::env; mod macros; pub fn load_infura_key_from_env() -> String { dotenv().ok(); env::var("INFURA").expect("Missing Infura API key!") } +mod proofs; +pub mod receipt; pub mod types; -use alloy::rpc::types::Log as AlloyLog; -use types::{Log, H256}; - -pub async fn get_ethereum_transaction_proof_inputs( - target_index: u32, - block_hash: &str, -) -> MerkleProofInput { - let key = load_infura_key_from_env(); - println!("Key: {}", key); - let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; - let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); - let block = provider - .get_block_by_hash( - B256::from_str(block_hash).unwrap(), - BlockTransactionsKind::Full, - ) - .await - .expect("Failed to get Block!") - .expect("Block not found!"); - - let memdb = Arc::new(MemoryDB::new(true)); - let mut trie = EthTrie::new(memdb.clone()); - - for (index, tx) in block.transactions.txns().enumerate() { - let path = alloy_rlp::encode(index); - let mut encoded_tx = vec![]; - match &tx.inner { - // Legacy transactions have no difference between network and 2718 - TxEnvelope::Legacy(tx) => tx.eip2718_encode(&mut encoded_tx), - TxEnvelope::Eip2930(tx) => { - tx.eip2718_encode(&mut encoded_tx); - } - TxEnvelope::Eip1559(tx) => { - tx.eip2718_encode(&mut encoded_tx); - } - TxEnvelope::Eip4844(tx) => { - tx.eip2718_encode(&mut encoded_tx); - } - TxEnvelope::Eip7702(tx) => { - tx.eip2718_encode(&mut encoded_tx); - } - _ => panic!("Unsupported transaction type"), - } - trie.insert(&path, &encoded_tx).expect("Failed to insert"); - } - trie.root_hash().unwrap(); - let tx_key: Vec = alloy_rlp::encode(target_index); - let proof: Vec> = trie.get_proof(&tx_key).unwrap(); - MerkleProofInput { - proof, - root_hash: block.header.transactions_root.to_vec(), - key: tx_key, - } -} - -pub async fn get_ethereum_receipt_proof_inputs( - target_index: u32, - block_hash: &str, -) -> MerkleProofInput { - let key = load_infura_key_from_env(); - println!("Key: {}", key); - let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; - let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); - let block_hash_b256 = B256::from_str(block_hash).unwrap(); - let block = provider - .get_block_by_hash( - B256::from_str(block_hash).unwrap(), - alloy::rpc::types::BlockTransactionsKind::Full, - ) - .await - .expect("Failed to get Block!") - .expect("Block not found!"); - let receipts: Vec = provider - .get_block_receipts(alloy::eips::BlockId::Hash(block_hash_b256.into())) - .await - .unwrap() - .unwrap(); - let memdb = Arc::new(MemoryDB::new(true)); - let mut trie = EthTrie::new(memdb.clone()); - for (index, receipt) in receipts.into_iter().enumerate() { - let inner: ReceiptEnvelope = receipt.inner; - let mut out: Vec = Vec::new(); - let index_encoded = alloy_rlp::encode(index); - match inner { - ReceiptEnvelope::Eip2930(r) => { - let prefix: u8 = 0x01; - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Eip1559(r) => { - let prefix: u8 = 0x02; - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Eip4844(r) => { - let prefix: u8 = 0x03; - out.put_u8(0x03); - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Eip7702(r) => { - let prefix: u8 = 0x04; - out.put_u8(0x04); - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Legacy(r) => { - insert_receipt(r, &mut trie, index_encoded, None); - } - _ => { - eprintln!("Critical: Unknown Receipt Type") - } - } - } - trie.root_hash().unwrap(); - let receipt_key: Vec = alloy_rlp::encode(target_index); - let proof = trie.get_proof(&receipt_key).unwrap(); - MerkleProofInput { - proof, - root_hash: block.header.receipts_root.to_vec(), - key: receipt_key, - } -} - -fn insert_receipt( - r: ReceiptWithBloom, - trie: &mut EthTrie, - index_encoded: Vec, - prefix: Option, -) { - let status = r.status(); - let cumulative_gas_used = r.cumulative_gas_used(); - let bloom = r.logs_bloom; - let mut logs: Vec = Vec::new(); - for l in r.logs() { - let mut topics: Vec = Vec::new(); - for t in l.topics() { - topics.push(H256::from_slice(t.as_ref())); - } - logs.push(Log { - address: l.address(), - topics, - data: l.data().data.to_vec(), - }); - } - let list_encode: [&dyn Encodable; 4] = [&status, &cumulative_gas_used, &bloom, &logs]; - let mut payload: Vec = Vec::new(); - alloy_rlp::encode_list::<_, dyn Encodable>(&list_encode, &mut payload); - let mut out: Vec = Vec::new(); - if let Some(prefix) = prefix { - out.put_u8(prefix); - }; - out.put_slice(&payload); - trie.insert(&index_encoded, &out).expect("Failed to insert"); -} - -#[cfg(test)] -mod test { - use super::load_infura_key_from_env; - use crate::{ - get_ethereum_receipt_proof_inputs, get_ethereum_transaction_proof_inputs, insert_receipt, - types::{Log, H256}, - }; - use alloy::{ - consensus::{Account, ReceiptEnvelope}, - hex::{self, FromHex, ToHex}, - primitives::{Address, Bloom, FixedBytes, B256}, - providers::{Provider, ProviderBuilder}, - rpc::types::TransactionReceipt, - }; - use alloy_rlp::{BufMut, Encodable}; - use crypto_ops::keccak::digest_keccak; - use eth_trie::{EthTrie, MemoryDB, Trie, DB}; - use keccak_hash::keccak; - use std::{str::FromStr, sync::Arc}; - use url::Url; - - #[test] - fn test_infura_key() { - let key = load_infura_key_from_env(); - println!("Key: {}", key); - } - - #[tokio::test] - async fn test_get_and_verify_ethereum_transaction_merkle_proof() { - let key = load_infura_key_from_env(); - println!("Key: {}", key); - let rpc_url: String = "https://mainnet.infura.io/v3/".to_string() + &key; - let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); - let block_hash = "0x8230bd00f36e52e68dd4a46bfcddeceacbb689d808327f4c76dbdf8d33d58ca8"; - let target_index: u32 = 15u32; - let inputs: crypto_ops::MerkleProofInput = - get_ethereum_transaction_proof_inputs(target_index, block_hash).await; - - let block = provider - .get_block_by_hash( - B256::from_str(block_hash).unwrap(), - alloy::rpc::types::BlockTransactionsKind::Full, - ) - .await - .expect("Failed to get Block!") - .expect("Block not found!"); - - let transaction = verify_merkle_proof( - block.header.transactions_root, - inputs.proof, - &alloy_rlp::encode(target_index), - ); - - println!( - "Verified {:?} against {:?}", - &transaction, &block.header.transactions_root - ); - } - - #[tokio::test] - async fn test_get_and_verify_ethereum_receipt_merkle_proof() { - let key = load_infura_key_from_env(); - println!("Key: {}", key); - let rpc_url: String = "https://mainnet.infura.io/v3/".to_string() + &key; - let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); - let block_hash = "0x8230bd00f36e52e68dd4a46bfcddeceacbb689d808327f4c76dbdf8d33d58ca8"; - let target_index: u32 = 0u32; - let inputs: crypto_ops::MerkleProofInput = - get_ethereum_receipt_proof_inputs(target_index, block_hash).await; - - let block = provider - .get_block_by_hash( - B256::from_str(block_hash).unwrap(), - alloy::rpc::types::BlockTransactionsKind::Full, - ) - .await - .expect("Failed to get Block!") - .expect("Block not found!"); - - let transaction = verify_merkle_proof( - block.header.receipts_root, - inputs.proof, - &alloy_rlp::encode(target_index), - ); - - println!( - "Verified {:?} against {:?}", - &transaction, &block.header.receipts_root - ); - } - - #[test] - fn compare_hash_fn() { - let input: Vec = vec![0, 0, 0]; - let keccak_hash = keccak(input.clone()); - println!("keccak hash: {:?}", &keccak_hash.as_bytes()); - let sha3_hash = digest_keccak(&input); - println!("sha3 hash: {:?}", &sha3_hash); - } - - fn verify_merkle_proof(root_hash: B256, proof: Vec>, key: &[u8]) -> Vec { - let proof_db = Arc::new(MemoryDB::new(true)); - for node_encoded in proof.clone().into_iter() { - let hash: B256 = keccak(&node_encoded).as_fixed_bytes().into(); - proof_db.insert(hash.as_slice(), node_encoded).unwrap(); - } - let mut trie = EthTrie::from(proof_db, root_hash).expect("Invalid root"); - println!("Root from Merkle Proof: {:?}", trie.root_hash().unwrap()); - trie.verify_proof(root_hash, key, proof) - .expect("Failed to verify Merkle Proof") - .expect("Key does not exist!") - } - - #[tokio::test] - async fn test_verify_receipt_merkle_proof() { - let key = load_infura_key_from_env(); - println!("Key: {}", key); - let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; - let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); - let block_hash = "0xe1dd1d7e0fa5263787bf0dca315a065bbd466ce6b827ef0b619502359feadac3"; - let block_hash_b256 = B256::from_str(block_hash).unwrap(); - let block = provider - .get_block_by_hash( - B256::from_str(block_hash).unwrap(), - alloy::rpc::types::BlockTransactionsKind::Full, - ) - .await - .expect("Failed to get Block!") - .expect("Block not found!"); - let receipts: Vec = provider - .get_block_receipts(alloy::eips::BlockId::Hash(block_hash_b256.into())) - .await - .unwrap() - .unwrap(); - let memdb = Arc::new(MemoryDB::new(true)); - let mut trie = EthTrie::new(memdb.clone()); - for (index, receipt) in receipts.into_iter().enumerate() { - let inner: ReceiptEnvelope = receipt.inner; - let mut out: Vec = Vec::new(); - let index_encoded = alloy_rlp::encode(index); - match inner { - ReceiptEnvelope::Eip2930(r) => { - let prefix: u8 = 0x01; - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Eip1559(r) => { - let prefix: u8 = 0x02; - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Eip4844(r) => { - let prefix: u8 = 0x03; - out.put_u8(0x03); - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Eip7702(r) => { - let prefix: u8 = 0x04; - out.put_u8(0x04); - insert_receipt(r, &mut trie, index_encoded, Some(prefix)); - } - ReceiptEnvelope::Legacy(r) => { - insert_receipt(r, &mut trie, index_encoded, None); - } - _ => { - eprintln!("Critical: Unknown Receipt Type") - } - } - } - assert_eq!(&block.header.receipts_root, &trie.root_hash().unwrap()) - } - - #[tokio::test] - async fn dump_test_block() { - use std::fs::File; - use std::io::Write; - let key = load_infura_key_from_env(); - println!("Key: {}", key); - let rpc_url: String = "https://mainnet.infura.io/v3/".to_string() + &key; - let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); - let block_hash = "0x2683344180300c9eb6a2b7ef4f9ab6136ac230de731e742d482394b162d6a43b"; - - let block = provider - .get_block_by_hash( - B256::from_str(block_hash).unwrap(), - alloy::rpc::types::BlockTransactionsKind::Full, - ) - .await - .expect("Failed to get Block!") - .expect("Block not found!"); - - let mut block_file = File::create("./data/block.dat").unwrap(); - block_file - .write_all(&serde_json::to_vec(&block).unwrap()) - .expect("Failed to write to block file"); - } - - #[tokio::test] - async fn test_verify_account_and_storage_proof() { - use std::fs::File; - use std::io::Read; - let mut block_file = File::open("./data/block.dat").unwrap(); - let mut block_buffer: Vec = vec![]; - block_file - .read_to_end(&mut block_buffer) - .expect("Failed to read block file"); - let key = load_infura_key_from_env(); - let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; - let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); - - let block = provider - .get_block( - alloy::eips::BlockId::Number(provider.get_block_number().await.unwrap().into()), - alloy::rpc::types::BlockTransactionsKind::Full, - ) - .await - .unwrap() - .unwrap(); - - let proof = provider - .get_proof( - Address::from_hex("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(), - vec![FixedBytes::from_hex( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap()], - ) - .await - .expect("Failed to get proof"); - println!("Proof: {:?}", &proof); - let account_proof: Vec = verify_merkle_proof( - block.header.state_root, - proof - .account_proof - .clone() - .into_iter() - .map(|b| b.to_vec()) - .collect(), - // key: keccak hash of the account hash - &digest_keccak(&hex::decode("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap()), - ); - - let decoded_account: Account = alloy_rlp::decode_exact(&account_proof).unwrap(); - assert_eq!( - decoded_account.storage_root.encode_hex::(), - hex::encode(&proof.storage_hash) - ); - let _ = verify_merkle_proof( - proof.storage_hash, - proof - .storage_proof - .first() - .unwrap() - .proof - .clone() - .into_iter() - .map(|b| b.to_vec()) - .collect(), - &digest_keccak( - &hex::decode("0000000000000000000000000000000000000000000000000000000000000000") - .unwrap(), - ), - ); - println!("Verified Account Proof against Block Root & Storage Proof against Account Root") - } - - #[test] - fn test_encode_receipt() { - let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); - let status = false; - let cumulative_gas = 0x1u64; - let bloom = Bloom::new([0; 256]); - let logs: Vec = vec![Log { - address: Address::from_hex("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ - H256::from_slice( - &hex::decode( - "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), - ), - H256::from_slice( - &hex::decode( - "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), - ), - ], - data: hex::decode("0100ff").unwrap().to_vec(), - }]; - - let list_encode: [&dyn Encodable; 4] = [&status, &cumulative_gas, &bloom, &logs]; - let mut out: Vec = Vec::new(); - alloy_rlp::encode_list::<_, dyn Encodable>(&list_encode, &mut out); - - let mut o: Vec = Vec::new(); - logs.encode(&mut o); - println!("Outputs: {:?}", &o); - println!("Result: {:?}", &out); - println!("Expectation: {:?}", &expected); - assert_eq!(out, expected); - } -} - -/* -Storage Proof: [EIP1186StorageProof { key: Hash(0x0000000000000000000000000000000000000000000000000000000000000000), value: 1134972014892877928712953364190483482895670224936, proof: [0xf90211a0159e7ca4556d4e19a25a2536fb7de76e9bf3acbdaa5026f76e451518e98ccb77a0dfbb9dab1e5c517935c2432f6e9ab417481252413cdf0d49e233d49c1a74f4c3a0f536cf070243c692d2a86b48b5f94d17e38321b9f5126c787bb51ae3a2fc00ada0e52e8951b6f11787e28f00e2dc4d572bdcd3de56c25b35319aa3733945eb4cf6a0b5127f9ae185fb7817875c812f50e0b0e19be7e35dc84b5fee3e8d4e7d61f11ba033d5dd3cbac129dcc6384a065f7784331afc146b041b2c878d475d970c135e9ca0e32b15b66e0313810235f65df5df2627e13355560f428db951a88bfdd4a09c7fa055ffe102b7653a34622e6bdcf86896c377dc370eda7918d1619fcfc3e5624e78a022abb25ea28248075f5a09911589dee42c2695300d3fc9db53f52c5bd8544b6ea0c5844fe6de29b18798ed5d0782f6c3d184ab4c66fd646fd3c39480f59f3d1f39a0bb9ac6bbe29c665e711e2d2f785b4398991ef8a9cdb240468f40216c18911407a09d282fe0b3a84f410c5160b0ec51398c132f5c8f63c9a7a3b10356321b5c9fa6a020440b9bbafc0d0ed9e35997d8c36a1dcbe4bd89ba7712f206e44a6935cf3c10a0a58663cefcdd3a9a8e4ba49e584e0706150148527df146f02206cd21fe350893a0cb3e2f8e701792fd4790ba6468a47b93f21e60eeadad6c3777c479d4892f368ca05994fc7ace3f6b64d520b637413c2368236ce8136891833c34a471514b3eddf480, 0xf90211a0fec5ce2777fa890d7433a61aa81f7cbc75ff57a5badaae3e15f90118f8a0881ba0f129f1b128d0ee6a4785aae36f0cb108f06fe74393932cba832d4000ded56689a03e824ebff2a5b5a887e3355e41ee3409d9bf2a67af9c421c48b4793c598cd692a0ee1ef9cd1e3cc662a34464d205b1e4251c22410d3b78d234f5983bc8e9a234d7a06f320b898a36c572be9557f04a53607c87dc58359611d63a4d27a544e718fef1a0f5368d63204dc97038744ca87b222fae12d7ba0b54b91897a454342e51ee7936a06db150da0e48174ee9636cdbfa82c58b0bb7641b46a57939cf4c1bca675c0a7ea03715bfb63affee5d89dcc0a003774f0eff9fbd7bef926247d2bc1341610535a2a0736698252e432febd9a39bdeb5761bacb4505acf22e7d324312a159b90f9604da021ab3a390132fe16eaa12772aba1ff369372fff09a8a2f3d35347d911d4b9b20a001eb734670b80ed28a9a66a30e3a83073569796652bfcc91ee3e277325dfafa9a017905b08a03622b6bd0c678559324404fba9adc01bdc006909472433b47c5bd0a005095d30b8e76ccf6218e16b525bbc703454a6c0035835c04bdb851b2428cee3a0d81faaf03e08da11c3b33519ebf32f74c057c8c7eaa367d0433175b157bd4f0da04c42b452ee94b594599a4fc6ca02b3cb31038dd64537b3e0145fc912a86dea18a036af617e16c883eec2e6fa60b4b427728017846b4ca708ca1d05d91272bcbc9080, 0xf90211a0df4d774b4dc5e4bf6c6915c6d889dbe9e36b364ac6a99a93832b6b1710c6b90fa0e40776dd795a6f0fc1a7013a20e33fd7b86f7035764307af288e1f62328392dea0453ef21b7d313751bc4ec76e1a9fd7858f517cdfbd43ecae9741d6574e792f93a0aa782feb3bd5f6acb914fc85b47f21e4fb35c0825f5a2f9f2fe62097bb891736a0f9dd6351ca148f010ffe1f7587fde5fe63026185ba17a58c273166a0f83e7533a0eff4a301c5d6ca7f5fd24fd4cc256a9c2c60c05c96f5e69883526f551dc058a4a0d019d39da940b50fc02b10fcd421b87416fe07fc2ea9d97fc7ed77b2cfd0c3dfa0fd7da8856a12acf7fd0874066bd09b74f59da7dc0e3a0868e19c36eb58be84bfa011e621f30f50fdc79344194c7c128ea0c84676c600f573219df2eb88d7501d63a0e5e38fa6ac7e50a04d72df1b35d7fb834b65ceb40c9bc8b91b7298d5c64169faa093f50f828283b0ad89c8d47064ea0002bc1a26e80b51101157db8b9bc10719c7a05f0353bf77e91ba5fb341022c68a3ccdfe363ab4a534089e90150f467a644463a0d0c2c43722348b996c78fbc646dc9afb2528b79e7e3c4d1b118df8e408314643a04db525a945a0410d078b43ab287c8e2a8f2e55bb3df307bfd893dfcad4db3914a0e9f35ac3cad2239a4910bd0e93cb8dd4646ce7d09d1f6f3a6a334dc60c0782c7a0837047f1c5ff5b298d9a9a25cfbf255572f0d04696683c05ff59270339a3e86480, 0xf90211a0e2ca64ae529075620877150c41c06ee33397aa9aead27bf8074dffd505c975f5a0db1cd7d1abe822345a954498d749e5406c0e0b90a19b8655d9c3751a543cb77da0fb398296cc34be049babfc067d5cd28a7b50968fe5f05ccb378b4f9d7dbbda1ea0dacb81f3e0130eaa66a04e00bfd15f0cf70a342b7ac541d3f066bad2a7299778a0a191ebb38fb7614b96d79427270c0755aeaf2beb9d59631d98d16f4bc7f6dc65a008091207166a4253f96e1ee895819038ab70cd2f4b27fa6eb52ec142d4e58a0aa09a113c36f6e23dae3314ecd7358a9d0160cc3fcf26d28d7ec5f4403cf5e15c84a0df59486f9769ebe4974621e0ae830476d0ffd5a89c0d31ce4c192183f3ad009aa00cadac6dd57d9ee9b430c1cfe40016daee7f666e13fe4a174afa6eef13706252a01b4ff7c4b70485834450077a4093117d1f4e4b4c5c4276671e5a7a52789b2e9fa04abbe591e1f327f726c0bbe168bff20475172d4fd63acd9933227c1495a9b94fa0652f123d856a806ea9d57fd399eed98d51a44588e4bf6d09f784e659fb7044a2a0ed16548443f417533261d35bca2a1918f8ea235ff9f5ae6ff595a00cdc787a73a09b7c8f9712660c4061d7ea0a617255d1c64431a7f3ff8327d18dff713ac33028a092ba4b394f51cb7f2fd861dfccfded38e81f9c19f2e9a8e53ab091d9b456264ca03003f470a5df696027da8d2254497655bafac957248bf56c33cb5101eb70ee5380, 0xf90211a0f3f18d6137f5867680e9fa619a64a0d1219f226a3efaf6a2984bdba51e0643ada0f7bbbd7316355473103ddd064be4426aaaff58e3323f829ce4bb1cdcc8f28b4fa0e9d293c729f85f94ae3ab82bd218760111d8475b436de2a920d371f1c54052faa04fe703989a2141ba8e0d7281fbecd20b1df144490fd38aceaf13fd5b20008914a0b69e02b499a875eb7b788f98d5c97ad589d4b4066709424518a784d44d2b5e8fa0926d19c899776b49e45795c0cffaa8ff70ebb708bcc38a0beec2072e802dc14da0313ac10759fd92b6eb3b5f184173a2fc08d4523b45264fe12c98f9489b3c3277a0ba20155cc8761a6d4c1d8d38656adb254334a67bf50f7bbcef8804805cdacc40a0e4503b8905fe1fd79e6ed8c2864c98af51fa521399071c9ecc71a25ca73d84a8a0e74239bea0a082ec243a70a581befc4cc8057243788531f60544d2fdb7e58602a07f6d5530fab1ffabf0210affa9b84ccfc168a936db7187fefab6f30d9efa1012a05d78b052b59a09204f24c93ada6a17a6452df4ca6101df6042340610696a403fa07721cd100d940caa315627f1864d84ca3574c144464def50e09b112d004697c7a0a004bf8978e314f8d453bd1084c86b94ef87e390288a10df9447f42f229e145ba0112e5abe86fa4deedb100916c0d14a33b889bd34e31419cb53a5872b8e27aa91a05afa364d9e0e60570b92975507f8224c1029e1abb51ab3e91e3da194dd1d244880, 0xf90111a022b9d68ec893e46a41c316bde1406b19b1b15e51f5c35f3dfbbaeff3e302661e80a00ad2d2e385dff997534ad374657268101fda36e31285552d4ac0f06209ff6df6808080a07a52523bb1990b64c50fd6648a657e92e196788ba02f98ad0c1fd3d25b0b0d53a0294bde53062110328103e301062faad0e1175bc1e3649eadc5b281a298f4df1680a06634703c405b8015855bb7bea0084457878c1b806f1db3997ed8889a82f85c948080a032f769f61f78abf648e9985feb6e0712a65ed03fdd534069a16594c895712f11a0de64a3a75f4323d8f7c9db0c643ef1c2bf521e28ad8540271be9d4e7b9da198a80a06f23ca1f167b52e366a7cb9628d5d7d5ca2af5377e951e6907721d6a2227210380, 0xf8718080a03269a56e4a8c5a0db5480a777c04f55435708b2aa668221eca0a51597f917eed8080808080808080a0188adbc95db819328813c282bfe9d78819dff2c5c83041ba936431635424da8a80a0f4873cb7178849abe99b3515f3195aebc99d9534da6ec2e0810451fa4b9991d8808080, 0xf49d39548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594c6cde7c39eb2f0f0095f41570af89efc2c1ea828] }] - - -*/ +pub use proofs::{ + account::get_ethereum_account_proof_inputs, receipt::get_ethereum_receipt_proof_inputs, + storage::get_ethereum_storage_proof_inputs, transaction::get_ethereum_transaction_proof_inputs, +}; diff --git a/trie-utils/src/macros.rs b/trie-utils/src/macros.rs index 012dbbb..750d9bb 100644 --- a/trie-utils/src/macros.rs +++ b/trie-utils/src/macros.rs @@ -7,7 +7,6 @@ macro_rules! encode { $e.encode(&mut vec); println!("{}: {:?}", stringify!($e), vec); } - }; ($out:ident, $e:expr, $($others:expr),+) => { { diff --git a/trie-utils/src/proofs/account.rs b/trie-utils/src/proofs/account.rs new file mode 100644 index 0000000..e61633d --- /dev/null +++ b/trie-utils/src/proofs/account.rs @@ -0,0 +1,36 @@ +use crate::load_infura_key_from_env; +use alloy::{ + primitives::Address, + providers::{Provider, ProviderBuilder}, +}; +use crypto_ops::{keccak::digest_keccak, types::MerkleProofInput}; +use std::{io::Read, str::FromStr}; +use url::Url; + +pub async fn get_ethereum_account_proof_inputs(address: Address) -> MerkleProofInput { + let key = load_infura_key_from_env(); + let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block = provider + .get_block( + alloy::eips::BlockId::Number(provider.get_block_number().await.unwrap().into()), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .unwrap() + .unwrap(); + let proof = provider + .get_proof(address, vec![]) + .await + .expect("Failed to get proof"); + + MerkleProofInput { + proof: proof + .account_proof + .into_iter() + .map(|b| b.to_vec()) + .collect(), + root_hash: block.header.state_root.to_vec(), + key: digest_keccak(&address.bytes().collect::, _>>().unwrap()).to_vec(), + } +} diff --git a/trie-utils/src/proofs/mod.rs b/trie-utils/src/proofs/mod.rs new file mode 100644 index 0000000..228d85b --- /dev/null +++ b/trie-utils/src/proofs/mod.rs @@ -0,0 +1,4 @@ +pub mod account; +pub mod receipt; +pub mod storage; +pub mod transaction; diff --git a/trie-utils/src/proofs/receipt.rs b/trie-utils/src/proofs/receipt.rs new file mode 100644 index 0000000..f9cb20d --- /dev/null +++ b/trie-utils/src/proofs/receipt.rs @@ -0,0 +1,80 @@ +use crate::{load_infura_key_from_env, receipt::insert_receipt}; +use alloy::{ + consensus::ReceiptEnvelope, + primitives::B256, + providers::{Provider, ProviderBuilder}, + rpc::types::TransactionReceipt, +}; +use alloy_rlp::BufMut; +use crypto_ops::types::MerkleProofInput; +use eth_trie::{EthTrie, MemoryDB, Trie}; +use std::{str::FromStr, sync::Arc}; +use url::Url; + +pub async fn get_ethereum_receipt_proof_inputs( + target_index: u32, + block_hash: &str, +) -> MerkleProofInput { + let key = load_infura_key_from_env(); + println!("Key: {}", key); + let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block_hash_b256 = B256::from_str(block_hash).unwrap(); + let block = provider + .get_block_by_hash( + B256::from_str(block_hash).unwrap(), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .expect("Failed to get Block!") + .expect("Block not found!"); + let receipts: Vec = provider + .get_block_receipts(alloy::eips::BlockId::Hash(block_hash_b256.into())) + .await + .unwrap() + .unwrap(); + let memdb = Arc::new(MemoryDB::new(true)); + let mut trie = EthTrie::new(memdb.clone()); + + for (index, receipt) in receipts.into_iter().enumerate() { + let inner: ReceiptEnvelope = receipt.inner; + let mut out: Vec = Vec::new(); + let index_encoded = alloy_rlp::encode(index); + match inner { + ReceiptEnvelope::Eip2930(r) => { + let prefix: u8 = 0x01; + insert_receipt(r, &mut trie, index_encoded, Some(prefix)); + } + ReceiptEnvelope::Eip1559(r) => { + let prefix: u8 = 0x02; + insert_receipt(r, &mut trie, index_encoded, Some(prefix)); + } + ReceiptEnvelope::Eip4844(r) => { + let prefix: u8 = 0x03; + out.put_u8(0x03); + insert_receipt(r, &mut trie, index_encoded, Some(prefix)); + } + ReceiptEnvelope::Eip7702(r) => { + let prefix: u8 = 0x04; + out.put_u8(0x04); + insert_receipt(r, &mut trie, index_encoded, Some(prefix)); + } + ReceiptEnvelope::Legacy(r) => { + insert_receipt(r, &mut trie, index_encoded, None); + } + _ => { + eprintln!("Critical: Unknown Receipt Type") + } + } + } + + trie.root_hash().unwrap(); + let receipt_key: Vec = alloy_rlp::encode(target_index); + let proof = trie.get_proof(&receipt_key).unwrap(); + + MerkleProofInput { + proof, + root_hash: block.header.receipts_root.to_vec(), + key: receipt_key, + } +} diff --git a/trie-utils/src/proofs/storage.rs b/trie-utils/src/proofs/storage.rs new file mode 100644 index 0000000..e8acc39 --- /dev/null +++ b/trie-utils/src/proofs/storage.rs @@ -0,0 +1,58 @@ +use crate::load_infura_key_from_env; +use alloy::{ + primitives::{Address, FixedBytes}, + providers::{Provider, ProviderBuilder}, +}; +use crypto_ops::{keccak::digest_keccak, types::MerkleProofListInput}; +use std::{io::Read, str::FromStr}; +use url::Url; + +pub async fn get_ethereum_storage_proof_inputs( + address: Address, + keys: Vec>, +) -> MerkleProofListInput { + let key = load_infura_key_from_env(); + let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block = provider + .get_block( + alloy::eips::BlockId::Number(provider.get_block_number().await.unwrap().into()), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .unwrap() + .unwrap(); + let proof = provider + .get_proof(address, keys) + .await + .expect("Failed to get proof"); + + MerkleProofListInput { + account_proof: proof + .account_proof + .into_iter() + .map(|b| b.to_vec()) + .collect(), + storage_proofs: proof + .storage_proof + .to_vec() + .into_iter() + .map(|p| p.proof.into_iter().map(|b| b.to_vec()).collect()) + .collect(), + root_hash: block.header.state_root.to_vec(), + account_key: digest_keccak(&address.bytes().collect::, _>>().unwrap()) + .to_vec(), + storage_keys: proof + .storage_proof + .to_vec() + .into_iter() + .map(|p| { + p.key + .as_b256() + .bytes() + .collect::, _>>() + .unwrap() + }) + .collect(), + } +} diff --git a/trie-utils/src/proofs/transaction.rs b/trie-utils/src/proofs/transaction.rs new file mode 100644 index 0000000..d8f5548 --- /dev/null +++ b/trie-utils/src/proofs/transaction.rs @@ -0,0 +1,61 @@ +use crate::load_infura_key_from_env; +use alloy::{ + consensus::TxEnvelope, + primitives::B256, + providers::{Provider, ProviderBuilder}, +}; +use crypto_ops::types::MerkleProofInput; +use eth_trie::{EthTrie, MemoryDB, Trie}; +use std::{str::FromStr, sync::Arc}; +use url::Url; + +pub async fn get_ethereum_transaction_proof_inputs( + target_index: u32, + block_hash: &str, +) -> MerkleProofInput { + let key = load_infura_key_from_env(); + println!("Key: {}", key); + let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block = provider + .get_block_by_hash( + B256::from_str(block_hash).unwrap(), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .expect("Failed to get Block!") + .expect("Block not found!"); + let memdb = Arc::new(MemoryDB::new(true)); + let mut trie = EthTrie::new(memdb.clone()); + + for (index, tx) in block.transactions.txns().enumerate() { + let path = alloy_rlp::encode(index); + let mut encoded_tx = vec![]; + match &tx.inner { + TxEnvelope::Legacy(tx) => tx.eip2718_encode(&mut encoded_tx), + TxEnvelope::Eip2930(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + TxEnvelope::Eip1559(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + TxEnvelope::Eip4844(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + TxEnvelope::Eip7702(tx) => { + tx.eip2718_encode(&mut encoded_tx); + } + _ => panic!("Unsupported transaction type"), + } + trie.insert(&path, &encoded_tx).expect("Failed to insert"); + } + + trie.root_hash().unwrap(); + let tx_key: Vec = alloy_rlp::encode(target_index); + let proof: Vec> = trie.get_proof(&tx_key).unwrap(); + MerkleProofInput { + proof, + root_hash: block.header.transactions_root.to_vec(), + key: tx_key, + } +} diff --git a/trie-utils/src/receipt.rs b/trie-utils/src/receipt.rs new file mode 100644 index 0000000..39ce8a7 --- /dev/null +++ b/trie-utils/src/receipt.rs @@ -0,0 +1,38 @@ +use alloy::consensus::{ReceiptWithBloom, TxReceipt}; +use alloy::rpc::types::Log as AlloyLog; +use alloy_rlp::{BufMut, Encodable}; +use eth_trie::{EthTrie, MemoryDB, Trie}; + +use crate::types::{Log, H256}; + +pub fn insert_receipt( + r: ReceiptWithBloom, + trie: &mut EthTrie, + index_encoded: Vec, + prefix: Option, +) { + let status = r.status(); + let cumulative_gas_used = r.cumulative_gas_used(); + let bloom = r.logs_bloom; + let mut logs: Vec = Vec::new(); + for l in r.logs() { + let mut topics: Vec = Vec::new(); + for t in l.topics() { + topics.push(H256::from_slice(t.as_ref())); + } + logs.push(Log { + address: l.address(), + topics, + data: l.data().data.to_vec(), + }); + } + let list_encode: [&dyn Encodable; 4] = [&status, &cumulative_gas_used, &bloom, &logs]; + let mut payload: Vec = Vec::new(); + alloy_rlp::encode_list::<_, dyn Encodable>(&list_encode, &mut payload); + let mut out: Vec = Vec::new(); + if let Some(prefix) = prefix { + out.put_u8(prefix); + }; + out.put_slice(&payload); + trie.insert(&index_encoded, &out).expect("Failed to insert"); +} diff --git a/trie-utils/src/types.rs b/trie-utils/src/types.rs index 821e8a1..1026986 100644 --- a/trie-utils/src/types.rs +++ b/trie-utils/src/types.rs @@ -8,7 +8,6 @@ pub struct Log { pub topics: Vec, pub data: Vec, } - impl Log { fn rlp_header(&self) -> alloy_rlp::Header { let payload_length = @@ -19,7 +18,6 @@ impl Log { } } } - impl Encodable for Log { fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { let header = self.rlp_header(); @@ -33,7 +31,6 @@ impl Encodable for Log { #[derive(Debug, RlpEncodableWrapper, PartialEq, Clone)] pub struct H256(pub [u8; 32]); - impl H256 { pub fn zero() -> Self { Self([0u8; 32]) diff --git a/trie-utils/tests/account.rs b/trie-utils/tests/account.rs new file mode 100644 index 0000000..55c6a99 --- /dev/null +++ b/trie-utils/tests/account.rs @@ -0,0 +1,76 @@ +#[cfg(test)] +mod test { + use std::str::FromStr; + + use alloy::{ + consensus::Account, + hex::{self, FromHex, ToHex}, + primitives::{Address, FixedBytes}, + providers::{Provider, ProviderBuilder}, + }; + use crypto_ops::{keccak::digest_keccak, types::MerkleProofInput, verify_merkle_proof}; + use trie_utils::{get_ethereum_account_proof_inputs, load_infura_key_from_env}; + use url::Url; + + /// This test could fail in case a new block is produced during execution. + /// The risk of this happening is accepted and it's recommended to re-run the test + /// in the very rare case where it fails for said reason. + /// The same is true for the test in storage.rs + #[tokio::test] + async fn test_verify_account_and_storage_proof() { + let key = load_infura_key_from_env(); + let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block = provider + .get_block( + alloy::eips::BlockId::Number(provider.get_block_number().await.unwrap().into()), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .unwrap() + .unwrap(); + let proof = provider + .get_proof( + Address::from_hex("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(), + vec![FixedBytes::from_hex( + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap()], + ) + .await + .expect("Failed to get proof"); + + let inputs: MerkleProofInput = get_ethereum_account_proof_inputs( + Address::from_hex("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(), + ) + .await; + println!("Proof: {:?}", &proof); + let account_proof: Vec = verify_merkle_proof( + block.header.state_root, + inputs.proof, + &digest_keccak(&hex::decode("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap()), + ); + let decoded_account: Account = alloy_rlp::decode_exact(&account_proof).unwrap(); + assert_eq!( + decoded_account.storage_root.encode_hex::(), + hex::encode(&proof.storage_hash) + ); + let _ = verify_merkle_proof( + proof.storage_hash, + proof + .storage_proof + .first() + .unwrap() + .proof + .clone() + .into_iter() + .map(|b| b.to_vec()) + .collect(), + &digest_keccak( + &hex::decode("0000000000000000000000000000000000000000000000000000000000000000") + .unwrap(), + ), + ); + println!("[Success] Verified Account Proof against Block Root") + } +} diff --git a/trie-utils/tests/receipt.rs b/trie-utils/tests/receipt.rs new file mode 100644 index 0000000..39ec4be --- /dev/null +++ b/trie-utils/tests/receipt.rs @@ -0,0 +1,42 @@ +#[cfg(test)] +mod tests { + use alloy::{ + primitives::B256, + providers::{Provider, ProviderBuilder}, + }; + use crypto_ops::verify_merkle_proof; + use std::str::FromStr; + use trie_utils::{get_ethereum_receipt_proof_inputs, load_infura_key_from_env}; + use url::Url; + + #[tokio::test] + async fn test_get_and_verify_ethereum_receipt_merkle_proof() { + let key = load_infura_key_from_env(); + let rpc_url: String = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block_hash = "0x8230bd00f36e52e68dd4a46bfcddeceacbb689d808327f4c76dbdf8d33d58ca8"; + let target_index: u32 = 0u32; + let inputs: crypto_ops::types::MerkleProofInput = + get_ethereum_receipt_proof_inputs(target_index, block_hash).await; + + let block = provider + .get_block_by_hash( + B256::from_str(block_hash).unwrap(), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .expect("Failed to get Block!") + .expect("Block not found!"); + + let transaction = verify_merkle_proof( + block.header.receipts_root, + inputs.proof, + &alloy_rlp::encode(target_index), + ); + + println!( + "[Success] Verified {:?} against {:?}.", + &transaction, &block.header.receipts_root + ); + } +} diff --git a/trie-utils/tests/rlp.rs b/trie-utils/tests/rlp.rs new file mode 100644 index 0000000..714894d --- /dev/null +++ b/trie-utils/tests/rlp.rs @@ -0,0 +1,43 @@ +#[cfg(test)] +mod tests { + use alloy::{ + hex::{self, FromHex}, + primitives::{Address, Bloom}, + }; + use alloy_rlp::Encodable; + use trie_utils::types::{Log, H256}; + + #[test] + fn test_encode_receipt() { + let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + let status = false; + let cumulative_gas = 0x1u64; + let bloom = Bloom::new([0; 256]); + let logs: Vec = vec![Log { + address: Address::from_hex("0000000000000000000000000000000000000011").unwrap(), + topics: vec![ + H256::from_slice( + &hex::decode( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + ), + H256::from_slice( + &hex::decode( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ), + ], + data: hex::decode("0100ff").unwrap().to_vec(), + }]; + + let list_encode: [&dyn Encodable; 4] = [&status, &cumulative_gas, &bloom, &logs]; + let mut out: Vec = Vec::new(); + alloy_rlp::encode_list::<_, dyn Encodable>(&list_encode, &mut out); + + let mut o: Vec = Vec::new(); + logs.encode(&mut o); + assert_eq!(out, expected); + } +} diff --git a/trie-utils/tests/storage.rs b/trie-utils/tests/storage.rs new file mode 100644 index 0000000..84a4643 --- /dev/null +++ b/trie-utils/tests/storage.rs @@ -0,0 +1,72 @@ +#[cfg(test)] +mod tests { + #[allow(deprecated)] + use alloy::{ + consensus::Account, + hex::{self, FromHex, ToHex}, + primitives::{Address, FixedBytes}, + providers::{Provider, ProviderBuilder}, + }; + use crypto_ops::{keccak::digest_keccak, verify_merkle_proof}; + use std::str::FromStr; + use trie_utils::{get_ethereum_storage_proof_inputs, load_infura_key_from_env}; + use url::Url; + + /// This test could fail in case a new block is produced during execution. + /// The risk of this happening is accepted and it's recommended to re-run the test + /// in the very rare case where it fails for said reason. + /// The same is true for the test in account.rs + #[tokio::test] + async fn test_verify_account_and_storage_proof() { + let key = load_infura_key_from_env(); + let rpc_url = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block = provider + .get_block( + alloy::eips::BlockId::Number(provider.get_block_number().await.unwrap().into()), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .unwrap() + .unwrap(); + let proof = provider + .get_proof( + Address::from_hex("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(), + vec![FixedBytes::from_hex( + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap()], + ) + .await + .expect("Failed to get proof"); + let account_proof: Vec = verify_merkle_proof( + block.header.state_root, + proof + .account_proof + .clone() + .into_iter() + .map(|b| b.to_vec()) + .collect(), + &digest_keccak(&hex::decode("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap()), + ); + let decoded_account: Account = alloy_rlp::decode_exact(&account_proof).unwrap(); + assert_eq!( + decoded_account.storage_root.encode_hex::(), + hex::encode(&proof.storage_hash) + ); + let storage_proof = get_ethereum_storage_proof_inputs( + Address::from_hex("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(), + vec![FixedBytes::from_hex( + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap()], + ) + .await; + let _ = verify_merkle_proof( + proof.storage_hash, + storage_proof.storage_proofs.first().unwrap().to_owned(), + &digest_keccak(&storage_proof.storage_keys.first().unwrap()), + ); + println!("[Success] Verified Account Proof against Block Root and Storage Proof against Account Root.") + } +} diff --git a/trie-utils/tests/transaction.rs b/trie-utils/tests/transaction.rs new file mode 100644 index 0000000..cbcce9f --- /dev/null +++ b/trie-utils/tests/transaction.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +mod tests { + use alloy::{ + primitives::B256, + providers::{Provider, ProviderBuilder}, + }; + use crypto_ops::verify_merkle_proof; + use std::str::FromStr; + use trie_utils::{get_ethereum_transaction_proof_inputs, load_infura_key_from_env}; + use url::Url; + + #[tokio::test] + async fn test_get_and_verify_ethereum_transaction_merkle_proof() { + let key = load_infura_key_from_env(); + let rpc_url: String = "https://mainnet.infura.io/v3/".to_string() + &key; + let provider = ProviderBuilder::new().on_http(Url::from_str(&rpc_url).unwrap()); + let block_hash = "0x8230bd00f36e52e68dd4a46bfcddeceacbb689d808327f4c76dbdf8d33d58ca8"; + let target_index: u32 = 15u32; + let inputs: crypto_ops::types::MerkleProofInput = + get_ethereum_transaction_proof_inputs(target_index, block_hash).await; + let block = provider + .get_block_by_hash( + B256::from_str(block_hash).unwrap(), + alloy::rpc::types::BlockTransactionsKind::Full, + ) + .await + .expect("Failed to get Block!") + .expect("Block not found!"); + let transaction = verify_merkle_proof( + block.header.transactions_root, + inputs.proof, + &alloy_rlp::encode(target_index), + ); + println!( + "[Success] Verified {:?} against {:?}.", + &transaction, &block.header.transactions_root + ); + } +}