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 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<Vec<Keypair>>
.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()
}

View File

@ -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<DerivationPath>,
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<S: AsRef<str>>(
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<S: AsRef<str>>(
"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<DerivationPath>,
legacy: bool,
) -> Result<Keypair, Box<dyn error::Error>> {
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)
);
}

View File

@ -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 `<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.
To display the wallet address of a Paper Wallet:
```bash
solana-keygen pubkey ask://
solana-keygen pubkey prompt://
```
#### 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:
```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

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.
```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=<ACCOUNT>/<CHANGE>`.
By default, `prompt:` will derive solana's base derivation path `m/44'/501'`. To
derive a child key, supply the `?key=<ACCOUNT>/<CHANGE>` 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/<PURPOSE>/<COIN_TYPE>/<ACCOUNT>/<CHANGE>`.
```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 <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
`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

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 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)) => {

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;
/// 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<DerivationPath>,
) -> Result<Keypair, Box<dyn error::Error>> {
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