Skip to content

Commit

Permalink
SignerSource: rename input scheme to prompt, default to bip44 solan…
Browse files Browse the repository at this point in the history
…a base key (solana-labs#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
  • Loading branch information
CriesofCarrots authored May 11, 2021
1 parent 8eb05d6 commit a5ec3a0
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 41 deletions.
4 changes: 2 additions & 2 deletions clap-utils/src/input_parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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()
}
Expand Down
85 changes: 66 additions & 19 deletions clap-utils/src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
Expand Down Expand Up @@ -140,19 +141,29 @@ impl DefaultSigner {
pub(crate) struct SignerSource {
pub kind: SignerSourceKind,
pub derivation_path: Option<DerivationPath>,
pub legacy: bool,
}

impl SignerSource {
fn new(kind: SignerSourceKind) -> Self {
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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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())),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand All @@ -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 = || {
Expand All @@ -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 {
Expand Down Expand Up @@ -525,28 +561,32 @@ 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();
assert!(
matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource {
kind: SignerSourceKind::Pubkey(p),
derivation_path: None,
legacy: false,
}
if p == pubkey)
);
Expand All @@ -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)
);

Expand All @@ -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 {
Expand All @@ -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();
Expand All @@ -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)
);
}
Expand Down
4 changes: 2 additions & 2 deletions docs/src/cli/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/src/running-validator/validator-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
18 changes: 9 additions & 9 deletions docs/src/wallet-guide/paper-wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion keygen/src/keygen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)) => {
Expand Down
Loading

0 comments on commit a5ec3a0

Please sign in to comment.