[clap-v3-utils, keygen] Refactor mnemonics, derivation path, and argument parsing clap logic in keygen (#30977)
* move mnemonic clap logic from keygen to clap-v3-utils * move derivation path clap logic from keygen to clap-v3-utils * move keygen clap logic from keygen to clap-v3-utils * resolve error from rebase * resolve conflict from rebase * reorganize derivation_path and mnemonic as modules inside keygen
This commit is contained in:
parent
125a18550d
commit
618d8cf2a6
|
@ -0,0 +1,34 @@
|
||||||
|
use {
|
||||||
|
clap::{Arg, ArgMatches},
|
||||||
|
solana_sdk::derivation_path::DerivationPath,
|
||||||
|
std::error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DEFAULT_DERIVATION_PATH: &str = "m/44'/501'/0'/0'";
|
||||||
|
|
||||||
|
pub 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."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acquire_derivation_path(
|
||||||
|
matches: &ArgMatches,
|
||||||
|
) -> Result<Option<DerivationPath>, Box<dyn error::Error>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
use {
|
||||||
|
crate::{keypair::prompt_passphrase, ArgConstant},
|
||||||
|
bip39::Language,
|
||||||
|
clap::{Arg, ArgMatches},
|
||||||
|
std::error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NO_PASSPHRASE: &str = "";
|
||||||
|
|
||||||
|
pub const WORD_COUNT_ARG: ArgConstant<'static> = ArgConstant {
|
||||||
|
long: "word-count",
|
||||||
|
name: "word_count",
|
||||||
|
help: "Specify the number of words that will be present in the generated seed phrase",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LANGUAGE_ARG: ArgConstant<'static> = ArgConstant {
|
||||||
|
long: "language",
|
||||||
|
name: "language",
|
||||||
|
help: "Specify the mnemonic language that will be present in the generated seed phrase",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NO_PASSPHRASE_ARG: ArgConstant<'static> = ArgConstant {
|
||||||
|
long: "no-bip39-passphrase",
|
||||||
|
name: "no_passphrase",
|
||||||
|
help: "Do not prompt for a BIP39 passphrase",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn word_count_arg<'a>() -> Arg<'a> {
|
||||||
|
Arg::new(WORD_COUNT_ARG.name)
|
||||||
|
.long(WORD_COUNT_ARG.long)
|
||||||
|
.possible_values(["12", "15", "18", "21", "24"])
|
||||||
|
.default_value("12")
|
||||||
|
.value_name("NUMBER")
|
||||||
|
.takes_value(true)
|
||||||
|
.help(WORD_COUNT_ARG.help)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_arg<'a>() -> Arg<'a> {
|
||||||
|
Arg::new(LANGUAGE_ARG.name)
|
||||||
|
.long(LANGUAGE_ARG.long)
|
||||||
|
.possible_values([
|
||||||
|
"english",
|
||||||
|
"chinese-simplified",
|
||||||
|
"chinese-traditional",
|
||||||
|
"japanese",
|
||||||
|
"spanish",
|
||||||
|
"korean",
|
||||||
|
"french",
|
||||||
|
"italian",
|
||||||
|
])
|
||||||
|
.default_value("english")
|
||||||
|
.value_name("LANGUAGE")
|
||||||
|
.takes_value(true)
|
||||||
|
.help(LANGUAGE_ARG.help)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_passphrase_arg<'a>() -> Arg<'a> {
|
||||||
|
Arg::new(NO_PASSPHRASE_ARG.name)
|
||||||
|
.long(NO_PASSPHRASE_ARG.long)
|
||||||
|
.alias("no-passphrase")
|
||||||
|
.help(NO_PASSPHRASE_ARG.help)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acquire_language(matches: &ArgMatches) -> Language {
|
||||||
|
match matches.value_of(LANGUAGE_ARG.name).unwrap() {
|
||||||
|
"english" => Language::English,
|
||||||
|
"chinese-simplified" => Language::ChineseSimplified,
|
||||||
|
"chinese-traditional" => Language::ChineseTraditional,
|
||||||
|
"japanese" => Language::Japanese,
|
||||||
|
"spanish" => Language::Spanish,
|
||||||
|
"korean" => Language::Korean,
|
||||||
|
"french" => Language::French,
|
||||||
|
"italian" => Language::Italian,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_passphrase_and_message() -> (String, String) {
|
||||||
|
(NO_PASSPHRASE.to_string(), "".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acquire_passphrase_and_message(
|
||||||
|
matches: &ArgMatches,
|
||||||
|
) -> Result<(String, String), Box<dyn error::Error>> {
|
||||||
|
if matches.is_present(NO_PASSPHRASE_ARG.name) {
|
||||||
|
Ok(no_passphrase_and_message())
|
||||||
|
} else {
|
||||||
|
match prompt_passphrase(
|
||||||
|
"\nFor added security, enter a BIP39 passphrase\n\
|
||||||
|
\nNOTE! This passphrase improves security of the recovery seed phrase NOT the\n\
|
||||||
|
keypair file itself, which is stored as insecure plain text\n\
|
||||||
|
\nBIP39 Passphrase (empty for none): ",
|
||||||
|
) {
|
||||||
|
Ok(passphrase) => {
|
||||||
|
println!();
|
||||||
|
Ok((passphrase, " and your BIP39 passphrase".to_string()))
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
keygen::mnemonic::{language_arg, no_passphrase_arg, word_count_arg},
|
||||||
|
ArgConstant,
|
||||||
|
},
|
||||||
|
clap::{Arg, ArgMatches, Command},
|
||||||
|
std::{error, path::Path},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod derivation_path;
|
||||||
|
pub mod mnemonic;
|
||||||
|
|
||||||
|
pub const NO_OUTFILE_ARG: ArgConstant<'static> = ArgConstant {
|
||||||
|
long: "no-outfile",
|
||||||
|
name: "no_outfile",
|
||||||
|
help: "Only print a seed phrase and pubkey. Do not output a keypair file",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn no_outfile_arg<'a>() -> Arg<'a> {
|
||||||
|
Arg::new(NO_OUTFILE_ARG.name)
|
||||||
|
.long(NO_OUTFILE_ARG.long)
|
||||||
|
.help(NO_OUTFILE_ARG.help)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait KeyGenerationCommonArgs {
|
||||||
|
fn key_generation_common_args(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyGenerationCommonArgs for Command<'_> {
|
||||||
|
fn key_generation_common_args(self) -> Self {
|
||||||
|
self.arg(word_count_arg())
|
||||||
|
.arg(language_arg())
|
||||||
|
.arg(no_passphrase_arg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_for_overwrite(
|
||||||
|
outfile: &str,
|
||||||
|
matches: &ArgMatches,
|
||||||
|
) -> Result<(), Box<dyn error::Error>> {
|
||||||
|
let force = matches.is_present("force");
|
||||||
|
if !force && Path::new(outfile).exists() {
|
||||||
|
let err_msg = format!("Refusing to overwrite {outfile} without --force flag");
|
||||||
|
return Err(err_msg.into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ impl std::fmt::Debug for DisplayError {
|
||||||
pub mod fee_payer;
|
pub mod fee_payer;
|
||||||
pub mod input_parsers;
|
pub mod input_parsers;
|
||||||
pub mod input_validators;
|
pub mod input_validators;
|
||||||
|
pub mod keygen;
|
||||||
pub mod keypair;
|
pub mod keypair;
|
||||||
pub mod memo;
|
pub mod memo;
|
||||||
pub mod nonce;
|
pub mod nonce;
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
#![allow(clippy::integer_arithmetic)]
|
#![allow(clippy::integer_arithmetic)]
|
||||||
use {
|
use {
|
||||||
bip39::{Language, Mnemonic, MnemonicType, Seed},
|
bip39::{Mnemonic, MnemonicType, Seed},
|
||||||
clap::{crate_description, crate_name, Arg, ArgMatches, Command},
|
clap::{crate_description, crate_name, Arg, ArgMatches, Command},
|
||||||
solana_clap_v3_utils::{
|
solana_clap_v3_utils::{
|
||||||
input_parsers::STDOUT_OUTFILE_TOKEN,
|
input_parsers::STDOUT_OUTFILE_TOKEN,
|
||||||
input_validators::{is_parsable, is_prompt_signer_source},
|
input_validators::{is_parsable, is_prompt_signer_source},
|
||||||
|
keygen::{
|
||||||
|
check_for_overwrite,
|
||||||
|
derivation_path::{acquire_derivation_path, derivation_path_arg},
|
||||||
|
mnemonic::{
|
||||||
|
acquire_language, acquire_passphrase_and_message, no_passphrase_and_message,
|
||||||
|
WORD_COUNT_ARG,
|
||||||
|
},
|
||||||
|
no_outfile_arg, KeyGenerationCommonArgs, NO_OUTFILE_ARG,
|
||||||
|
},
|
||||||
keypair::{
|
keypair::{
|
||||||
keypair_from_path, keypair_from_seed_phrase, prompt_passphrase, signer_from_path,
|
keypair_from_path, keypair_from_seed_phrase, signer_from_path,
|
||||||
SKIP_SEED_PHRASE_VALIDATION_ARG,
|
SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||||
},
|
},
|
||||||
ArgConstant, DisplayError,
|
DisplayError,
|
||||||
},
|
},
|
||||||
solana_cli_config::{Config, CONFIG_FILE},
|
solana_cli_config::{Config, CONFIG_FILE},
|
||||||
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
derivation_path::DerivationPath,
|
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction},
|
||||||
message::Message,
|
message::Message,
|
||||||
pubkey::{write_pubkey_file, Pubkey},
|
pubkey::{write_pubkey_file, Pubkey},
|
||||||
|
@ -26,7 +34,6 @@ use {
|
||||||
std::{
|
std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
error,
|
error,
|
||||||
path::Path,
|
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
|
@ -48,102 +55,12 @@ mod smallest_length_44_public_key {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NO_PASSPHRASE: &str = "";
|
|
||||||
const DEFAULT_DERIVATION_PATH: &str = "m/44'/501'/0'/0'";
|
|
||||||
|
|
||||||
struct GrindMatch {
|
struct GrindMatch {
|
||||||
starts: String,
|
starts: String,
|
||||||
ends: String,
|
ends: String,
|
||||||
count: AtomicU64,
|
count: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
const WORD_COUNT_ARG: ArgConstant<'static> = ArgConstant {
|
|
||||||
long: "word-count",
|
|
||||||
name: "word_count",
|
|
||||||
help: "Specify the number of words that will be present in the generated seed phrase",
|
|
||||||
};
|
|
||||||
|
|
||||||
const LANGUAGE_ARG: ArgConstant<'static> = ArgConstant {
|
|
||||||
long: "language",
|
|
||||||
name: "language",
|
|
||||||
help: "Specify the mnemonic language that will be present in the generated seed phrase",
|
|
||||||
};
|
|
||||||
|
|
||||||
const NO_PASSPHRASE_ARG: ArgConstant<'static> = ArgConstant {
|
|
||||||
long: "no-bip39-passphrase",
|
|
||||||
name: "no_passphrase",
|
|
||||||
help: "Do not prompt for a BIP39 passphrase",
|
|
||||||
};
|
|
||||||
|
|
||||||
const NO_OUTFILE_ARG: ArgConstant<'static> = ArgConstant {
|
|
||||||
long: "no-outfile",
|
|
||||||
name: "no_outfile",
|
|
||||||
help: "Only print a seed phrase and pubkey. Do not output a keypair file",
|
|
||||||
};
|
|
||||||
|
|
||||||
fn word_count_arg<'a>() -> Arg<'a> {
|
|
||||||
Arg::new(WORD_COUNT_ARG.name)
|
|
||||||
.long(WORD_COUNT_ARG.long)
|
|
||||||
.possible_values(["12", "15", "18", "21", "24"])
|
|
||||||
.default_value("12")
|
|
||||||
.value_name("NUMBER")
|
|
||||||
.takes_value(true)
|
|
||||||
.help(WORD_COUNT_ARG.help)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn language_arg<'a>() -> Arg<'a> {
|
|
||||||
Arg::new(LANGUAGE_ARG.name)
|
|
||||||
.long(LANGUAGE_ARG.long)
|
|
||||||
.possible_values([
|
|
||||||
"english",
|
|
||||||
"chinese-simplified",
|
|
||||||
"chinese-traditional",
|
|
||||||
"japanese",
|
|
||||||
"spanish",
|
|
||||||
"korean",
|
|
||||||
"french",
|
|
||||||
"italian",
|
|
||||||
])
|
|
||||||
.default_value("english")
|
|
||||||
.value_name("LANGUAGE")
|
|
||||||
.takes_value(true)
|
|
||||||
.help(LANGUAGE_ARG.help)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn no_passphrase_arg<'a>() -> Arg<'a> {
|
|
||||||
Arg::new(NO_PASSPHRASE_ARG.name)
|
|
||||||
.long(NO_PASSPHRASE_ARG.long)
|
|
||||||
.alias("no-passphrase")
|
|
||||||
.help(NO_PASSPHRASE_ARG.help)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn no_outfile_arg<'a>() -> Arg<'a> {
|
|
||||||
Arg::new(NO_OUTFILE_ARG.name)
|
|
||||||
.long(NO_OUTFILE_ARG.long)
|
|
||||||
.help(NO_OUTFILE_ARG.help)
|
|
||||||
}
|
|
||||||
|
|
||||||
trait KeyGenerationCommonArgs {
|
|
||||||
fn key_generation_common_args(self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyGenerationCommonArgs for Command<'_> {
|
|
||||||
fn key_generation_common_args(self) -> Self {
|
|
||||||
self.arg(word_count_arg())
|
|
||||||
.arg(language_arg())
|
|
||||||
.arg(no_passphrase_arg())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_for_overwrite(outfile: &str, matches: &ArgMatches) -> Result<(), Box<dyn error::Error>> {
|
|
||||||
let force = matches.is_present("force");
|
|
||||||
if !force && Path::new(outfile).exists() {
|
|
||||||
let err_msg = format!("Refusing to overwrite {outfile} without --force flag");
|
|
||||||
return Err(err_msg.into());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_keypair_from_matches(
|
fn get_keypair_from_matches(
|
||||||
matches: &ArgMatches,
|
matches: &ArgMatches,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
@ -226,45 +143,6 @@ fn grind_validator_starts_and_ends_with(v: &str) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn acquire_language(matches: &ArgMatches) -> Language {
|
|
||||||
match matches.value_of(LANGUAGE_ARG.name).unwrap() {
|
|
||||||
"english" => Language::English,
|
|
||||||
"chinese-simplified" => Language::ChineseSimplified,
|
|
||||||
"chinese-traditional" => Language::ChineseTraditional,
|
|
||||||
"japanese" => Language::Japanese,
|
|
||||||
"spanish" => Language::Spanish,
|
|
||||||
"korean" => Language::Korean,
|
|
||||||
"french" => Language::French,
|
|
||||||
"italian" => Language::Italian,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn no_passphrase_and_message() -> (String, String) {
|
|
||||||
(NO_PASSPHRASE.to_string(), "".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn acquire_passphrase_and_message(
|
|
||||||
matches: &ArgMatches,
|
|
||||||
) -> Result<(String, String), Box<dyn error::Error>> {
|
|
||||||
if matches.is_present(NO_PASSPHRASE_ARG.name) {
|
|
||||||
Ok(no_passphrase_and_message())
|
|
||||||
} else {
|
|
||||||
match prompt_passphrase(
|
|
||||||
"\nFor added security, enter a BIP39 passphrase\n\
|
|
||||||
\nNOTE! This passphrase improves security of the recovery seed phrase NOT the\n\
|
|
||||||
keypair file itself, which is stored as insecure plain text\n\
|
|
||||||
\nBIP39 Passphrase (empty for none): ",
|
|
||||||
) {
|
|
||||||
Ok(passphrase) => {
|
|
||||||
println!();
|
|
||||||
Ok((passphrase, " and your BIP39 passphrase".to_string()))
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn grind_print_info(grind_matches: &[GrindMatch], num_threads: usize) {
|
fn grind_print_info(grind_matches: &[GrindMatch], num_threads: usize) {
|
||||||
println!("Searching with {num_threads} threads for:");
|
println!("Searching with {num_threads} threads for:");
|
||||||
for gm in grind_matches {
|
for gm in grind_matches {
|
||||||
|
@ -342,33 +220,6 @@ fn grind_parse_args(
|
||||||
grind_matches
|
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<Option<DerivationPath>, Box<dyn error::Error>> {
|
|
||||||
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 app<'a>(num_threads: &'a str, crate_version: &'a str) -> Command<'a> {
|
fn app<'a>(num_threads: &'a str, crate_version: &'a str) -> Command<'a> {
|
||||||
Command::new(crate_name!())
|
Command::new(crate_name!())
|
||||||
.about(crate_description!())
|
.about(crate_description!())
|
||||||
|
|
Loading…
Reference in New Issue