ethers_signers/wallet/
mod.rs

1mod mnemonic;
2pub use mnemonic::MnemonicBuilder;
3
4mod private_key;
5pub use private_key::WalletError;
6
7#[cfg(all(feature = "yubihsm", not(target_arch = "wasm32")))]
8mod yubi;
9
10use crate::{to_eip155_v, Signer};
11use ethers_core::{
12    k256::{
13        ecdsa::{signature::hazmat::PrehashSigner, RecoveryId, Signature as RecoverableSignature},
14        elliptic_curve::FieldBytes,
15        Secp256k1,
16    },
17    types::{
18        transaction::{eip2718::TypedTransaction, eip712::Eip712},
19        Address, Signature, H256, U256,
20    },
21    utils::hash_message,
22};
23
24use async_trait::async_trait;
25use std::fmt;
26
27/// An Ethereum private-public key pair which can be used for signing messages.
28///
29/// # Examples
30///
31/// ## Signing and Verifying a message
32///
33/// The wallet can be used to produce ECDSA [`Signature`] objects, which can be
34/// then verified. Note that this uses [`hash_message`] under the hood which will
35/// prefix the message being hashed with the `Ethereum Signed Message` domain separator.
36///
37/// ```
38/// use ethers_core::rand::thread_rng;
39/// use ethers_signers::{LocalWallet, Signer};
40///
41/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
42/// let wallet = LocalWallet::new(&mut thread_rng());
43///
44/// // Optionally, the wallet's chain id can be set, in order to use EIP-155
45/// // replay protection with different chains
46/// let wallet = wallet.with_chain_id(1337u64);
47///
48/// // The wallet can be used to sign messages
49/// let message = b"hello";
50/// let signature = wallet.sign_message(message).await?;
51/// assert_eq!(signature.recover(&message[..]).unwrap(), wallet.address());
52///
53/// // LocalWallet is clonable:
54/// let wallet_clone = wallet.clone();
55/// let signature2 = wallet_clone.sign_message(message).await?;
56/// assert_eq!(signature, signature2);
57/// # Ok(())
58/// # }
59/// ```
60///
61/// [`Signature`]: ethers_core::types::Signature
62/// [`hash_message`]: fn@ethers_core::utils::hash_message
63#[derive(Clone)]
64pub struct Wallet<D: PrehashSigner<(RecoverableSignature, RecoveryId)>> {
65    /// The Wallet's private Key
66    pub(crate) signer: D,
67    /// The wallet's address
68    pub(crate) address: Address,
69    /// The wallet's chain id (for EIP-155)
70    pub(crate) chain_id: u64,
71}
72
73impl<D: PrehashSigner<(RecoverableSignature, RecoveryId)>> Wallet<D> {
74    /// Construct a new wallet with an external Signer
75    pub fn new_with_signer(signer: D, address: Address, chain_id: u64) -> Self {
76        Wallet { signer, address, chain_id }
77    }
78}
79
80#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
81#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
82impl<D: Sync + Send + PrehashSigner<(RecoverableSignature, RecoveryId)>> Signer for Wallet<D> {
83    type Error = WalletError;
84
85    async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
86        &self,
87        message: S,
88    ) -> Result<Signature, Self::Error> {
89        let message = message.as_ref();
90        let message_hash = hash_message(message);
91
92        self.sign_hash(message_hash)
93    }
94
95    async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<Signature, Self::Error> {
96        let mut tx_with_chain = tx.clone();
97        if tx_with_chain.chain_id().is_none() {
98            // in the case we don't have a chain_id, let's use the signer chain id instead
99            tx_with_chain.set_chain_id(self.chain_id);
100        }
101        self.sign_transaction_sync(&tx_with_chain)
102    }
103
104    async fn sign_typed_data<T: Eip712 + Send + Sync>(
105        &self,
106        payload: &T,
107    ) -> Result<Signature, Self::Error> {
108        let encoded =
109            payload.encode_eip712().map_err(|e| Self::Error::Eip712Error(e.to_string()))?;
110
111        self.sign_hash(H256::from(encoded))
112    }
113
114    fn address(&self) -> Address {
115        self.address
116    }
117
118    /// Gets the wallet's chain id
119    fn chain_id(&self) -> u64 {
120        self.chain_id
121    }
122
123    /// Sets the wallet's chain_id, used in conjunction with EIP-155 signing
124    fn with_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
125        self.chain_id = chain_id.into();
126        self
127    }
128}
129
130impl<D: PrehashSigner<(RecoverableSignature, RecoveryId)>> Wallet<D> {
131    /// Synchronously signs the provided transaction, normalizing the signature `v` value with
132    /// EIP-155 using the transaction's `chain_id`, or the signer's `chain_id` if the transaction
133    /// does not specify one.
134    pub fn sign_transaction_sync(&self, tx: &TypedTransaction) -> Result<Signature, WalletError> {
135        // rlp (for sighash) must have the same chain id as v in the signature
136        let chain_id = tx.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id);
137        let mut tx = tx.clone();
138        tx.set_chain_id(chain_id);
139
140        let sighash = tx.sighash();
141        let mut sig = self.sign_hash(sighash)?;
142
143        // sign_hash sets `v` to recid + 27, so we need to subtract 27 before normalizing
144        sig.v = to_eip155_v(sig.v as u8 - 27, chain_id);
145        Ok(sig)
146    }
147
148    /// Signs the provided hash.
149    pub fn sign_hash(&self, hash: H256) -> Result<Signature, WalletError> {
150        let (recoverable_sig, recovery_id) = self.signer.sign_prehash(hash.as_ref())?;
151
152        let v = u8::from(recovery_id) as u64 + 27;
153
154        let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
155        let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
156        let r = U256::from_big_endian(r_bytes.as_slice());
157        let s = U256::from_big_endian(s_bytes.as_slice());
158
159        Ok(Signature { r, s, v })
160    }
161
162    /// Gets the wallet's signer
163    pub fn signer(&self) -> &D {
164        &self.signer
165    }
166}
167
168// do not log the signer
169impl<D: PrehashSigner<(RecoverableSignature, RecoveryId)>> fmt::Debug for Wallet<D> {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        f.debug_struct("Wallet")
172            .field("address", &self.address)
173            .field("chain_Id", &self.chain_id)
174            .finish()
175    }
176}