diff --git a/Cargo.lock b/Cargo.lock index e19b4b28036754..090f14e86ac8b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3868,7 +3868,6 @@ dependencies = [ "ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "hidapi 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5679,6 +5678,11 @@ dependencies = [ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" @@ -6469,6 +6473,7 @@ dependencies = [ "checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/cli/src/main.rs b/cli/src/main.rs index 31e009d95e88d7..fd5e99e309df45 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -26,7 +26,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result ("RPC Url", config.url, CliConfig::default_json_rpc_url()), + "url" => ("RPC URL", config.url, CliConfig::default_json_rpc_url()), "keypair" => ( "Key Path", config.keypair_path, @@ -38,12 +38,12 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result, Vec) = devices .iter() - .filter(|&device_info| device_info == &info) + .filter(|&device_info| device_info.matches(&info)) .map(|device_info| (device_info.pubkey, device_info.get_pretty_path())) .unzip(); if pubkeys.is_empty() { diff --git a/remote-wallet/src/remote_wallet.rs b/remote-wallet/src/remote_wallet.rs index 5da4ca12787c26..7e55f50d65a98c 100644 --- a/remote-wallet/src/remote_wallet.rs +++ b/remote-wallet/src/remote_wallet.rs @@ -25,6 +25,9 @@ pub enum RemoteWalletError { #[error("device with non-supported product ID or vendor ID was detected")] InvalidDevice, + #[error("invalid derivation path: {0}")] + InvalidDerivationPath(String), + #[error("invalid path: {0}")] InvalidPath(String), @@ -186,32 +189,48 @@ pub struct RemoteWalletInfo { impl RemoteWalletInfo { pub fn parse_path(mut path: String) -> Result<(Self, DerivationPath), RemoteWalletError> { - if is_remote_wallet_path(&path).is_ok() { - let path = path.split_off(6); - let mut parts = path.split('/'); - let mut wallet_info = RemoteWalletInfo::default(); - let manufacturer = parts.next().unwrap(); - wallet_info.manufacturer = manufacturer.to_string(); - wallet_info.model = parts.next().unwrap_or("").to_string(); - wallet_info.pubkey = parts - .next() - .and_then(|pubkey_str| Pubkey::from_str(pubkey_str).ok()) - .unwrap_or_default(); - let derivation_path = parts - .next() - .map(|account| { - let account = account.parse::().unwrap(); - let change = parts.next().and_then(|change| change.parse::().ok()); - DerivationPath { account, change } - }) - .unwrap_or(DerivationPath { - account: 0, - change: None, - }); - Ok((wallet_info, derivation_path)) - } else { - Err(RemoteWalletError::InvalidPath(path)) + let mut path = path.split_off(6); + if path.ends_with('/') { + path.pop(); + } + let mut parts = path.split('/'); + let mut wallet_info = RemoteWalletInfo::default(); + let manufacturer = parts.next().unwrap(); + wallet_info.manufacturer = manufacturer.to_string(); + wallet_info.model = parts.next().unwrap_or("").to_string(); + wallet_info.pubkey = parts + .next() + .and_then(|pubkey_str| Pubkey::from_str(pubkey_str).ok()) + .unwrap_or_default(); + + let mut derivation_path = DerivationPath::default(); + if let Some(purpose) = parts.next() { + if purpose.replace("'", "") != "44" { + return Err(RemoteWalletError::InvalidDerivationPath(format!( + "Incorrect purpose number, found: {}, must be 44", + purpose + ))); + } + if let Some(coin) = parts.next() { + if coin.replace("'", "") != "501" { + return Err(RemoteWalletError::InvalidDerivationPath(format!( + "Incorrect coin number, found: {}, must be 501", + coin + ))); + } + if let Some(account) = parts.next() { + derivation_path.account = account.replace("'", "").parse::().unwrap(); + derivation_path.change = parts + .next() + .and_then(|change| change.replace("'", "").parse::().ok()); + } + } else { + return Err(RemoteWalletError::InvalidDerivationPath( + "Derivation path too short, missing coin number".to_string(), + )); + } } + Ok((wallet_info, derivation_path)) } pub fn get_pretty_path(&self) -> String { @@ -220,10 +239,8 @@ impl RemoteWalletInfo { self.manufacturer, self.model, self.pubkey, ) } -} -impl PartialEq for RemoteWalletInfo { - fn eq(&self, other: &Self) -> bool { + pub(crate) fn matches(&self, other: &Self) -> bool { self.manufacturer == other.manufacturer && (self.model == other.model || self.model == "" || other.model == "") && (self.pubkey == other.pubkey @@ -232,52 +249,6 @@ impl PartialEq for RemoteWalletInfo { } } -pub fn is_remote_wallet_path(value: &str) -> Result<(), String> { - if value.starts_with("usb://") { - let (_, path) = value.split_at(6); - let mut parts = path.split('/'); - let manufacturer = parts.next().unwrap(); - if manufacturer != "" { - if let Some(_model) = parts.next() { - if let Some(pubkey_str) = parts.next() { - let _pubkey = Pubkey::from_str(pubkey_str).map_err(|e| { - format!( - "Unable to parse pubkey in remote wallet path, provided: {}, err: {:?}", - pubkey_str, e - ) - })?; - if let Some(account) = parts.next() { - let _account = account - .parse::() - .map_err(|e| { - format!( - "Unable to parse account in remote wallet path, provided: {}, err: {:?}", - account, e - ) - })?; - - if let Some(change) = parts.next() { - let _change = change - .parse::() - .map_err(|e| { - format!( - "Unable to parse change in remote wallet path, provided: {}, err: {:?}", - account, e - ) - })?; - } - } - } - } - return Ok(()); - } - } - Err(format!( - "Unable to parse input as remote wallet path, provided: {}", - value - )) -} - #[derive(Default, PartialEq, Clone)] pub struct DerivationPath { pub account: u16, @@ -313,25 +284,55 @@ mod tests { #[test] 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)) + .unwrap(); + assert!(wallet_info.matches(&RemoteWalletInfo { + model: "nano-s".to_string(), + manufacturer: "ledger".to_string(), + serial: "".to_string(), + pubkey, + })); assert_eq!( - RemoteWalletInfo::parse_path(format!("usb://ledger/nano-s/{:?}/1/2", pubkey)).unwrap(), - ( - RemoteWalletInfo { - model: "nano-s".to_string(), - manufacturer: "ledger".to_string(), - serial: "".to_string(), - pubkey, - }, - DerivationPath { - account: 1, - change: Some(2), - } - ) - ) + derivation_path, + DerivationPath { + account: 1, + 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()); } #[test] - fn test_remote_wallet_info_partial_eq() { + fn test_remote_wallet_info_matches() { let pubkey = Pubkey::new_rand(); let info = RemoteWalletInfo { manufacturer: "Ledger".to_string(), @@ -341,45 +342,17 @@ mod tests { }; let mut test_info = RemoteWalletInfo::default(); test_info.manufacturer = "Not Ledger".to_string(); - assert_ne!(info, test_info); + assert!(!info.matches(&test_info)); test_info.manufacturer = "Ledger".to_string(); - assert_eq!(info, test_info); + assert!(info.matches(&test_info)); test_info.model = "Other".to_string(); - assert_ne!(info, test_info); + assert!(!info.matches(&test_info)); test_info.model = "Nano S".to_string(); - assert_eq!(info, test_info); + assert!(info.matches(&test_info)); let another_pubkey = Pubkey::new_rand(); test_info.pubkey = another_pubkey; - assert_ne!(info, test_info); + assert!(!info.matches(&test_info)); test_info.pubkey = pubkey; - assert_eq!(info, test_info); - } - - #[test] - fn test_is_remote_wallet_path() { - assert!(is_remote_wallet_path("usb://").is_err()); - assert_eq!(is_remote_wallet_path("usb://ledger"), Ok(())); - assert_eq!(is_remote_wallet_path("usb://ledger/nano-s"), Ok(())); - assert!(is_remote_wallet_path("usb://ledger/nano-s/notpubkey").is_err()); - - let pubkey = Pubkey::new_rand(); - assert_eq!( - is_remote_wallet_path(&format!("usb://ledger/nano-s/{:?}", pubkey)), - Ok(()) - ); - assert_eq!( - is_remote_wallet_path(&format!("usb://ledger/nano-s/{:?}/1", pubkey)), - Ok(()) - ); - assert!(is_remote_wallet_path(&format!("usb://ledger/nano-s/{:?}/a", pubkey)).is_err()); - assert!(is_remote_wallet_path(&format!("usb://ledger/nano-s/{:?}/65537", pubkey)).is_err()); - assert_eq!( - is_remote_wallet_path(&format!("usb://ledger/nano-s/{:?}/1/1", pubkey)), - Ok(()) - ); - assert!(is_remote_wallet_path(&format!("usb://ledger/nano-s/{:?}/1/b", pubkey)).is_err()); - assert!( - is_remote_wallet_path(&format!("usb://ledger/nano-s/{:?}/1/65537", pubkey)).is_err() - ); + assert!(info.matches(&test_info)); } }