SignerSource: rename input scheme to `prompt`, default to bip44 solana base key (#17154)

* Rename ask to prompt

* Default to Solana bip44 base if no derivation-path

* Add SignerSource legacy field, support legacy ASK

* Update docs

* Fix docs: validator current doesn't support uri SignerSources
This commit is contained in:
Tyera Eulberg 2021-05-10 19:28:47 -06:00 committed by GitHub
parent 8eb05d6ed4
commit a5ec3a0547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 41 deletions

View File

@ -57,7 +57,7 @@ pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
if let Some(value) = matches.value_of(name) { if let Some(value) = matches.value_of(name) {
if value == ASK_KEYWORD { if value == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); 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 { } else {
read_keypair_file(value).ok() read_keypair_file(value).ok()
} }
@ -72,7 +72,7 @@ pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Keypair>>
.filter_map(|value| { .filter_map(|value| {
if value == ASK_KEYWORD { if value == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); 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 { } else {
read_keypair_file(value).ok() read_keypair_file(value).ok()
} }

View File

@ -18,7 +18,8 @@ use {
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
signature::{ 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, read_keypair, read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
}, },
}, },
@ -140,6 +141,7 @@ impl DefaultSigner {
pub(crate) struct SignerSource { pub(crate) struct SignerSource {
pub kind: SignerSourceKind, pub kind: SignerSourceKind,
pub derivation_path: Option<DerivationPath>, pub derivation_path: Option<DerivationPath>,
pub legacy: bool,
} }
impl SignerSource { impl SignerSource {
@ -147,12 +149,21 @@ impl SignerSource {
Self { Self {
kind, kind,
derivation_path: None, derivation_path: None,
legacy: false,
}
}
fn new_legacy(kind: SignerSourceKind) -> Self {
Self {
kind,
derivation_path: None,
legacy: true,
} }
} }
} }
pub(crate) enum SignerSourceKind { pub(crate) enum SignerSourceKind {
Ask, Prompt,
Filepath(String), Filepath(String),
Usb(RemoteWalletLocator), Usb(RemoteWalletLocator),
Stdin, Stdin,
@ -181,9 +192,10 @@ pub(crate) fn parse_signer_source<S: AsRef<str>>(
if let Some(scheme) = uri.scheme() { if let Some(scheme) = uri.scheme() {
let scheme = scheme.as_str().to_ascii_lowercase(); let scheme = scheme.as_str().to_ascii_lowercase();
match scheme.as_str() { match scheme.as_str() {
"ask" => Ok(SignerSource { "prompt" => Ok(SignerSource {
kind: SignerSourceKind::Ask, kind: SignerSourceKind::Prompt,
derivation_path: DerivationPath::from_uri_any_query(&uri)?, derivation_path: DerivationPath::from_uri_any_query(&uri)?,
legacy: false,
}), }),
"file" => Ok(SignerSource::new(SignerSourceKind::Filepath( "file" => Ok(SignerSource::new(SignerSourceKind::Filepath(
uri.path().to_string(), uri.path().to_string(),
@ -192,13 +204,14 @@ pub(crate) fn parse_signer_source<S: AsRef<str>>(
"usb" => Ok(SignerSource { "usb" => Ok(SignerSource {
kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?), kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?),
derivation_path: DerivationPath::from_uri_key_query(&uri)?, derivation_path: DerivationPath::from_uri_key_query(&uri)?,
legacy: false,
}), }),
_ => Err(SignerSourceError::UnrecognizedSource), _ => Err(SignerSourceError::UnrecognizedSource),
} }
} else { } else {
match source { match source {
"-" => Ok(SignerSource::new(SignerSourceKind::Stdin)), "-" => 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) { _ => match Pubkey::from_str(source) {
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))), Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
Err(_) => std::fs::metadata(source) Err(_) => std::fs::metadata(source)
@ -259,15 +272,17 @@ pub fn signer_from_path_with_config(
let SignerSource { let SignerSource {
kind, kind,
derivation_path, derivation_path,
legacy,
} = parse_signer_source(path)?; } = parse_signer_source(path)?;
match kind { match kind {
SignerSourceKind::Ask => { SignerSourceKind::Prompt => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
Ok(Box::new(keypair_from_seed_phrase( Ok(Box::new(keypair_from_seed_phrase(
keypair_name, keypair_name,
skip_validation, skip_validation,
false, false,
derivation_path, derivation_path,
legacy,
)?)) )?))
} }
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) { SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
@ -339,18 +354,30 @@ pub fn resolve_signer_from_path(
let SignerSource { let SignerSource {
kind, kind,
derivation_path, derivation_path,
legacy,
} = parse_signer_source(path)?; } = parse_signer_source(path)?;
match kind { match kind {
SignerSourceKind::Ask => { SignerSourceKind::Prompt => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); 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 // This method validates the seed phrase, but returns `None` because there is no path
// on disk or to a device // 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) { SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
Err(e) => Err(std::io::Error::new( Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other, 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()), .into()),
Ok(_) => Ok(Some(path.to_string())), 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 ASK_KEYWORD: &str = "ASK";
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant { pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
@ -412,6 +439,7 @@ pub fn keypair_from_seed_phrase(
skip_validation: bool, skip_validation: bool,
confirm_pubkey: bool, confirm_pubkey: bool,
derivation_path: Option<DerivationPath>, derivation_path: Option<DerivationPath>,
legacy: bool,
) -> Result<Keypair, Box<dyn error::Error>> { ) -> Result<Keypair, Box<dyn error::Error>> {
let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?; let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?;
let seed_phrase = seed_phrase.trim(); let seed_phrase = seed_phrase.trim();
@ -422,8 +450,12 @@ pub fn keypair_from_seed_phrase(
let keypair = if skip_validation { let keypair = if skip_validation {
let passphrase = prompt_passphrase(&passphrase_prompt)?; let passphrase = prompt_passphrase(&passphrase_prompt)?;
let seed = generate_seed_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase); if legacy {
keypair_from_seed_and_derivation_path(&seed, derivation_path)? 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 { } else {
let sanitized = sanitize_seed_phrase(seed_phrase); let sanitized = sanitize_seed_phrase(seed_phrase);
let parse_language_fn = || { let parse_language_fn = || {
@ -446,7 +478,11 @@ pub fn keypair_from_seed_phrase(
let mnemonic = parse_language_fn()?; let mnemonic = parse_language_fn()?;
let passphrase = prompt_passphrase(&passphrase_prompt)?; let passphrase = prompt_passphrase(&passphrase_prompt)?;
let seed = Seed::new(&mnemonic, &passphrase); 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 { if confirm_pubkey {
@ -525,21 +561,24 @@ mod tests {
SignerSource { SignerSource {
kind: SignerSourceKind::Stdin, kind: SignerSourceKind::Stdin,
derivation_path: None, derivation_path: None,
legacy: false,
} }
)); ));
let ask = "stdin:".to_string(); let stdin = "stdin:".to_string();
assert!(matches!( assert!(matches!(
parse_signer_source(&ask).unwrap(), parse_signer_source(&stdin).unwrap(),
SignerSource { SignerSource {
kind: SignerSourceKind::Stdin, kind: SignerSourceKind::Stdin,
derivation_path: None, derivation_path: None,
legacy: false,
} }
)); ));
assert!(matches!( assert!(matches!(
parse_signer_source(ASK_KEYWORD).unwrap(), parse_signer_source(ASK_KEYWORD).unwrap(),
SignerSource { SignerSource {
kind: SignerSourceKind::Ask, kind: SignerSourceKind::Prompt,
derivation_path: None, derivation_path: None,
legacy: true,
} }
)); ));
let pubkey = Pubkey::new_unique(); let pubkey = Pubkey::new_unique();
@ -547,6 +586,7 @@ mod tests {
matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource { matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource {
kind: SignerSourceKind::Pubkey(p), kind: SignerSourceKind::Pubkey(p),
derivation_path: None, derivation_path: None,
legacy: false,
} }
if p == pubkey) if p == pubkey)
); );
@ -567,12 +607,14 @@ mod tests {
matches!(parse_signer_source(absolute_path_str).unwrap(), SignerSource { matches!(parse_signer_source(absolute_path_str).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p), kind: SignerSourceKind::Filepath(p),
derivation_path: None, derivation_path: None,
legacy: false,
} if p == absolute_path_str) } if p == absolute_path_str)
); );
assert!( assert!(
matches!(parse_signer_source(&relative_path_str).unwrap(), SignerSource { matches!(parse_signer_source(&relative_path_str).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p), kind: SignerSourceKind::Filepath(p),
derivation_path: None, derivation_path: None,
legacy: false,
} if p == relative_path_str) } if p == relative_path_str)
); );
@ -584,6 +626,7 @@ mod tests {
assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource { assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource {
kind: SignerSourceKind::Usb(u), kind: SignerSourceKind::Usb(u),
derivation_path: None, derivation_path: None,
legacy: false,
} if u == expected_locator)); } if u == expected_locator));
let usb = "usb://ledger?key=0/0".to_string(); let usb = "usb://ledger?key=0/0".to_string();
let expected_locator = RemoteWalletLocator { let expected_locator = RemoteWalletLocator {
@ -594,6 +637,7 @@ mod tests {
assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource { assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource {
kind: SignerSourceKind::Usb(u), kind: SignerSourceKind::Usb(u),
derivation_path: d, derivation_path: d,
legacy: false,
} if u == expected_locator && d == expected_derivation_path)); } if u == expected_locator && d == expected_derivation_path));
// Catchall into SignerSource::Filepath fails // Catchall into SignerSource::Filepath fails
let junk = "sometextthatisnotapubkeyorfile".to_string(); let junk = "sometextthatisnotapubkeyorfile".to_string();
@ -603,24 +647,27 @@ mod tests {
Err(SignerSourceError::IoError(_)) Err(SignerSourceError::IoError(_))
)); ));
let ask = "ask:".to_string(); let prompt = "prompt:".to_string();
assert!(matches!( assert!(matches!(
parse_signer_source(&ask).unwrap(), parse_signer_source(&prompt).unwrap(),
SignerSource { SignerSource {
kind: SignerSourceKind::Ask, kind: SignerSourceKind::Prompt,
derivation_path: None, derivation_path: None,
legacy: false,
} }
)); ));
assert!( assert!(
matches!(parse_signer_source(&format!("file:{}", absolute_path_str)).unwrap(), SignerSource { matches!(parse_signer_source(&format!("file:{}", absolute_path_str)).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p), kind: SignerSourceKind::Filepath(p),
derivation_path: None, derivation_path: None,
legacy: false,
} if p == absolute_path_str) } if p == absolute_path_str)
); );
assert!( assert!(
matches!(parse_signer_source(&format!("file:{}", relative_path_str)).unwrap(), SignerSource { matches!(parse_signer_source(&format!("file:{}", relative_path_str)).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p), kind: SignerSourceKind::Filepath(p),
derivation_path: None, derivation_path: None,
legacy: false,
} if p == relative_path_str) } if p == relative_path_str)
); );
} }

View File

@ -48,13 +48,13 @@ on your wallet type.
In a paper wallet, the keypair is securely derived from the seed words and 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 optional passphrase you entered when the wallet was create. To use a paper
wallet keypair anywhere the `<KEYPAIR>` text is shown in examples or help wallet keypair anywhere the `<KEYPAIR>` 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. enter your seed words when you run the command.
To display the wallet address of a Paper Wallet: To display the wallet address of a Paper Wallet:
```bash ```bash
solana-keygen pubkey ask:// solana-keygen pubkey prompt://
``` ```
#### File System Wallet #### File System Wallet

View File

@ -155,7 +155,7 @@ solana-keygen new --no-outfile
The corresponding identity public key can now be viewed by running: The corresponding identity public key can now be viewed by running:
```bash ```bash
solana-keygen pubkey ask:// solana-keygen pubkey ASK
``` ```
and then entering your seed phrase. 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) > [paper wallet seed phrase](../wallet-guide/paper-wallet.md)
> for your `--identity` and/or > for your `--identity` and/or
> `--authorized-voter` keypairs. To use these, pass the respective argument as > `--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. > 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 Confirm your validator connected to the network by opening a new terminal and

View File

@ -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. with the solana command-line tools using the `ask` uri scheme.
```bash ```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. > 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. validation.
```bash ```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_ will display a string of base-58 character. This is the base _wallet address_
associated with your seed phrase. 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 hierarchical derivation of private keys from your seed phrase and passphrase by
adding either the `?key=` query string or the `?full-path=` query string. 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 By default, `prompt:` will derive solana's base derivation path `m/44'/501'`. To
string, or `?key=<ACCOUNT>/<CHANGE>`. derive a child key, supply the `?key=<ACCOUNT>/<CHANGE>` query string.
```bash ```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/<PURPOSE>/<COIN_TYPE>/<ACCOUNT>/<CHANGE>`. To use a derivation path other than solana's standard BIP44, you can supply `?full-path=m/<PURPOSE>/<COIN_TYPE>/<ACCOUNT>/<CHANGE>`.
```bash ```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 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`: `solana-keygen verify`:
```bash ```bash
solana-keygen verify <PUBKEY> ask:// solana-keygen verify <PUBKEY> prompt://
``` ```
where `<PUBKEY>` is replaced with the wallet address and they keyword `ask://` where `<PUBKEY>` is replaced with the wallet address and the keyword `prompt://`
tells the command to prompt you for the keypair's seed phrase; `key` and 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 `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 phrase will not be displayed as you type. After entering your seed phrase, the

View File

@ -589,7 +589,7 @@ fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box<dyn error::Error>> {
} }
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); 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")?; output_keypair(&keypair, &outfile, "recovered")?;
} }
("grind", Some(matches)) => { ("grind", Some(matches)) => {

View File

@ -398,16 +398,13 @@ pub fn keypair_from_seed(seed: &[u8]) -> Result<Keypair, Box<dyn error::Error>>
} }
/// Generates a Keypair using Bip32 Hierarchical Derivation if derivation-path is provided; /// 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( pub fn keypair_from_seed_and_derivation_path(
seed: &[u8], seed: &[u8],
derivation_path: Option<DerivationPath>, derivation_path: Option<DerivationPath>,
) -> Result<Keypair, Box<dyn error::Error>> { ) -> Result<Keypair, Box<dyn error::Error>> {
if let Some(derivation_path) = derivation_path { let derivation_path = derivation_path.unwrap_or_else(DerivationPath::default);
bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into()) bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into())
} else {
keypair_from_seed(seed)
}
} }
/// Generates a Keypair using Bip32 Hierarchical Derivation /// Generates a Keypair using Bip32 Hierarchical Derivation