1use super::Wallet;
3
4use crate::wallet::mnemonic::MnemonicBuilderError;
5use coins_bip32::Bip32Error;
6use coins_bip39::MnemonicError;
7#[cfg(not(target_arch = "wasm32"))]
8use elliptic_curve::rand_core;
9#[cfg(not(target_arch = "wasm32"))]
10use eth_keystore::KeystoreError;
11use ethers_core::{
12 k256::ecdsa::{self, SigningKey},
13 rand::{CryptoRng, Rng},
14 utils::secret_key_to_address,
15};
16#[cfg(not(target_arch = "wasm32"))]
17use std::path::Path;
18use std::str::FromStr;
19use thiserror::Error;
20
21#[derive(Error, Debug)]
22pub enum WalletError {
24 #[error(transparent)]
26 Bip32Error(#[from] Bip32Error),
27 #[error(transparent)]
29 Bip39Error(#[from] MnemonicError),
30 #[cfg(not(target_arch = "wasm32"))]
32 #[error(transparent)]
33 EthKeystoreError(#[from] KeystoreError),
34 #[error(transparent)]
36 EcdsaError(#[from] ecdsa::Error),
37 #[error(transparent)]
39 HexError(#[from] hex::FromHexError),
40 #[error(transparent)]
42 IoError(#[from] std::io::Error),
43 #[error(transparent)]
45 MnemonicBuilderError(#[from] MnemonicBuilderError),
46 #[error("error encoding eip712 struct: {0:?}")]
48 Eip712Error(String),
49}
50
51impl Wallet<SigningKey> {
52 #[cfg(not(target_arch = "wasm32"))]
57 pub fn new_keystore<P, R, S>(
58 dir: P,
59 rng: &mut R,
60 password: S,
61 name: Option<&str>,
62 ) -> Result<(Self, String), WalletError>
63 where
64 P: AsRef<Path>,
65 R: Rng + CryptoRng + rand_core::CryptoRng,
66 S: AsRef<[u8]>,
67 {
68 let (secret, uuid) = eth_keystore::new(dir, rng, password, name)?;
69 let signer = SigningKey::from_bytes(secret.as_slice().into())?;
70 let address = secret_key_to_address(&signer);
71 Ok((Self { signer, address, chain_id: 1 }, uuid))
72 }
73
74 #[cfg(not(target_arch = "wasm32"))]
76 pub fn decrypt_keystore<P, S>(keypath: P, password: S) -> Result<Self, WalletError>
77 where
78 P: AsRef<Path>,
79 S: AsRef<[u8]>,
80 {
81 let secret = eth_keystore::decrypt_key(keypath, password)?;
82 let signer = SigningKey::from_bytes(secret.as_slice().into())?;
83 let address = secret_key_to_address(&signer);
84 Ok(Self { signer, address, chain_id: 1 })
85 }
86
87 #[cfg(not(target_arch = "wasm32"))]
92 pub fn encrypt_keystore<P, R, B, S>(
93 keypath: P,
94 rng: &mut R,
95 pk: B,
96 password: S,
97 name: Option<&str>,
98 ) -> Result<(Self, String), WalletError>
99 where
100 P: AsRef<Path>,
101 R: Rng + CryptoRng,
102 B: AsRef<[u8]>,
103 S: AsRef<[u8]>,
104 {
105 let uuid = eth_keystore::encrypt_key(keypath, rng, &pk, password, name)?;
106 let signer = SigningKey::from_slice(pk.as_ref())?;
107 let address = secret_key_to_address(&signer);
108 Ok((Self { signer, address, chain_id: 1 }, uuid))
109 }
110
111 pub fn new<R: Rng + CryptoRng>(rng: &mut R) -> Self {
113 let signer = SigningKey::random(rng);
114 let address = secret_key_to_address(&signer);
115 Self { signer, address, chain_id: 1 }
116 }
117
118 pub fn from_bytes(bytes: &[u8]) -> Result<Self, WalletError> {
120 let signer = SigningKey::from_bytes(bytes.into())?;
121 let address = secret_key_to_address(&signer);
122 Ok(Self { signer, address, chain_id: 1 })
123 }
124}
125
126impl PartialEq for Wallet<SigningKey> {
127 fn eq(&self, other: &Self) -> bool {
128 self.signer.to_bytes().eq(&other.signer.to_bytes()) &&
129 self.address == other.address &&
130 self.chain_id == other.chain_id
131 }
132}
133
134impl From<SigningKey> for Wallet<SigningKey> {
135 fn from(signer: SigningKey) -> Self {
136 let address = secret_key_to_address(&signer);
137
138 Self { signer, address, chain_id: 1 }
139 }
140}
141
142use ethers_core::k256::SecretKey as K256SecretKey;
143
144impl From<K256SecretKey> for Wallet<SigningKey> {
145 fn from(key: K256SecretKey) -> Self {
146 let signer = key.into();
147 let address = secret_key_to_address(&signer);
148
149 Self { signer, address, chain_id: 1 }
150 }
151}
152
153impl FromStr for Wallet<SigningKey> {
154 type Err = WalletError;
155
156 fn from_str(src: &str) -> Result<Self, Self::Err> {
157 let src = hex::decode(src.strip_prefix("0X").unwrap_or(src))?;
158
159 if src.len() != 32 {
160 return Err(WalletError::HexError(hex::FromHexError::InvalidStringLength))
161 }
162
163 let sk = SigningKey::from_bytes(src.as_slice().into())?;
164 Ok(sk.into())
165 }
166}
167
168impl TryFrom<&str> for Wallet<SigningKey> {
169 type Error = WalletError;
170
171 fn try_from(value: &str) -> Result<Self, Self::Error> {
172 value.parse()
173 }
174}
175
176impl TryFrom<String> for Wallet<SigningKey> {
177 type Error = WalletError;
178
179 fn try_from(value: String) -> Result<Self, Self::Error> {
180 value.parse()
181 }
182}
183
184#[cfg(test)]
185#[cfg(not(target_arch = "wasm32"))]
186mod tests {
187 use super::*;
188 use crate::{LocalWallet, Signer};
189 use ethers_core::types::Address;
190 use tempfile::tempdir;
191
192 #[test]
193 fn parse_pk() {
194 let s = "6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea30b";
195 let _pk: Wallet<SigningKey> = s.parse().unwrap();
196 }
197
198 #[test]
199 fn parse_short_key() {
200 let s = "6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea3";
201 assert!(s.len() < 64);
202 let pk = s.parse::<LocalWallet>().unwrap_err();
203 match pk {
204 WalletError::HexError(hex::FromHexError::InvalidStringLength) => {}
205 _ => panic!("Unexpected error"),
206 }
207 }
208
209 async fn test_encrypted_json_keystore(key: Wallet<SigningKey>, uuid: &str, dir: &Path) {
210 let message = "Some data";
212 let signature = key.sign_message(message).await.unwrap();
213
214 let path = Path::new(dir).join(uuid);
217 let key2 = Wallet::<SigningKey>::decrypt_keystore(path.clone(), "randpsswd").unwrap();
218
219 let signature2 = key2.sign_message(message).await.unwrap();
220 assert_eq!(signature, signature2);
221
222 std::fs::remove_file(&path).unwrap();
223 }
224
225 #[tokio::test]
226 async fn encrypted_json_keystore_new() {
227 let dir = tempdir().unwrap();
229 let mut rng = rand::thread_rng();
230 let (key, uuid) =
231 Wallet::<SigningKey>::new_keystore(&dir, &mut rng, "randpsswd", None).unwrap();
232
233 test_encrypted_json_keystore(key, &uuid, dir.path()).await;
234 }
235
236 #[tokio::test]
237 async fn encrypted_json_keystore_from_pk() {
238 let dir = tempdir().unwrap();
240 let mut rng = rand::thread_rng();
241
242 let private_key =
243 hex::decode("6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea30b")
244 .unwrap();
245
246 let (key, uuid) =
247 Wallet::<SigningKey>::encrypt_keystore(&dir, &mut rng, private_key, "randpsswd", None)
248 .unwrap();
249
250 test_encrypted_json_keystore(key, &uuid, dir.path()).await;
251 }
252
253 #[tokio::test]
254 async fn signs_msg() {
255 let message = "Some data";
256 let hash = ethers_core::utils::hash_message(message);
257 let key = Wallet::<SigningKey>::new(&mut rand::thread_rng());
258 let address = key.address;
259
260 let signature = key.sign_message(message).await.unwrap();
262
263 let recovered = signature.recover(message).unwrap();
265
266 let recovered2 = signature.recover(hash).unwrap();
268
269 signature.verify(message, address).unwrap();
271
272 assert_eq!(recovered, address);
273 assert_eq!(recovered2, address);
274 }
275
276 #[tokio::test]
277 #[cfg(not(feature = "celo"))]
278 async fn signs_tx() {
279 use crate::TypedTransaction;
280 use ethers_core::types::{TransactionRequest, U64};
281 let tx: TypedTransaction = TransactionRequest {
284 from: None,
285 to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse::<Address>().unwrap().into()),
286 value: Some(1_000_000_000.into()),
287 gas: Some(2_000_000.into()),
288 nonce: Some(0.into()),
289 gas_price: Some(21_000_000_000u128.into()),
290 data: None,
291 chain_id: Some(U64::one()),
292 }
293 .into();
294 let wallet: Wallet<SigningKey> =
295 "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap();
296 let wallet = wallet.with_chain_id(tx.chain_id().unwrap().as_u64());
297
298 let sig = wallet.sign_transaction(&tx).await.unwrap();
299 let sighash = tx.sighash();
300 sig.verify(sighash, wallet.address).unwrap();
301 }
302
303 #[tokio::test]
304 #[cfg(not(feature = "celo"))]
305 async fn signs_tx_empty_chain_id() {
306 use crate::TypedTransaction;
307 use ethers_core::types::TransactionRequest;
308 let tx: TypedTransaction = TransactionRequest {
311 from: None,
312 to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse::<Address>().unwrap().into()),
313 value: Some(1_000_000_000.into()),
314 gas: Some(2_000_000.into()),
315 nonce: Some(0.into()),
316 gas_price: Some(21_000_000_000u128.into()),
317 data: None,
318 chain_id: None,
319 }
320 .into();
321 let wallet: Wallet<SigningKey> =
322 "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap();
323 let wallet = wallet.with_chain_id(1u64);
324
325 let sig = wallet.sign_transaction(&tx).await.unwrap();
327
328 let mut tx = tx;
331 tx.set_chain_id(1);
332 let sighash = tx.sighash();
333 sig.verify(sighash, wallet.address).unwrap();
334 }
335
336 #[test]
337 #[cfg(not(feature = "celo"))]
338 fn signs_tx_empty_chain_id_sync() {
339 use crate::TypedTransaction;
340 use ethers_core::types::TransactionRequest;
341
342 let chain_id = 1337u64;
343 let tx: TypedTransaction = TransactionRequest {
346 from: None,
347 to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse::<Address>().unwrap().into()),
348 value: Some(1_000_000_000u64.into()),
349 gas: Some(2_000_000u64.into()),
350 nonce: Some(0u64.into()),
351 gas_price: Some(21_000_000_000u128.into()),
352 data: None,
353 chain_id: None,
354 }
355 .into();
356 let wallet: Wallet<SigningKey> =
357 "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap();
358 let wallet = wallet.with_chain_id(chain_id);
359
360 let sig = wallet.sign_transaction_sync(&tx).unwrap();
363
364 let recid = (sig.v - 35) % 2;
366 assert_eq!(sig.v, chain_id * 2 + 35 + recid);
368
369 let mut tx = tx;
372 tx.set_chain_id(chain_id);
373 let sighash = tx.sighash();
374 sig.verify(sighash, wallet.address).unwrap();
375 }
376
377 #[test]
378 fn key_to_address() {
379 let wallet: Wallet<SigningKey> =
380 "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
381 assert_eq!(
382 wallet.address,
383 Address::from_str("7E5F4552091A69125d5DfCb7b8C2659029395Bdf").expect("Decoding failed")
384 );
385
386 let wallet: Wallet<SigningKey> =
387 "0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap();
388 assert_eq!(
389 wallet.address,
390 Address::from_str("2B5AD5c4795c026514f8317c7a215E218DcCD6cF").expect("Decoding failed")
391 );
392
393 let wallet: Wallet<SigningKey> =
394 "0000000000000000000000000000000000000000000000000000000000000003".parse().unwrap();
395 assert_eq!(
396 wallet.address,
397 Address::from_str("6813Eb9362372EEF6200f3b1dbC3f819671cBA69").expect("Decoding failed")
398 );
399 }
400
401 #[test]
402 fn key_from_bytes() {
403 let wallet: Wallet<SigningKey> =
404 "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
405
406 let key_as_bytes = wallet.signer.to_bytes();
407 let wallet_from_bytes = Wallet::from_bytes(&key_as_bytes).unwrap();
408
409 assert_eq!(wallet.address, wallet_from_bytes.address);
410 assert_eq!(wallet.chain_id, wallet_from_bytes.chain_id);
411 assert_eq!(wallet.signer, wallet_from_bytes.signer);
412 }
413
414 #[test]
415 fn key_from_str() {
416 let wallet: Wallet<SigningKey> =
417 "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
418
419 let wallet_0x: Wallet<SigningKey> =
421 "0x0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
422 assert_eq!(wallet.address, wallet_0x.address);
423 assert_eq!(wallet.chain_id, wallet_0x.chain_id);
424 assert_eq!(wallet.signer, wallet_0x.signer);
425
426 let wallet_0x_cap: Wallet<SigningKey> =
428 "0X0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
429 assert_eq!(wallet.address, wallet_0x_cap.address);
430 assert_eq!(wallet.chain_id, wallet_0x_cap.chain_id);
431 assert_eq!(wallet.signer, wallet_0x_cap.signer);
432
433 let wallet_0x_tryfrom_str: Wallet<SigningKey> =
435 "0x0000000000000000000000000000000000000000000000000000000000000001"
436 .try_into()
437 .unwrap();
438 assert_eq!(wallet.address, wallet_0x_tryfrom_str.address);
439 assert_eq!(wallet.chain_id, wallet_0x_tryfrom_str.chain_id);
440 assert_eq!(wallet.signer, wallet_0x_tryfrom_str.signer);
441
442 let wallet_0x_tryfrom_string: Wallet<SigningKey> =
444 "0x0000000000000000000000000000000000000000000000000000000000000001"
445 .to_string()
446 .try_into()
447 .unwrap();
448 assert_eq!(wallet.address, wallet_0x_tryfrom_string.address);
449 assert_eq!(wallet.chain_id, wallet_0x_tryfrom_string.chain_id);
450 assert_eq!(wallet.signer, wallet_0x_tryfrom_string.signer);
451
452 "0z0000000000000000000000000000000000000000000000000000000000000001"
454 .parse::<Wallet<SigningKey>>()
455 .unwrap_err();
456 }
457}