diff --git a/Cargo.lock b/Cargo.lock index ac8533d8fc..fcf67aea6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,7 @@ dependencies = [ "derive_more", "hex-literal", "itoa", + "k256", "ruint", "serde", "tiny-keccak", @@ -187,6 +188,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "bit-set" version = "0.5.3" @@ -257,6 +264,12 @@ dependencies = [ "serde", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "convert_case" version = "0.4.0" @@ -278,6 +291,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -288,6 +313,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -308,7 +343,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -317,6 +354,37 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -339,6 +407,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fnv" version = "1.0.7" @@ -353,6 +431,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -372,6 +451,17 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -415,6 +505,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "indexmap" version = "2.2.3" @@ -431,6 +530,18 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "kona-common" version = "0.0.1" @@ -738,6 +849,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ruint" version = "1.11.1" @@ -811,6 +932,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.22" @@ -868,6 +1002,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "smallvec" version = "1.13.1" @@ -902,6 +1046,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" diff --git a/crates/derive/Cargo.toml b/crates/derive/Cargo.toml index 5405236e20..1ddac3330b 100644 --- a/crates/derive/Cargo.toml +++ b/crates/derive/Cargo.toml @@ -32,4 +32,6 @@ proptest = "1.4.0" spin = { version = "0.9.8", features = ["mutex"] } # Spin is used for testing synchronization primitives [features] +default = ["serde", "k256"] serde = ["dep:serde", "alloy-primitives/serde"] +k256 = ["alloy-primitives/k256", "alloy-consensus/k256"] diff --git a/crates/derive/src/sources/blobs.rs b/crates/derive/src/sources/blobs.rs index 49805ab13a..ba07e75d12 100644 --- a/crates/derive/src/sources/blobs.rs +++ b/crates/derive/src/sources/blobs.rs @@ -1,12 +1,12 @@ //! Blob Data Source use crate::{ - traits::{AsyncIterator, BlobProvider, ChainProvider}, + traits::{AsyncIterator, BlobProvider, ChainProvider, SignedRecoverable}, types::{BlobData, BlockInfo, IndexedBlobHash, StageError, StageResult}, }; use alloc::{boxed::Box, vec::Vec}; -use alloy_consensus::TxEnvelope; -use alloy_primitives::{Address, Bytes}; +use alloy_consensus::{Transaction, TxEip4844Variant, TxEnvelope, TxType}; +use alloy_primitives::{Address, Bytes, TxKind}; use anyhow::Result; use async_trait::async_trait; @@ -58,43 +58,57 @@ where } } - fn extract_blob_data(&self, _: Vec) -> (Vec, Vec) { - // let mut index = 0; - // let mut data = Vec::new(); - // let mut hashes = Vec::new(); - // for tx in txs { - // if tx.to() != Some(self.batcher_address) { - // index += tx.blob_hashes().map_or(0, |h| h.len()); - // continue; - // } - // if tx.from() != Some(self.signer) { - // index += tx.blob_hashes().map_or(0, |h| h.len()); - // continue; - // } - // if tx.tx_type() != TxType::Eip4844 { - // let calldata = tx.data().clone(); - // let blob_data = BlobData { data: None, calldata: Some(calldata) }; - // data.push(blob_data); - // continue; - // } - // if !tx.data().is_empty() { - // // TODO(refcell): Add a warning log here if the blob data is not empty - // // https://github.com/ethereum-optimism/optimism/blob/develop/op-node/rollup/derive/blob_data_source.go#L136 - // } - // let blob_hashes = if let Some(b) = tx.blob_hashes() { - // b - // } else { - // continue; - // }; - // for blob in blob_hashes { - // let indexed = IndexedBlobHash { hash: blob, index }; - // hashes.push(indexed); - // data.push(BlobData::default()); - // index += 1; - // } - // } - // (data, hashes) - todo!("Need ecrecover abstraction") + fn extract_blob_data(&self, txs: Vec) -> (Vec, Vec) { + let mut index = 0; + let mut data = Vec::new(); + let mut hashes = Vec::new(); + for tx in txs { + let (tx_kind, calldata, blob_hashes) = match &tx { + TxEnvelope::Legacy(tx) => (tx.tx().to(), tx.tx().input.clone(), None), + TxEnvelope::Eip2930(tx) => (tx.tx().to(), tx.tx().input.clone(), None), + TxEnvelope::Eip1559(tx) => (tx.tx().to(), tx.tx().input.clone(), None), + TxEnvelope::Eip4844(blob_tx_wrapper) => match blob_tx_wrapper.tx() { + TxEip4844Variant::TxEip4844(tx) => { + (tx.to(), tx.input.clone(), Some(tx.blob_versioned_hashes.clone())) + } + TxEip4844Variant::TxEip4844WithSidecar(tx) => { + let tx = tx.tx(); + (tx.to(), tx.input.clone(), Some(tx.blob_versioned_hashes.clone())) + } + }, + }; + let TxKind::Call(to) = tx_kind else { continue }; + + if to != self.batcher_address { + index += blob_hashes.map_or(0, |h| h.len()); + continue; + } + if tx.recover_public_key().unwrap_or_default() != self.signer { + index += blob_hashes.map_or(0, |h| h.len()); + continue; + } + if tx.tx_type() != TxType::Eip4844 { + let blob_data = BlobData { data: None, calldata: Some(calldata.to_vec().into()) }; + data.push(blob_data); + continue; + } + if !calldata.is_empty() { + // TODO(refcell): Add a warning log here if the blob data is not empty + // https://github.com/ethereum-optimism/optimism/blob/develop/op-node/rollup/derive/blob_data_source.go#L136 + } + let blob_hashes = if let Some(b) = blob_hashes { + b + } else { + continue; + }; + for blob in blob_hashes { + let indexed = IndexedBlobHash { hash: blob, index }; + hashes.push(indexed); + data.push(BlobData::default()); + index += 1; + } + } + (data, hashes) } /// Loads blob data into the source if it is not open. diff --git a/crates/derive/src/sources/calldata.rs b/crates/derive/src/sources/calldata.rs index 23aa5d6cb8..c88ffdbe5f 100644 --- a/crates/derive/src/sources/calldata.rs +++ b/crates/derive/src/sources/calldata.rs @@ -1,11 +1,12 @@ //! CallData Source use crate::{ - traits::{AsyncIterator, ChainProvider}, + traits::{AsyncIterator, ChainProvider, SignedRecoverable}, types::{BlockInfo, StageError, StageResult}, }; use alloc::{boxed::Box, collections::VecDeque}; -use alloy_primitives::{Address, Bytes}; +use alloy_consensus::{Transaction, TxEnvelope}; +use alloy_primitives::{Address, Bytes, TxKind}; use async_trait::async_trait; /// A data iterator that reads from calldata. @@ -49,30 +50,37 @@ impl CalldataSource { /// Loads the calldata into the source if it is not open. async fn load_calldata(&mut self) -> anyhow::Result<()> { - // if self.open { - // return Ok(()); - // } - // - // let (_, txs) = - // self.chain_provider.block_info_and_transactions_by_hash(self.block_ref.hash).await?; - // - // self.calldata = txs - // .iter() - // .filter_map(|_| { - // if tx.to() != Some(self.batcher_address) { - // return None; - // } - // if tx.from() != Some(self.signer) { - // return None; - // } - // Some(tx.data()) - // }) - // .collect::>(); - // - // self.open = true; - // - // Ok(()) - todo!("Need ecrecover abstraction") + if self.open { + return Ok(()); + } + + let (_, txs) = + self.chain_provider.block_info_and_transactions_by_hash(self.block_ref.hash).await?; + + self.calldata = txs + .iter() + .filter_map(|tx| { + let (tx_kind, data) = match tx { + TxEnvelope::Legacy(tx) => (tx.tx().to(), tx.tx().input()), + TxEnvelope::Eip2930(tx) => (tx.tx().to(), tx.tx().input()), + TxEnvelope::Eip1559(tx) => (tx.tx().to(), tx.tx().input()), + _ => return None, + }; + let TxKind::Call(to) = tx_kind else { return None }; + + if to != self.batcher_address { + return None; + } + if tx.recover_public_key().ok()? != self.signer { + return None; + } + Some(data.to_vec().into()) + }) + .collect::>(); + + self.open = true; + + Ok(()) } } diff --git a/crates/derive/src/traits/ecrecover.rs b/crates/derive/src/traits/ecrecover.rs new file mode 100644 index 0000000000..e20ccbdae1 --- /dev/null +++ b/crates/derive/src/traits/ecrecover.rs @@ -0,0 +1,47 @@ +//! This module contains the [SignedRecoverable] trait. +//! +//! This trait exists to allow for alternative implementations of the `recover_public_key` method +//! for signed types that can supply the original message hash for public key recovery. By default, +//! it is implemented for [alloy_consensus::TxEnvelope] if the `k256` feature is enabled. + +use alloy_consensus::TxEnvelope; +use alloy_primitives::Address; +use anyhow::Result; + +#[cfg(feature = "k256")] +use alloy_primitives::{Signature, B256}; +#[cfg(feature = "k256")] +use anyhow::anyhow; + +/// Represents a signed transaction that can be recovered. +pub trait SignedRecoverable { + /// Recovers the public key from the signature and the message hash. + fn recover_public_key(&self) -> Result
; +} + +#[cfg(feature = "k256")] +impl SignedRecoverable for TxEnvelope { + fn recover_public_key(&self) -> Result
{ + match self { + TxEnvelope::Legacy(signed_tx) => { + recover_public_key(*signed_tx.signature(), &signed_tx.signature_hash()) + } + TxEnvelope::Eip2930(signed_tx) => { + recover_public_key(*signed_tx.signature(), &signed_tx.signature_hash()) + } + TxEnvelope::Eip1559(signed_tx) => { + recover_public_key(*signed_tx.signature(), &signed_tx.signature_hash()) + } + TxEnvelope::Eip4844(signed_tx) => { + recover_public_key(*signed_tx.signature(), &signed_tx.signature_hash()) + } + } + } +} + +/// Recovers the public key from a signature and a message hash. +#[cfg(feature = "k256")] +#[inline] +fn recover_public_key(sig: Signature, message_hash: &B256) -> Result
{ + sig.recover_address_from_prehash(message_hash).map_err(|e| anyhow!(e)) +} diff --git a/crates/derive/src/traits/mod.rs b/crates/derive/src/traits/mod.rs index 6700afb024..f720820c9d 100644 --- a/crates/derive/src/traits/mod.rs +++ b/crates/derive/src/traits/mod.rs @@ -7,6 +7,9 @@ pub use data_sources::*; mod stages; pub use stages::ResettableStage; +mod ecrecover; +pub use ecrecover::SignedRecoverable; + mod telemetry; pub use telemetry::{LogLevel, TelemetryProvider};