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:
parent
ab475e4849
commit
2374cf09e2
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
|
||||||
} else if keypair == ASK_KEYWORD {
|
|
||||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||||
keypair_from_seed_phrase("pubkey recovery", skip_validation, false)
|
Ok(Box::new(keypair_from_seed_phrase(
|
||||||
} else if keypair.starts_with("usb://") {
|
"pubkey recovery",
|
||||||
Err(String::from("Remote wallet signing not yet implemented").into())
|
skip_validation,
|
||||||
} else {
|
false,
|
||||||
read_keypair_file(keypair)
|
)?))
|
||||||
}
|
}
|
||||||
}
|
KeypairUrl::Filepath(path) => Ok(Box::new(read_keypair_file(&path)?)),
|
||||||
|
KeypairUrl::Stdin => {
|
||||||
fn get_pubkey_from_matches(
|
|
||||||
matches: &ArgMatches,
|
|
||||||
config: Config,
|
|
||||||
) -> Result<Pubkey, Box<dyn error::Error>> {
|
|
||||||
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();
|
let mut stdin = std::io::stdin();
|
||||||
read_keypair(&mut stdin).map(|keypair| keypair.pubkey())
|
Ok(Box::new(read_keypair(&mut stdin)?))
|
||||||
} else if keypair == ASK_KEYWORD {
|
}
|
||||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
KeypairUrl::Usb(path) => {
|
||||||
keypair_from_seed_phrase("pubkey recovery", skip_validation, false)
|
let (remote_wallet_info, mut derivation_path) = RemoteWalletInfo::parse_path(path)?;
|
||||||
.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") {
|
if let Some(derivation) = derivation_of(matches, "derivation_path") {
|
||||||
derivation_path = derivation;
|
derivation_path = derivation;
|
||||||
}
|
}
|
||||||
let ledger = get_ledger_from_info(remote_wallet_info)?;
|
let ledger = get_ledger_from_info(remote_wallet_info)?;
|
||||||
Ok(ledger.get_pubkey(&derivation_path)?)
|
Ok(Box::new(generate_remote_keypair(ledger, 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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod ledger;
|
pub mod ledger;
|
||||||
|
pub mod remote_keypair;
|
||||||
pub mod remote_wallet;
|
pub mod remote_wallet;
|
||||||
|
|
|
@ -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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,8 +287,7 @@ 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();
|
||||||
.unwrap();
|
|
||||||
assert!(wallet_info.matches(&RemoteWalletInfo {
|
assert!(wallet_info.matches(&RemoteWalletInfo {
|
||||||
model: "nano-s".to_string(),
|
model: "nano-s".to_string(),
|
||||||
manufacturer: "ledger".to_string(),
|
manufacturer: "ledger".to_string(),
|
||||||
|
@ -303,10 +301,8 @@ mod tests {
|
||||||
change: Some(2),
|
change: Some(2),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let (wallet_info, derivation_path) = RemoteWalletInfo::parse_path(format!(
|
let (wallet_info, derivation_path) =
|
||||||
"usb://ledger/nano-s/{:?}/44'/501'/1'/2'",
|
RemoteWalletInfo::parse_path(format!("ledger/nano-s/{:?}/44'/501'/1'/2'", pubkey))
|
||||||
pubkey
|
|
||||||
))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(wallet_info.matches(&RemoteWalletInfo {
|
assert!(wallet_info.matches(&RemoteWalletInfo {
|
||||||
model: "nano-s".to_string(),
|
model: "nano-s".to_string(),
|
||||||
|
@ -322,16 +318,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
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]
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
Loading…
Reference in New Issue