Skip to content

Commit e1fb9d9

Browse files
authored
feat(network): use FullSigner in EthereumWallet to sign data (#2523)
* feat(`network`): use `FullSigner` in `EthereumWallet` * feat(`network`): EthWallet.sign_hash_with * clippy * remove signer_mut * fix
1 parent 54cac25 commit e1fb9d9

File tree

6 files changed

+143
-16
lines changed

6 files changed

+143
-16
lines changed

crates/network/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ rustdoc-args = [
2323
workspace = true
2424

2525
[dependencies]
26+
2627
alloy-consensus = { workspace = true, features = ["std"] }
2728
alloy-consensus-any = { workspace = true, features = ["std", "serde"] }
2829
alloy-eips = { workspace = true, features = ["serde"] }
@@ -47,3 +48,4 @@ thiserror.workspace = true
4748

4849
[features]
4950
k256 = ["alloy-primitives/k256", "alloy-consensus/k256"]
51+
eip712 = ["alloy-signer/eip712"]

crates/network/src/ethereum/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::Network;
33
mod builder;
44

55
mod wallet;
6-
pub use wallet::{EthereumWallet, IntoWallet};
6+
pub use wallet::{ArcFullSigner, EthereumWallet, IntoWallet};
77

88
/// Types for a mainnet-like Ethereum network.
99
#[derive(Clone, Copy, Debug)]

crates/network/src/ethereum/wallet.rs

Lines changed: 119 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
use crate::{AnyNetwork, AnyTxEnvelope, AnyTypedTransaction, Network, NetworkWallet, TxSigner};
1+
use crate::{
2+
AnyNetwork, AnyTxEnvelope, AnyTypedTransaction, FullSigner, Network, NetworkWallet, TxSigner,
3+
};
24
use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction};
35
use alloy_primitives::{map::AddressHashMap, Address, Signature};
4-
use std::{fmt::Debug, sync::Arc};
6+
use std::{fmt::Debug, ops::Deref, sync::Arc};
57

68
use super::Ethereum;
79

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

1517
impl std::fmt::Debug for EthereumWallet {
@@ -23,7 +25,7 @@ impl std::fmt::Debug for EthereumWallet {
2325

2426
impl<S> From<S> for EthereumWallet
2527
where
26-
S: TxSigner<Signature> + Send + Sync + 'static,
28+
S: FullSigner<Signature> + Send + Sync + 'static,
2729
{
2830
fn from(signer: S) -> Self {
2931
Self::new(signer)
@@ -34,7 +36,7 @@ impl EthereumWallet {
3436
/// Create a new signer with the given signer as the default signer.
3537
pub fn new<S>(signer: S) -> Self
3638
where
37-
S: TxSigner<Signature> + Send + Sync + 'static,
39+
S: FullSigner<Signature> + Send + Sync + 'static,
3840
{
3941
let mut this = Self::default();
4042
this.register_default_signer(signer);
@@ -48,9 +50,10 @@ impl EthereumWallet {
4850
/// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
4951
pub fn register_signer<S>(&mut self, signer: S)
5052
where
51-
S: TxSigner<Signature> + Send + Sync + 'static,
53+
S: FullSigner<Signature> + Send + Sync + 'static,
5254
{
53-
self.signers.insert(signer.address(), Arc::new(signer));
55+
let arc_signer = ArcFullSigner::new(signer);
56+
self.signers.insert(arc_signer.address(), arc_signer);
5457
}
5558

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

@@ -90,15 +93,12 @@ impl EthereumWallet {
9093
}
9194

9295
/// Get the default signer.
93-
pub fn default_signer(&self) -> Arc<dyn TxSigner<Signature> + Send + Sync + 'static> {
96+
pub fn default_signer(&self) -> ArcFullSigner {
9497
self.signers.get(&self.default).cloned().expect("invalid signer")
9598
}
9699

97100
/// Get the signer for the given address.
98-
pub fn signer_by_address(
99-
&self,
100-
address: Address,
101-
) -> Option<Arc<dyn TxSigner<Signature> + Send + Sync + 'static>> {
101+
pub fn signer_by_address(&self, address: Address) -> Option<ArcFullSigner> {
102102
self.signers.get(&address).cloned()
103103
}
104104

@@ -115,6 +115,61 @@ impl EthereumWallet {
115115
.sign_transaction(tx)
116116
.await
117117
}
118+
119+
/// Signs a hash with the given signer address.
120+
///
121+
/// Hash can be arbitrary data or EIP-712 typed data hash.
122+
///
123+
/// # Example
124+
///
125+
/// ```ignore
126+
/// use alloy_sol_types::{sol, eip712_domain};
127+
/// use alloy_primitives::{address, keccak256, B256};
128+
/// use alloy_signer_local::PrivateKeySigner;
129+
/// sol! {
130+
/// struct Test {
131+
/// uint256 value;
132+
/// }
133+
/// }
134+
///
135+
/// #[tokio::main]
136+
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
137+
/// let domain = eip712_domain! {
138+
/// name: "Test",
139+
/// version: "1.0",
140+
/// chain_id: 1,
141+
/// verifying_contract: address!("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"),
142+
/// salt: keccak256("test_salt"),
143+
/// };
144+
///
145+
/// let alice: PrivateKeySigner = "0x...".parse()?;
146+
/// let bob: PrivateKeySigner = "0x...".parse()?;
147+
///
148+
/// let wallet = EthereumWallet::new(alice);
149+
/// wallet.register_signer(bob);
150+
///
151+
/// let t = Test { value: U256::from(0x42) };
152+
///
153+
/// let hash = t.eip712_signing_hash(&domain);
154+
///
155+
/// let signature = wallet.sign_hash_with(alice.address(), &hash).await?;
156+
///
157+
/// Ok(())
158+
/// }
159+
/// ```
160+
#[cfg(feature = "eip712")]
161+
pub async fn sign_hash_with(
162+
&self,
163+
signer: Address,
164+
hash: &alloy_primitives::B256,
165+
) -> alloy_signer::Result<Signature> {
166+
self.signer_by_address(signer)
167+
.ok_or_else(|| {
168+
alloy_signer::Error::other(format!("Missing signing credential for {signer}"))
169+
})?
170+
.sign_hash(hash)
171+
.await
172+
}
118173
}
119174

120175
impl<N> NetworkWallet<N> for EthereumWallet
@@ -207,3 +262,53 @@ impl<W: NetworkWallet<N>, N: Network> IntoWallet<N> for W {
207262
self
208263
}
209264
}
265+
266+
/// Wrapper type for [`FullSigner`] that is used in [`EthereumWallet`].
267+
///
268+
/// This is useful to disambiguate the function calls on a signer via [`EthereumWallet`] as
269+
/// [`TxSigner`] and [`Signer`] have the same methods e.g [`TxSigner::address`] and
270+
/// [`Signer::address`]
271+
///
272+
/// [`Signer`]: alloy_signer::Signer
273+
/// [`Signer::address`]: alloy_signer::Signer::address
274+
#[derive(Clone)]
275+
pub struct ArcFullSigner(Arc<dyn FullSigner<Signature> + Send + Sync>);
276+
277+
impl Debug for ArcFullSigner {
278+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279+
f.debug_struct("ArcFullSigner").field("address", &self.address()).finish()
280+
}
281+
}
282+
283+
impl ArcFullSigner {
284+
/// Create a new [`ArcFullSigner`] from a given [`FullSigner`].
285+
pub fn new<S>(signer: S) -> Self
286+
where
287+
S: FullSigner<Signature> + Send + Sync + 'static,
288+
{
289+
Self(Arc::new(signer))
290+
}
291+
292+
/// Get the address of the signer.
293+
pub fn address(&self) -> Address {
294+
self.0.address()
295+
}
296+
297+
/// Get the underlying [`FullSigner`] as a reference.
298+
pub fn signer(&self) -> &dyn FullSigner<Signature> {
299+
self.0.as_ref()
300+
}
301+
302+
/// Get the underlying [`FullSigner`] as an owned value.
303+
pub fn into_signer(self) -> Arc<dyn FullSigner<Signature> + Send + Sync> {
304+
self.0
305+
}
306+
}
307+
308+
impl Deref for ArcFullSigner {
309+
type Target = dyn FullSigner<Signature> + Send + Sync;
310+
311+
fn deref(&self) -> &Self::Target {
312+
self.0.as_ref()
313+
}
314+
}

crates/network/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub use transaction::{
2020
};
2121

2222
mod ethereum;
23-
pub use ethereum::{Ethereum, EthereumWallet, IntoWallet};
23+
pub use ethereum::{ArcFullSigner, Ethereum, EthereumWallet, IntoWallet};
2424

2525
/// Types for handling unknown network types.
2626
pub mod any;

crates/provider/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ wasmtimer.workspace = true
7878
alloy-consensus = { workspace = true, features = ["kzg", "k256"] }
7979
alloy-primitives = { workspace = true, features = ["rand"] }
8080
alloy-node-bindings.workspace = true
81+
alloy-network = { workspace = true, features = ["eip712"] }
8182
alloy-rpc-client = { workspace = true, features = ["reqwest"] }
8283
alloy-rlp.workspace = true
8384
alloy-signer.workspace = true

crates/provider/src/provider/wallet.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ where
9393
mod test {
9494
use super::*;
9595
use crate::ProviderBuilder;
96+
use alloy_primitives::{address, U256};
97+
use alloy_sol_types::{sol, Eip712Domain};
9698
use itertools::Itertools;
9799

98100
#[test]
@@ -109,4 +111,21 @@ mod test {
109111

110112
assert!(provider.signer_addresses().contains(&provider.default_signer_address()));
111113
}
114+
115+
#[tokio::test]
116+
async fn sign_hash() {
117+
sol! {
118+
struct Test {
119+
uint256 value;
120+
}
121+
}
122+
use alloy_sol_types::SolStruct;
123+
let provider = ProviderBuilder::new().connect_anvil_with_wallet();
124+
125+
let t = Test { value: U256::from(0x42) };
126+
let domain = Eip712Domain::default();
127+
let hash = t.eip712_signing_hash(&domain);
128+
let signer = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
129+
let _ = provider.wallet().sign_hash_with(signer, &hash).await.unwrap();
130+
}
112131
}

0 commit comments

Comments
 (0)