diff --git a/clap-utils/src/input_parsers.rs b/clap-utils/src/input_parsers.rs index 702d14ce46..e64785142f 100644 --- a/clap-utils/src/input_parsers.rs +++ b/clap-utils/src/input_parsers.rs @@ -57,7 +57,7 @@ pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option { if let Some(value) = matches.value_of(name) { if value == ASK_KEYWORD { let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); - keypair_from_seed_phrase(name, skip_validation, true, None).ok() + keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() } else { read_keypair_file(value).ok() } @@ -72,7 +72,7 @@ pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option> .filter_map(|value| { if value == ASK_KEYWORD { let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); - keypair_from_seed_phrase(name, skip_validation, true, None).ok() + keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() } else { read_keypair_file(value).ok() } diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index cd2e930176..c767468543 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -18,7 +18,8 @@ use { message::Message, pubkey::Pubkey, signature::{ - generate_seed_from_seed_phrase_and_passphrase, keypair_from_seed_and_derivation_path, + generate_seed_from_seed_phrase_and_passphrase, keypair_from_seed, + keypair_from_seed_and_derivation_path, keypair_from_seed_phrase_and_passphrase, read_keypair, read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer, }, }, @@ -140,6 +141,7 @@ impl DefaultSigner { pub(crate) struct SignerSource { pub kind: SignerSourceKind, pub derivation_path: Option, + pub legacy: bool, } impl SignerSource { @@ -147,12 +149,21 @@ impl SignerSource { Self { kind, derivation_path: None, + legacy: false, + } + } + + fn new_legacy(kind: SignerSourceKind) -> Self { + Self { + kind, + derivation_path: None, + legacy: true, } } } pub(crate) enum SignerSourceKind { - Ask, + Prompt, Filepath(String), Usb(RemoteWalletLocator), Stdin, @@ -181,9 +192,10 @@ pub(crate) fn parse_signer_source>( if let Some(scheme) = uri.scheme() { let scheme = scheme.as_str().to_ascii_lowercase(); match scheme.as_str() { - "ask" => Ok(SignerSource { - kind: SignerSourceKind::Ask, + "prompt" => Ok(SignerSource { + kind: SignerSourceKind::Prompt, derivation_path: DerivationPath::from_uri_any_query(&uri)?, + legacy: false, }), "file" => Ok(SignerSource::new(SignerSourceKind::Filepath( uri.path().to_string(), @@ -192,13 +204,14 @@ pub(crate) fn parse_signer_source>( "usb" => Ok(SignerSource { kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?), derivation_path: DerivationPath::from_uri_key_query(&uri)?, + legacy: false, }), _ => Err(SignerSourceError::UnrecognizedSource), } } else { match source { "-" => Ok(SignerSource::new(SignerSourceKind::Stdin)), - ASK_KEYWORD => Ok(SignerSource::new(SignerSourceKind::Ask)), + ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)), _ => match Pubkey::from_str(source) { Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))), Err(_) => std::fs::metadata(source) @@ -259,15 +272,17 @@ pub fn signer_from_path_with_config( let SignerSource { kind, derivation_path, + legacy, } = parse_signer_source(path)?; match kind { - SignerSourceKind::Ask => { + SignerSourceKind::Prompt => { let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); Ok(Box::new(keypair_from_seed_phrase( keypair_name, skip_validation, false, derivation_path, + legacy, )?)) } SignerSourceKind::Filepath(path) => match read_keypair_file(&path) { @@ -339,18 +354,30 @@ pub fn resolve_signer_from_path( let SignerSource { kind, derivation_path, + legacy, } = parse_signer_source(path)?; match kind { - SignerSourceKind::Ask => { + SignerSourceKind::Prompt => { let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); // This method validates the seed phrase, but returns `None` because there is no path // on disk or to a device - keypair_from_seed_phrase(keypair_name, skip_validation, false, derivation_path).map(|_| None) + keypair_from_seed_phrase( + keypair_name, + skip_validation, + false, + derivation_path, + legacy, + ) + .map(|_| None) } SignerSourceKind::Filepath(path) => match read_keypair_file(&path) { Err(e) => Err(std::io::Error::new( std::io::ErrorKind::Other, - format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e), + format!( + "could not read keypair file \"{}\". \ + Run \"solana-keygen new\" to create a keypair file: {}", + path, e + ), ) .into()), Ok(_) => Ok(Some(path.to_string())), @@ -383,7 +410,7 @@ pub fn resolve_signer_from_path( } } -// Keyword used to indicate that the user should be asked for a keypair seed phrase +// Keyword used to indicate that the user should be prompted for a keypair seed phrase pub const ASK_KEYWORD: &str = "ASK"; pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant { @@ -412,6 +439,7 @@ pub fn keypair_from_seed_phrase( skip_validation: bool, confirm_pubkey: bool, derivation_path: Option, + legacy: bool, ) -> Result> { let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?; let seed_phrase = seed_phrase.trim(); @@ -422,8 +450,12 @@ pub fn keypair_from_seed_phrase( let keypair = if skip_validation { let passphrase = prompt_passphrase(&passphrase_prompt)?; - let seed = generate_seed_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase); - keypair_from_seed_and_derivation_path(&seed, derivation_path)? + if legacy { + keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)? + } else { + let seed = generate_seed_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase); + keypair_from_seed_and_derivation_path(&seed, derivation_path)? + } } else { let sanitized = sanitize_seed_phrase(seed_phrase); let parse_language_fn = || { @@ -446,7 +478,11 @@ pub fn keypair_from_seed_phrase( let mnemonic = parse_language_fn()?; let passphrase = prompt_passphrase(&passphrase_prompt)?; let seed = Seed::new(&mnemonic, &passphrase); - keypair_from_seed_and_derivation_path(&seed.as_bytes(), derivation_path)? + if legacy { + keypair_from_seed(seed.as_bytes())? + } else { + keypair_from_seed_and_derivation_path(&seed.as_bytes(), derivation_path)? + } }; if confirm_pubkey { @@ -525,21 +561,24 @@ mod tests { SignerSource { kind: SignerSourceKind::Stdin, derivation_path: None, + legacy: false, } )); - let ask = "stdin:".to_string(); + let stdin = "stdin:".to_string(); assert!(matches!( - parse_signer_source(&ask).unwrap(), + parse_signer_source(&stdin).unwrap(), SignerSource { kind: SignerSourceKind::Stdin, derivation_path: None, + legacy: false, } )); assert!(matches!( parse_signer_source(ASK_KEYWORD).unwrap(), SignerSource { - kind: SignerSourceKind::Ask, + kind: SignerSourceKind::Prompt, derivation_path: None, + legacy: true, } )); let pubkey = Pubkey::new_unique(); @@ -547,6 +586,7 @@ mod tests { matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource { kind: SignerSourceKind::Pubkey(p), derivation_path: None, + legacy: false, } if p == pubkey) ); @@ -567,12 +607,14 @@ mod tests { matches!(parse_signer_source(absolute_path_str).unwrap(), SignerSource { kind: SignerSourceKind::Filepath(p), derivation_path: None, + legacy: false, } if p == absolute_path_str) ); assert!( matches!(parse_signer_source(&relative_path_str).unwrap(), SignerSource { kind: SignerSourceKind::Filepath(p), derivation_path: None, + legacy: false, } if p == relative_path_str) ); @@ -584,6 +626,7 @@ mod tests { assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource { kind: SignerSourceKind::Usb(u), derivation_path: None, + legacy: false, } if u == expected_locator)); let usb = "usb://ledger?key=0/0".to_string(); let expected_locator = RemoteWalletLocator { @@ -594,6 +637,7 @@ mod tests { assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource { kind: SignerSourceKind::Usb(u), derivation_path: d, + legacy: false, } if u == expected_locator && d == expected_derivation_path)); // Catchall into SignerSource::Filepath fails let junk = "sometextthatisnotapubkeyorfile".to_string(); @@ -603,24 +647,27 @@ mod tests { Err(SignerSourceError::IoError(_)) )); - let ask = "ask:".to_string(); + let prompt = "prompt:".to_string(); assert!(matches!( - parse_signer_source(&ask).unwrap(), + parse_signer_source(&prompt).unwrap(), SignerSource { - kind: SignerSourceKind::Ask, + kind: SignerSourceKind::Prompt, derivation_path: None, + legacy: false, } )); assert!( matches!(parse_signer_source(&format!("file:{}", absolute_path_str)).unwrap(), SignerSource { kind: SignerSourceKind::Filepath(p), derivation_path: None, + legacy: false, } if p == absolute_path_str) ); assert!( matches!(parse_signer_source(&format!("file:{}", relative_path_str)).unwrap(), SignerSource { kind: SignerSourceKind::Filepath(p), derivation_path: None, + legacy: false, } if p == relative_path_str) ); } diff --git a/docs/src/cli/conventions.md b/docs/src/cli/conventions.md index bdfc777a1d..87521d12d9 100644 --- a/docs/src/cli/conventions.md +++ b/docs/src/cli/conventions.md @@ -48,13 +48,13 @@ on your wallet type. In a paper wallet, the keypair is securely derived from the seed words and optional passphrase you entered when the wallet was create. To use a paper wallet keypair anywhere the `` text is shown in examples or help -documents, enter the uri scheme `ask://` and the program will prompt you to +documents, enter the uri scheme `prompt://` and the program will prompt you to enter your seed words when you run the command. To display the wallet address of a Paper Wallet: ```bash -solana-keygen pubkey ask:// +solana-keygen pubkey prompt:// ``` #### File System Wallet diff --git a/docs/src/running-validator/validator-start.md b/docs/src/running-validator/validator-start.md index 5c303d0271..f5e3342604 100644 --- a/docs/src/running-validator/validator-start.md +++ b/docs/src/running-validator/validator-start.md @@ -155,7 +155,7 @@ solana-keygen new --no-outfile The corresponding identity public key can now be viewed by running: ```bash -solana-keygen pubkey ask:// +solana-keygen pubkey ASK ``` and then entering your seed phrase. @@ -294,7 +294,7 @@ The ledger will be placed in the `ledger/` directory by default, use the > [paper wallet seed phrase](../wallet-guide/paper-wallet.md) > for your `--identity` and/or > `--authorized-voter` keypairs. To use these, pass the respective argument as -> `solana-validator --identity ask:// ... --authorized-voter ask:// ...` +> `solana-validator --identity ASK ... --authorized-voter ASK ...` > and you will be prompted to enter your seed phrases and optional passphrase. Confirm your validator connected to the network by opening a new terminal and diff --git a/docs/src/wallet-guide/paper-wallet.md b/docs/src/wallet-guide/paper-wallet.md index 15d5fe5280..83c1e6b3f0 100644 --- a/docs/src/wallet-guide/paper-wallet.md +++ b/docs/src/wallet-guide/paper-wallet.md @@ -91,7 +91,7 @@ to use your seed phrase (and a passphrase if you chose to use one) as a signer with the solana command-line tools using the `ask` uri scheme. ```bash -solana-keygen pubkey ask:// +solana-keygen pubkey prompt:// ``` > Note that you could potentially use different passphrases for the same seed phrase. Each unique passphrase will yield a different keypair. @@ -103,10 +103,10 @@ will need to pass the `--skip-seed-phrase-validation` argument and forego this validation. ```bash -solana-keygen pubkey ask:// --skip-seed-phrase-validation +solana-keygen pubkey prompt:// --skip-seed-phrase-validation ``` -After entering your seed phrase with `solana-keygen pubkey ask://` the console +After entering your seed phrase with `solana-keygen pubkey prompt://` the console will display a string of base-58 character. This is the base _wallet address_ associated with your seed phrase. @@ -128,17 +128,17 @@ The solana-cli supports hierarchical derivation of private keys from your seed phrase and passphrase by adding either the `?key=` query string or the `?full-path=` query string. -To use solana's BIP44 derivation path `m/44'/501'`, supply the `?key=m` query -string, or `?key=/`. +By default, `prompt:` will derive solana's base derivation path `m/44'/501'`. To +derive a child key, supply the `?key=/` query string. ```bash -solana-keygen pubkey ask://?key=0/1 +solana-keygen pubkey prompt://?key=0/1 ``` To use a derivation path other than solana's standard BIP44, you can supply `?full-path=m////`. ```bash -solana-keygen pubkey ask://?full-path=m/44/2017/0/1 +solana-keygen pubkey prompt://?full-path=m/44/2017/0/1 ``` Because Solana uses Ed25519 keypairs, as per @@ -153,10 +153,10 @@ To verify you control the private key of a paper wallet address, use `solana-keygen verify`: ```bash -solana-keygen verify ask:// +solana-keygen verify prompt:// ``` -where `` is replaced with the wallet address and they keyword `ask://` +where `` is replaced with the wallet address and the keyword `prompt://` tells the command to prompt you for the keypair's seed phrase; `key` and `full-path` query-strings accepted. Note that for security reasons, your seed phrase will not be displayed as you type. After entering your seed phrase, the diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index 7274cdc5f6..0b7b603c05 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -589,7 +589,7 @@ fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box> { } let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); - let keypair = keypair_from_seed_phrase("recover", skip_validation, true, None)?; + let keypair = keypair_from_seed_phrase("recover", skip_validation, true, None, true)?; output_keypair(&keypair, &outfile, "recovered")?; } ("grind", Some(matches)) => { diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index 9874ddcdbd..8102b9582a 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -398,16 +398,13 @@ pub fn keypair_from_seed(seed: &[u8]) -> Result> } /// Generates a Keypair using Bip32 Hierarchical Derivation if derivation-path is provided; -/// otherwise builds standard Keypair using the seed as SecretKey +/// otherwise generates the base Bip44 Solana keypair from the seed pub fn keypair_from_seed_and_derivation_path( seed: &[u8], derivation_path: Option, ) -> Result> { - if let Some(derivation_path) = derivation_path { - bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into()) - } else { - keypair_from_seed(seed) - } + let derivation_path = derivation_path.unwrap_or_else(DerivationPath::default); + bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into()) } /// Generates a Keypair using Bip32 Hierarchical Derivation