Enable remote-wallet signing in solana-keygen (#8267)

* Add fallible methods to KeypairUtil

* Add RemoteKeypair struct and impl KeypairUtil

* Implement RemoteKeypair in keygen; also add parse_keypair_path for cleanup
This commit is contained in:
Tyera Eulberg 2020-02-13 14:08:35 -07:00 committed by GitHub
parent ab475e4849
commit 2374cf09e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 150 additions and 85 deletions

View File

@ -12,6 +12,25 @@ use std::{
process::exit, process::exit,
}; };
pub enum KeypairUrl {
Ask,
Filepath(String),
Usb(String),
Stdin,
}
pub fn parse_keypair_path(path: &str) -> KeypairUrl {
if path == "-" {
KeypairUrl::Stdin
} else if path == ASK_KEYWORD {
KeypairUrl::Ask
} else if path.starts_with("usb://") {
KeypairUrl::Usb(path.split_at(6).1.to_string())
} else {
KeypairUrl::Filepath(path.to_string())
}
}
// Keyword used to indicate that the user should be asked for a keypair seed phrase // Keyword used to indicate that the user should be asked for a keypair seed phrase
pub const ASK_KEYWORD: &str = "ASK"; pub const ASK_KEYWORD: &str = "ASK";

View File

@ -1990,9 +1990,17 @@ impl KeypairUtil for FaucetKeypair {
self.transaction.message().account_keys[0] self.transaction.message().account_keys[0]
} }
fn try_pubkey(&self) -> Result<Pubkey, Box<dyn error::Error>> {
Ok(self.pubkey())
}
fn sign_message(&self, _msg: &[u8]) -> Signature { fn sign_message(&self, _msg: &[u8]) -> Signature {
self.transaction.signatures[0] self.transaction.signatures[0]
} }
fn try_sign_message(&self, message: &[u8]) -> Result<Signature, Box<dyn error::Error>> {
Ok(self.sign_message(message))
}
} }
pub fn request_and_confirm_airdrop( pub fn request_and_confirm_airdrop(

View File

@ -9,16 +9,17 @@ use solana_clap_utils::{
input_parsers::derivation_of, input_parsers::derivation_of,
input_validators::is_derivation, input_validators::is_derivation,
keypair::{ keypair::{
keypair_from_seed_phrase, prompt_passphrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG, keypair_from_seed_phrase, parse_keypair_path, prompt_passphrase, KeypairUrl,
SKIP_SEED_PHRASE_VALIDATION_ARG,
}, },
}; };
use solana_cli_config::config::{Config, CONFIG_FILE}; use solana_cli_config::config::{Config, CONFIG_FILE};
use solana_remote_wallet::{ use solana_remote_wallet::{
ledger::get_ledger_from_info, ledger::{generate_remote_keypair, get_ledger_from_info},
remote_wallet::{RemoteWallet, RemoteWalletInfo}, remote_wallet::RemoteWalletInfo,
}; };
use solana_sdk::{ use solana_sdk::{
pubkey::{write_pubkey_file, Pubkey}, pubkey::write_pubkey_file,
signature::{ signature::{
keypair_from_seed, read_keypair, read_keypair_file, write_keypair, write_keypair_file, keypair_from_seed, read_keypair, read_keypair_file, write_keypair, write_keypair_file,
Keypair, KeypairUtil, Keypair, KeypairUtil,
@ -56,9 +57,9 @@ fn check_for_overwrite(outfile: &str, matches: &ArgMatches) {
fn get_keypair_from_matches( fn get_keypair_from_matches(
matches: &ArgMatches, matches: &ArgMatches,
config: Config, config: Config,
) -> Result<Keypair, Box<dyn error::Error>> { ) -> Result<Box<dyn KeypairUtil>, Box<dyn error::Error>> {
let mut path = dirs::home_dir().expect("home directory"); let mut path = dirs::home_dir().expect("home directory");
let keypair = if matches.is_present("keypair") { let path = if matches.is_present("keypair") {
matches.value_of("keypair").unwrap() matches.value_of("keypair").unwrap()
} else if config.keypair_path != "" { } else if config.keypair_path != "" {
&config.keypair_path &config.keypair_path
@ -67,50 +68,28 @@ fn get_keypair_from_matches(
path.to_str().unwrap() path.to_str().unwrap()
}; };
if keypair == "-" { match parse_keypair_path(path) {
let mut stdin = std::io::stdin(); KeypairUrl::Ask => {
read_keypair(&mut stdin) let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
} else if keypair == ASK_KEYWORD { Ok(Box::new(keypair_from_seed_phrase(
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); "pubkey recovery",
keypair_from_seed_phrase("pubkey recovery", skip_validation, false) skip_validation,
} else if keypair.starts_with("usb://") { false,
Err(String::from("Remote wallet signing not yet implemented").into()) )?))
} else { }
read_keypair_file(keypair) 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)?))
fn get_pubkey_from_matches( }
matches: &ArgMatches, KeypairUrl::Usb(path) => {
config: Config, let (remote_wallet_info, mut derivation_path) = RemoteWalletInfo::parse_path(path)?;
) -> Result<Pubkey, Box<dyn error::Error>> { if let Some(derivation) = derivation_of(matches, "derivation_path") {
let mut path = dirs::home_dir().expect("home directory"); derivation_path = derivation;
let keypair = if matches.is_present("keypair") { }
matches.value_of("keypair").unwrap() let ledger = get_ledger_from_info(remote_wallet_info)?;
} else if config.keypair_path != "" { Ok(Box::new(generate_remote_keypair(ledger, derivation_path)))
&config.keypair_path
} else {
path.extend(&[".config", "solana", "id.json"]);
path.to_str().unwrap()
};
if keypair == "-" {
let mut stdin = std::io::stdin();
read_keypair(&mut stdin).map(|keypair| keypair.pubkey())
} else if keypair == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase("pubkey recovery", skip_validation, false)
.map(|keypair| keypair.pubkey())
} else if keypair.starts_with("usb://") {
let (remote_wallet_info, mut derivation_path) =
RemoteWalletInfo::parse_path(keypair.to_string())?;
if let Some(derivation) = derivation_of(matches, "derivation_path") {
derivation_path = derivation;
} }
let ledger = get_ledger_from_info(remote_wallet_info)?;
Ok(ledger.get_pubkey(&derivation_path)?)
} else {
read_keypair_file(keypair).map(|keypair| keypair.pubkey())
} }
} }
@ -434,7 +413,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
match matches.subcommand() { match matches.subcommand() {
("pubkey", Some(matches)) => { ("pubkey", Some(matches)) => {
let pubkey = get_pubkey_from_matches(matches, config)?; let pubkey = get_keypair_from_matches(matches, config)?.try_pubkey()?;
if matches.is_present("outfile") { if matches.is_present("outfile") {
let outfile = matches.value_of("outfile").unwrap(); let outfile = matches.value_of("outfile").unwrap();
@ -613,7 +592,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
("verify", Some(matches)) => { ("verify", Some(matches)) => {
let keypair = get_keypair_from_matches(matches, config)?; let keypair = get_keypair_from_matches(matches, config)?;
let test_data = b"test"; let test_data = b"test";
let signature = keypair.sign_message(test_data); let signature = keypair.try_sign_message(test_data)?;
let pubkey_bs58 = matches.value_of("pubkey").unwrap(); let pubkey_bs58 = matches.value_of("pubkey").unwrap();
let pubkey = bs58::decode(pubkey_bs58).into_vec().unwrap(); let pubkey = bs58::decode(pubkey_bs58).into_vec().unwrap();
if signature.verify(&pubkey, test_data) { if signature.verify(&pubkey, test_data) {

View File

@ -1,5 +1,9 @@
use crate::remote_wallet::{ use crate::{
initialize_wallet_manager, DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, remote_keypair::RemoteKeypair,
remote_wallet::{
initialize_wallet_manager, DerivationPath, RemoteWallet, RemoteWalletError,
RemoteWalletInfo, RemoteWalletType,
},
}; };
use dialoguer::{theme::ColorfulTheme, Select}; use dialoguer::{theme::ColorfulTheme, Select};
use log::*; use log::*;
@ -365,3 +369,13 @@ pub fn get_ledger_from_info(
}; };
wallet_manager.get_ledger(&wallet_base_pubkey) wallet_manager.get_ledger(&wallet_base_pubkey)
} }
pub fn generate_remote_keypair(
ledger: Arc<LedgerWallet>,
derivation_path: DerivationPath,
) -> RemoteKeypair {
RemoteKeypair {
wallet_type: RemoteWalletType::Ledger(ledger),
derivation_path,
}
}

View File

@ -1,2 +1,3 @@
pub mod ledger; pub mod ledger;
pub mod remote_keypair;
pub mod remote_wallet; pub mod remote_wallet;

View File

@ -0,0 +1,38 @@
use crate::remote_wallet::{DerivationPath, RemoteWallet, RemoteWalletType};
use solana_sdk::{
pubkey::Pubkey,
signature::{KeypairUtil, Signature},
};
use std::error;
pub struct RemoteKeypair {
pub wallet_type: RemoteWalletType,
pub derivation_path: DerivationPath,
}
impl RemoteKeypair {
pub fn new(wallet_type: RemoteWalletType, derivation_path: DerivationPath) -> Self {
Self {
wallet_type,
derivation_path,
}
}
}
impl KeypairUtil for RemoteKeypair {
fn try_pubkey(&self) -> Result<Pubkey, Box<dyn error::Error>> {
match &self.wallet_type {
RemoteWalletType::Ledger(wallet) => wallet
.get_pubkey(&self.derivation_path)
.map_err(|e| e.into()),
}
}
fn try_sign_message(&self, message: &[u8]) -> Result<Signature, Box<dyn error::Error>> {
match &self.wallet_type {
RemoteWalletType::Ledger(wallet) => wallet
.sign_message(&self.derivation_path, message)
.map_err(|e| e.into()),
}
}
}

View File

@ -192,7 +192,6 @@ pub struct RemoteWalletInfo {
impl RemoteWalletInfo { impl RemoteWalletInfo {
pub fn parse_path(mut path: String) -> Result<(Self, DerivationPath), RemoteWalletError> { pub fn parse_path(mut path: String) -> Result<(Self, DerivationPath), RemoteWalletError> {
let mut path = path.split_off(6);
if path.ends_with('/') { if path.ends_with('/') {
path.pop(); path.pop();
} }
@ -288,7 +287,22 @@ mod tests {
fn test_parse_path() { fn test_parse_path() {
let pubkey = Pubkey::new_rand(); let pubkey = Pubkey::new_rand();
let (wallet_info, derivation_path) = let (wallet_info, derivation_path) =
RemoteWalletInfo::parse_path(format!("usb://ledger/nano-s/{:?}/44/501/1/2", pubkey)) RemoteWalletInfo::parse_path(format!("ledger/nano-s/{:?}/44/501/1/2", pubkey)).unwrap();
assert!(wallet_info.matches(&RemoteWalletInfo {
model: "nano-s".to_string(),
manufacturer: "ledger".to_string(),
serial: "".to_string(),
pubkey,
}));
assert_eq!(
derivation_path,
DerivationPath {
account: 1,
change: Some(2),
}
);
let (wallet_info, derivation_path) =
RemoteWalletInfo::parse_path(format!("ledger/nano-s/{:?}/44'/501'/1'/2'", pubkey))
.unwrap(); .unwrap();
assert!(wallet_info.matches(&RemoteWalletInfo { assert!(wallet_info.matches(&RemoteWalletInfo {
model: "nano-s".to_string(), model: "nano-s".to_string(),
@ -303,35 +317,13 @@ mod tests {
change: Some(2), change: Some(2),
} }
); );
let (wallet_info, derivation_path) = RemoteWalletInfo::parse_path(format!(
"usb://ledger/nano-s/{:?}/44'/501'/1'/2'",
pubkey
))
.unwrap();
assert!(wallet_info.matches(&RemoteWalletInfo {
model: "nano-s".to_string(),
manufacturer: "ledger".to_string(),
serial: "".to_string(),
pubkey,
}));
assert_eq!(
derivation_path,
DerivationPath {
account: 1,
change: Some(2),
}
);
assert!(RemoteWalletInfo::parse_path(format!( assert!(
"usb://ledger/nano-s/{:?}/43/501/1/2", RemoteWalletInfo::parse_path(format!("ledger/nano-s/{:?}/43/501/1/2", pubkey)).is_err()
pubkey );
)) assert!(
.is_err()); RemoteWalletInfo::parse_path(format!("ledger/nano-s/{:?}/44/500/1/2", pubkey)).is_err()
assert!(RemoteWalletInfo::parse_path(format!( );
"usb://ledger/nano-s/{:?}/44/500/1/2",
pubkey
))
.is_err());
} }
#[test] #[test]

View File

@ -131,8 +131,14 @@ impl FromStr for Signature {
} }
pub trait KeypairUtil { pub trait KeypairUtil {
fn pubkey(&self) -> Pubkey; fn pubkey(&self) -> Pubkey {
fn sign_message(&self, message: &[u8]) -> Signature; self.try_pubkey().unwrap_or_default()
}
fn try_pubkey(&self) -> Result<Pubkey, Box<dyn error::Error>>;
fn sign_message(&self, message: &[u8]) -> Signature {
self.try_sign_message(message).unwrap_or_default()
}
fn try_sign_message(&self, message: &[u8]) -> Result<Signature, Box<dyn error::Error>>;
} }
impl KeypairUtil for Keypair { impl KeypairUtil for Keypair {
@ -141,9 +147,17 @@ impl KeypairUtil for Keypair {
Pubkey::new(self.0.public.as_ref()) Pubkey::new(self.0.public.as_ref())
} }
fn try_pubkey(&self) -> Result<Pubkey, Box<dyn error::Error>> {
Ok(self.pubkey())
}
fn sign_message(&self, message: &[u8]) -> Signature { fn sign_message(&self, message: &[u8]) -> Signature {
Signature::new(&self.0.sign(message).to_bytes()) Signature::new(&self.0.sign(message).to_bytes())
} }
fn try_sign_message(&self, message: &[u8]) -> Result<Signature, Box<dyn error::Error>> {
Ok(self.sign_message(message))
}
} }
pub fn read_keypair<R: Read>(reader: &mut R) -> Result<Keypair, Box<dyn error::Error>> { pub fn read_keypair<R: Read>(reader: &mut R) -> Result<Keypair, Box<dyn error::Error>> {