Allow secure keypair input for solana-archiver
and solana
cli tools (#7106)
* Add seed phrase keypair recover to archiver * Add seed phrase keypair to cli with ASK keyword * cli main tweaks
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG};
|
||||
use clap::ArgMatches;
|
||||
use solana_sdk::{
|
||||
native_token::sol_to_lamports,
|
||||
@ -32,7 +33,12 @@ where
|
||||
// Return the keypair for an argument with filename `name` or None if not present.
|
||||
pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
|
||||
if let Some(value) = matches.value_of(name) {
|
||||
read_keypair_file(value).ok()
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::keypair::ASK_KEYWORD;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::read_keypair_file;
|
||||
|
||||
@ -16,6 +17,16 @@ pub fn is_keypair(string: String) -> Result<(), String> {
|
||||
.map_err(|err| format!("{:?}", err))
|
||||
}
|
||||
|
||||
// Return an error if a keypair file cannot be parsed
|
||||
pub fn is_keypair_or_ask_keyword(string: String) -> Result<(), String> {
|
||||
if string.as_str() == ASK_KEYWORD {
|
||||
return Ok(());
|
||||
}
|
||||
read_keypair_file(&string)
|
||||
.map(|_| ())
|
||||
.map_err(|err| format!("{:?}", err))
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as pubkey string or keypair file location
|
||||
pub fn is_pubkey_or_keypair(string: String) -> Result<(), String> {
|
||||
is_pubkey(string.clone()).or_else(|_| is_keypair(string))
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::ArgConstant;
|
||||
use bip39::{Language, Mnemonic, Seed};
|
||||
use clap::values_t;
|
||||
use rpassword::prompt_password_stderr;
|
||||
@ -7,11 +8,41 @@ use solana_sdk::signature::{
|
||||
};
|
||||
use std::error;
|
||||
|
||||
pub const ASK_SEED_PHRASE_ARG: &str = "ask_seed_phrase";
|
||||
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: &str = "skip_seed_phrase_validation";
|
||||
// Keyword used to indicate that the user should be asked for a keypair seed phrase
|
||||
pub const ASK_KEYWORD: &str = "ASK";
|
||||
|
||||
pub const ASK_SEED_PHRASE_ARG: ArgConstant<'static> = ArgConstant {
|
||||
long: "ask-seed-phrase",
|
||||
name: "ask_seed_phrase",
|
||||
help: "Securely recover a keypair using a seed phrase and optional passphrase",
|
||||
};
|
||||
|
||||
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
|
||||
long: "skip-seed-phrase-validation",
|
||||
name: "skip_seed_phrase_validation",
|
||||
help: "Skip validation of seed phrases. Use this if your phrase does not use the BIP39 official English word list",
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Source {
|
||||
File,
|
||||
Generated,
|
||||
SeedPhrase,
|
||||
}
|
||||
|
||||
pub struct KeypairWithSource {
|
||||
pub keypair: Keypair,
|
||||
pub source: Source,
|
||||
}
|
||||
|
||||
impl KeypairWithSource {
|
||||
fn new(keypair: Keypair, source: Source) -> Self {
|
||||
Self { keypair, source }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads user input from stdin to retrieve a seed phrase and passphrase for keypair derivation
|
||||
pub fn keypair_from_seed_phrase(
|
||||
pub(crate) fn keypair_from_seed_phrase(
|
||||
keypair_name: &str,
|
||||
skip_validation: bool,
|
||||
) -> Result<Keypair, Box<dyn error::Error>> {
|
||||
@ -33,17 +64,6 @@ pub fn keypair_from_seed_phrase(
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeypairWithGenerated {
|
||||
pub keypair: Keypair,
|
||||
pub generated: bool,
|
||||
}
|
||||
|
||||
impl KeypairWithGenerated {
|
||||
fn new(keypair: Keypair, generated: bool) -> Self {
|
||||
Self { keypair, generated }
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks CLI arguments to determine whether a keypair should be:
|
||||
/// - inputted securely via stdin,
|
||||
/// - read in from a file,
|
||||
@ -51,33 +71,32 @@ impl KeypairWithGenerated {
|
||||
pub fn keypair_input(
|
||||
matches: &clap::ArgMatches,
|
||||
keypair_name: &str,
|
||||
) -> Result<KeypairWithGenerated, Box<dyn error::Error>> {
|
||||
) -> Result<KeypairWithSource, Box<dyn error::Error>> {
|
||||
let ask_seed_phrase_matches =
|
||||
values_t!(matches.values_of(ASK_SEED_PHRASE_ARG), String).unwrap_or_default();
|
||||
values_t!(matches.values_of(ASK_SEED_PHRASE_ARG.name), String).unwrap_or_default();
|
||||
let keypair_match_name = keypair_name.replace('-', "_");
|
||||
if ask_seed_phrase_matches
|
||||
.iter()
|
||||
.any(|s| s.as_str() == keypair_name)
|
||||
{
|
||||
if matches.value_of(keypair_match_name).is_some() {
|
||||
let ask_seed_phrase_kebab = ASK_SEED_PHRASE_ARG.replace('_', "-");
|
||||
clap::Error::with_description(
|
||||
&format!(
|
||||
"`--{} {}` cannot be used with `{} <PATH>`",
|
||||
ask_seed_phrase_kebab, keypair_name, keypair_name
|
||||
ASK_SEED_PHRASE_ARG.long, keypair_name, keypair_name
|
||||
),
|
||||
clap::ErrorKind::ArgumentConflict,
|
||||
)
|
||||
.exit();
|
||||
}
|
||||
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG);
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(keypair_name, skip_validation)
|
||||
.map(|keypair| KeypairWithGenerated::new(keypair, false))
|
||||
.map(|keypair| KeypairWithSource::new(keypair, Source::SeedPhrase))
|
||||
} else if let Some(keypair_file) = matches.value_of(keypair_match_name) {
|
||||
read_keypair_file(keypair_file).map(|keypair| KeypairWithGenerated::new(keypair, false))
|
||||
read_keypair_file(keypair_file).map(|keypair| KeypairWithSource::new(keypair, Source::File))
|
||||
} else {
|
||||
Ok(KeypairWithGenerated::new(Keypair::new(), true))
|
||||
Ok(KeypairWithSource::new(Keypair::new(), Source::Generated))
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +108,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_keypair_input() {
|
||||
let arg_matches = ArgMatches::default();
|
||||
let KeypairWithGenerated { generated, .. } = keypair_input(&arg_matches, "").unwrap();
|
||||
assert!(generated);
|
||||
let KeypairWithSource { source, .. } = keypair_input(&arg_matches, "").unwrap();
|
||||
assert_eq!(source, Source::Generated);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,12 @@ macro_rules! version {
|
||||
};
|
||||
}
|
||||
|
||||
pub struct ArgConstant<'a> {
|
||||
pub long: &'a str,
|
||||
pub name: &'a str,
|
||||
pub help: &'a str,
|
||||
}
|
||||
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
pub mod keypair;
|
||||
|
Reference in New Issue
Block a user