Skip to content

reapply: "feat(network): use FullSigner in EthereumWallet to sign data" #2648

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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: 2 additions & 0 deletions crates/network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ rustdoc-args = [
workspace = true

[dependencies]

alloy-consensus = { workspace = true, features = ["std"] }
alloy-consensus-any = { workspace = true, features = ["std", "serde"] }
alloy-eips = { workspace = true, features = ["serde"] }
Expand All @@ -47,3 +48,4 @@ thiserror.workspace = true

[features]
k256 = ["alloy-primitives/k256", "alloy-consensus/k256"]
eip712 = ["alloy-signer/eip712"]
2 changes: 1 addition & 1 deletion crates/network/src/ethereum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::Network;
mod builder;

mod wallet;
pub use wallet::{EthereumWallet, IntoWallet};
pub use wallet::{ArcFullSigner, EthereumWallet, IntoWallet};

/// Types for a mainnet-like Ethereum network.
#[derive(Clone, Copy, Debug)]
Expand Down
133 changes: 119 additions & 14 deletions crates/network/src/ethereum/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use crate::{AnyNetwork, AnyTxEnvelope, AnyTypedTransaction, Network, NetworkWallet, TxSigner};
use crate::{
AnyNetwork, AnyTxEnvelope, AnyTypedTransaction, FullSigner, Network, NetworkWallet, TxSigner,
};
use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction};
use alloy_primitives::{map::AddressHashMap, Address, Signature};
use std::{fmt::Debug, sync::Arc};
use std::{fmt::Debug, ops::Deref, sync::Arc};

use super::Ethereum;

/// A wallet capable of signing any transaction for the Ethereum network.
#[derive(Clone, Default)]
pub struct EthereumWallet {
default: Address,
signers: AddressHashMap<Arc<dyn TxSigner<Signature> + Send + Sync>>,
signers: AddressHashMap<ArcFullSigner>,
}

