[clap-v3-utils] Add functions to parse directly from `SignerSource` (#34678)

* add `_from_source` function variants for signer, keypair, and pubkey

* make `parse_signer_source` an associated function of `SignerSource`

* refactor `SignerSource` into `input_parsers::signer`

* make `_from_source` functions public

* remove unnecessary import
This commit is contained in:
samkim-crypto 2024-01-25 06:27:02 +09:00 committed by GitHub
parent 5898b9a2f7
commit 3004eaa9bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 386 additions and 301 deletions

View File

@ -1,20 +1,162 @@
use {
crate::{
input_parsers::{keypair_of, keypairs_of, pubkey_of, pubkeys_of},
keypair::{
parse_signer_source, pubkey_from_path, resolve_signer_from_path, signer_from_path,
SignerSource, SignerSourceError, SignerSourceKind,
},
keypair::{pubkey_from_path, resolve_signer_from_path, signer_from_path, ASK_KEYWORD},
},
clap::{builder::ValueParser, ArgMatches},
solana_remote_wallet::remote_wallet::RemoteWalletManager,
solana_remote_wallet::{
locator::{Locator as RemoteWalletLocator, LocatorError as RemoteWalletLocatorError},
remote_wallet::RemoteWalletManager,
},
solana_sdk::{
derivation_path::{DerivationPath, DerivationPathError},
pubkey::Pubkey,
signature::{Keypair, Signature, Signer},
},
std::{error, rc::Rc, str::FromStr},
thiserror::Error,
};
const SIGNER_SOURCE_PROMPT: &str = "prompt";
const SIGNER_SOURCE_FILEPATH: &str = "file";
const SIGNER_SOURCE_USB: &str = "usb";
const SIGNER_SOURCE_STDIN: &str = "stdin";
const SIGNER_SOURCE_PUBKEY: &str = "pubkey";
#[derive(Debug, Error)]
pub enum SignerSourceError {
#[error("unrecognized signer source")]
UnrecognizedSource,
#[error(transparent)]
RemoteWalletLocatorError(#[from] RemoteWalletLocatorError),
#[error(transparent)]
DerivationPathError(#[from] DerivationPathError),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error("unsupported source")]
UnsupportedSource,
}
#[derive(Clone)]
pub enum SignerSourceKind {
Prompt,
Filepath(String),
Usb(RemoteWalletLocator),
Stdin,
Pubkey(Pubkey),
}
impl AsRef<str> for SignerSourceKind {
fn as_ref(&self) -> &str {
match self {
Self::Prompt => SIGNER_SOURCE_PROMPT,
Self::Filepath(_) => SIGNER_SOURCE_FILEPATH,
Self::Usb(_) => SIGNER_SOURCE_USB,
Self::Stdin => SIGNER_SOURCE_STDIN,
Self::Pubkey(_) => SIGNER_SOURCE_PUBKEY,
}
}
}
impl std::fmt::Debug for SignerSourceKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s: &str = self.as_ref();
write!(f, "{s}")
}
}
#[derive(Debug, Clone)]
pub 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) fn parse<S: AsRef<str>>(source: S) -> Result<Self, SignerSourceError> {
let source = source.as_ref();
let source = {
#[cfg(target_family = "windows")]
{
// trim matched single-quotes since cmd.exe won't
let mut source = source;
while let Some(trimmed) = source.strip_prefix('\'') {
source = if let Some(trimmed) = trimmed.strip_suffix('\'') {
trimmed
} else {
break;
}
}
source.replace('\\', "/")
}
#[cfg(not(target_family = "windows"))]
{
source.to_string()
}
};
match uriparse::URIReference::try_from(source.as_str()) {
Err(_) => Err(SignerSourceError::UnrecognizedSource),
Ok(uri) => {
if let Some(scheme) = uri.scheme() {
let scheme = scheme.as_str().to_ascii_lowercase();
match scheme.as_str() {
SIGNER_SOURCE_PROMPT => Ok(SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: DerivationPath::from_uri_any_query(&uri)?,
legacy: false,
}),
SIGNER_SOURCE_FILEPATH => Ok(SignerSource::new(
SignerSourceKind::Filepath(uri.path().to_string()),
)),
SIGNER_SOURCE_USB => Ok(SignerSource {
kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?),
derivation_path: DerivationPath::from_uri_key_query(&uri)?,
legacy: false,
}),
SIGNER_SOURCE_STDIN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
_ => {
#[cfg(target_family = "windows")]
// On Windows, an absolute path's drive letter will be parsed as the URI
// scheme. Assume a filepath source in case of a single character shceme.
if scheme.len() == 1 {
return Ok(SignerSource::new(SignerSourceKind::Filepath(source)));
}
Err(SignerSourceError::UnrecognizedSource)
}
}
} else {
match source.as_str() {
STDOUT_OUTFILE_TOKEN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)),
_ => match Pubkey::from_str(source.as_str()) {
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
Err(_) => std::fs::metadata(source.as_str())
.map(|_| SignerSource::new(SignerSourceKind::Filepath(source)))
.map_err(|err| err.into()),
},
}
}
}
}
}
}
// Sentinel value used to indicate to write to screen instead of file
pub const STDOUT_OUTFILE_TOKEN: &str = "-";
@ -72,7 +214,7 @@ impl SignerSourceParserBuilder {
pub fn build(self) -> ValueParser {
ValueParser::from(
move |arg: &str| -> Result<SignerSource, SignerSourceError> {
let signer_source = parse_signer_source(arg)?;
let signer_source = SignerSource::parse(arg)?;
if !self.allow_legacy && signer_source.legacy {
return Err(SignerSourceError::UnsupportedSource);
}
@ -240,11 +382,130 @@ mod tests {
super::*,
assert_matches::assert_matches,
clap::{Arg, Command},
solana_remote_wallet::locator::Manufacturer,
solana_sdk::signature::write_keypair_file,
std::fs,
tempfile::NamedTempFile,
};
#[test]
fn test_parse_signer_source() {
assert_matches!(
SignerSource::parse(STDOUT_OUTFILE_TOKEN).unwrap(),
SignerSource {
kind: SignerSourceKind::Stdin,
derivation_path: None,
legacy: false,
}
);
let stdin = "stdin:".to_string();
assert_matches!(
SignerSource::parse(stdin).unwrap(),
SignerSource {
kind: SignerSourceKind::Stdin,
derivation_path: None,
legacy: false,
}
);
assert_matches!(
SignerSource::parse(ASK_KEYWORD).unwrap(),
SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: None,
legacy: true,
}
);
let pubkey = Pubkey::new_unique();
assert!(
matches!(SignerSource::parse(pubkey.to_string()).unwrap(), SignerSource {
kind: SignerSourceKind::Pubkey(p),
derivation_path: None,
legacy: false,
}
if p == pubkey)
);
// Set up absolute and relative path strs
let file0 = NamedTempFile::new().unwrap();
let path = file0.path();
assert!(path.is_absolute());
let absolute_path_str = path.to_str().unwrap();
let file1 = NamedTempFile::new_in(std::env::current_dir().unwrap()).unwrap();
let path = file1.path().file_name().unwrap().to_str().unwrap();
let path = std::path::Path::new(path);
assert!(path.is_relative());
let relative_path_str = path.to_str().unwrap();
assert!(
matches!(SignerSource::parse(absolute_path_str).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == absolute_path_str)
);
assert!(
matches!(SignerSource::parse(relative_path_str).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == relative_path_str)
);
let usb = "usb://ledger".to_string();
let expected_locator = RemoteWalletLocator {
manufacturer: Manufacturer::Ledger,
pubkey: None,
};
assert_matches!(SignerSource::parse(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 {
manufacturer: Manufacturer::Ledger,
pubkey: None,
};
let expected_derivation_path = Some(DerivationPath::new_bip44(Some(0), Some(0)));
assert_matches!(SignerSource::parse(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();
assert!(Pubkey::from_str(&junk).is_err());
assert_matches!(
SignerSource::parse(&junk),
Err(SignerSourceError::IoError(_))
);
let prompt = "prompt:".to_string();
assert_matches!(
SignerSource::parse(prompt).unwrap(),
SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: None,
legacy: false,
}
);
assert!(
matches!(SignerSource::parse(format!("file:{absolute_path_str}")).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == absolute_path_str)
);
assert!(
matches!(SignerSource::parse(format!("file:{relative_path_str}")).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == relative_path_str)
);
}
fn app<'ab>() -> Command<'ab> {
Command::new("test")
.arg(

View File

@ -1,5 +1,8 @@
use {
crate::keypair::{parse_signer_source, SignerSourceKind, ASK_KEYWORD},
crate::{
input_parsers::signer::{SignerSource, SignerSourceKind},
keypair::ASK_KEYWORD,
},
chrono::DateTime,
solana_sdk::{
clock::{Epoch, Slot},
@ -119,7 +122,7 @@ pub fn is_prompt_signer_source(string: &str) -> Result<(), String> {
if string == ASK_KEYWORD {
return Ok(());
}
match parse_signer_source(string)
match SignerSource::parse(string)
.map_err(|err| format!("{err}"))?
.kind
{
@ -154,7 +157,7 @@ pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
match parse_signer_source(string.as_ref())
match SignerSource::parse(string.as_ref())
.map_err(|err| format!("{err}"))?
.kind
{

View File

@ -11,7 +11,7 @@
use {
crate::{
input_parsers::{signer::try_pubkeys_sigs_of, STDOUT_OUTFILE_TOKEN},
input_parsers::signer::{try_pubkeys_sigs_of, SignerSource, SignerSourceKind},
offline::{SIGNER_ARG, SIGN_ONLY_ARG},
ArgConstant,
},
@ -19,12 +19,11 @@ use {
clap::ArgMatches,
rpassword::prompt_password,
solana_remote_wallet::{
locator::{Locator as RemoteWalletLocator, LocatorError as RemoteWalletLocatorError},
remote_keypair::generate_remote_keypair,
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
},
solana_sdk::{
derivation_path::{DerivationPath, DerivationPathError},
derivation_path::DerivationPath,
hash::Hash,
message::Message,
pubkey::Pubkey,
@ -37,15 +36,12 @@ use {
solana_zk_token_sdk::encryption::{auth_encryption::AeKey, elgamal::ElGamalKeypair},
std::{
cell::RefCell,
convert::TryFrom,
error,
io::{stdin, stdout, Write},
ops::Deref,
process::exit,
rc::Rc,
str::FromStr,
},
thiserror::Error,
};
pub struct SignOnly {
@ -166,7 +162,7 @@ impl DefaultSigner {
fn path(&self) -> Result<&str, Box<dyn std::error::Error>> {
if !self.is_path_checked.borrow().deref() {
parse_signer_source(&self.path)
SignerSource::parse(&self.path)
.and_then(|s| {
if let SignerSourceKind::Filepath(path) = &s.kind {
std::fs::metadata(path).map(|_| ()).map_err(|e| e.into())
@ -371,148 +367,6 @@ impl DefaultSigner {
}
}
#[derive(Debug, Clone)]
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,
}
}
}
const SIGNER_SOURCE_PROMPT: &str = "prompt";
const SIGNER_SOURCE_FILEPATH: &str = "file";
const SIGNER_SOURCE_USB: &str = "usb";
const SIGNER_SOURCE_STDIN: &str = "stdin";
const SIGNER_SOURCE_PUBKEY: &str = "pubkey";
#[derive(Clone)]
pub(crate) enum SignerSourceKind {
Prompt,
Filepath(String),
Usb(RemoteWalletLocator),
Stdin,
Pubkey(Pubkey),
}
impl AsRef<str> for SignerSourceKind {
fn as_ref(&self) -> &str {
match self {
Self::Prompt => SIGNER_SOURCE_PROMPT,
Self::Filepath(_) => SIGNER_SOURCE_FILEPATH,
Self::Usb(_) => SIGNER_SOURCE_USB,
Self::Stdin => SIGNER_SOURCE_STDIN,
Self::Pubkey(_) => SIGNER_SOURCE_PUBKEY,
}
}
}
impl std::fmt::Debug for SignerSourceKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s: &str = self.as_ref();
write!(f, "{s}")
}
}
#[derive(Debug, Error)]
pub(crate) enum SignerSourceError {
#[error("unrecognized signer source")]
UnrecognizedSource,
#[error(transparent)]
RemoteWalletLocatorError(#[from] RemoteWalletLocatorError),
#[error(transparent)]
DerivationPathError(#[from] DerivationPathError),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error("unsupported source")]
UnsupportedSource,
}
pub(crate) fn parse_signer_source<S: AsRef<str>>(
source: S,
) -> Result<SignerSource, SignerSourceError> {
let source = source.as_ref();
let source = {
#[cfg(target_family = "windows")]
{
// trim matched single-quotes since cmd.exe won't
let mut source = source;
while let Some(trimmed) = source.strip_prefix('\'') {
source = if let Some(trimmed) = trimmed.strip_suffix('\'') {
trimmed
} else {
break;
}
}
source.replace('\\', "/")
}
#[cfg(not(target_family = "windows"))]
{
source.to_string()
}
};
match uriparse::URIReference::try_from(source.as_str()) {
Err(_) => Err(SignerSourceError::UnrecognizedSource),
Ok(uri) => {
if let Some(scheme) = uri.scheme() {
let scheme = scheme.as_str().to_ascii_lowercase();
match scheme.as_str() {
SIGNER_SOURCE_PROMPT => Ok(SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: DerivationPath::from_uri_any_query(&uri)?,
legacy: false,
}),
SIGNER_SOURCE_FILEPATH => Ok(SignerSource::new(SignerSourceKind::Filepath(
uri.path().to_string(),
))),
SIGNER_SOURCE_USB => Ok(SignerSource {
kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?),
derivation_path: DerivationPath::from_uri_key_query(&uri)?,
legacy: false,
}),
SIGNER_SOURCE_STDIN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
_ => {
#[cfg(target_family = "windows")]
// On Windows, an absolute path's drive letter will be parsed as the URI
// scheme. Assume a filepath source in case of a single character shceme.
if scheme.len() == 1 {
return Ok(SignerSource::new(SignerSourceKind::Filepath(source)));
}
Err(SignerSourceError::UnrecognizedSource)
}
}
} else {
match source.as_str() {
STDOUT_OUTFILE_TOKEN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)),
_ => match Pubkey::from_str(source.as_str()) {
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
Err(_) => std::fs::metadata(source.as_str())
.map(|_| SignerSource::new(SignerSourceKind::Filepath(source)))
.map_err(|err| err.into()),
},
}
}
}
}
}
pub fn presigner_from_pubkey_sigs(
pubkey: &Pubkey,
signers: &[(Pubkey, Signature)],
@ -697,6 +551,16 @@ pub fn signer_from_path(
signer_from_path_with_config(matches, path, keypair_name, wallet_manager, &config)
}
pub fn signer_from_source(
matches: &ArgMatches,
source: &SignerSource,
keypair_name: &str,
wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
let config = SignerFromPathConfig::default();
signer_from_source_with_config(matches, source, keypair_name, wallet_manager, &config)
}
/// Loads a [Signer] from one of several possible sources.
///
/// The `path` is not strictly a file system path, but is interpreted as various
@ -760,12 +624,23 @@ pub fn signer_from_path_with_config(
keypair_name: &str,
wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
config: &SignerFromPathConfig,
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
let source = SignerSource::parse(path)?;
signer_from_source_with_config(matches, &source, keypair_name, wallet_manager, config)
}
pub fn signer_from_source_with_config(
matches: &ArgMatches,
source: &SignerSource,
keypair_name: &str,
wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
config: &SignerFromPathConfig,
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
let SignerSource {
kind,
derivation_path,
legacy,
} = parse_signer_source(path)?;
} = source;
match kind {
SignerSourceKind::Prompt => {
let skip_validation = matches.try_contains_id(SKIP_SEED_PHRASE_VALIDATION_ARG.name)?;
@ -773,11 +648,11 @@ pub fn signer_from_path_with_config(
keypair_name,
skip_validation,
false,
derivation_path,
legacy,
derivation_path.clone(),
*legacy,
)?))
}
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
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 \"{path}\". Run \"solana-keygen new\" to create a keypair file: {e}"),
@ -796,8 +671,8 @@ pub fn signer_from_path_with_config(
if let Some(wallet_manager) = wallet_manager {
let confirm_key = matches.try_contains_id("confirm_key").unwrap_or(false);
Ok(Box::new(generate_remote_keypair(
locator,
derivation_path.unwrap_or_default(),
locator.clone(),
derivation_path.clone().unwrap_or_default(),
wallet_manager,
confirm_key,
keypair_name,
@ -809,11 +684,11 @@ pub fn signer_from_path_with_config(
SignerSourceKind::Pubkey(pubkey) => {
let presigner = try_pubkeys_sigs_of(matches, SIGNER_ARG.name)?
.as_ref()
.and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners));
.and_then(|presigners| presigner_from_pubkey_sigs(pubkey, presigners));
if let Some(presigner) = presigner {
Ok(Box::new(presigner))
} else if config.allow_null_signer || matches.try_contains_id(SIGN_ONLY_ARG.name)? {
Ok(Box::new(NullSigner::new(&pubkey)))
Ok(Box::new(NullSigner::new(pubkey)))
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
@ -868,10 +743,19 @@ pub fn pubkey_from_path(
keypair_name: &str,
wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<Pubkey, Box<dyn error::Error>> {
let SignerSource { kind, .. } = parse_signer_source(path)?;
match kind {
let source = SignerSource::parse(path)?;
pubkey_from_source(matches, &source, keypair_name, wallet_manager)
}
pub fn pubkey_from_source(
matches: &ArgMatches,
source: &SignerSource,
keypair_name: &str,
wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<Pubkey, Box<dyn error::Error>> {
match source.kind {
SignerSourceKind::Pubkey(pubkey) => Ok(pubkey),
_ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()),
_ => Ok(signer_from_source(matches, source, keypair_name, wallet_manager)?.pubkey()),
}
}
@ -880,12 +764,22 @@ pub fn resolve_signer_from_path(
path: &str,
keypair_name: &str,
wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<Option<String>, Box<dyn error::Error>> {
let source = SignerSource::parse(path)?;
resolve_signer_from_source(matches, &source, keypair_name, wallet_manager)
}
pub fn resolve_signer_from_source(
matches: &ArgMatches,
source: &SignerSource,
keypair_name: &str,
wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<Option<String>, Box<dyn error::Error>> {
let SignerSource {
kind,
derivation_path,
legacy,
} = parse_signer_source(path)?;
} = source;
match kind {
SignerSourceKind::Prompt => {
let skip_validation = matches.try_contains_id(SKIP_SEED_PHRASE_VALIDATION_ARG.name)?;
@ -895,12 +789,12 @@ pub fn resolve_signer_from_path(
keypair_name,
skip_validation,
false,
derivation_path,
legacy,
derivation_path.clone(),
*legacy,
)
.map(|_| None)
}
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
SignerSourceKind::Filepath(path) => match read_keypair_file(path) {
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
@ -924,8 +818,8 @@ pub fn resolve_signer_from_path(
if let Some(wallet_manager) = wallet_manager {
let confirm_key = matches.try_contains_id("confirm_key").unwrap_or(false);
let path = generate_remote_keypair(
locator,
derivation_path.unwrap_or_default(),
locator.clone(),
derivation_path.clone().unwrap_or_default(),
wallet_manager,
confirm_key,
keypair_name,
@ -936,7 +830,7 @@ pub fn resolve_signer_from_path(
Err(RemoteWalletError::NoDeviceFound.into())
}
}
_ => Ok(Some(path.to_string())),
SignerSourceKind::Pubkey(pubkey) => Ok(Some(pubkey.to_string())),
}
}
@ -1015,6 +909,20 @@ pub fn keypair_from_path(
Ok(keypair)
}
pub fn keypair_from_source(
matches: &ArgMatches,
source: &SignerSource,
keypair_name: &str,
confirm_pubkey: bool,
) -> Result<Keypair, Box<dyn error::Error>> {
let skip_validation = matches.try_contains_id(SKIP_SEED_PHRASE_VALIDATION_ARG.name)?;
let keypair = encodable_key_from_source(source, keypair_name, skip_validation)?;
if confirm_pubkey {
confirm_encodable_keypair_pubkey(&keypair, "pubkey");
}
Ok(keypair)
}
/// Loads an [ElGamalKeypair] from one of several possible sources.
///
/// If `confirm_pubkey` is `true` then after deriving the keypair, the user will
@ -1063,6 +971,20 @@ pub fn elgamal_keypair_from_path(
Ok(elgamal_keypair)
}
pub fn elgamal_keypair_from_source(
matches: &ArgMatches,
source: &SignerSource,
elgamal_keypair_name: &str,
confirm_pubkey: bool,
) -> Result<ElGamalKeypair, Box<dyn error::Error>> {
let skip_validation = matches.try_contains_id(SKIP_SEED_PHRASE_VALIDATION_ARG.name)?;
let elgamal_keypair = encodable_key_from_source(source, elgamal_keypair_name, skip_validation)?;
if confirm_pubkey {
confirm_encodable_keypair_pubkey(&elgamal_keypair, "ElGamal pubkey");
}
Ok(elgamal_keypair)
}
fn confirm_encodable_keypair_pubkey<K: EncodableKeypair>(keypair: &K, pubkey_label: &str) {
let pubkey = keypair.encodable_pubkey().to_string();
println!("Recovered {pubkey_label} `{pubkey:?}`. Continue? (y/n): ");
@ -1114,24 +1036,42 @@ pub fn ae_key_from_path(
encodable_key_from_path(path, key_name, skip_validation)
}
pub fn ae_key_from_source(
matches: &ArgMatches,
source: &SignerSource,
key_name: &str,
) -> Result<AeKey, Box<dyn error::Error>> {
let skip_validation = matches.try_contains_id(SKIP_SEED_PHRASE_VALIDATION_ARG.name)?;
encodable_key_from_source(source, key_name, skip_validation)
}
fn encodable_key_from_path<K: EncodableKey + SeedDerivable>(
path: &str,
keypair_name: &str,
skip_validation: bool,
) -> Result<K, Box<dyn error::Error>> {
let source = SignerSource::parse(path)?;
encodable_key_from_source(&source, keypair_name, skip_validation)
}
fn encodable_key_from_source<K: EncodableKey + SeedDerivable>(
source: &SignerSource,
keypair_name: &str,
skip_validation: bool,
) -> Result<K, Box<dyn error::Error>> {
let SignerSource {
kind,
derivation_path,
legacy,
} = parse_signer_source(path)?;
} = source;
match kind {
SignerSourceKind::Prompt => Ok(encodable_key_from_seed_phrase(
keypair_name,
skip_validation,
derivation_path,
legacy,
derivation_path.clone(),
*legacy,
)?),
SignerSourceKind::Filepath(path) => match K::read_from_file(&path) {
SignerSourceKind::Filepath(path) => match K::read_from_file(path) {
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
@ -1270,11 +1210,10 @@ mod tests {
use {
super::*,
crate::offline::OfflineArgs,
assert_matches::assert_matches,
clap::{Arg, Command},
solana_remote_wallet::{locator::Manufacturer, remote_wallet::initialize_wallet_manager},
solana_remote_wallet::remote_wallet::initialize_wallet_manager,
solana_sdk::{signer::keypair::write_keypair_file, system_instruction},
tempfile::{NamedTempFile, TempDir},
tempfile::TempDir,
};
#[test]
@ -1317,124 +1256,6 @@ mod tests {
assert_eq!(signer_pubkeys, expect);
}
#[test]
fn test_parse_signer_source() {
assert_matches!(
parse_signer_source(STDOUT_OUTFILE_TOKEN).unwrap(),
SignerSource {
kind: SignerSourceKind::Stdin,
derivation_path: None,
legacy: false,
}
);
let stdin = "stdin:".to_string();
assert_matches!(
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::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)
);
// Set up absolute and relative path strs
let file0 = NamedTempFile::new().unwrap();
let path = file0.path();
assert!(path.is_absolute());
let absolute_path_str = path.to_str().unwrap();
let file1 = NamedTempFile::new_in(std::env::current_dir().unwrap()).unwrap();
let path = file1.path().file_name().unwrap().to_str().unwrap();
let path = std::path::Path::new(path);
assert!(path.is_relative());
let relative_path_str = path.to_str().unwrap();
assert!(
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)
);
let usb = "usb://ledger".to_string();
let expected_locator = RemoteWalletLocator {
manufacturer: Manufacturer::Ledger,
pubkey: None,
};
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 {
manufacturer: Manufacturer::Ledger,
pubkey: None,
};
let expected_derivation_path = Some(DerivationPath::new_bip44(Some(0), Some(0)));
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();
assert!(Pubkey::from_str(&junk).is_err());
assert_matches!(
parse_signer_source(&junk),
Err(SignerSourceError::IoError(_))
);
let prompt = "prompt:".to_string();
assert_matches!(
parse_signer_source(prompt).unwrap(),
SignerSource {
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)
);
}
#[test]
fn signer_from_path_with_file() -> Result<(), Box<dyn std::error::Error>> {
let dir = TempDir::new()?;