From f05fbbbd8c96e297e0f2eecfa015504f3cc2fa39 Mon Sep 17 00:00:00 2001 From: jonas pauli Date: Mon, 9 Dec 2024 16:47:04 +0100 Subject: [PATCH] heavy refactor, cleanup and documentation --- Cargo.lock | 1 + README.md | 144 ++++--- circuits/Cargo.lock | 2 +- circuits/elf/riscv32im-succinct-zkvm-elf | Bin 401432 -> 409032 bytes circuits/merkle-proof/src/main.rs | 2 +- crypto-ops/Cargo.toml | 8 +- crypto-ops/src/keccak.rs | 5 +- crypto-ops/src/lib.rs | 14 +- crypto-ops/src/types.rs | 17 + prover/Cargo.toml | 3 + prover/src/bin/main.rs | 6 +- trie-utils/src/lib.rs | 477 +---------------------- trie-utils/src/macros.rs | 1 - trie-utils/src/proofs/account.rs | 36 ++ trie-utils/src/proofs/mod.rs | 4 + trie-utils/src/proofs/receipt.rs | 80 ++++ trie-utils/src/proofs/storage.rs | 58 +++ trie-utils/src/proofs/transaction.rs | 61 +++ trie-utils/src/receipt.rs | 38 ++ trie-utils/src/types.rs | 3 - trie-utils/tests/account.rs | 76 ++++ trie-utils/tests/receipt.rs | 42 ++ trie-utils/tests/rlp.rs | 43 ++ trie-utils/tests/storage.rs | 72 ++++ trie-utils/tests/transaction.rs | 39 ++ 25 files changed, 686 insertions(+), 546 deletions(-) create mode 100644 crypto-ops/src/types.rs create mode 100644 trie-utils/src/proofs/account.rs create mode 100644 trie-utils/src/proofs/mod.rs create mode 100644 trie-utils/src/proofs/receipt.rs create mode 100644 trie-utils/src/proofs/storage.rs create mode 100644 trie-utils/src/proofs/transaction.rs create mode 100644 trie-utils/src/receipt.rs create mode 100644 trie-utils/tests/account.rs create mode 100644 trie-utils/tests/receipt.rs create mode 100644 trie-utils/tests/rlp.rs create mode 100644 trie-utils/tests/storage.rs create mode 100644 trie-utils/tests/transaction.rs 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 56e64270377e03e37ab0baef6aa5a743ac18a4a7..deb7e86ebebd5138f854c23533ed4c80c4e8428c 100755 GIT binary patch delta 98719 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 delta 96112 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

(L)ZFVi?t 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 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 + ); + } +}