Skip to content

Commit

Permalink
Ledger hardware wallet integration (#8068)
Browse files Browse the repository at this point in the history
* Initial remote wallet module

* Add clap derivation tooling

* Add remote-wallet path apis

* Implement remote-wallet in solana-keygen

* Implement remote-wallet in cli for read-only pubkey usage

* Linux: Use udev backend; add udev rules tool

* Ignore Ledger live test

* Cli api adjustments

(cherry picked from commit ed0c1d3)
  • Loading branch information
CriesofCarrots authored and Tyera Eulberg committed Feb 7, 2020
1 parent 9c5ef19 commit b596add
Show file tree
Hide file tree
Showing 16 changed files with 1,158 additions and 32 deletions.
52 changes: 51 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ members = [
"archiver",
"archiver-lib",
"archiver-utils",
"remote-wallet",
"runtime",
"sdk",
"sdk-c",
Expand Down
1 change: 1 addition & 0 deletions clap-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ edition = "2018"
clap = "2.33.0"
rpassword = "4.0"
semver = "0.9.0"
solana-remote-wallet = { path = "../remote-wallet", version = "0.23.3" }
solana-sdk = { path = "../sdk", version = "0.23.3" }
tiny-bip39 = "0.7.0"
url = "2.1.0"
Expand Down
47 changes: 47 additions & 0 deletions clap-utils/src/input_parsers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG};
use chrono::DateTime;
use clap::ArgMatches;
use solana_remote_wallet::remote_wallet::DerivationPath;
use solana_sdk::{
clock::UnixTimestamp,
native_token::sol_to_lamports,
Expand Down Expand Up @@ -100,6 +101,16 @@ pub fn amount_of(matches: &ArgMatches<'_>, name: &str, unit: &str) -> Option<u64
}
}

pub fn derivation_of(matches: &ArgMatches<'_>, name: &str) -> Option<DerivationPath> {
matches.value_of(name).map(|derivation_str| {
let derivation_str = derivation_str.replace("'", "");
let mut parts = derivation_str.split('/');
let account = parts.next().unwrap().parse::<u16>().unwrap();
let change = parts.next().map(|change| change.parse::<u16>().unwrap());
DerivationPath { account, change }
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -277,4 +288,40 @@ mod tests {
.get_matches_from(vec!["test", "--single", "1.5", "--unit", "lamports"]);
assert_eq!(amount_of(&matches, "single", "unit"), None);
}

#[test]
fn test_derivation_of() {
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "2/3"]);
assert_eq!(
derivation_of(&matches, "single"),
Some(DerivationPath {
account: 2,
change: Some(3)
})
);
assert_eq!(derivation_of(&matches, "another"), None);
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "2"]);
assert_eq!(
derivation_of(&matches, "single"),
Some(DerivationPath {
account: 2,
change: None
})
);
assert_eq!(derivation_of(&matches, "another"), None);
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "2'/3'"]);
assert_eq!(
derivation_of(&matches, "single"),
Some(DerivationPath {
account: 2,
change: Some(3)
})
);
}
}
52 changes: 49 additions & 3 deletions clap-utils/src/input_validators.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::keypair::ASK_KEYWORD;
use chrono::DateTime;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{read_keypair_file, Signature};
use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::{read_keypair_file, Signature},
};
use std::str::FromStr;

// Return an error if a pubkey cannot be parsed.
Expand Down Expand Up @@ -141,3 +143,47 @@ pub fn is_rfc3339_datetime(value: String) -> Result<(), String> {
.map(|_| ())
.map_err(|e| format!("{:?}", e))
}

pub fn is_derivation(value: String) -> Result<(), String> {
let value = value.replace("'", "");
let mut parts = value.split('/');
let account = parts.next().unwrap();
account
.parse::<u16>()
.map_err(|e| {
format!(
"Unable to parse derivation, provided: {}, err: {:?}",
account, e
)
})
.and_then(|_| {
if let Some(change) = parts.next() {
change.parse::<u16>().map_err(|e| {
format!(
"Unable to parse derivation, provided: {}, err: {:?}",
change, e
)
})
} else {
Ok(0)
}
})
.map(|_| ())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_is_derivation() {
assert_eq!(is_derivation("2".to_string()), Ok(()));
assert_eq!(is_derivation("0".to_string()), Ok(()));
assert_eq!(is_derivation("0/2".to_string()), Ok(()));
assert_eq!(is_derivation("0'/2'".to_string()), Ok(()));
assert!(is_derivation("a".to_string()).is_err());
assert!(is_derivation("65537".to_string()).is_err());
assert!(is_derivation("a/b".to_string()).is_err());
assert!(is_derivation("0/65537".to_string()).is_err());
}
}
9 changes: 7 additions & 2 deletions clap-utils/src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {

#[derive(Debug, PartialEq)]
pub enum Source {
File,
Generated,
Path,
SeedPhrase,
}

Expand Down Expand Up @@ -131,7 +131,12 @@ pub fn keypair_input(
keypair_from_seed_phrase(keypair_name, skip_validation, true)
.map(|keypair| KeypairWithSource::new(keypair, Source::SeedPhrase))
} else if let Some(keypair_file) = matches.value_of(keypair_match_name) {
read_keypair_file(keypair_file).map(|keypair| KeypairWithSource::new(keypair, Source::File))
if keypair_file.starts_with("usb://") {
Ok(KeypairWithSource::new(Keypair::new(), Source::Path))
} else {
read_keypair_file(keypair_file)
.map(|keypair| KeypairWithSource::new(keypair, Source::Path))
}
} else {
Ok(KeypairWithSource::new(Keypair::new(), Source::Generated))
}
Expand Down
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ solana-config-program = { path = "../programs/config", version = "0.23.3" }
solana-faucet = { path = "../faucet", version = "0.23.3" }
solana-logger = { path = "../logger", version = "0.23.3" }
solana-net-utils = { path = "../net-utils", version = "0.23.3" }
solana-remote-wallet = { path = "../remote-wallet", version = "0.23.3" }
solana-runtime = { path = "../runtime", version = "0.23.3" }
solana-sdk = { path = "../sdk", version = "0.23.3" }
solana-stake-program = { path = "../programs/stake", version = "0.23.3" }
Expand Down
Loading

0 comments on commit b596add

Please sign in to comment.