impl std::fmt::Debug for EthereumWallet {
Expand All @@ -23,7 +25,7 @@ impl std::fmt::Debug for EthereumWallet {

impl<S> From<S> for EthereumWallet
where
S: TxSigner<Signature> + Send + Sync + 'static,
S: FullSigner<Signature> + Send + Sync + 'static,
{
fn from(signer: S) -> Self {
Self::new(signer)
Expand All @@ -34,7 +36,7 @@ impl EthereumWallet {
/// 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,
S: FullSigner<Signature> + Send + Sync + 'static,
{
let mut this = Self::default();
this.register_default_signer(signer);
Expand All @@ -48,9 +50,10 @@ impl EthereumWallet {
/// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
pub fn register_signer<S>(&mut self, signer: S)
where
S: TxSigner<Signature> + Send + Sync + 'static,
S: FullSigner<Signature> + Send + Sync + 'static,
{
self.signers.insert(signer.address(), Arc::new(signer));
let arc_signer = ArcFullSigner::new(signer);
self.signers.insert(arc_signer.address(), arc_signer);
}

/// Register a new signer on this object, and set it as the default signer.
Expand All @@ -61,9 +64,9 @@ impl EthereumWallet {
/// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
pub fn register_default_signer<S>(&mut self, signer: S)
where
S: TxSigner<Signature> + Send + Sync + 'static,
S: FullSigner<Signature> + Send + Sync + 'static,
{
self.default = signer.address();
self.default = TxSigner::address(&signer);
self.register_signer(signer);
}

Expand All @@ -90,15 +93,12 @@ impl EthereumWallet {
}

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

Expand All @@ -115,6 +115,61 @@ impl EthereumWallet {
.sign_transaction(tx)
.await
}

/// Signs a hash with the given signer address.
///
/// Hash can be arbitrary data or EIP-712 typed data hash.
///
/// # Example
///
/// ```ignore
/// use alloy_sol_types::{sol, eip712_domain};
/// use alloy_primitives::{address, keccak256, B256};
/// use alloy_signer_local::PrivateKeySigner;
/// sol! {
/// struct Test {
/// uint256 value;
/// }
/// }
///
/// #[tokio::main]
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let domain = eip712_domain! {
/// name: "Test",
/// version: "1.0",
/// chain_id: 1,
/// verifying_contract: address!("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"),
/// salt: keccak256("test_salt"),
/// };
///
/// let alice: PrivateKeySigner = "0x...".parse()?;
/// let bob: PrivateKeySigner = "0x...".parse()?;
///
/// let wallet = EthereumWallet::new(alice);
/// wallet.register_signer(bob);
///
/// let t = Test { value: U256::from(0x42) };
///
/// let hash = t.eip712_signing_hash(&domain);
///
/// let signature = wallet.sign_hash_with(alice.address(), &hash).await?;
///
/// Ok(())
/// }
/// ```
#[cfg(feature = "eip712")]
pub async fn sign_hash_with(
&self,
signer: Address,
hash: &alloy_primitives::B256,
) -> alloy_signer::Result<Signature> {
self.signer_by_address(signer)
.ok_or_else(|| {
alloy_signer::Error::other(format!("Missing signing credential for {signer}"))
})?
.sign_hash(hash)
.await
}
}

impl<N> NetworkWallet<N> for EthereumWallet
Expand Down Expand Up @@ -207,3 +262,53 @@ impl<W: NetworkWallet<N>, N: Network> IntoWallet<N> for W {
self
}
}

/// Wrapper type for [`FullSigner`] that is used in [`EthereumWallet`].
///
/// This is useful to disambiguate the function calls on a signer via [`EthereumWallet`] as
/// [`TxSigner`] and [`Signer`] have the same methods e.g [`TxSigner::address`] and
/// [`Signer::address`]
///
/// [`Signer`]: alloy_signer::Signer
/// [`Signer::address`]: alloy_signer::Signer::address
#[derive(Clone)]
pub struct ArcFullSigner(Arc<dyn FullSigner<Signature> + Send + Sync>);

impl Debug for ArcFullSigner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArcFullSigner").field("address", &self.address()).finish()
}
}

impl ArcFullSigner {
/// Create a new [`ArcFullSigner`] from a given [`FullSigner`].
pub fn new<S>(signer: S) -> Self
where
S: FullSigner<Signature> + Send + Sync + 'static,
{
Self(Arc::new(signer))
}

/// Get the address of the signer.
pub fn address(&self) -> Address {
self.0.address()
}

/// Get the underlying [`FullSigner`] as a reference.
pub fn signer(&self) -> &dyn FullSigner<Signature> {
self.0.as_ref()
}

/// Get the underlying [`FullSigner`] as an owned value.
pub fn into_signer(self) -> Arc<dyn FullSigner<Signature> + Send + Sync> {
self.0
}
}

impl Deref for ArcFullSigner {
type Target = dyn FullSigner<Signature> + Send + Sync;

fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
2 changes: 1 addition & 1 deletion crates/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub use transaction::{
};

mod ethereum;
pub use ethereum::{Ethereum, EthereumWallet, IntoWallet};
pub use ethereum::{ArcFullSigner, Ethereum, EthereumWallet, IntoWallet};

/// Types for handling unknown network types.
pub mod any;
Expand Down
1 change: 1 addition & 0 deletions crates/provider/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ wasmtimer.workspace = true
alloy-consensus = { workspace = true, features = ["kzg", "k256"] }
alloy-primitives = { workspace = true, features = ["rand"] }
alloy-node-bindings.workspace = true
alloy-network = { workspace = true, features = ["eip712"] }
alloy-rpc-client = { workspace = true, features = ["reqwest"] }
alloy-rlp.workspace = true
alloy-signer.workspace = true
Expand Down
19 changes: 19 additions & 0 deletions crates/provider/src/provider/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ where
mod test {
use super::*;
use crate::ProviderBuilder;
use alloy_primitives::{address, U256};
use alloy_sol_types::{sol, Eip712Domain};
use itertools::Itertools;

#[test]
Expand All @@ -109,4 +111,21 @@ mod test {

assert!(provider.signer_addresses().contains(&provider.default_signer_address()));
}

#[tokio::test]
async fn sign_hash() {
sol! {
struct Test {
uint256 value;
}
}
use alloy_sol_types::SolStruct;
let provider = ProviderBuilder::new().connect_anvil_with_wallet();

let t = Test { value: U256::from(0x42) };
let domain = Eip712Domain::default();
let hash = t.eip712_signing_hash(&domain);
let signer = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
let _ = provider.wallet().sign_hash_with(signer, &hash).await.unwrap();
}
}
Loading