Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

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

Merged
merged 1 commit into from
May 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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