2021-02-16 13:48:20 -08:00
#![ allow(clippy::integer_arithmetic) ]
2021-12-03 09:00:31 -08:00
use {
bip39 ::{ Language , Mnemonic , MnemonicType , Seed } ,
clap ::{
crate_description , crate_name , value_t , value_t_or_exit , values_t_or_exit , App ,
AppSettings , Arg , ArgMatches , SubCommand ,
2020-02-07 10:26:56 -08:00
} ,
2021-12-03 09:00:31 -08:00
solana_clap_utils ::{
input_parsers ::STDOUT_OUTFILE_TOKEN ,
input_validators ::{ is_parsable , is_prompt_signer_source } ,
keypair ::{
keypair_from_path , keypair_from_seed_phrase , prompt_passphrase , signer_from_path ,
SKIP_SEED_PHRASE_VALIDATION_ARG ,
} ,
ArgConstant , DisplayError ,
} ,
solana_cli_config ::{ Config , CONFIG_FILE } ,
solana_remote_wallet ::remote_wallet ::RemoteWalletManager ,
solana_sdk ::{
instruction ::{ AccountMeta , Instruction } ,
message ::Message ,
pubkey ::{ write_pubkey_file , Pubkey } ,
signature ::{ keypair_from_seed , write_keypair , write_keypair_file , Keypair , Signer } ,
} ,
std ::{
collections ::HashSet ,
error ,
path ::Path ,
process ::exit ,
sync ::{
atomic ::{ AtomicBool , AtomicU64 , Ordering } ,
Arc ,
} ,
thread ,
time ::Instant ,
2019-11-03 19:41:26 -08:00
} ,
2019-10-10 16:01:03 -07:00
} ;
2019-06-07 17:54:54 -07:00
2019-09-12 18:37:29 -07:00
const NO_PASSPHRASE : & str = " " ;
2020-01-28 20:19:19 -08:00
struct GrindMatch {
starts : String ,
ends : String ,
count : AtomicU64 ,
}
2021-03-27 22:47:50 -07:00
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 " ,
2021-12-19 21:33:21 -08:00
help : " Specify the mnemonic language that will be present in the generated seed phrase " ,
2021-03-27 22:47:50 -07:00
} ;
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 , ' b > ( ) -> Arg < ' a , ' b > {
Arg ::with_name ( 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 , ' b > ( ) -> Arg < ' a , ' b > {
Arg ::with_name ( 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 , ' b > ( ) -> Arg < ' a , ' b > {
Arg ::with_name ( NO_PASSPHRASE_ARG . name )
. long ( NO_PASSPHRASE_ARG . long )
. alias ( " no-passphrase " )
. help ( NO_PASSPHRASE_ARG . help )
}
fn no_outfile_arg < ' a , ' b > ( ) -> Arg < ' a , ' b > {
Arg ::with_name ( NO_OUTFILE_ARG . name )
. long ( NO_OUTFILE_ARG . long )
. conflicts_with_all ( & [ " outfile " , " silent " ] )
. help ( NO_OUTFILE_ARG . help )
}
trait KeyGenerationCommonArgs {
fn key_generation_common_args ( self ) -> Self ;
}
impl KeyGenerationCommonArgs for App < '_ , '_ > {
fn key_generation_common_args ( self ) -> Self {
self . arg ( word_count_arg ( ) )
. arg ( language_arg ( ) )
. arg ( no_passphrase_arg ( ) )
}
}
2019-06-07 17:54:54 -07:00
fn check_for_overwrite ( outfile : & str , matches : & ArgMatches ) {
let force = matches . is_present ( " force " ) ;
if ! force & & Path ::new ( outfile ) . exists ( ) {
eprintln! ( " Refusing to overwrite {} without --force flag " , outfile ) ;
exit ( 1 ) ;
}
}
2018-07-12 10:59:40 -07:00
2020-01-31 18:27:37 -08:00
fn get_keypair_from_matches (
matches : & ArgMatches ,
config : Config ,
2020-04-18 11:54:21 -07:00
wallet_manager : & mut Option < Arc < RemoteWalletManager > > ,
2020-02-20 13:28:55 -08:00
) -> Result < Box < dyn Signer > , Box < dyn error ::Error > > {
2020-10-31 09:09:17 -07:00
let mut path = dirs_next ::home_dir ( ) . expect ( " home directory " ) ;
2020-02-13 13:08:35 -08:00
let path = if matches . is_present ( " keypair " ) {
2020-01-31 18:27:37 -08:00
matches . value_of ( " keypair " ) . unwrap ( )
2020-12-13 17:26:34 -08:00
} else if ! config . keypair_path . is_empty ( ) {
2020-01-31 18:27:37 -08:00
& config . keypair_path
2019-12-05 14:32:42 -08:00
} else {
path . extend ( & [ " .config " , " solana " , " id.json " ] ) ;
path . to_str ( ) . unwrap ( )
} ;
2020-04-18 11:54:21 -07:00
signer_from_path ( matches , path , " pubkey recovery " , wallet_manager )
2020-02-07 10:26:56 -08:00
}
2019-10-10 16:01:03 -07:00
fn output_keypair (
keypair : & Keypair ,
outfile : & str ,
source : & str ,
) -> Result < ( ) , Box < dyn error ::Error > > {
2021-05-18 08:35:07 -07:00
if outfile = = STDOUT_OUTFILE_TOKEN {
2019-10-10 16:01:03 -07:00
let mut stdout = std ::io ::stdout ( ) ;
2021-06-18 06:34:46 -07:00
write_keypair ( keypair , & mut stdout ) ? ;
2019-10-10 16:01:03 -07:00
} else {
2021-06-18 06:34:46 -07:00
write_keypair_file ( keypair , outfile ) ? ;
2020-05-11 08:39:10 -07:00
println! ( " Wrote {} keypair to {} " , source , outfile ) ;
2019-10-10 16:01:03 -07:00
}
Ok ( ( ) )
}
2020-01-28 20:19:19 -08:00
fn grind_validator_starts_with ( v : String ) -> Result < ( ) , String > {
if v . matches ( ':' ) . count ( ) ! = 1 | | ( v . starts_with ( ':' ) | | v . ends_with ( ':' ) ) {
return Err ( String ::from ( " Expected : between PREFIX and COUNT " ) ) ;
}
let args : Vec < & str > = v . split ( ':' ) . collect ( ) ;
bs58 ::decode ( & args [ 0 ] )
. into_vec ( )
. map_err ( | err | format! ( " {} : {:?} " , args [ 0 ] , err ) ) ? ;
let count = args [ 1 ] . parse ::< u64 > ( ) ;
if count . is_err ( ) | | count . unwrap ( ) = = 0 {
return Err ( String ::from ( " Expected COUNT to be of type u64 " ) ) ;
}
Ok ( ( ) )
}
fn grind_validator_ends_with ( v : String ) -> Result < ( ) , String > {
if v . matches ( ':' ) . count ( ) ! = 1 | | ( v . starts_with ( ':' ) | | v . ends_with ( ':' ) ) {
return Err ( String ::from ( " Expected : between SUFFIX and COUNT " ) ) ;
}
let args : Vec < & str > = v . split ( ':' ) . collect ( ) ;
bs58 ::decode ( & args [ 0 ] )
. into_vec ( )
. map_err ( | err | format! ( " {} : {:?} " , args [ 0 ] , err ) ) ? ;
let count = args [ 1 ] . parse ::< u64 > ( ) ;
if count . is_err ( ) | | count . unwrap ( ) = = 0 {
return Err ( String ::from ( " Expected COUNT to be of type u64 " ) ) ;
}
Ok ( ( ) )
}
fn grind_validator_starts_and_ends_with ( v : String ) -> Result < ( ) , String > {
if v . matches ( ':' ) . count ( ) ! = 2 | | ( v . starts_with ( ':' ) | | v . ends_with ( ':' ) ) {
return Err ( String ::from (
" Expected : between PREFIX and SUFFIX and COUNT " ,
) ) ;
}
let args : Vec < & str > = v . split ( ':' ) . collect ( ) ;
bs58 ::decode ( & args [ 0 ] )
. into_vec ( )
. map_err ( | err | format! ( " {} : {:?} " , args [ 0 ] , err ) ) ? ;
bs58 ::decode ( & args [ 1 ] )
. into_vec ( )
. map_err ( | err | format! ( " {} : {:?} " , args [ 1 ] , err ) ) ? ;
let count = args [ 2 ] . parse ::< u64 > ( ) ;
if count . is_err ( ) | | count . unwrap ( ) = = 0 {
return Err ( String ::from ( " Expected COUNT to be a u64 " ) ) ;
}
Ok ( ( ) )
}
2021-03-27 22:47:50 -07:00
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 (
" \n For 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 ) ,
}
}
}
2021-01-27 12:18:12 -08:00
fn grind_print_info ( grind_matches : & [ GrindMatch ] , num_threads : usize ) {
println! ( " Searching with {} threads for: " , num_threads ) ;
2020-01-28 20:19:19 -08:00
for gm in grind_matches {
let mut msg = Vec ::< String > ::new ( ) ;
if gm . count . load ( Ordering ::Relaxed ) > 1 {
msg . push ( " pubkeys " . to_string ( ) ) ;
msg . push ( " start " . to_string ( ) ) ;
msg . push ( " end " . to_string ( ) ) ;
} else {
msg . push ( " pubkey " . to_string ( ) ) ;
msg . push ( " starts " . to_string ( ) ) ;
msg . push ( " ends " . to_string ( ) ) ;
}
println! (
" \t {} {} that {} with '{}' and {} with '{}' " ,
gm . count . load ( Ordering ::Relaxed ) ,
msg [ 0 ] ,
msg [ 1 ] ,
gm . starts ,
msg [ 2 ] ,
gm . ends
) ;
}
}
fn grind_parse_args (
2020-03-17 07:00:37 -07:00
ignore_case : bool ,
2020-01-28 20:19:19 -08:00
starts_with_args : HashSet < String > ,
ends_with_args : HashSet < String > ,
starts_and_ends_with_args : HashSet < String > ,
2021-01-27 12:18:12 -08:00
num_threads : usize ,
2020-01-28 20:19:19 -08:00
) -> Vec < GrindMatch > {
let mut grind_matches = Vec ::< GrindMatch > ::new ( ) ;
for sw in starts_with_args {
let args : Vec < & str > = sw . split ( ':' ) . collect ( ) ;
grind_matches . push ( GrindMatch {
2020-03-17 07:29:17 -07:00
starts : if ignore_case {
args [ 0 ] . to_lowercase ( )
} else {
args [ 0 ] . to_string ( )
} ,
2020-01-28 20:19:19 -08:00
ends : " " . to_string ( ) ,
count : AtomicU64 ::new ( args [ 1 ] . parse ::< u64 > ( ) . unwrap ( ) ) ,
} ) ;
}
for ew in ends_with_args {
let args : Vec < & str > = ew . split ( ':' ) . collect ( ) ;
grind_matches . push ( GrindMatch {
starts : " " . to_string ( ) ,
2020-03-17 07:29:17 -07:00
ends : if ignore_case {
args [ 0 ] . to_lowercase ( )
} else {
args [ 0 ] . to_string ( )
} ,
2020-01-28 20:19:19 -08:00
count : AtomicU64 ::new ( args [ 1 ] . parse ::< u64 > ( ) . unwrap ( ) ) ,
} ) ;
}
for swew in starts_and_ends_with_args {
let args : Vec < & str > = swew . split ( ':' ) . collect ( ) ;
grind_matches . push ( GrindMatch {
2020-03-17 07:29:17 -07:00
starts : if ignore_case {
args [ 0 ] . to_lowercase ( )
} else {
args [ 0 ] . to_string ( )
} ,
ends : if ignore_case {
args [ 1 ] . to_lowercase ( )
} else {
args [ 1 ] . to_string ( )
} ,
2020-01-28 20:19:19 -08:00
count : AtomicU64 ::new ( args [ 2 ] . parse ::< u64 > ( ) . unwrap ( ) ) ,
} ) ;
}
2021-01-27 12:18:12 -08:00
grind_print_info ( & grind_matches , num_threads ) ;
2020-01-28 20:19:19 -08:00
grind_matches
}
2018-12-08 21:44:20 -08:00
fn main ( ) -> Result < ( ) , Box < dyn error ::Error > > {
2021-01-27 12:18:12 -08:00
let default_num_threads = num_cpus ::get ( ) . to_string ( ) ;
2019-03-13 20:54:30 -07:00
let matches = App ::new ( crate_name! ( ) )
. about ( crate_description! ( ) )
2020-05-11 15:02:01 -07:00
. version ( solana_version ::version! ( ) )
2019-06-07 17:54:54 -07:00
. setting ( AppSettings ::SubcommandRequiredElseHelp )
2020-01-31 18:27:37 -08:00
. arg ( {
let arg = Arg ::with_name ( " config_file " )
. short ( " C " )
. long ( " config " )
2020-03-16 08:24:59 -07:00
. value_name ( " FILEPATH " )
2020-01-31 18:27:37 -08:00
. takes_value ( true )
. global ( true )
. help ( " Configuration file to use " ) ;
if let Some ( ref config_file ) = * CONFIG_FILE {
2021-06-18 06:34:46 -07:00
arg . default_value ( config_file )
2020-01-31 18:27:37 -08:00
} else {
arg
}
} )
2019-12-05 14:32:42 -08:00
. subcommand (
SubCommand ::with_name ( " verify " )
. about ( " Verify a keypair can sign and verify a message. " )
. arg (
2020-01-31 18:27:37 -08:00
Arg ::with_name ( " pubkey " )
2019-12-05 14:32:42 -08:00
. index ( 1 )
2020-03-16 08:24:59 -07:00
. value_name ( " PUBKEY " )
2019-12-05 14:32:42 -08:00
. takes_value ( true )
2020-01-31 18:27:37 -08:00
. required ( true )
. help ( " Public key " ) ,
2019-12-05 14:32:42 -08:00
)
. arg (
2020-01-31 18:27:37 -08:00
Arg ::with_name ( " keypair " )
2019-12-05 14:32:42 -08:00
. index ( 2 )
2020-03-16 11:27:09 -07:00
. value_name ( " KEYPAIR " )
2019-12-05 14:32:42 -08:00
. takes_value ( true )
2020-03-16 11:27:09 -07:00
. help ( " Filepath or URL to a keypair " ) ,
2019-12-05 14:32:42 -08:00
)
)
2019-03-19 14:19:50 -07:00
. subcommand (
SubCommand ::with_name ( " new " )
2021-02-02 12:26:09 -08:00
. about ( " Generate new keypair file from a random seed phrase and optional BIP39 passphrase " )
2019-06-07 17:54:54 -07:00
. setting ( AppSettings ::DisableVersion )
2019-03-19 14:19:50 -07:00
. arg (
Arg ::with_name ( " outfile " )
. short ( " o " )
. long ( " outfile " )
2020-03-16 08:24:59 -07:00
. value_name ( " FILEPATH " )
2019-03-19 14:19:50 -07:00
. takes_value ( true )
. help ( " Path to generated file " ) ,
2019-06-07 17:54:54 -07:00
)
. arg (
Arg ::with_name ( " force " )
. short ( " f " )
. long ( " force " )
. help ( " Overwrite the output file if it exists " ) ,
2019-09-12 18:37:29 -07:00
)
. arg (
Arg ::with_name ( " silent " )
. short ( " s " )
. long ( " silent " )
2019-11-25 20:33:15 -08:00
. help ( " Do not display seed phrase. Useful when piping output to other programs that prompt for user input, like gpg " ) ,
2019-10-30 20:47:42 -07:00
)
2021-03-27 22:47:50 -07:00
. key_generation_common_args ( )
2021-06-11 12:25:03 -07:00
. arg ( no_outfile_arg ( ) )
2019-11-03 19:41:26 -08:00
)
. subcommand (
SubCommand ::with_name ( " grind " )
. about ( " Grind for vanity keypairs " )
. setting ( AppSettings ::DisableVersion )
. arg (
Arg ::with_name ( " ignore_case " )
. long ( " ignore-case " )
2020-01-28 20:19:19 -08:00
. help ( " Performs case insensitive matches " ) ,
2019-11-03 19:41:26 -08:00
)
. arg (
2020-01-28 20:19:19 -08:00
Arg ::with_name ( " starts_with " )
. long ( " starts-with " )
. value_name ( " PREFIX:COUNT " )
. number_of_values ( 1 )
2019-11-03 19:41:26 -08:00
. takes_value ( true )
. multiple ( true )
2020-01-28 20:19:19 -08:00
. validator ( grind_validator_starts_with )
. help ( " Saves specified number of keypairs whos public key starts with the indicated prefix \n Example: --starts-with sol:4 \n PREFIX type is Base58 \n COUNT type is u64 " ) ,
2019-11-03 19:41:26 -08:00
)
2019-10-30 20:47:42 -07:00
. arg (
2020-01-28 20:19:19 -08:00
Arg ::with_name ( " ends_with " )
. long ( " ends-with " )
. value_name ( " SUFFIX:COUNT " )
. number_of_values ( 1 )
2019-10-30 20:47:42 -07:00
. takes_value ( true )
2019-11-03 19:41:26 -08:00
. multiple ( true )
2020-01-28 20:19:19 -08:00
. validator ( grind_validator_ends_with )
. help ( " Saves specified number of keypairs whos public key ends with the indicated suffix \n Example: --ends-with ana:4 \n SUFFIX type is Base58 \n COUNT type is u64 " ) ,
)
. arg (
Arg ::with_name ( " starts_and_ends_with " )
. long ( " starts-and-ends-with " )
. value_name ( " PREFIX:SUFFIX:COUNT " )
. number_of_values ( 1 )
. takes_value ( true )
. multiple ( true )
. validator ( grind_validator_starts_and_ends_with )
. help ( " Saves specified number of keypairs whos public key starts and ends with the indicated perfix and suffix \n Example: --starts-and-ends-with sol:ana:4 \n PREFIX and SUFFIX type is Base58 \n COUNT type is u64 " ) ,
2021-01-27 12:18:12 -08:00
)
. arg (
Arg ::with_name ( " num_threads " )
. long ( " num-threads " )
. value_name ( " NUMBER " )
. takes_value ( true )
. validator ( is_parsable ::< usize > )
. default_value ( & default_num_threads )
. help ( " Specify the number of grind threads " ) ,
2021-03-27 22:47:50 -07:00
)
. arg (
Arg ::with_name ( " use_mnemonic " )
. long ( " use-mnemonic " )
. help ( " Generate using a mnemonic key phrase. Expect a significant slowdown in this mode " ) ,
)
. key_generation_common_args ( )
2021-06-11 12:25:03 -07:00
. arg (
no_outfile_arg ( )
// Require a seed phrase to avoid generating a keypair
// but having no way to get the private key
. requires ( " use_mnemonic " )
)
2019-03-19 14:19:50 -07:00
)
. subcommand (
SubCommand ::with_name ( " pubkey " )
2019-04-23 19:24:42 -07:00
. about ( " Display the pubkey from a keypair file " )
2019-06-07 17:54:54 -07:00
. setting ( AppSettings ::DisableVersion )
2019-03-19 14:19:50 -07:00
. arg (
2020-01-31 18:27:37 -08:00
Arg ::with_name ( " keypair " )
2019-03-19 14:19:50 -07:00
. index ( 1 )
2020-03-16 11:27:09 -07:00
. value_name ( " KEYPAIR " )
2019-03-19 14:19:50 -07:00
. takes_value ( true )
2020-03-16 11:27:09 -07:00
. help ( " Filepath or URL to a keypair " ) ,
2019-03-19 14:19:50 -07:00
)
2019-11-26 12:30:07 -08:00
. arg (
Arg ::with_name ( SKIP_SEED_PHRASE_VALIDATION_ARG . name )
. long ( SKIP_SEED_PHRASE_VALIDATION_ARG . long )
. help ( SKIP_SEED_PHRASE_VALIDATION_ARG . help ) ,
)
2019-03-19 14:19:50 -07:00
. arg (
Arg ::with_name ( " outfile " )
. short ( " o " )
. long ( " outfile " )
2020-03-16 08:24:59 -07:00
. value_name ( " FILEPATH " )
2019-03-19 14:19:50 -07:00
. takes_value ( true )
. help ( " Path to generated file " ) ,
2019-06-07 17:54:54 -07:00
)
. arg (
Arg ::with_name ( " force " )
. short ( " f " )
. long ( " force " )
. help ( " Overwrite the output file if it exists " ) ,
2020-02-07 10:26:56 -08:00
)
2019-03-19 14:19:50 -07:00
)
2019-09-12 18:37:29 -07:00
. subcommand (
SubCommand ::with_name ( " recover " )
2021-02-02 12:26:09 -08:00
. about ( " Recover keypair from seed phrase and optional BIP39 passphrase " )
2019-09-12 18:37:29 -07:00
. setting ( AppSettings ::DisableVersion )
2021-05-12 12:33:11 -07:00
. arg (
Arg ::with_name ( " prompt_signer " )
. index ( 1 )
. value_name ( " KEYPAIR " )
. takes_value ( true )
. validator ( is_prompt_signer_source )
. help ( " `prompt:` URI scheme or `ASK` keyword " ) ,
)
2019-09-12 18:37:29 -07:00
. arg (
Arg ::with_name ( " outfile " )
. short ( " o " )
. long ( " outfile " )
2020-03-16 08:24:59 -07:00
. value_name ( " FILEPATH " )
2019-09-12 18:37:29 -07:00
. takes_value ( true )
. help ( " Path to generated file " ) ,
)
. arg (
Arg ::with_name ( " force " )
. short ( " f " )
. long ( " force " )
. help ( " Overwrite the output file if it exists " ) ,
2019-11-25 20:33:15 -08:00
)
. arg (
Arg ::with_name ( SKIP_SEED_PHRASE_VALIDATION_ARG . name )
. long ( SKIP_SEED_PHRASE_VALIDATION_ARG . long )
. help ( SKIP_SEED_PHRASE_VALIDATION_ARG . help ) ,
2019-09-12 18:37:29 -07:00
) ,
2019-11-25 20:33:15 -08:00
2019-09-12 18:37:29 -07:00
)
2018-12-07 19:01:28 -08:00
. get_matches ( ) ;
2020-03-12 23:20:49 -07:00
do_main ( & matches ) . map_err ( | err | DisplayError ::new_as_boxed ( err ) . into ( ) )
}
fn do_main ( matches : & ArgMatches < '_ > ) -> Result < ( ) , Box < dyn error ::Error > > {
2020-01-31 18:27:37 -08:00
let config = if let Some ( config_file ) = matches . value_of ( " config_file " ) {
Config ::load ( config_file ) . unwrap_or_default ( )
} else {
Config ::default ( )
} ;
2018-07-12 15:29:49 -07:00
2020-04-18 11:54:21 -07:00
let mut wallet_manager = None ;
2020-02-24 16:03:30 -08:00
2019-03-19 14:19:50 -07:00
match matches . subcommand ( ) {
2019-06-07 17:54:54 -07:00
( " pubkey " , Some ( matches ) ) = > {
2020-04-18 11:54:21 -07:00
let pubkey =
get_keypair_from_matches ( matches , config , & mut wallet_manager ) ? . try_pubkey ( ) ? ;
2019-03-19 14:19:50 -07:00
2019-06-07 17:54:54 -07:00
if matches . is_present ( " outfile " ) {
let outfile = matches . value_of ( " outfile " ) . unwrap ( ) ;
2021-06-18 06:34:46 -07:00
check_for_overwrite ( outfile , matches ) ;
2020-02-07 10:26:56 -08:00
write_pubkey_file ( outfile , pubkey ) ? ;
2019-03-19 14:19:50 -07:00
} else {
2020-02-07 10:26:56 -08:00
println! ( " {} " , pubkey ) ;
2019-03-19 14:19:50 -07:00
}
}
2019-06-07 17:54:54 -07:00
( " new " , Some ( matches ) ) = > {
2020-10-31 09:09:17 -07:00
let mut path = dirs_next ::home_dir ( ) . expect ( " home directory " ) ;
2019-06-07 17:54:54 -07:00
let outfile = if matches . is_present ( " outfile " ) {
2019-11-25 21:43:03 -08:00
matches . value_of ( " outfile " )
2021-03-27 22:47:50 -07:00
} else if matches . is_present ( NO_OUTFILE_ARG . name ) {
2019-11-25 21:43:03 -08:00
None
2019-03-19 14:19:50 -07:00
} else {
path . extend ( & [ " .config " , " solana " , " id.json " ] ) ;
2019-11-25 21:43:03 -08:00
Some ( path . to_str ( ) . unwrap ( ) )
2019-03-19 14:19:50 -07:00
} ;
2019-11-25 21:43:03 -08:00
match outfile {
2021-05-18 08:35:07 -07:00
Some ( STDOUT_OUTFILE_TOKEN ) = > ( ) ,
2021-06-18 06:34:46 -07:00
Some ( outfile ) = > check_for_overwrite ( outfile , matches ) ,
2019-11-25 21:43:03 -08:00
None = > ( ) ,
2019-06-07 17:54:54 -07:00
}
2019-09-12 18:37:29 -07:00
2021-03-27 22:47:50 -07:00
let word_count = value_t! ( matches . value_of ( WORD_COUNT_ARG . name ) , usize ) . unwrap ( ) ;
2019-12-03 20:39:45 -08:00
let mnemonic_type = MnemonicType ::for_word_count ( word_count ) ? ;
2021-03-27 22:47:50 -07:00
let language = acquire_language ( matches ) ;
2021-02-02 12:26:09 -08:00
let silent = matches . is_present ( " silent " ) ;
if ! silent {
println! ( " Generating a new keypair " ) ;
}
2020-10-16 19:51:53 -07:00
let mnemonic = Mnemonic ::new ( mnemonic_type , language ) ;
2021-03-27 22:47:50 -07:00
let ( passphrase , passphrase_message ) = acquire_passphrase_and_message ( matches ) . unwrap ( ) ;
2021-02-02 12:26:09 -08:00
2019-11-25 20:33:15 -08:00
let seed = Seed ::new ( & mnemonic , & passphrase ) ;
2019-11-03 19:41:26 -08:00
let keypair = keypair_from_seed ( seed . as_bytes ( ) ) ? ;
2019-09-12 18:37:29 -07:00
2019-11-25 21:43:03 -08:00
if let Some ( outfile ) = outfile {
2021-06-18 06:34:46 -07:00
output_keypair ( & keypair , outfile , " new " )
2020-04-16 10:10:59 -07:00
. map_err ( | err | format! ( " Unable to write {} : {} " , outfile , err ) ) ? ;
2019-11-25 21:43:03 -08:00
}
2019-09-12 18:37:29 -07:00
if ! silent {
2019-10-30 20:47:42 -07:00
let phrase : & str = mnemonic . phrase ( ) ;
2019-09-12 18:37:29 -07:00
let divider = String ::from_utf8 ( vec! [ b '=' ; phrase . len ( ) ] ) . unwrap ( ) ;
2020-05-11 08:39:10 -07:00
println! (
2021-02-02 12:26:09 -08:00
" {} \n pubkey: {} \n {} \n Save this seed phrase{} to recover your new keypair: \n {} \n {} " ,
& divider , keypair . pubkey ( ) , & divider , passphrase_message , phrase , & divider
2019-09-12 18:37:29 -07:00
) ;
}
}
( " recover " , Some ( matches ) ) = > {
2020-10-31 09:09:17 -07:00
let mut path = dirs_next ::home_dir ( ) . expect ( " home directory " ) ;
2019-09-12 18:37:29 -07:00
let outfile = if matches . is_present ( " outfile " ) {
matches . value_of ( " outfile " ) . unwrap ( )
} else {
path . extend ( & [ " .config " , " solana " , " id.json " ] ) ;
path . to_str ( ) . unwrap ( )
} ;
2021-05-18 08:35:07 -07:00
if outfile ! = STDOUT_OUTFILE_TOKEN {
2021-06-18 06:34:46 -07:00
check_for_overwrite ( outfile , matches ) ;
2019-09-12 18:37:29 -07:00
}
2021-05-12 12:33:11 -07:00
let keypair_name = " recover " ;
let keypair = if let Some ( path ) = matches . value_of ( " prompt_signer " ) {
keypair_from_path ( matches , path , keypair_name , true ) ?
} else {
let skip_validation = matches . is_present ( SKIP_SEED_PHRASE_VALIDATION_ARG . name ) ;
keypair_from_seed_phrase ( keypair_name , skip_validation , true , None , true ) ?
} ;
2021-06-18 06:34:46 -07:00
output_keypair ( & keypair , outfile , " recovered " ) ? ;
2019-03-19 14:19:50 -07:00
}
2019-11-03 19:41:26 -08:00
( " grind " , Some ( matches ) ) = > {
2019-11-12 13:24:37 -08:00
let ignore_case = matches . is_present ( " ignore_case " ) ;
2020-01-28 20:19:19 -08:00
let starts_with_args = if matches . is_present ( " starts_with " ) {
values_t_or_exit! ( matches , " starts_with " , String )
2019-11-03 19:41:26 -08:00
. into_iter ( )
2019-11-12 13:24:37 -08:00
. map ( | s | if ignore_case { s . to_lowercase ( ) } else { s } )
2019-11-03 19:41:26 -08:00
. collect ( )
} else {
HashSet ::new ( )
} ;
2020-01-28 20:19:19 -08:00
let ends_with_args = if matches . is_present ( " ends_with " ) {
values_t_or_exit! ( matches , " ends_with " , String )
. into_iter ( )
. map ( | s | if ignore_case { s . to_lowercase ( ) } else { s } )
. collect ( )
} else {
HashSet ::new ( )
} ;
let starts_and_ends_with_args = if matches . is_present ( " starts_and_ends_with " ) {
values_t_or_exit! ( matches , " starts_and_ends_with " , String )
2019-11-03 19:41:26 -08:00
. into_iter ( )
2019-11-12 13:24:37 -08:00
. map ( | s | if ignore_case { s . to_lowercase ( ) } else { s } )
2019-11-03 19:41:26 -08:00
. collect ( )
} else {
HashSet ::new ( )
} ;
2020-01-28 20:19:19 -08:00
if starts_with_args . is_empty ( )
& & ends_with_args . is_empty ( )
& & starts_and_ends_with_args . is_empty ( )
{
2019-11-03 19:41:26 -08:00
eprintln! (
2020-01-28 20:19:19 -08:00
" Error: No keypair search criteria provided (--starts-with or --ends-with or --starts-and-ends-with) "
2019-11-03 19:41:26 -08:00
) ;
exit ( 1 ) ;
}
2021-01-27 12:18:12 -08:00
let num_threads = value_t_or_exit! ( matches . value_of ( " num_threads " ) , usize ) ;
2020-03-17 07:37:08 -07:00
let grind_matches = grind_parse_args (
ignore_case ,
starts_with_args ,
ends_with_args ,
starts_and_ends_with_args ,
2021-01-27 12:18:12 -08:00
num_threads ,
2020-03-17 07:37:08 -07:00
) ;
2020-01-28 20:19:19 -08:00
2021-03-27 22:47:50 -07:00
let use_mnemonic = matches . is_present ( " use_mnemonic " ) ;
let word_count = value_t! ( matches . value_of ( WORD_COUNT_ARG . name ) , usize ) . unwrap ( ) ;
let mnemonic_type = MnemonicType ::for_word_count ( word_count ) ? ;
let language = acquire_language ( matches ) ;
let ( passphrase , passphrase_message ) = if use_mnemonic {
acquire_passphrase_and_message ( matches ) . unwrap ( )
} else {
no_passphrase_and_message ( )
} ;
let no_outfile = matches . is_present ( NO_OUTFILE_ARG . name ) ;
2020-01-28 20:19:19 -08:00
let grind_matches_thread_safe = Arc ::new ( grind_matches ) ;
2019-11-03 19:41:26 -08:00
let attempts = Arc ::new ( AtomicU64 ::new ( 1 ) ) ;
let found = Arc ::new ( AtomicU64 ::new ( 0 ) ) ;
let start = Instant ::now ( ) ;
2020-01-28 20:19:19 -08:00
let done = Arc ::new ( AtomicBool ::new ( false ) ) ;
2019-11-03 19:41:26 -08:00
2021-01-27 12:18:12 -08:00
let thread_handles : Vec < _ > = ( 0 .. num_threads )
2020-03-24 10:33:53 -07:00
. map ( | _ | {
let done = done . clone ( ) ;
let attempts = attempts . clone ( ) ;
let found = found . clone ( ) ;
let grind_matches_thread_safe = grind_matches_thread_safe . clone ( ) ;
2021-03-27 22:47:50 -07:00
let passphrase = passphrase . clone ( ) ;
let passphrase_message = passphrase_message . clone ( ) ;
2019-11-03 19:41:26 -08:00
2020-03-24 10:33:53 -07:00
thread ::spawn ( move | | loop {
if done . load ( Ordering ::Relaxed ) {
break ;
2019-11-03 19:41:26 -08:00
}
2020-03-24 10:33:53 -07:00
let attempts = attempts . fetch_add ( 1 , Ordering ::Relaxed ) ;
if attempts % 1_000_000 = = 0 {
println! (
" Searched {} keypairs in {}s. {} matches found. " ,
attempts ,
start . elapsed ( ) . as_secs ( ) ,
found . load ( Ordering ::Relaxed ) ,
) ;
2019-11-03 19:41:26 -08:00
}
2021-03-27 22:47:50 -07:00
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 ( ) )
} else {
( Keypair ::new ( ) , " " . to_string ( ) )
} ;
2020-03-24 10:33:53 -07:00
let mut pubkey = bs58 ::encode ( keypair . pubkey ( ) ) . into_string ( ) ;
if ignore_case {
pubkey = pubkey . to_lowercase ( ) ;
}
let mut total_matches_found = 0 ;
for i in 0 .. grind_matches_thread_safe . len ( ) {
if grind_matches_thread_safe [ i ] . count . load ( Ordering ::Relaxed ) = = 0 {
total_matches_found + = 1 ;
continue ;
}
if ( ! grind_matches_thread_safe [ i ] . starts . is_empty ( )
& & grind_matches_thread_safe [ i ] . ends . is_empty ( )
& & pubkey . starts_with ( & grind_matches_thread_safe [ i ] . starts ) )
| | ( grind_matches_thread_safe [ i ] . starts . is_empty ( )
& & ! grind_matches_thread_safe [ i ] . ends . is_empty ( )
& & pubkey . ends_with ( & grind_matches_thread_safe [ i ] . ends ) )
| | ( ! grind_matches_thread_safe [ i ] . starts . is_empty ( )
& & ! grind_matches_thread_safe [ i ] . ends . is_empty ( )
& & pubkey . starts_with ( & grind_matches_thread_safe [ i ] . starts )
& & pubkey . ends_with ( & grind_matches_thread_safe [ i ] . ends ) )
{
let _found = found . fetch_add ( 1 , Ordering ::Relaxed ) ;
grind_matches_thread_safe [ i ]
. count
. fetch_sub ( 1 , Ordering ::Relaxed ) ;
2021-03-27 22:47:50 -07:00
if ! no_outfile {
write_keypair_file ( & keypair , & format! ( " {} .json " , keypair . pubkey ( ) ) )
2020-03-24 10:33:53 -07:00
. unwrap ( ) ;
2021-03-27 22:47:50 -07:00
println! (
" Wrote keypair to {} " ,
& format! ( " {} .json " , keypair . pubkey ( ) )
) ;
}
if use_mnemonic {
let divider = String ::from_utf8 ( vec! [ b '=' ; phrase . len ( ) ] ) . unwrap ( ) ;
println! (
" {} \n Found matching key {} " ,
& divider , keypair . pubkey ( ) ) ;
println! (
" \n Save this seed phrase{} to recover your new keypair: \n {} \n {} " ,
passphrase_message , phrase , & divider
) ;
}
2020-03-24 10:33:53 -07:00
}
}
if total_matches_found = = grind_matches_thread_safe . len ( ) {
done . store ( true , Ordering ::Relaxed ) ;
}
} )
} )
. collect ( ) ;
for thread_handle in thread_handles {
thread_handle . join ( ) . unwrap ( ) ;
2020-01-28 20:19:19 -08:00
}
2019-11-03 19:41:26 -08:00
}
2019-12-05 14:32:42 -08:00
( " verify " , Some ( matches ) ) = > {
2020-04-18 11:54:21 -07:00
let keypair = get_keypair_from_matches ( matches , config , & mut wallet_manager ) ? ;
2020-06-24 13:52:38 -07:00
let simple_message = Message ::new (
2021-03-03 21:46:48 -08:00
& [ Instruction ::new_with_bincode (
2020-06-24 13:52:38 -07:00
Pubkey ::default ( ) ,
& 0 ,
vec! [ AccountMeta ::new ( keypair . pubkey ( ) , true ) ] ,
) ] ,
Some ( & keypair . pubkey ( ) ) ,
)
2020-02-27 18:23:28 -08:00
. serialize ( ) ;
let signature = keypair . try_sign_message ( & simple_message ) ? ;
2019-12-05 14:32:42 -08:00
let pubkey_bs58 = matches . value_of ( " pubkey " ) . unwrap ( ) ;
let pubkey = bs58 ::decode ( pubkey_bs58 ) . into_vec ( ) . unwrap ( ) ;
2020-02-27 18:23:28 -08:00
if signature . verify ( & pubkey , & simple_message ) {
2019-12-05 14:32:42 -08:00
println! ( " Verification for public key: {} : Success " , pubkey_bs58 ) ;
} else {
println! ( " Verification for public key: {} : Failed " , pubkey_bs58 ) ;
exit ( 1 ) ;
}
}
2019-06-07 17:54:54 -07:00
_ = > unreachable! ( ) ,
2018-07-12 15:29:49 -07:00
}
2019-03-19 14:19:50 -07:00
2018-07-12 10:59:40 -07:00
Ok ( ( ) )
}