Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: allow multiple signing credentials in NetworkSigner #515

Merged
merged 9 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/network/src/any/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,6 @@ impl TransactionBuilder<AnyNetwork> for WithOtherFields<TransactionRequest> {
self,
signer: &S,
) -> BuilderResult<alloy_consensus::TxEnvelope> {
Ok(signer.sign_transaction(self.build_unsigned()?).await?)
Ok(signer.sign_request(self).await?)
}
}
2 changes: 1 addition & 1 deletion crates/network/src/ethereum/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl TransactionBuilder<Ethereum> for TransactionRequest {
self,
signer: &S,
) -> BuilderResult<<Ethereum as Network>::TxEnvelope> {
Ok(signer.sign_transaction(self.build_unsigned()?).await?)
Ok(signer.sign_request(self).await?)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these were dropping the from field during the build process regardless of whether it were set in the first place 😓

}
}

Expand Down
92 changes: 81 additions & 11 deletions crates/network/src/ethereum/signer.rs
Original file line number Diff line number Diff line change
@@ -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<dyn TxSigner<Signature> + Send + Sync>);
pub struct EthereumSigner {
default: Address,
secp_signers: BTreeMap<Address, Arc<dyn TxSigner<Signature> + 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()
}
}

Expand All @@ -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<S>(signer: S) -> Self
where
S: TxSigner<Signature> + 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<S>(&mut self, signer: S)
where
S: TxSigner<Signature> + 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<S>(&mut self, signer: S)
where
S: TxSigner<Signature> + Send + Sync + 'static,
{
self.default = signer.address();
self.register_signer(signer);
}

/// Get the default signer.
pub fn default_signer(&self) -> Arc<dyn TxSigner<Signature> + 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<Arc<dyn TxSigner<Signature> + Send + Sync + 'static>> {
self.secp_signers.get(&address).cloned()
}

async fn sign_transaction_inner(
&self,
sender: Address,
tx: &mut dyn SignableTransaction<Signature>,
) -> alloy_signer::Result<Signature> {
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
}
}

Expand All @@ -46,22 +100,38 @@ impl<N> NetworkSigner<N> for EthereumSigner
where
N: Network<UnsignedTx = TypedTransaction, TxEnvelope = TxEnvelope>,
{
async fn sign_transaction(&self, tx: TypedTransaction) -> alloy_signer::Result<TxEnvelope> {
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<Item = Address> {
self.secp_signers.keys().copied()
}

async fn sign_transaction_from(
&self,
sender: Address,
tx: TypedTransaction,
) -> alloy_signer::Result<TxEnvelope> {
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())
}
}
Expand Down
51 changes: 45 additions & 6 deletions crates/network/src/transaction/signer.rs
Original file line number Diff line number Diff line change
@@ -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<N: Network>: 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<Item = Address>;

/// Asynchronously sign an unsigned transaction, with a specified
/// credential.
async fn sign_transaction_from(
&self,
sender: Address,
tx: N::UnsignedTx,
) -> alloy_signer::Result<N::TxEnvelope>;

/// Asynchronously sign an unsigned transaction.
async fn sign_transaction(&self, tx: N::UnsignedTx) -> alloy_signer::Result<N::TxEnvelope>;
fn sign_transaction(
&self,
tx: N::UnsignedTx,
) -> impl_future!(<Output = alloy_signer::Result<N::TxEnvelope>>) {
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<N::TxEnvelope> {
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
}
}

Expand All @@ -40,6 +73,9 @@ pub trait NetworkSigner<N: Network>: std::fmt::Debug + Send + Sync {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait TxSigner<Signature> {
/// Get the address of the signer.
fn address(&self) -> Address;

/// Asynchronously sign an unsigned transaction.
async fn sign_transaction(
&self,
Expand All @@ -63,6 +99,9 @@ pub trait TxSigner<Signature> {
/// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
/// [`ChainId`]: alloy_primitives::ChainId
pub trait TxSignerSync<Signature> {
/// Get the address of the signer.
fn address(&self) -> Address;

/// Synchronously sign an unsigned transaction.
fn sign_transaction_sync(
&self,
Expand Down
4 changes: 4 additions & 0 deletions crates/signer-aws/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signature> for AwsSigner {
fn address(&self) -> Address {
self.address
}

#[inline]
async fn sign_transaction(
&self,
Expand Down
4 changes: 4 additions & 0 deletions crates/signer-gcp/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signature> for GcpSigner {
fn address(&self) -> Address {
self.address
}

#[inline]
async fn sign_transaction(
&self,
Expand Down
4 changes: 4 additions & 0 deletions crates/signer-ledger/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signature> for LedgerSigner {
fn address(&self) -> Address {
self.address
}

#[inline]
async fn sign_transaction(
&self,
Expand Down
4 changes: 4 additions & 0 deletions crates/signer-trezor/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signature> for TrezorSigner {
fn address(&self) -> Address {
self.address
}

#[inline]
async fn sign_transaction(
&self,
Expand Down
8 changes: 8 additions & 0 deletions crates/signer-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ impl<D> TxSigner<Signature> for Wallet<D>
where
D: PrehashSigner<(ecdsa::Signature, RecoveryId)> + Send + Sync,
{
fn address(&self) -> Address {
self.address
}

async fn sign_transaction(
&self,
tx: &mut dyn SignableTransaction<Signature>,
Expand All @@ -191,6 +195,10 @@ impl<D> TxSignerSync<Signature> for Wallet<D>
where
D: PrehashSigner<(ecdsa::Signature, RecoveryId)>,
{
fn address(&self) -> Address {
self.address
}

fn sign_transaction_sync(
&self,
tx: &mut dyn SignableTransaction<Signature>,
Expand Down
Loading