diff --git a/crates/network/src/any/builder.rs b/crates/network/src/any/builder.rs index 18a589b1d27..ef367d7e83d 100644 --- a/crates/network/src/any/builder.rs +++ b/crates/network/src/any/builder.rs @@ -130,6 +130,6 @@ impl TransactionBuilder for WithOtherFields { self, signer: &S, ) -> BuilderResult { - Ok(signer.sign_transaction(self.build_unsigned()?).await?) + Ok(signer.sign_request(self).await?) } } diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index f7604839c8a..8bc3b9d6205 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -148,7 +148,7 @@ impl TransactionBuilder for TransactionRequest { self, signer: &S, ) -> BuilderResult<::TxEnvelope> { - Ok(signer.sign_transaction(self.build_unsigned()?).await?) + Ok(signer.sign_request(self).await?) } } diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index ef5a6e53bba..8aae8168952 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -1,16 +1,23 @@ use crate::{Network, NetworkSigner, TxSigner}; use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction}; +use alloy_primitives::Address; use alloy_signer::Signature; use async_trait::async_trait; -use std::sync::Arc; +use std::{collections::BTreeMap, sync::Arc}; /// A signer capable of signing any transaction for the Ethereum network. #[derive(Clone)] -pub struct EthereumSigner(Arc + Send + Sync>); +pub struct EthereumSigner { + default: Address, + secp_signers: BTreeMap + Send + Sync>>, +} impl std::fmt::Debug for EthereumSigner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("EthereumSigner").finish() + f.debug_struct("EthereumSigner") + .field("default_signer", &self.default) + .field("credentials", &self.secp_signers.len()) + .finish() } } @@ -24,19 +31,66 @@ where } impl EthereumSigner { - /// Create a new Ethereum signer. + /// Create a new signer with the given signer as the default signer. pub fn new(signer: S) -> Self where S: TxSigner + Send + Sync + 'static, { - Self(Arc::new(signer)) + let mut this = Self { default: Default::default(), secp_signers: BTreeMap::new() }; + this.register_default_signer(signer); + this + } + + /// Register a new signer on this object. This signer will be used to sign + /// [`TransactionRequest`] and [`TypedTransaction`] object that specify the + /// signer's address in the `from` field. + /// + /// [`TransactionRequest`]: alloy_rpc_types::TransactionRequest + pub fn register_signer(&mut self, signer: S) + where + S: TxSigner + Send + Sync + 'static, + { + self.secp_signers.insert(signer.address(), Arc::new(signer)); + } + + /// Register a new signer on this object, and set it as the default signer. + /// This signer will be used to sign [`TransactionRequest`] and + /// [`TypedTransaction`] objects that do not specify a signer address in the + /// `from` field. + /// + /// [`TransactionRequest`]: alloy_rpc_types::TransactionRequest + pub fn register_default_signer(&mut self, signer: S) + where + S: TxSigner + Send + Sync + 'static, + { + self.default = signer.address(); + self.register_signer(signer); + } + + /// Get the default signer. + pub fn default_signer(&self) -> Arc + Send + Sync + 'static> { + self.secp_signers.get(&self.default).cloned().expect("invalid signer") + } + + /// Get the signer for the given address. + pub fn signer_by_address( + &self, + address: Address, + ) -> Option + Send + Sync + 'static>> { + self.secp_signers.get(&address).cloned() } async fn sign_transaction_inner( &self, + sender: Address, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - self.0.sign_transaction(tx).await + self.signer_by_address(sender) + .ok_or_else(|| { + alloy_signer::Error::other(format!("Missing signing credential for {}", sender)) + })? + .sign_transaction(tx) + .await } } @@ -46,22 +100,38 @@ impl NetworkSigner for EthereumSigner where N: Network, { - async fn sign_transaction(&self, tx: TypedTransaction) -> alloy_signer::Result { + fn default_signer(&self) -> Address { + self.default + } + + fn is_signer_for(&self, address: &Address) -> bool { + self.secp_signers.contains_key(address) + } + + fn signers(&self) -> impl Iterator { + self.secp_signers.keys().copied() + } + + async fn sign_transaction_from( + &self, + sender: Address, + tx: TypedTransaction, + ) -> alloy_signer::Result { match tx { TypedTransaction::Legacy(mut t) => { - let sig = self.sign_transaction_inner(&mut t).await?; + let sig = self.sign_transaction_inner(sender, &mut t).await?; Ok(t.into_signed(sig).into()) } TypedTransaction::Eip2930(mut t) => { - let sig = self.sign_transaction_inner(&mut t).await?; + let sig = self.sign_transaction_inner(sender, &mut t).await?; Ok(t.into_signed(sig).into()) } TypedTransaction::Eip1559(mut t) => { - let sig = self.sign_transaction_inner(&mut t).await?; + let sig = self.sign_transaction_inner(sender, &mut t).await?; Ok(t.into_signed(sig).into()) } TypedTransaction::Eip4844(mut t) => { - let sig = self.sign_transaction_inner(&mut t).await?; + let sig = self.sign_transaction_inner(sender, &mut t).await?; Ok(t.into_signed(sig).into()) } } diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 155b530579f..862a731a2d2 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -1,25 +1,58 @@ use crate::{Network, TransactionBuilder}; use alloy_consensus::SignableTransaction; +use alloy_primitives::Address; use async_trait::async_trait; +use futures_utils_wasm::impl_future; /// A signer capable of signing any transaction for the given network. /// -/// Network crate authors should implement this trait on a type capable of signing any transaction -/// (regardless of signature type) on a given network. Signer crate authors should instead implement -/// [`TxSigner`] to signify signing capability for specific signature types. +/// Network crate authors should implement this trait on a type capable of +/// signing any transaction (regardless of signature type) on a given network. +/// Signer crate authors should instead implement [`TxSigner`] to signify +/// signing capability for specific signature types. +/// +/// Network signers are expected to contain one or more signing credentials, +/// keyed by signing address. The default signer address should be used when +/// no specific signer address is specified. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait NetworkSigner: std::fmt::Debug + Send + Sync { + /// Get the default signer address. This address should be used + /// in [`NetworkSigner::sign_transaction_from`] when no specific signer is + /// specified. + fn default_signer(&self) -> Address; + + /// Return true if the signer contains a credential for the given address. + fn is_signer_for(&self, address: &Address) -> bool; + + /// Return an iterator of all signer addresses. + fn signers(&self) -> impl Iterator; + + /// Asynchronously sign an unsigned transaction, with a specified + /// credential. + async fn sign_transaction_from( + &self, + sender: Address, + tx: N::UnsignedTx, + ) -> alloy_signer::Result; + /// Asynchronously sign an unsigned transaction. - async fn sign_transaction(&self, tx: N::UnsignedTx) -> alloy_signer::Result; + fn sign_transaction( + &self, + tx: N::UnsignedTx, + ) -> impl_future!(>) { + self.sign_transaction_from(self.default_signer(), tx) + } - /// Asynchronously sign a transaction request. + /// Asynchronously sign a transaction request, using the sender specified + /// in the `from` field. async fn sign_request( &self, request: N::TransactionRequest, ) -> alloy_signer::Result { + let sender = request.from().unwrap_or_else(|| self.default_signer()); let tx = request.build_unsigned().map_err(alloy_signer::Error::other)?; - self.sign_transaction(tx).await + self.sign_transaction_from(sender, tx).await } } @@ -40,6 +73,9 @@ pub trait NetworkSigner: std::fmt::Debug + Send + Sync { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait TxSigner { + /// Get the address of the signer. + fn address(&self) -> Address; + /// Asynchronously sign an unsigned transaction. async fn sign_transaction( &self, @@ -63,6 +99,9 @@ pub trait TxSigner { /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 /// [`ChainId`]: alloy_primitives::ChainId pub trait TxSignerSync { + /// Get the address of the signer. + fn address(&self) -> Address; + /// Synchronously sign an unsigned transaction. fn sign_transaction_sync( &self, diff --git a/crates/signer-aws/src/signer.rs b/crates/signer-aws/src/signer.rs index 5cb111283ba..28e74f5b990 100644 --- a/crates/signer-aws/src/signer.rs +++ b/crates/signer-aws/src/signer.rs @@ -96,6 +96,10 @@ pub enum AwsSignerError { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl alloy_network::TxSigner for AwsSigner { + fn address(&self) -> Address { + self.address + } + #[inline] async fn sign_transaction( &self, diff --git a/crates/signer-gcp/src/signer.rs b/crates/signer-gcp/src/signer.rs index 640751c3315..0607d8b762b 100644 --- a/crates/signer-gcp/src/signer.rs +++ b/crates/signer-gcp/src/signer.rs @@ -148,6 +148,10 @@ pub enum GcpSignerError { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl alloy_network::TxSigner for GcpSigner { + fn address(&self) -> Address { + self.address + } + #[inline] async fn sign_transaction( &self, diff --git a/crates/signer-ledger/src/signer.rs b/crates/signer-ledger/src/signer.rs index b61a42a2545..ac4d7b8def9 100644 --- a/crates/signer-ledger/src/signer.rs +++ b/crates/signer-ledger/src/signer.rs @@ -31,6 +31,10 @@ pub struct LedgerSigner { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl alloy_network::TxSigner for LedgerSigner { + fn address(&self) -> Address { + self.address + } + #[inline] async fn sign_transaction( &self, diff --git a/crates/signer-trezor/src/signer.rs b/crates/signer-trezor/src/signer.rs index 16f03c70021..382f2391629 100644 --- a/crates/signer-trezor/src/signer.rs +++ b/crates/signer-trezor/src/signer.rs @@ -67,6 +67,10 @@ impl Signer for TrezorSigner { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl alloy_network::TxSigner for TrezorSigner { + fn address(&self) -> Address { + self.address + } + #[inline] async fn sign_transaction( &self, diff --git a/crates/signer-wallet/src/lib.rs b/crates/signer-wallet/src/lib.rs index 068253b32d0..a52af134fc8 100644 --- a/crates/signer-wallet/src/lib.rs +++ b/crates/signer-wallet/src/lib.rs @@ -179,6 +179,10 @@ impl TxSigner for Wallet where D: PrehashSigner<(ecdsa::Signature, RecoveryId)> + Send + Sync, { + fn address(&self) -> Address { + self.address + } + async fn sign_transaction( &self, tx: &mut dyn SignableTransaction, @@ -191,6 +195,10 @@ impl TxSignerSync for Wallet where D: PrehashSigner<(ecdsa::Signature, RecoveryId)>, { + fn address(&self) -> Address { + self.address + } + fn sign_transaction_sync( &self, tx: &mut dyn SignableTransaction,