From 3be3a3d92dc8632c8c87e4f4285ab8ccd6f4279a Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 14:56:02 -0400 Subject: [PATCH 1/9] feature: signer multiplexing --- crates/network/src/any/builder.rs | 2 +- crates/network/src/ethereum/builder.rs | 2 +- crates/network/src/ethereum/signer.rs | 84 ++++++++++++++++++++---- crates/network/src/transaction/signer.rs | 31 ++++++++- crates/signer-aws/src/signer.rs | 4 ++ crates/signer-gcp/src/signer.rs | 4 ++ crates/signer-ledger/src/signer.rs | 4 ++ crates/signer-trezor/src/signer.rs | 4 ++ crates/signer-wallet/src/lib.rs | 8 +++ 9 files changed, 128 insertions(+), 15 deletions(-) diff --git a/crates/network/src/any/builder.rs b/crates/network/src/any/builder.rs index 18a589b1d27..b2b8fde1578 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.build_unsigned()?.into()).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..2b1d3efdbbf 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_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. + 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).map(|s| Arc::clone(s)).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).map(|s| Arc::clone(s)) } async fn sign_transaction_inner( &self, + sender: Option
, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - self.0.sign_transaction(tx).await + let address = sender.unwrap_or_else(|| self.default); + + self.signer_by_address(address) + .ok_or_else(|| { + alloy_signer::Error::other(format!("Missing signing credential for {}", address)) + })? + .sign_transaction(tx) + .await } } @@ -46,22 +100,30 @@ impl NetworkSigner for EthereumSigner where N: Network, { - async fn sign_transaction(&self, tx: TypedTransaction) -> alloy_signer::Result { + fn default_signer(&self) -> Address { + self.default + } + + async fn sign_transaction_from( + &self, + sender: Option
, + 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..34a77a15cd5 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -1,6 +1,8 @@ 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. /// @@ -10,16 +12,35 @@ use async_trait::async_trait; #[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; + + /// Asynchronously sign an unsigned transaction, with a specified + /// credential. + async fn sign_transaction_from( + &self, + sender: Option
, + 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(None, tx) + } /// Asynchronously sign a transaction request. async fn sign_request( &self, request: N::TransactionRequest, ) -> alloy_signer::Result { + let sender = request.from(); 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 +61,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 +87,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, From ddf68ed26ea875d0244706ba9d9cd7893c098f01 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 15:01:12 -0400 Subject: [PATCH 2/9] fix: register default on instantiation --- crates/network/src/ethereum/signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 2b1d3efdbbf..795683a5ebd 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -37,7 +37,7 @@ impl EthereumSigner { S: TxSigner + Send + Sync + 'static, { let mut this = Self { default: Default::default(), secp_signers: BTreeMap::new() }; - this.register_signer(signer); + this.register_default_signer(signer); this } From 73b500b01b082bd59a26a1356229b2f0a1ec5c9a Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 15:05:13 -0400 Subject: [PATCH 3/9] docs: fixup --- crates/network/src/transaction/signer.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 34a77a15cd5..1522e332af9 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -6,9 +6,14 @@ 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 { @@ -33,7 +38,8 @@ pub trait NetworkSigner: std::fmt::Debug + Send + Sync { self.sign_transaction_from(None, 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, From 1c8aaf53a788f4c74ea8078f79303bd72bda9edc Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 15:11:57 -0400 Subject: [PATCH 4/9] fix: clippy --- crates/network/src/ethereum/signer.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 795683a5ebd..dc4e5a375aa 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -57,6 +57,8 @@ impl EthereumSigner { /// 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, @@ -67,7 +69,7 @@ impl EthereumSigner { /// Get the default signer. pub fn default_signer(&self) -> Arc + Send + Sync + 'static> { - self.secp_signers.get(&self.default).map(|s| Arc::clone(s)).expect("invalid signer") + self.secp_signers.get(&self.default).map(Arc::clone).expect("invalid signer") } /// Get the signer for the given address. @@ -75,7 +77,7 @@ impl EthereumSigner { &self, address: Address, ) -> Option + Send + Sync + 'static>> { - self.secp_signers.get(&address).map(|s| Arc::clone(s)) + self.secp_signers.get(&address).map(Arc::clone) } async fn sign_transaction_inner( @@ -83,7 +85,7 @@ impl EthereumSigner { sender: Option
, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - let address = sender.unwrap_or_else(|| self.default); + let address = sender.unwrap_or(self.default); self.signer_by_address(address) .ok_or_else(|| { From 6eff69cc3d3e5f2b166d388ad8501133056e8c91 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 15:19:52 -0400 Subject: [PATCH 5/9] fix: sign_request --- crates/network/src/any/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/network/src/any/builder.rs b/crates/network/src/any/builder.rs index b2b8fde1578..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_request(self.build_unsigned()?.into()).await?) + Ok(signer.sign_request(self).await?) } } From 3420569124c47e098f35d64fc9f625abe3f1fc12 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 15:20:38 -0400 Subject: [PATCH 6/9] fix: clippy --- crates/network/src/ethereum/signer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index dc4e5a375aa..07518d48f42 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -69,7 +69,7 @@ impl EthereumSigner { /// Get the default signer. pub fn default_signer(&self) -> Arc + Send + Sync + 'static> { - self.secp_signers.get(&self.default).map(Arc::clone).expect("invalid signer") + self.secp_signers.get(&self.default).cloned().expect("invalid signer") } /// Get the signer for the given address. @@ -77,7 +77,7 @@ impl EthereumSigner { &self, address: Address, ) -> Option + Send + Sync + 'static>> { - self.secp_signers.get(&address).map(Arc::clone) + self.secp_signers.get(&address).cloned() } async fn sign_transaction_inner( From 8dbd816a9c8775b6c1909c5f598a6185bf281efe Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 16:26:46 -0400 Subject: [PATCH 7/9] feat: is_signer_for and signers --- crates/network/src/ethereum/signer.rs | 8 ++++++++ crates/network/src/transaction/signer.rs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 07518d48f42..93b0959a164 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -106,6 +106,14 @@ where 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() + } + async fn sign_transaction_from( &self, sender: Option
, diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 1522e332af9..47bd2a96b0f 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -22,6 +22,12 @@ pub trait NetworkSigner: std::fmt::Debug + Send + Sync { /// 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( From 0e807026597187c8982cec4966d54ba01aba9fea Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 16:27:53 -0400 Subject: [PATCH 8/9] fix: signer returns copied addresses --- crates/network/src/ethereum/signer.rs | 4 ++-- crates/network/src/transaction/signer.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 93b0959a164..9cce3f21e6d 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -110,8 +110,8 @@ where self.secp_signers.contains_key(address) } - fn signers(&self) -> impl Iterator { - self.secp_signers.keys() + fn signers(&self) -> impl Iterator { + self.secp_signers.keys().copied() } async fn sign_transaction_from( diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 47bd2a96b0f..ea70e848508 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -26,7 +26,7 @@ pub trait NetworkSigner: std::fmt::Debug + Send + Sync { fn is_signer_for(&self, address: &Address) -> bool; /// Return an iterator of all signer addresses. - fn signers(&self) -> impl Iterator; + fn signers(&self) -> impl Iterator; /// Asynchronously sign an unsigned transaction, with a specified /// credential. From f2eba4cf098331497669684d77ed2b095f18f9c3 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Apr 2024 16:31:03 -0400 Subject: [PATCH 9/9] fix: remove unnecessary option --- crates/network/src/ethereum/signer.rs | 10 ++++------ crates/network/src/transaction/signer.rs | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 9cce3f21e6d..8aae8168952 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -82,14 +82,12 @@ impl EthereumSigner { async fn sign_transaction_inner( &self, - sender: Option
, + sender: Address, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - let address = sender.unwrap_or(self.default); - - self.signer_by_address(address) + self.signer_by_address(sender) .ok_or_else(|| { - alloy_signer::Error::other(format!("Missing signing credential for {}", address)) + alloy_signer::Error::other(format!("Missing signing credential for {}", sender)) })? .sign_transaction(tx) .await @@ -116,7 +114,7 @@ where async fn sign_transaction_from( &self, - sender: Option
, + sender: Address, tx: TypedTransaction, ) -> alloy_signer::Result { match tx { diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index ea70e848508..862a731a2d2 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -32,7 +32,7 @@ pub trait NetworkSigner: std::fmt::Debug + Send + Sync { /// credential. async fn sign_transaction_from( &self, - sender: Option
, + sender: Address, tx: N::UnsignedTx, ) -> alloy_signer::Result; @@ -41,7 +41,7 @@ pub trait NetworkSigner: std::fmt::Debug + Send + Sync { &self, tx: N::UnsignedTx, ) -> impl_future!(>) { - self.sign_transaction_from(None, tx) + self.sign_transaction_from(self.default_signer(), tx) } /// Asynchronously sign a transaction request, using the sender specified @@ -50,7 +50,7 @@ pub trait NetworkSigner: std::fmt::Debug + Send + Sync { &self, request: N::TransactionRequest, ) -> alloy_signer::Result { - let sender = request.from(); + let sender = request.from().unwrap_or_else(|| self.default_signer()); let tx = request.build_unsigned().map_err(alloy_signer::Error::other)?; self.sign_transaction_from(sender, tx).await }