diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index ee802d59d2..4792dd6e23 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -12,6 +12,25 @@ use std::{ 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 pub const ASK_KEYWORD: &str = "ASK"; diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 090a5534ca..976e69fe79 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1990,9 +1990,17 @@ impl KeypairUtil for FaucetKeypair { self.transaction.message().account_keys[0] } + fn try_pubkey(&self) -> Result> { + Ok(self.pubkey()) + } + fn sign_message(&self, _msg: &[u8]) -> Signature { self.transaction.signatures[0] } + + fn try_sign_message(&self, message: &[u8]) -> Result> { + Ok(self.sign_message(message)) + } } pub fn request_and_confirm_airdrop( diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index 4998615729..3d40f9a216 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -9,16 +9,17 @@ use solana_clap_utils::{ input_parsers::derivation_of, input_validators::is_derivation, 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_remote_wallet::{ - ledger::get_ledger_from_info, - remote_wallet::{RemoteWallet, RemoteWalletInfo}, + ledger::{generate_remote_keypair, get_ledger_from_info}, + remote_wallet::RemoteWalletInfo, }; use solana_sdk::{ - pubkey::{write_pubkey_file, Pubkey}, + pubkey::write_pubkey_file, signature::{ keypair_from_seed, read_keypair, read_keypair_file, write_keypair, write_keypair_file, Keypair, KeypairUtil, @@ -56,9 +57,9 @@ fn check_for_overwrite(outfile: &str, matches: &ArgMatches) { fn get_keypair_from_matches( matches: &ArgMatches, config: Config, -) -> Result> { +) -> Result, Box> { 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() } else if config.keypair_path != "" { &config.keypair_path @@ -67,50 +68,28 @@ fn get_keypair_from_matches( path.to_str().unwrap() }; - if keypair == "-" { - let mut stdin = std::io::stdin(); - read_keypair(&mut stdin) - } 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) - } else if keypair.starts_with("usb://") { - Err(String::from("Remote wallet signing not yet implemented").into()) - } else { - read_keypair_file(keypair) - } -} - -fn get_pubkey_from_matches( - matches: &ArgMatches, - config: Config, -) -> Result> { - let mut path = dirs::home_dir().expect("home directory"); - let keypair = if matches.is_present("keypair") { - matches.value_of("keypair").unwrap() - } else if config.keypair_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; + 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( + "pubkey recovery", + 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) => { + let (remote_wallet_info, mut derivation_path) = RemoteWalletInfo::parse_path(path)?; + if let Some(derivation) = derivation_of(matches, "derivation_path") { + derivation_path = derivation; + } + let ledger = get_ledger_from_info(remote_wallet_info)?; + Ok(Box::new(generate_remote_keypair(ledger, derivation_path))) } - 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> { match matches.subcommand() { ("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") { let outfile = matches.value_of("outfile").unwrap(); @@ -613,7 +592,7 @@ fn main() -> Result<(), Box> { ("verify", Some(matches)) => { let keypair = get_keypair_from_matches(matches, config)?; 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::decode(pubkey_bs58).into_vec().unwrap(); if signature.verify(&pubkey, test_data) { diff --git a/remote-wallet/src/ledger.rs b/remote-wallet/src/ledger.rs index 56da1a5f1b..5c064acab1 100644 --- a/remote-wallet/src/ledger.rs +++ b/remote-wallet/src/ledger.rs @@ -1,5 +1,9 @@ -use crate::remote_wallet::{ - initialize_wallet_manager, DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, +use crate::{ + remote_keypair::RemoteKeypair, + remote_wallet::{ + initialize_wallet_manager, DerivationPath, RemoteWallet, RemoteWalletError, + RemoteWalletInfo, RemoteWalletType, + }, }; use dialoguer::{theme::ColorfulTheme, Select}; use log::*; @@ -365,3 +369,13 @@ pub fn get_ledger_from_info( }; wallet_manager.get_ledger(&wallet_base_pubkey) } + +pub fn generate_remote_keypair( + ledger: Arc, + derivation_path: DerivationPath, +) -> RemoteKeypair { + RemoteKeypair { + wallet_type: RemoteWalletType::Ledger(ledger), + derivation_path, + } +} diff --git a/remote-wallet/src/lib.rs b/remote-wallet/src/lib.rs index 313dde888b..2831721d81 100644 --- a/remote-wallet/src/lib.rs +++ b/remote-wallet/src/lib.rs @@ -1,2 +1,3 @@ pub mod ledger; +pub mod remote_keypair; pub mod remote_wallet; diff --git a/remote-wallet/src/remote_keypair.rs b/remote-wallet/src/remote_keypair.rs new file mode 100644 index 0000000000..febe359284 --- /dev/null +++ b/remote-wallet/src/remote_keypair.rs @@ -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> { + 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> { + match &self.wallet_type { + RemoteWalletType::Ledger(wallet) => wallet + .sign_message(&self.derivation_path, message) + .map_err(|e| e.into()), + } + } +} diff --git a/remote-wallet/src/remote_wallet.rs b/remote-wallet/src/remote_wallet.rs index 0689b814d5..72c04574b5 100644 --- a/remote-wallet/src/remote_wallet.rs +++ b/remote-wallet/src/remote_wallet.rs @@ -192,7 +192,6 @@ pub struct RemoteWalletInfo { impl RemoteWalletInfo { pub fn parse_path(mut path: String) -> Result<(Self, DerivationPath), RemoteWalletError> { - let mut path = path.split_off(6); if path.ends_with('/') { path.pop(); } @@ -288,7 +287,22 @@ mod tests { fn test_parse_path() { let pubkey = Pubkey::new_rand(); 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(); assert!(wallet_info.matches(&RemoteWalletInfo { model: "nano-s".to_string(), @@ -303,35 +317,13 @@ mod tests { 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!( - "usb://ledger/nano-s/{:?}/43/501/1/2", - pubkey - )) - .is_err()); - assert!(RemoteWalletInfo::parse_path(format!( - "usb://ledger/nano-s/{:?}/44/500/1/2", - pubkey - )) - .is_err()); + assert!( + RemoteWalletInfo::parse_path(format!("ledger/nano-s/{:?}/43/501/1/2", pubkey)).is_err() + ); + assert!( + RemoteWalletInfo::parse_path(format!("ledger/nano-s/{:?}/44/500/1/2", pubkey)).is_err() + ); } #[test] diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index eb620ed9da..6b58ec3329 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -131,8 +131,14 @@ impl FromStr for Signature { } pub trait KeypairUtil { - fn pubkey(&self) -> Pubkey; - fn sign_message(&self, message: &[u8]) -> Signature; + fn pubkey(&self) -> Pubkey { + self.try_pubkey().unwrap_or_default() + } + fn try_pubkey(&self) -> Result>; + fn sign_message(&self, message: &[u8]) -> Signature { + self.try_sign_message(message).unwrap_or_default() + } + fn try_sign_message(&self, message: &[u8]) -> Result>; } impl KeypairUtil for Keypair { @@ -141,9 +147,17 @@ impl KeypairUtil for Keypair { Pubkey::new(self.0.public.as_ref()) } + fn try_pubkey(&self) -> Result> { + Ok(self.pubkey()) + } + fn sign_message(&self, message: &[u8]) -> Signature { Signature::new(&self.0.sign(message).to_bytes()) } + + fn try_sign_message(&self, message: &[u8]) -> Result> { + Ok(self.sign_message(message)) + } } pub fn read_keypair(reader: &mut R) -> Result> {