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