CLI: dynamic signing reboot (#8384)
* Add keypair_util_from_path helper * Cli: impl config.keypair as a trait object * SDK: Add Debug and PartialEq for dyn Signer * ClapUtils: Arg parsing from pubkey+signers to Presigner * Impl Signers for &dyn Signer collections * CLI: Add helper for getting signers from args * CLI: Replace SigningAuthority with Signer trait-objs * CLI: Drop disused signers command field * CLI: Drop redundant tests * Add clap validator that handles all current signer types * clap_utils: Factor Presigner resolution to helper * SDK: `From` for boxing Signer implementors to trait objects * SDK: Derive `Clone` for `Presigner` * Remove panic * Cli: dedup signers in transfer for remote-wallet ergonomics * Update docs vis-a-vis ASK changes * Cli: update transaction types to use new dynamic-signer methods * CLI: Fix tests No. 1 what to do about write_keypair outstanding * Work around `CliConfig`'s signer not necessarily being a `Keypair` * CLI: Fix tests No. 2 * Remove unused arg * Remove unused methods * Move offline arg constants upstream * Make cli signing fallible Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com>
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG};
|
||||
use crate::keypair::{
|
||||
keypair_from_seed_phrase, keypair_util_from_path, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
};
|
||||
use chrono::DateTime;
|
||||
use clap::ArgMatches;
|
||||
use solana_remote_wallet::remote_wallet::DerivationPath;
|
||||
@ -93,6 +95,18 @@ pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubk
|
||||
})
|
||||
}
|
||||
|
||||
// Return a signer from matches at `name`
|
||||
pub fn signer_of(
|
||||
name: &str,
|
||||
matches: &ArgMatches<'_>,
|
||||
) -> Result<Option<Box<dyn Signer>>, Box<dyn std::error::Error>> {
|
||||
if let Some(location) = matches.value_of(name) {
|
||||
keypair_util_from_path(matches, location, name).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
value_of(matches, name).map(sol_to_lamports)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::keypair::ASK_KEYWORD;
|
||||
use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
|
||||
use chrono::DateTime;
|
||||
use solana_remote_wallet::remote_keypair::generate_remote_keypair;
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
@ -50,6 +51,16 @@ pub fn is_pubkey_or_keypair_or_ask_keyword(string: String) -> Result<(), String>
|
||||
is_pubkey(string.clone()).or_else(|_| is_keypair_or_ask_keyword(string))
|
||||
}
|
||||
|
||||
pub fn is_valid_signer(string: String) -> Result<(), String> {
|
||||
match parse_keypair_path(&string) {
|
||||
KeypairUrl::Usb(path) => generate_remote_keypair(path, None)
|
||||
.map(|_| ())
|
||||
.map_err(|err| format!("{:?}", err)),
|
||||
KeypairUrl::Filepath(path) => is_keypair(path),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as pubkey=signature string
|
||||
pub fn is_pubkey_sig(string: String) -> Result<(), String> {
|
||||
let mut signer = string.split('=');
|
||||
|
@ -1,14 +1,24 @@
|
||||
use crate::ArgConstant;
|
||||
use crate::{
|
||||
input_parsers::{derivation_of, pubkeys_sigs_of},
|
||||
offline::SIGNER_ARG,
|
||||
ArgConstant,
|
||||
};
|
||||
use bip39::{Language, Mnemonic, Seed};
|
||||
use clap::values_t;
|
||||
use clap::{values_t, ArgMatches, Error, ErrorKind};
|
||||
use rpassword::prompt_password_stderr;
|
||||
use solana_sdk::signature::{
|
||||
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair_file, Keypair, Signer,
|
||||
use solana_remote_wallet::remote_keypair::generate_remote_keypair;
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{
|
||||
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
|
||||
read_keypair_file, Keypair, Presigner, Signature, Signer,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
error,
|
||||
io::{stdin, stdout, Write},
|
||||
process::exit,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
pub enum KeypairUrl {
|
||||
@ -16,6 +26,7 @@ pub enum KeypairUrl {
|
||||
Filepath(String),
|
||||
Usb(String),
|
||||
Stdin,
|
||||
Pubkey(Pubkey),
|
||||
}
|
||||
|
||||
pub fn parse_keypair_path(path: &str) -> KeypairUrl {
|
||||
@ -25,11 +36,66 @@ pub fn parse_keypair_path(path: &str) -> KeypairUrl {
|
||||
KeypairUrl::Ask
|
||||
} else if path.starts_with("usb://") {
|
||||
KeypairUrl::Usb(path.split_at(6).1.to_string())
|
||||
} else if let Ok(pubkey) = Pubkey::from_str(path) {
|
||||
KeypairUrl::Pubkey(pubkey)
|
||||
} else {
|
||||
KeypairUrl::Filepath(path.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn presigner_from_pubkey_sigs(
|
||||
pubkey: &Pubkey,
|
||||
signers: &[(Pubkey, Signature)],
|
||||
) -> Option<Presigner> {
|
||||
signers.iter().find_map(|(signer, sig)| {
|
||||
if *signer == *pubkey {
|
||||
Some(Presigner::new(signer, sig))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn keypair_util_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
Ok(Box::new(keypair_from_seed_phrase(
|
||||
keypair_name,
|
||||
skip_validation,
|
||||
false,
|
||||
)?))
|
||||
}
|
||||
KeypairUrl::Filepath(path) => Ok(Box::new(read_keypair_file(&path)?)),
|
||||
KeypairUrl::Stdin => {
|
||||
let mut stdin = std::io::stdin();
|
||||
Ok(Box::new(read_keypair(&mut stdin)?))
|
||||
}
|
||||
KeypairUrl::Usb(path) => Ok(Box::new(generate_remote_keypair(
|
||||
path,
|
||||
derivation_of(matches, "derivation_path"),
|
||||
)?)),
|
||||
KeypairUrl::Pubkey(pubkey) => {
|
||||
let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name)
|
||||
.as_ref()
|
||||
.and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners));
|
||||
if let Some(presigner) = presigner {
|
||||
Ok(Box::new(presigner))
|
||||
} else {
|
||||
Err(Error::with_description(
|
||||
"Missing signature for supplied pubkey",
|
||||
ErrorKind::MissingRequiredArgument,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keyword used to indicate that the user should be asked for a keypair seed phrase
|
||||
pub const ASK_KEYWORD: &str = "ASK";
|
||||
|
||||
|
@ -26,3 +26,4 @@ pub struct ArgConstant<'a> {
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
pub mod keypair;
|
||||
pub mod offline;
|
||||
|
19
clap-utils/src/offline.rs
Normal file
19
clap-utils/src/offline.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use crate::ArgConstant;
|
||||
|
||||
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "blockhash",
|
||||
long: "blockhash",
|
||||
help: "Use the supplied blockhash",
|
||||
};
|
||||
|
||||
pub const SIGN_ONLY_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "sign_only",
|
||||
long: "sign-only",
|
||||
help: "Sign the transaction offline",
|
||||
};
|
||||
|
||||
pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "signer",
|
||||
long: "signer",
|
||||
help: "Provide a public-key/signature pair for the transaction",
|
||||
};
|
Reference in New Issue
Block a user