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:
parent
8eb05d6ed4
commit
a5ec3a0547
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) => {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue