From 2afd7199d1e664c776d2876250a849c83181911e Mon Sep 17 00:00:00 2001 From: teddav Date: Thu, 19 Oct 2023 11:55:54 +0200 Subject: [PATCH 1/6] feat(anvil): add random mnemonic generation and generation from seed to Anvil --- Cargo.lock | 1 + crates/anvil/Cargo.toml | 1 + crates/anvil/src/cmd.rs | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c2721a4d82e..ec10ab0aea3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,6 +305,7 @@ dependencies = [ "memory-db", "parking_lot", "pretty_assertions", + "rand 0.8.5", "serde", "serde_json", "serde_repr", diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index 4183cc13396a..1ec423bfc6da 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -61,6 +61,7 @@ thiserror = "1" yansi = "0.5" tempfile = "3" itertools.workspace = true +rand = "0.8" # cli clap = { version = "4", features = ["derive", "env", "wrap_help"], optional = true } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 533272f44675..9eede065b1f9 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -7,9 +7,13 @@ use crate::{ use anvil_server::ServerConfig; use clap::Parser; use core::fmt; -use ethers::utils::WEI_IN_ETHER; +use ethers::{ + signers::coins_bip39::{English, Mnemonic}, + utils::WEI_IN_ETHER, +}; use foundry_config::{Chain, Config}; use futures::FutureExt; +use rand::{rngs::StdRng, SeedableRng}; use std::{ future::Future, net::IpAddr, @@ -45,9 +49,23 @@ pub struct NodeArgs { pub timestamp: Option, /// BIP39 mnemonic phrase used for generating accounts. - #[clap(long, short)] + /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used + #[clap(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])] pub mnemonic: Option, + /// Automatically generates a BIP39 mnemonic phrase, and derives accounts from it. + /// Cannot be used with other `mnemonic` options + /// You can specify the number of words you want in the mnemonic. + /// [default: 12] + #[clap(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))] + pub mnemonic_random: Option, + + /// Generates a BIP39 mnemonic phrase from a given seed + /// CAREFUL: this is obviously NOT SAFE and should only be used for testing + /// Cannot be used with other `mnemonic` options + #[clap(long, conflicts_with_all = &["mnemonic", "mnemonic_random"])] + pub mnemonic_seed: Option, + /// Sets the derivation path of the child key to be derived. /// /// [default: m/44'/60'/0'/0/] @@ -218,6 +236,17 @@ impl NodeArgs { .chain_id(self.evm_opts.chain_id.unwrap_or_else(|| CHAIN_ID.into())); if let Some(ref mnemonic) = self.mnemonic { gen = gen.phrase(mnemonic); + } else if let Some(count) = self.mnemonic_random { + let mut rng = rand::thread_rng(); + let mnemonic = match Mnemonic::::new_with_count(&mut rng, count) { + Ok(mnemonic) => mnemonic.to_phrase(), + Err(_) => DEFAULT_MNEMONIC.to_string(), + }; + gen = gen.phrase(mnemonic); + } else if let Some(seed) = self.mnemonic_seed { + let mut seed = StdRng::seed_from_u64(seed); + let mnemonic = Mnemonic::::new(&mut seed).to_phrase(); + gen = gen.phrase(mnemonic); } if let Some(ref derivation) = self.derivation_path { gen = gen.derivation_path(derivation); From 45147fab321a369bf61f6922db636af1814ca8e0 Mon Sep 17 00:00:00 2001 From: teddav Date: Thu, 19 Oct 2023 12:51:04 +0200 Subject: [PATCH 2/6] Cast: added a new 'wallet' option to generate a mnemonic and associated accounts --- crates/cast/bin/cmd/wallet/mod.rs | 46 +++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 6f14585e5278..24e25a542f0d 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -2,8 +2,11 @@ use alloy_primitives::Address; use clap::Parser; use ethers::{ core::rand::thread_rng, - signers::{LocalWallet, Signer}, - types::{transaction::eip712::TypedData, Signature}, + signers::{ + coins_bip39::{English, Mnemonic}, + LocalWallet, MnemonicBuilder, Signer, + }, + types::{transaction::eip712::TypedData, Address, Signature}, }; use eyre::{Context, Result}; use foundry_cli::opts::{RawWallet, Wallet}; @@ -38,6 +41,18 @@ pub enum WalletSubcommands { unsafe_password: Option, }, + /// Generates a random BIP39 mnemonic phrase + #[clap(visible_alias = "nm")] + NewMnemonic { + /// Number of words for the mnemonic + #[clap(long, short, default_value = "12")] + words: usize, + + /// Number of accounts to display + #[clap(long, short, default_value = "1")] + accounts: u8, + }, + /// Generate a vanity address. #[clap(visible_alias = "va")] Vanity(VanityArgs), @@ -149,6 +164,33 @@ impl WalletSubcommands { println!("Private key: 0x{}", hex::encode(wallet.signer().to_bytes())); } } + WalletSubcommands::NewMnemonic { words, accounts } => { + let mut rng = thread_rng(); + let phrase = Mnemonic::::new_with_count(&mut rng, words)?.to_phrase(); + + let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); + let derivation_path = "m/44'/60'/0'/0/"; + let wallets = (0..accounts) + .into_iter() + .map(|i| { + builder + .clone() + .derivation_path(&format!("{derivation_path}{i}")) + .unwrap() + .build() + .unwrap() + }) + .collect::>(); + + println!("Successfully generated a new mnemonic."); + println!("Phrase:\n{phrase}"); + println!("\nAccounts:"); + for (i, wallet) in wallets.iter().enumerate() { + println!("- Account {i}:"); + println!("Address: {}", SimpleCast::to_checksum_address(&wallet.address())); + println!("Private key: 0x{}\n", hex::encode(wallet.signer().to_bytes())); + } + } WalletSubcommands::Vanity(cmd) => { cmd.run()?; } From e0dc8f102282448f1ef74b4607af9f47dab552a2 Mon Sep 17 00:00:00 2001 From: teddav Date: Thu, 19 Oct 2023 13:13:15 +0200 Subject: [PATCH 3/6] fix clippy --- crates/cast/bin/cmd/wallet/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 24e25a542f0d..b5ad96741ea7 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -170,9 +170,7 @@ impl WalletSubcommands { let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); let derivation_path = "m/44'/60'/0'/0/"; - let wallets = (0..accounts) - .into_iter() - .map(|i| { + let wallets = (0..accounts).map(|i| { builder .clone() .derivation_path(&format!("{derivation_path}{i}")) From c04381f0ef04a0794c85797918acf7e29fa09035 Mon Sep 17 00:00:00 2001 From: teddav Date: Thu, 19 Oct 2023 16:35:21 +0200 Subject: [PATCH 4/6] fix fmt --- crates/cast/bin/cmd/wallet/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index b5ad96741ea7..a28920e597c3 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -170,7 +170,8 @@ impl WalletSubcommands { let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); let derivation_path = "m/44'/60'/0'/0/"; - let wallets = (0..accounts).map(|i| { + let wallets = (0..accounts) + .map(|i| { builder .clone() .derivation_path(&format!("{derivation_path}{i}")) From b8c349743b417860e05649bcc5c02ca252d8865e Mon Sep 17 00:00:00 2001 From: teddav Date: Thu, 2 Nov 2023 19:38:25 +0100 Subject: [PATCH 5/6] update anvil option to 'unsafe' --- crates/anvil/src/cmd.rs | 6 ++++-- crates/cast/bin/cmd/wallet/mod.rs | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 9eede065b1f9..a1e28072a006 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -61,9 +61,11 @@ pub struct NodeArgs { pub mnemonic_random: Option, /// Generates a BIP39 mnemonic phrase from a given seed - /// CAREFUL: this is obviously NOT SAFE and should only be used for testing /// Cannot be used with other `mnemonic` options - #[clap(long, conflicts_with_all = &["mnemonic", "mnemonic_random"])] + /// + /// CAREFUL: this is NOT SAFE and should only be used for testing. + /// Never use the private keys generated in production. + #[clap(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])] pub mnemonic_seed: Option, /// Sets the derivation path of the child key to be derived. diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index a28920e597c3..5fac0c3a226a 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -6,8 +6,9 @@ use ethers::{ coins_bip39::{English, Mnemonic}, LocalWallet, MnemonicBuilder, Signer, }, - types::{transaction::eip712::TypedData, Address, Signature}, + types::{transaction::eip712::TypedData, Signature}, }; +use ethers_core::utils::to_checksum; use eyre::{Context, Result}; use foundry_cli::opts::{RawWallet, Wallet}; use foundry_common::fs; @@ -181,12 +182,12 @@ impl WalletSubcommands { }) .collect::>(); - println!("Successfully generated a new mnemonic."); + println!("{}", Paint::green("Successfully generated a new mnemonic.")); println!("Phrase:\n{phrase}"); println!("\nAccounts:"); for (i, wallet) in wallets.iter().enumerate() { println!("- Account {i}:"); - println!("Address: {}", SimpleCast::to_checksum_address(&wallet.address())); + println!("Address: {}", to_checksum(&wallet.address(), None)); println!("Private key: 0x{}\n", hex::encode(wallet.signer().to_bytes())); } } From 93e4d356c41e70f368fd339be26ec2cd05d996b1 Mon Sep 17 00:00:00 2001 From: Enrique Ortiz Date: Fri, 3 Nov 2023 12:11:09 -0400 Subject: [PATCH 6/6] chore: remove unwraps --- crates/cast/bin/cmd/wallet/mod.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 5fac0c3a226a..88e28d3b8f8c 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -172,15 +172,10 @@ impl WalletSubcommands { let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); let derivation_path = "m/44'/60'/0'/0/"; let wallets = (0..accounts) - .map(|i| { - builder - .clone() - .derivation_path(&format!("{derivation_path}{i}")) - .unwrap() - .build() - .unwrap() - }) - .collect::>(); + .map(|i| builder.clone().derivation_path(&format!("{derivation_path}{i}"))) + .collect::, _>>()?; + let wallets = + wallets.into_iter().map(|b| b.build()).collect::, _>>()?; println!("{}", Paint::green("Successfully generated a new mnemonic.")); println!("Phrase:\n{phrase}");