From 6899af26b07d24254607796d6957fe305d96ba0c Mon Sep 17 00:00:00 2001 From: DimAn Date: Wed, 7 Sep 2022 20:31:40 +0200 Subject: [PATCH] keygen: add the ability to use derivation path for new & grind commands (#21614) * keygen: add --use-derivation-path for new & grind * keygen: add prompt:// uri scheme to new and grind * fmt * migrate to clap-v3-utils * Revert "migrate to clap-v3-utils" This reverts commit 77f33262ce6c4e95ac1cc62cca32749516f7f357. * Revert "fmt" This reverts commit 038cd4ce9628c57dec1f5a4716e2c5baacbe57b3. * Revert "keygen: add prompt:// uri scheme to new and grind" This reverts commit 029ea61409a1a16ba2c45483ade2c01f84f25fac. * - remove `use` from arg - fix issue from first commit with default value for derivation path - refactor arg definition and acquiring --- keygen/src/keygen.rs | 58 ++++++++++++++++++++++++++++++++++++-- sdk/src/derivation_path.rs | 2 +- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index 953c9153bc..164c3627fb 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -14,10 +14,14 @@ use { solana_cli_config::{Config, CONFIG_FILE}, solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_sdk::{ + derivation_path::DerivationPath, instruction::{AccountMeta, Instruction}, message::Message, pubkey::{write_pubkey_file, Pubkey}, - signature::{keypair_from_seed, write_keypair, write_keypair_file, Keypair, Signer}, + signature::{ + keypair_from_seed, keypair_from_seed_and_derivation_path, write_keypair, + write_keypair_file, Keypair, Signer, + }, }, std::{ collections::HashSet, @@ -34,6 +38,7 @@ use { }; const NO_PASSPHRASE: &str = ""; +const DEFAULT_DERIVATION_PATH: &str = "m/44'/501'/0'/0'"; struct GrindMatch { starts: String, @@ -325,6 +330,33 @@ fn grind_parse_args( grind_matches } +fn derivation_path_arg<'a>() -> Arg<'a> { + Arg::new("derivation_path") + .long("derivation-path") + .value_name("DERIVATION_PATH") + .takes_value(true) + .min_values(0) + .max_values(1) + .help("Derivation path. All indexes will be promoted to hardened. \ + If arg is not presented then derivation path will not be used. \ + If arg is presented with empty DERIVATION_PATH value then m/44'/501'/0'/0' will be used." + ) +} + +fn acquire_derivation_path( + matches: &ArgMatches, +) -> Result, Box> { + if matches.is_present("derivation_path") { + Ok(Some(DerivationPath::from_absolute_path_str( + matches + .value_of("derivation_path") + .unwrap_or(DEFAULT_DERIVATION_PATH), + )?)) + } else { + Ok(None) + } +} + fn main() -> Result<(), Box> { let default_num_threads = num_cpus::get().to_string(); let matches = Command::new(crate_name!()) @@ -389,6 +421,9 @@ fn main() -> Result<(), Box> { .long("silent") .help("Do not display seed phrase. Useful when piping output to other programs that prompt for user input, like gpg"), ) + .arg( + derivation_path_arg() + ) .key_generation_common_args() .arg(no_outfile_arg() .conflicts_with_all(&["outfile", "silent"]) @@ -450,6 +485,10 @@ fn main() -> Result<(), Box> { .long("use-mnemonic") .help("Generate using a mnemonic key phrase. Expect a significant slowdown in this mode"), ) + .arg( + derivation_path_arg() + .requires("use_mnemonic") + ) .key_generation_common_args() .arg( no_outfile_arg() @@ -576,11 +615,17 @@ fn do_main(matches: &ArgMatches) -> Result<(), Box> { if !silent { println!("Generating a new keypair"); } + + let derivation_path = acquire_derivation_path(matches)?; + let mnemonic = Mnemonic::new(mnemonic_type, language); let (passphrase, passphrase_message) = acquire_passphrase_and_message(matches).unwrap(); let seed = Seed::new(&mnemonic, &passphrase); - let keypair = keypair_from_seed(seed.as_bytes())?; + let keypair = match derivation_path { + Some(_) => keypair_from_seed_and_derivation_path(seed.as_bytes(), derivation_path), + None => keypair_from_seed(seed.as_bytes()), + }?; if let Some(outfile) = outfile { output_keypair(&keypair, outfile, "new") @@ -671,6 +716,8 @@ fn do_main(matches: &ArgMatches) -> Result<(), Box> { let use_mnemonic = matches.is_present("use_mnemonic"); + let derivation_path = acquire_derivation_path(matches)?; + let word_count: usize = matches.value_of_t(WORD_COUNT_ARG.name).unwrap(); let mnemonic_type = MnemonicType::for_word_count(word_count)?; let language = acquire_language(matches); @@ -696,6 +743,7 @@ fn do_main(matches: &ArgMatches) -> Result<(), Box> { let grind_matches_thread_safe = grind_matches_thread_safe.clone(); let passphrase = passphrase.clone(); let passphrase_message = passphrase_message.clone(); + let derivation_path = derivation_path.clone(); thread::spawn(move || loop { if done.load(Ordering::Relaxed) { @@ -713,7 +761,11 @@ fn do_main(matches: &ArgMatches) -> Result<(), Box> { let (keypair, phrase) = if use_mnemonic { let mnemonic = Mnemonic::new(mnemonic_type, language); let seed = Seed::new(&mnemonic, &passphrase); - (keypair_from_seed(seed.as_bytes()).unwrap(), mnemonic.phrase().to_string()) + let keypair = match derivation_path { + Some(_) => keypair_from_seed_and_derivation_path(seed.as_bytes(), derivation_path.clone()), + None => keypair_from_seed(seed.as_bytes()), + }.unwrap(); + (keypair, mnemonic.phrase().to_string()) } else { (Keypair::new(), "".to_string()) }; diff --git a/sdk/src/derivation_path.rs b/sdk/src/derivation_path.rs index bb03ed9a50..f9396e554c 100644 --- a/sdk/src/derivation_path.rs +++ b/sdk/src/derivation_path.rs @@ -79,7 +79,7 @@ impl DerivationPath { Ok(Self::new_bip44_with_coin(coin, account, change)) } - fn from_absolute_path_str(path: &str) -> Result { + pub fn from_absolute_path_str(path: &str) -> Result { let inner = DerivationPath::_from_absolute_path_insecure_str(path)? .into_iter() .map(|c| ChildIndex::Hardened(c.to_u32()))