2023-09-01 00:26:13 -07:00
#![ allow(clippy::arithmetic_side_effects) ]
2021-12-03 09:00:31 -08:00
use {
2023-05-12 22:30:03 -07:00
bip39 ::{ Mnemonic , MnemonicType , Seed } ,
2023-09-28 07:03:41 -07:00
clap ::{ crate_description , crate_name , value_parser , Arg , ArgMatches , Command } ,
2022-04-22 03:40:53 -07:00
solana_clap_v3_utils ::{
2021-12-03 09:00:31 -08:00
input_parsers ::STDOUT_OUTFILE_TOKEN ,
2023-09-28 07:03:41 -07:00
input_validators ::is_prompt_signer_source ,
2023-05-12 22:30:03 -07:00
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 ,
} ,
2021-12-03 09:00:31 -08:00
keypair ::{
2023-05-12 22:30:03 -07:00
keypair_from_path , keypair_from_seed_phrase , signer_from_path ,
2021-12-03 09:00:31 -08:00
SKIP_SEED_PHRASE_VALIDATION_ARG ,
} ,
2023-05-12 22:30:03 -07:00
DisplayError ,
2021-12-03 09:00:31 -08:00
} ,
solana_cli_config ::{ Config , CONFIG_FILE } ,
solana_remote_wallet ::remote_wallet ::RemoteWalletManager ,
solana_sdk ::{
instruction ::{ AccountMeta , Instruction } ,
message ::Message ,
pubkey ::{ write_pubkey_file , Pubkey } ,
2022-09-07 11:31:40 -07:00
signature ::{
keypair_from_seed , keypair_from_seed_and_derivation_path , write_keypair ,
write_keypair_file , Keypair , Signer ,
} ,
2021-12-03 09:00:31 -08:00
} ,
std ::{
collections ::HashSet ,
error ,
2023-08-24 15:54:06 -07:00
rc ::Rc ,
2021-12-03 09:00:31 -08:00
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
2023-04-05 21:36:13 -07:00
mod smallest_length_44_public_key {
use solana_sdk ::{ pubkey , pubkey ::Pubkey } ;
pub ( super ) static PUBKEY : Pubkey = pubkey! ( " 21111111111111111111111111111111111111111111 " ) ;
#[ test ]
fn assert_length ( ) {
use crate ::smallest_length_44_public_key ;
assert_eq! ( smallest_length_44_public_key ::PUBKEY . to_string ( ) . len ( ) , 44 ) ;
}
}
2020-01-28 20:19:19 -08:00
struct GrindMatch {
starts : String ,
ends : String ,
count : AtomicU64 ,
}
2020-01-31 18:27:37 -08:00
fn get_keypair_from_matches (
matches : & ArgMatches ,
config : Config ,
2023-08-24 15:54:06 -07:00
wallet_manager : & mut Option < Rc < 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 {
2022-09-22 15:23:03 -07:00
path . extend ( [ " .config " , " solana " , " id.json " ] ) ;
2019-12-05 14:32:42 -08:00
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 ) ? ;
2022-12-06 06:30:06 -08:00
println! ( " Wrote {source} keypair to {outfile} " ) ;
2019-10-10 16:01:03 -07:00
}
Ok ( ( ) )
}
2022-04-22 03:40:53 -07:00
fn grind_validator_starts_with ( v : & str ) -> Result < ( ) , String > {
2020-01-28 20:19:19 -08:00
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 ( ( ) )
}
2022-04-22 03:40:53 -07:00
fn grind_validator_ends_with ( v : & str ) -> Result < ( ) , String > {
2020-01-28 20:19:19 -08:00
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 ( ( ) )
}
2022-04-22 03:40:53 -07:00
fn grind_validator_starts_and_ends_with ( v : & str ) -> Result < ( ) , String > {
2020-01-28 20:19:19 -08:00
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-01-27 12:18:12 -08:00
fn grind_print_info ( grind_matches : & [ GrindMatch ] , num_threads : usize ) {
2022-12-06 06:30:06 -08:00
println! ( " Searching with {num_threads} threads for: " ) ;
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
}
2023-04-05 15:43:52 -07:00
fn app < ' a > ( num_threads : & ' a str , crate_version : & ' a str ) -> Command < ' a > {
Command ::new ( crate_name! ( ) )
2019-03-13 20:54:30 -07:00
. about ( crate_description! ( ) )
2023-04-05 15:43:52 -07:00
. version ( crate_version )
2022-04-22 03:40:53 -07:00
. subcommand_required ( true )
. arg_required_else_help ( true )
2020-01-31 18:27:37 -08:00
. arg ( {
2022-04-22 03:40:53 -07:00
let arg = Arg ::new ( " config_file " )
. short ( 'C' )
2020-01-31 18:27:37 -08:00
. 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 (
2022-04-22 03:40:53 -07:00
Command ::new ( " verify " )
2019-12-05 14:32:42 -08:00
. about ( " Verify a keypair can sign and verify a message. " )
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " 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 (
2022-04-22 03:40:53 -07:00
Command ::new ( " new " )
2021-02-02 12:26:09 -08:00
. about ( " Generate new keypair file from a random seed phrase and optional BIP39 passphrase " )
2022-04-22 03:40:53 -07:00
. disable_version_flag ( true )
2019-03-19 14:19:50 -07:00
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " outfile " )
. short ( 'o' )
2019-03-19 14:19:50 -07:00
. 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " force " )
. short ( 'f' )
2019-06-07 17:54:54 -07:00
. long ( " force " )
. help ( " Overwrite the output file if it exists " ) ,
2019-09-12 18:37:29 -07:00
)
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " silent " )
. short ( 's' )
2019-09-12 18:37:29 -07:00
. 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
)
2022-09-07 11:31:40 -07:00
. arg (
derivation_path_arg ( )
)
2021-03-27 22:47:50 -07:00
. key_generation_common_args ( )
2022-04-26 12:21:07 -07:00
. arg ( no_outfile_arg ( )
. conflicts_with_all ( & [ " outfile " , " silent " ] )
)
2019-11-03 19:41:26 -08:00
)
. subcommand (
2022-04-22 03:40:53 -07:00
Command ::new ( " grind " )
2019-11-03 19:41:26 -08:00
. about ( " Grind for vanity keypairs " )
2022-04-22 03:40:53 -07:00
. disable_version_flag ( true )
2019-11-03 19:41:26 -08:00
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " ignore_case " )
2019-11-03 19:41:26 -08:00
. 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " starts_with " )
2020-01-28 20:19:19 -08:00
. long ( " starts-with " )
. value_name ( " PREFIX:COUNT " )
. number_of_values ( 1 )
2019-11-03 19:41:26 -08:00
. takes_value ( true )
2022-04-22 03:40:53 -07:00
. multiple_occurrences ( true )
. multiple_values ( 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " ends_with " )
2020-01-28 20:19:19 -08:00
. long ( " ends-with " )
. value_name ( " SUFFIX:COUNT " )
. number_of_values ( 1 )
2019-10-30 20:47:42 -07:00
. takes_value ( true )
2022-04-22 03:40:53 -07:00
. multiple_occurrences ( true )
. multiple_values ( 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " starts_and_ends_with " )
2020-01-28 20:19:19 -08:00
. long ( " starts-and-ends-with " )
. value_name ( " PREFIX:SUFFIX:COUNT " )
. number_of_values ( 1 )
. takes_value ( true )
2022-04-22 03:40:53 -07:00
. multiple_occurrences ( true )
. multiple_values ( true )
2020-01-28 20:19:19 -08:00
. 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " num_threads " )
2021-01-27 12:18:12 -08:00
. long ( " num-threads " )
. value_name ( " NUMBER " )
. takes_value ( true )
2023-09-28 07:03:41 -07:00
. value_parser ( value_parser! ( usize ) )
2023-04-05 15:43:52 -07:00
. default_value ( num_threads )
2021-01-27 12:18:12 -08:00
. help ( " Specify the number of grind threads " ) ,
2021-03-27 22:47:50 -07:00
)
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " use_mnemonic " )
2021-03-27 22:47:50 -07:00
. long ( " use-mnemonic " )
. help ( " Generate using a mnemonic key phrase. Expect a significant slowdown in this mode " ) ,
)
2022-09-07 11:31:40 -07:00
. arg (
derivation_path_arg ( )
. requires ( " use_mnemonic " )
)
2021-03-27 22:47:50 -07:00
. 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 (
2022-04-22 03:40:53 -07:00
Command ::new ( " pubkey " )
2019-04-23 19:24:42 -07:00
. about ( " Display the pubkey from a keypair file " )
2022-04-22 03:40:53 -07:00
. disable_version_flag ( true )
2019-03-19 14:19:50 -07:00
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( SKIP_SEED_PHRASE_VALIDATION_ARG . name )
2019-11-26 12:30:07 -08:00
. long ( SKIP_SEED_PHRASE_VALIDATION_ARG . long )
. help ( SKIP_SEED_PHRASE_VALIDATION_ARG . help ) ,
)
2019-03-19 14:19:50 -07:00
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " outfile " )
. short ( 'o' )
2019-03-19 14:19:50 -07:00
. 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " force " )
. short ( 'f' )
2019-06-07 17:54:54 -07:00
. 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 (
2022-04-22 03:40:53 -07:00
Command ::new ( " recover " )
2021-02-02 12:26:09 -08:00
. about ( " Recover keypair from seed phrase and optional BIP39 passphrase " )
2022-04-22 03:40:53 -07:00
. disable_version_flag ( true )
2021-05-12 12:33:11 -07:00
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( " prompt_signer " )
2021-05-12 12:33:11 -07:00
. 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " outfile " )
. short ( 'o' )
2019-09-12 18:37:29 -07:00
. 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 (
2022-04-22 03:40:53 -07:00
Arg ::new ( " force " )
. short ( 'f' )
2019-09-12 18:37:29 -07:00
. long ( " force " )
. help ( " Overwrite the output file if it exists " ) ,
2019-11-25 20:33:15 -08:00
)
. arg (
2022-04-22 03:40:53 -07:00
Arg ::new ( SKIP_SEED_PHRASE_VALIDATION_ARG . name )
2019-11-25 20:33:15 -08:00
. 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
)
2023-04-05 15:43:52 -07:00
}
2020-03-12 23:20:49 -07:00
2023-04-05 15:43:52 -07:00
fn main ( ) -> Result < ( ) , Box < dyn error ::Error > > {
let default_num_threads = num_cpus ::get ( ) . to_string ( ) ;
let matches = app ( & default_num_threads , solana_version ::version! ( ) )
. try_get_matches ( )
. unwrap_or_else ( | e | e . exit ( ) ) ;
2020-03-12 23:20:49 -07:00
do_main ( & matches ) . map_err ( | err | DisplayError ::new_as_boxed ( err ) . into ( ) )
}
2022-04-22 03:40:53 -07:00
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
2022-04-22 03:40:53 -07:00
let subcommand = matches . subcommand ( ) . unwrap ( ) ;
match subcommand {
( " pubkey " , 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 ( ) ;
2023-04-05 15:43:52 -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 {
2022-12-06 06:30:06 -08:00
println! ( " {pubkey} " ) ;
2019-03-19 14:19:50 -07:00
}
}
2022-04-22 03:40:53 -07:00
( " new " , 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 {
2022-09-22 15:23:03 -07:00
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 ) = > ( ) ,
2023-04-05 15:43:52 -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
2022-04-22 03:40:53 -07:00
let word_count : usize = matches . value_of_t ( WORD_COUNT_ARG . name ) . 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 " ) ;
}
2022-09-07 11:31:40 -07:00
let derivation_path = acquire_derivation_path ( matches ) ? ;
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 ) ;
2022-09-07 11:31:40 -07:00
let keypair = match derivation_path {
Some ( _ ) = > keypair_from_seed_and_derivation_path ( seed . as_bytes ( ) , derivation_path ) ,
None = > 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 " )
2022-12-06 06:30:06 -08: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
) ;
}
}
2022-04-22 03:40:53 -07:00
( " recover " , 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 {
2022-09-22 15:23:03 -07:00
path . extend ( [ " .config " , " solana " , " id.json " ] ) ;
2019-09-12 18:37:29 -07:00
path . to_str ( ) . unwrap ( )
} ;
2021-05-18 08:35:07 -07:00
if outfile ! = STDOUT_OUTFILE_TOKEN {
2023-04-05 15:43:52 -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
}
2022-04-22 03:40:53 -07:00
( " grind " , 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 " ) {
2022-04-22 03:40:53 -07:00
matches
. values_of_t_or_exit ::< String > ( " starts_with " )
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 " ) {
2022-04-22 03:40:53 -07:00
matches
. values_of_t_or_exit ::< String > ( " ends_with " )
2020-01-28 20:19:19 -08:00
. 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 " ) {
2022-04-22 03:40:53 -07:00
matches
. values_of_t_or_exit ::< String > ( " starts_and_ends_with " )
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 ( )
{
2023-04-05 15:43:52 -07:00
return Err (
" Error: No keypair search criteria provided (--starts-with or --ends-with or --starts-and-ends-with) " . into ( )
2019-11-03 19:41:26 -08:00
) ;
}
2023-09-28 07:03:41 -07:00
let num_threads = * matches . get_one ::< usize > ( " num_threads " ) . unwrap ( ) ;
2021-01-27 12:18:12 -08:00
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 " ) ;
2022-09-07 11:31:40 -07:00
let derivation_path = acquire_derivation_path ( matches ) ? ;
2022-04-22 03:40:53 -07:00
let word_count : usize = matches . value_of_t ( WORD_COUNT_ARG . name ) . unwrap ( ) ;
2021-03-27 22:47:50 -07:00
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 ) ;
2023-04-05 21:36:13 -07:00
// The vast majority of base58 encoded public keys have length 44, but
// these only encapsulate prefixes 1-9 and A-H. If the user is searching
// for a keypair that starts with a prefix of J-Z or a-z, then there is no
// reason to waste time searching for a keypair that will never match
let skip_len_44_pubkeys = grind_matches
. iter ( )
. map ( | g | {
let target_key = if ignore_case {
g . starts . to_ascii_uppercase ( )
} else {
g . starts . clone ( )
} ;
let target_key =
target_key + & ( 0 .. 44 - g . starts . len ( ) ) . map ( | _ | " 1 " ) . collect ::< String > ( ) ;
bs58 ::decode ( target_key ) . into_vec ( )
} )
. filter_map ( | s | s . ok ( ) )
. all ( | s | s . len ( ) > 32 ) ;
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 ( ) ;
2022-09-07 11:31:40 -07:00
let derivation_path = derivation_path . 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 ) ;
2022-09-07 11:31:40 -07:00
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 ( ) )
2021-03-27 22:47:50 -07:00
} else {
( Keypair ::new ( ) , " " . to_string ( ) )
} ;
2023-04-05 21:36:13 -07:00
// Skip keypairs that will never match the user specified prefix
if skip_len_44_pubkeys & & keypair . pubkey ( ) > = smallest_length_44_public_key ::PUBKEY {
continue ;
}
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
}
2022-04-22 03:40:53 -07:00
( " verify " , 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 ) {
2022-12-06 06:30:06 -08:00
println! ( " Verification for public key: {pubkey_bs58} : Success " ) ;
2019-12-05 14:32:42 -08:00
} else {
2023-04-05 15:43:52 -07:00
let err_msg = format! ( " Verification for public key: {pubkey_bs58} : Failed " ) ;
return Err ( err_msg . into ( ) ) ;
2019-12-05 14:32:42 -08:00
}
}
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 ( ( ) )
}
2023-04-05 15:43:52 -07:00
#[ cfg(test) ]
mod tests {
use {
super ::* ,
tempfile ::{ tempdir , TempDir } ,
} ;
fn process_test_command ( args : & [ & str ] ) -> Result < ( ) , Box < dyn error ::Error > > {
let default_num_threads = num_cpus ::get ( ) . to_string ( ) ;
let solana_version = solana_version ::version! ( ) ;
let app_matches = app ( & default_num_threads , solana_version ) . get_matches_from ( args ) ;
do_main ( & app_matches )
}
fn create_tmp_keypair_and_config_file (
keypair_out_dir : & TempDir ,
config_out_dir : & TempDir ,
) -> ( Pubkey , String , String ) {
let keypair = Keypair ::new ( ) ;
let keypair_path = keypair_out_dir
. path ( )
. join ( format! ( " {} -keypair " , keypair . pubkey ( ) ) ) ;
let keypair_outfile = keypair_path . into_os_string ( ) . into_string ( ) . unwrap ( ) ;
write_keypair_file ( & keypair , & keypair_outfile ) . unwrap ( ) ;
let config = Config {
keypair_path : keypair_outfile . clone ( ) ,
.. Config ::default ( )
} ;
let config_path = config_out_dir
. path ( )
. join ( format! ( " {} -config " , keypair . pubkey ( ) ) ) ;
let config_outfile = config_path . into_os_string ( ) . into_string ( ) . unwrap ( ) ;
config . save ( & config_outfile ) . unwrap ( ) ;
( keypair . pubkey ( ) , keypair_outfile , config_outfile )
}
fn tmp_outfile_path ( out_dir : & TempDir , name : & str ) -> String {
let path = out_dir . path ( ) . join ( name ) ;
path . into_os_string ( ) . into_string ( ) . unwrap ( )
}
#[ test ]
fn test_arguments ( ) {
let default_num_threads = num_cpus ::get ( ) . to_string ( ) ;
let solana_version = solana_version ::version! ( ) ;
// run clap internal assert statements
app ( & default_num_threads , solana_version ) . debug_assert ( ) ;
}
#[ test ]
fn test_verify ( ) {
let keypair_out_dir = tempdir ( ) . unwrap ( ) ;
let config_out_dir = tempdir ( ) . unwrap ( ) ;
let ( correct_pubkey , keypair_path , config_path ) =
create_tmp_keypair_and_config_file ( & keypair_out_dir , & config_out_dir ) ;
// success case using a keypair file
process_test_command ( & [
" solana-keygen " ,
" verify " ,
& correct_pubkey . to_string ( ) ,
& keypair_path ,
] )
. unwrap ( ) ;
// success case using a config file
process_test_command ( & [
" solana-keygen " ,
" verify " ,
& correct_pubkey . to_string ( ) ,
" --config " ,
& config_path ,
] )
. unwrap ( ) ;
// fail case using a keypair file
let incorrect_pubkey = Pubkey ::new_unique ( ) ;
let result = process_test_command ( & [
" solana-keygen " ,
" verify " ,
& incorrect_pubkey . to_string ( ) ,
& keypair_path ,
] )
. unwrap_err ( )
. to_string ( ) ;
let expected = format! ( " Verification for public key: {incorrect_pubkey} : Failed " ) ;
assert_eq! ( result , expected ) ;
// fail case using a config file
process_test_command ( & [
" solana-keygen " ,
" verify " ,
& incorrect_pubkey . to_string ( ) ,
" --config " ,
& config_path ,
] )
. unwrap_err ( )
. to_string ( ) ;
let expected = format! ( " Verification for public key: {incorrect_pubkey} : Failed " ) ;
assert_eq! ( result , expected ) ;
// keypair file takes precedence over config file
let alt_keypair_out_dir = tempdir ( ) . unwrap ( ) ;
let alt_config_out_dir = tempdir ( ) . unwrap ( ) ;
let ( _ , alt_keypair_path , alt_config_path ) =
create_tmp_keypair_and_config_file ( & alt_keypair_out_dir , & alt_config_out_dir ) ;
process_test_command ( & [
" solana-keygen " ,
" verify " ,
& correct_pubkey . to_string ( ) ,
& keypair_path ,
" --config " ,
& alt_config_path ,
] )
. unwrap ( ) ;
process_test_command ( & [
" solana-keygen " ,
" verify " ,
& correct_pubkey . to_string ( ) ,
& alt_keypair_path ,
" --config " ,
& config_path ,
] )
. unwrap_err ( )
. to_string ( ) ;
let expected = format! ( " Verification for public key: {incorrect_pubkey} : Failed " ) ;
assert_eq! ( result , expected ) ;
}
#[ test ]
fn test_pubkey ( ) {
let keypair_out_dir = tempdir ( ) . unwrap ( ) ;
let config_out_dir = tempdir ( ) . unwrap ( ) ;
let ( expected_pubkey , keypair_path , config_path ) =
create_tmp_keypair_and_config_file ( & keypair_out_dir , & config_out_dir ) ;
// success case using a keypair file
{
let outfile_dir = tempdir ( ) . unwrap ( ) ;
let outfile_path = tmp_outfile_path ( & outfile_dir , & expected_pubkey . to_string ( ) ) ;
process_test_command ( & [
" solana-keygen " ,
" pubkey " ,
& keypair_path ,
" --outfile " ,
& outfile_path ,
] )
. unwrap ( ) ;
let result_pubkey = solana_sdk ::pubkey ::read_pubkey_file ( & outfile_path ) . unwrap ( ) ;
assert_eq! ( result_pubkey , expected_pubkey ) ;
}
// success case using a config file
{
let outfile_dir = tempdir ( ) . unwrap ( ) ;
let outfile_path = tmp_outfile_path ( & outfile_dir , & expected_pubkey . to_string ( ) ) ;
process_test_command ( & [
" solana-keygen " ,
" pubkey " ,
" --config " ,
& config_path ,
" --outfile " ,
& outfile_path ,
] )
. unwrap ( ) ;
let result_pubkey = solana_sdk ::pubkey ::read_pubkey_file ( & outfile_path ) . unwrap ( ) ;
assert_eq! ( result_pubkey , expected_pubkey ) ;
}
// keypair file takes precedence over config file
{
let alt_keypair_out_dir = tempdir ( ) . unwrap ( ) ;
let alt_config_out_dir = tempdir ( ) . unwrap ( ) ;
let ( _ , _ , alt_config_path ) =
create_tmp_keypair_and_config_file ( & alt_keypair_out_dir , & alt_config_out_dir ) ;
let outfile_dir = tempdir ( ) . unwrap ( ) ;
let outfile_path = tmp_outfile_path ( & outfile_dir , & expected_pubkey . to_string ( ) ) ;
process_test_command ( & [
" solana-keygen " ,
" pubkey " ,
& keypair_path ,
" --config " ,
& alt_config_path ,
" --outfile " ,
& outfile_path ,
] )
. unwrap ( ) ;
let result_pubkey = solana_sdk ::pubkey ::read_pubkey_file ( & outfile_path ) . unwrap ( ) ;
assert_eq! ( result_pubkey , expected_pubkey ) ;
}
// refuse to overwrite file
{
let outfile_dir = tempdir ( ) . unwrap ( ) ;
let outfile_path = tmp_outfile_path ( & outfile_dir , & expected_pubkey . to_string ( ) ) ;
process_test_command ( & [
" solana-keygen " ,
" pubkey " ,
& keypair_path ,
" --outfile " ,
& outfile_path ,
] )
. unwrap ( ) ;
let result = process_test_command ( & [
" solana-keygen " ,
" pubkey " ,
" --config " ,
& config_path ,
" --outfile " ,
& outfile_path ,
] )
. unwrap_err ( )
. to_string ( ) ;
let expected = format! ( " Refusing to overwrite {outfile_path} without --force flag " ) ;
assert_eq! ( result , expected ) ;
}
}
#[ test ]
fn test_new ( ) {
let keypair_out_dir = tempdir ( ) . unwrap ( ) ;
let config_out_dir = tempdir ( ) . unwrap ( ) ;
let ( expected_pubkey , _ , _ ) =
create_tmp_keypair_and_config_file ( & keypair_out_dir , & config_out_dir ) ;
let outfile_dir = tempdir ( ) . unwrap ( ) ;
let outfile_path = tmp_outfile_path ( & outfile_dir , & expected_pubkey . to_string ( ) ) ;
// general success case
process_test_command ( & [
" solana-keygen " ,
" new " ,
" --outfile " ,
& outfile_path ,
" --no-bip39-passphrase " ,
] )
. unwrap ( ) ;
// refuse to overwrite file
let result = process_test_command ( & [
" solana-keygen " ,
" new " ,
" --outfile " ,
& outfile_path ,
" --no-bip39-passphrase " ,
] )
. unwrap_err ( )
. to_string ( ) ;
let expected = format! ( " Refusing to overwrite {outfile_path} without --force flag " ) ;
assert_eq! ( result , expected ) ;
// no outfile
process_test_command ( & [
" solana-keygen " ,
" new " ,
" --no-bip39-passphrase " ,
" --no-outfile " ,
] )
. unwrap ( ) ;
// sanity check on languages and word count combinations
let languages = [
" english " ,
" chinese-simplified " ,
" chinese-traditional " ,
" japanese " ,
" spanish " ,
" korean " ,
" french " ,
" italian " ,
] ;
let word_counts = [ " 12 " , " 15 " , " 18 " , " 21 " , " 24 " ] ;
for language in languages {
for word_count in word_counts {
process_test_command ( & [
" solana-keygen " ,
" new " ,
" --no-outfile " ,
" --no-bip39-passphrase " ,
" --language " ,
language ,
" --word-count " ,
word_count ,
] )
. unwrap ( ) ;
}
}
// sanity check derivation path
process_test_command ( & [
" solana-keygen " ,
" new " ,
" --no-bip39-passphrase " ,
" --no-outfile " ,
" --derivation-path " ,
// empty derivation path
] )
. unwrap ( ) ;
process_test_command ( & [
" solana-keygen " ,
" new " ,
" --no-bip39-passphrase " ,
" --no-outfile " ,
" --derivation-path " ,
" m/44'/501'/0'/0' " , // default derivation path
] )
. unwrap ( ) ;
let result = process_test_command ( & [
" solana-keygen " ,
" new " ,
" --no-bip39-passphrase " ,
" --no-outfile " ,
" --derivation-path " ,
" - " , // invalid derivation path
] )
. unwrap_err ( )
. to_string ( ) ;
let expected = " invalid derivation path: invalid prefix: - " ;
assert_eq! ( result , expected ) ;
}
#[ test ]
fn test_grind ( ) {
// simple sanity checks
process_test_command ( & [
" solana-keygen " ,
" grind " ,
" --no-outfile " ,
" --no-bip39-passphrase " ,
" --use-mnemonic " ,
" --starts-with " ,
" a:1 " ,
] )
. unwrap ( ) ;
process_test_command ( & [
" solana-keygen " ,
" grind " ,
" --no-outfile " ,
" --no-bip39-passphrase " ,
" --use-mnemonic " ,
" --ends-with " ,
" b:1 " ,
] )
. unwrap ( ) ;
}
}