From 6b0c93c5c3fe2a5b6e7a3dfe49755c6edc704510 Mon Sep 17 00:00:00 2001 From: edouardparis Date: Fri, 8 Mar 2024 18:25:07 +0100 Subject: [PATCH] Add jade hardware wallet --- gui/Cargo.lock | 124 ++++++++++--------- gui/Cargo.toml | 2 +- gui/src/app/view/hw.rs | 9 ++ gui/src/hw.rs | 245 ++++++++++++++++++++++++++++++------- gui/src/installer/view.rs | 36 ++++-- gui/ui/src/component/hw.rs | 41 ++++++- 6 files changed, 345 insertions(+), 112 deletions(-) diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 88652c99..cb661d5f 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -2,27 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "CoreFoundation-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" -dependencies = [ - "libc", - "mach 0.1.2", -] - -[[package]] -name = "IOKit-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a" -dependencies = [ - "CoreFoundation-sys", - "libc", - "mach 0.1.2", -] - [[package]] name = "ab_glyph" version = "0.2.20" @@ -214,9 +193,9 @@ dependencies = [ [[package]] name = "async-hwi" -version = "0.0.16" +version = "0.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912663643d018301fb5534949e8430515c7cdc9bf453bfefba2dc70dc854dd31" +checksum = "647a2513ce55652719759c6416e98de94b19a349dc94e1be4924b0487f73ce9b" dependencies = [ "async-trait", "bitbox-api", @@ -227,10 +206,17 @@ dependencies = [ "ledger-apdu", "ledger-transport-hidapi", "ledger_bitcoin_client", + "prost 0.12.2", + "prost-derive 0.12.2", "regex", + "reqwest", + "serde", + "serde_bytes", + "serde_cbor", "serialport", "tokio", "tokio-serial", + "zeroize", ] [[package]] @@ -1353,7 +1339,7 @@ checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4" dependencies = [ "bit_field", "flume", - "half", + "half 2.2.1", "lebe", "miniz_oxide", "rayon-core", @@ -1853,6 +1839,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "half" version = "2.2.1" @@ -2132,7 +2124,7 @@ dependencies = [ "bitflags 2.4.2", "bytemuck", "cosmic-text", - "half", + "half 2.2.1", "iced_core", "iced_futures", "image", @@ -2337,6 +2329,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -2810,19 +2812,10 @@ dependencies = [ ] [[package]] -name = "mach" -version = "0.1.2" +name = "mach2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" -dependencies = [ - "libc", -] - -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] @@ -2957,7 +2950,7 @@ checksum = "20a4c60ca5c9c0e114b3bd66ff4aa5f9b2b175442be51ca6c4365d687a97a2ac" dependencies = [ "log", "mio", - "nix 0.26.2", + "nix", "serialport", "winapi", ] @@ -3033,17 +3026,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.26.2" @@ -4253,6 +4235,25 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.186" @@ -4289,18 +4290,20 @@ dependencies = [ [[package]] name = "serialport" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12" +checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828" dependencies = [ - "CoreFoundation-sys", - "IOKit-sys", - "bitflags 1.3.2", + "bitflags 2.4.2", "cfg-if", + "core-foundation-sys", + "io-kit-sys", "libudev", - "mach 0.3.2", - "nix 0.24.3", + "mach2", + "nix", "regex", + "scopeguard", + "unescaper", "winapi", ] @@ -4942,6 +4945,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unescaper" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adf6ad32eb5b3cadff915f7b770faaac8f7ff0476633aa29eb0d9584d889d34" +dependencies = [ + "thiserror", +] + [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 15d5e102..c118a112 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -15,7 +15,7 @@ path = "src/main.rs" [dependencies] async-trait = "0.1" -async-hwi = "0.0.16" +async-hwi = "0.0.17" liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false, features = ["nonblocking_shutdown"] } liana_ui = { path = "ui" } backtrace = "0.3" diff --git a/gui/src/app/view/hw.rs b/gui/src/app/view/hw.rs index 20ab14d3..94804756 100644 --- a/gui/src/app/view/hw.rs +++ b/gui/src/app/view/hw.rs @@ -53,6 +53,9 @@ pub fn hw_list_view( UnsupportedReason::NotPartOfWallet(fg) => { hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg) } + UnsupportedReason::WrongNetwork => { + hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref()) + } _ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()), }, HardwareWallet::Locked { @@ -111,6 +114,9 @@ pub fn hw_list_view_for_registration( UnsupportedReason::NotPartOfWallet(fg) => { hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg) } + UnsupportedReason::WrongNetwork => { + hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref()) + } _ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()), }, HardwareWallet::Locked { @@ -180,6 +186,9 @@ pub fn hw_list_view_verify_address( UnsupportedReason::NotPartOfWallet(fg) => { hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg) } + UnsupportedReason::WrongNetwork => { + hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref()) + } _ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()), }, false, diff --git a/gui/src/hw.rs b/gui/src/hw.rs index e455e353..78093c60 100644 --- a/gui/src/hw.rs +++ b/gui/src/hw.rs @@ -8,7 +8,9 @@ use std::{ use crate::app::{settings, wallet::Wallet}; use async_hwi::{ bitbox::{api::runtime, BitBox02, PairingBitbox02}, - coldcard, ledger, specter, DeviceKind, Error as HWIError, Version, HWI, + coldcard, + jade::{self, Jade}, + ledger, specter, DeviceKind, Error as HWIError, Version, HWI, }; use liana::miniscript::bitcoin::{bip32::Fingerprint, hashes::hex::FromHex, Network}; use serde::{Deserialize, Serialize}; @@ -21,6 +23,7 @@ pub enum UnsupportedReason { }, Method(&'static str), NotPartOfWallet(Fingerprint), + WrongNetwork, } // Todo drop the Clone, to remove the Mutex on HardwareWallet::Locked @@ -51,7 +54,8 @@ pub enum HardwareWallet { } pub enum LockedDevice { - BitBox02(PairingBitbox02), + BitBox02(Box>), + Jade(Jade), } impl std::fmt::Debug for LockedDevice { @@ -221,25 +225,47 @@ impl HardwareWallets { *alias = self.aliases.get(fingerprint).cloned(); } HardwareWallet::Locked { device, id, .. } => { - if let Some(LockedDevice::BitBox02(bb)) = device.lock().unwrap().take() - { - let id = id.to_string(); - let id_cloned = id.clone(); - let network = self.network; - let wallet = self.wallet.clone(); - cmds.push(Command::perform( - async move { - let paired_bb = bb.wait_confirm().await?; - let mut bitbox2 = - BitBox02::from(paired_bb).with_network(network); - let fingerprint = bitbox2.get_master_fingerprint().await?; - let mut registered = false; - if let Some(wallet) = &wallet { - let desc = wallet.main_descriptor.to_string(); - bitbox2 = bitbox2.with_policy(&desc)?; - registered = - bitbox2.is_policy_registered(&desc).await?; - if wallet.descriptor_keys().contains(&fingerprint) { + match device.lock().unwrap().take() { + None => {} + Some(LockedDevice::BitBox02(bb)) => { + let id = id.to_string(); + let id_cloned = id.clone(); + let network = self.network; + let wallet = self.wallet.clone(); + cmds.push(Command::perform( + async move { + let paired_bb = bb.wait_confirm().await?; + let mut bitbox2 = + BitBox02::from(paired_bb).with_network(network); + let fingerprint = + bitbox2.get_master_fingerprint().await?; + let mut registered = false; + if let Some(wallet) = &wallet { + let desc = wallet.main_descriptor.to_string(); + bitbox2 = bitbox2.with_policy(&desc)?; + registered = + bitbox2.is_policy_registered(&desc).await?; + if wallet.descriptor_keys().contains(&fingerprint) { + Ok(HardwareWallet::Supported { + id: id.clone(), + kind: DeviceKind::BitBox02, + fingerprint, + device: bitbox2.into(), + version: None, + registered: Some(registered), + alias: None, + }) + } else { + Ok(HardwareWallet::Unsupported { + id: id.clone(), + kind: DeviceKind::BitBox02, + version: None, + reason: UnsupportedReason::NotPartOfWallet( + fingerprint, + ), + }) + } + } else { Ok(HardwareWallet::Supported { id: id.clone(), kind: DeviceKind::BitBox02, @@ -249,30 +275,31 @@ impl HardwareWallets { registered: Some(registered), alias: None, }) - } else { - Ok(HardwareWallet::Unsupported { - id: id.clone(), - kind: DeviceKind::BitBox02, - version: None, - reason: UnsupportedReason::NotPartOfWallet( - fingerprint, - ), - }) } - } else { - Ok(HardwareWallet::Supported { - id: id.clone(), - kind: DeviceKind::BitBox02, - fingerprint, - device: bitbox2.into(), - version: None, - registered: Some(registered), - alias: None, - }) - } - }, - |res| HardwareWalletMessage::Unlocked(id_cloned, res), - )); + }, + |res| HardwareWalletMessage::Unlocked(id_cloned, res), + )); + } + Some(LockedDevice::Jade(device)) => { + let id = id.clone(); + let id_cloned = id.clone(); + let network = self.network; + let wallet = self.wallet.clone(); + cmds.push(Command::perform( + async move { + device.auth().await?; + handle_jade_device( + id, + network, + device, + wallet.as_ref().map(|w| w.as_ref()), + None, + ) + .await + }, + |res| HardwareWalletMessage::Unlocked(id_cloned, res), + )); + } } } _ => {} @@ -286,8 +313,8 @@ impl HardwareWallets { } HardwareWalletMessage::Unlocked(id, res) => { match res { - Err(_) => { - warn!("Pairing failed with an external device"); + Err(e) => { + warn!("Pairing failed with an external device {}", e); self.list.retain(|hw| hw.id() != &id); } Ok(hw) => { @@ -317,6 +344,7 @@ impl HardwareWallets { iced::subscription::unfold( format!("refresh-{}", self.network), State { + network: self.network, keys_aliases: self.aliases.clone(), wallet: self.wallet.clone(), connected_supported_hws: Vec::new(), @@ -329,6 +357,7 @@ impl HardwareWallets { } struct State { + network: Network, keys_aliases: HashMap, wallet: Option>, connected_supported_hws: Vec, @@ -406,6 +435,38 @@ async fn refresh(mut state: State) -> (HardwareWalletMessage, State) { } Err(e) => warn!("Error while listing specter wallets: {}", e), } + + match jade::SerialTransport::enumerate_potential_ports() { + Ok(ports) => { + for port in ports { + let id = format!("jade-{}", port); + if state.connected_supported_hws.contains(&id) { + still.push(id); + } else { + let device = + Jade::new(jade::SerialTransport::new(port)).with_network(state.network); + match handle_jade_device( + id, + state.network, + device, + state.wallet.as_ref().map(|w| w.as_ref()), + Some(&state.keys_aliases), + ) + .await + { + Ok(hw) => { + hws.push(hw); + } + Err(e) => { + warn!("{:?}", e); + } + } + } + } + } + Err(e) => warn!("Error while listing jade devices: {}", e), + } + match ledger::LedgerSimulator::try_connect().await { Ok(mut device) => { let id = "ledger-simulator".to_string(); @@ -497,7 +558,9 @@ async fn refresh(mut state: State) -> (HardwareWalletMessage, State) { id, kind: DeviceKind::BitBox02, pairing_code: device.pairing_code().map(|s| s.replace('\n', " ")), - device: Arc::new(Mutex::new(Some(LockedDevice::BitBox02(device)))), + device: Arc::new(Mutex::new(Some(LockedDevice::BitBox02(Box::new( + device, + ))))), }); } } @@ -648,6 +711,94 @@ async fn refresh(mut state: State) -> (HardwareWalletMessage, State) { ) } +async fn handle_jade_device( + id: String, + network: Network, + device: Jade, + wallet: Option<&Wallet>, + keys_aliases: Option<&HashMap>, +) -> Result { + let info = device.get_info().await?; + let version = async_hwi::parse_version(&info.jade_version).ok(); + // Jade may not be setup for the current network + if (network == Network::Bitcoin + && info.jade_networks != jade::api::JadeNetworks::Main + && info.jade_networks != jade::api::JadeNetworks::All) + || (network != Network::Bitcoin && info.jade_networks == jade::api::JadeNetworks::Main) + { + Ok(HardwareWallet::Unsupported { + id, + kind: device.device_kind(), + version, + reason: UnsupportedReason::WrongNetwork, + }) + } else { + match info.jade_state { + jade::api::JadeState::Locked + | jade::api::JadeState::Temp + | jade::api::JadeState::Uninit + | jade::api::JadeState::Unsaved => Ok(HardwareWallet::Locked { + id, + kind: DeviceKind::Jade, + pairing_code: None, + device: Arc::new(Mutex::new(Some(LockedDevice::Jade(device)))), + }), + jade::api::JadeState::Ready => { + let kind = device.device_kind(); + let version = device.get_version().await.ok(); + let fingerprint = match device.get_master_fingerprint().await { + Err(HWIError::NetworkMismatch) => { + return Ok(HardwareWallet::Unsupported { + id: id.clone(), + kind, + version, + reason: UnsupportedReason::WrongNetwork, + }); + } + Err(e) => { + return Err(e); + } + Ok(fingerprint) => fingerprint, + }; + let alias = keys_aliases.and_then(|aliases| aliases.get(&fingerprint).cloned()); + if let Some(wallet) = &wallet { + if wallet.descriptor_keys().contains(&fingerprint) { + let desc = wallet.main_descriptor.to_string(); + let device = device.with_wallet(wallet.name.clone()); + let registered = device.is_wallet_registered(&wallet.name, &desc).await?; + Ok(HardwareWallet::Supported { + id: id.clone(), + kind, + fingerprint, + device: Arc::new(device), + version, + registered: Some(registered), + alias, + }) + } else { + Ok(HardwareWallet::Unsupported { + id: id.clone(), + kind, + version, + reason: UnsupportedReason::NotPartOfWallet(fingerprint), + }) + } + } else { + Ok(HardwareWallet::Supported { + id: id.clone(), + kind, + fingerprint, + device: Arc::new(device), + version, + registered: Some(false), + alias, + }) + } + } + } + } +} + struct AsRefWrap<'a, T> { inner: &'a T, } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 34070f2e..67820c71 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -23,7 +23,7 @@ use liana_ui::{ use crate::{ bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError}, - hw::{is_compatible_with_tapminiscript, HardwareWallet}, + hw::{is_compatible_with_tapminiscript, HardwareWallet, UnsupportedReason}, installer::{ message::{self, Message}, prompt, @@ -614,9 +614,20 @@ pub fn hardware_wallet_xpubs<'a>( hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } } - HardwareWallet::Unsupported { version, kind, .. } => { - hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()) - } + HardwareWallet::Unsupported { + version, + kind, + reason, + .. + } => match reason { + UnsupportedReason::NotPartOfWallet(fg) => { + hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg) + } + UnsupportedReason::WrongNetwork => { + hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref()) + } + _ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()), + }, HardwareWallet::Locked { kind, pairing_code, .. } => hw::locked_hardware_wallet(kind, pairing_code.as_ref()), @@ -1745,9 +1756,20 @@ pub fn hw_list_view( hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } } - HardwareWallet::Unsupported { version, kind, .. } => { - hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()) - } + HardwareWallet::Unsupported { + version, + kind, + reason, + .. + } => match reason { + UnsupportedReason::NotPartOfWallet(fg) => { + hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg) + } + UnsupportedReason::WrongNetwork => { + hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref()) + } + _ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()), + }, HardwareWallet::Locked { kind, pairing_code, .. } => hw::locked_hardware_wallet(kind, pairing_code.as_ref()), diff --git a/gui/ui/src/component/hw.rs b/gui/ui/src/component/hw.rs index d2b9978e..99c7df35 100644 --- a/gui/ui/src/component/hw.rs +++ b/gui/ui/src/component/hw.rs @@ -14,7 +14,14 @@ pub fn locked_hardware_wallet<'a, T: 'a, K: Display>( column(vec![ Row::new() .spacing(5) - .push(text::p1_bold("Locked, check code:")) + .push(text::p1_bold(format!( + "Locked{}", + if pairing_code.is_some() { + ", check code:" + } else { + "" + } + ))) .push_maybe(pairing_code.map(|a| text::p1_bold(a))) .into(), Row::new() @@ -277,6 +284,38 @@ pub fn registration_success_hardware_wallet<'a, T: 'a, K: Display, V: Display, F .padding(10) } +pub fn wrong_network_hardware_wallet<'a, T: 'a, K: Display, V: Display>( + kind: K, + version: Option, +) -> Container<'a, T> { + container( + row(vec![ + column(vec![ + Row::new() + .spacing(5) + .push(text::p1_bold("Wrong network in the device settings")) + .into(), + Row::new() + .spacing(5) + .push(text::caption(kind.to_string())) + .push_maybe(version.map(|v| text::caption(v.to_string()))) + .into(), + ]) + .width(Length::Fill) + .into(), + tooltip::Tooltip::new( + icon::warning_icon(), + "The wrong bitcoin application is open or the device was initialized with the wrong network", + tooltip::Position::Bottom, + ) + .style(theme::Container::Card(theme::Card::Simple)) + .into(), + ]) + .align_items(Alignment::Center), + ) + .padding(10) +} + pub fn unsupported_hardware_wallet<'a, T: 'a, K: Display, V: Display>( kind: K, version: Option,