Add jade hardware wallet

This commit is contained in:
edouardparis 2024-03-08 18:25:07 +01:00
parent b7a72e064d
commit 6b0c93c5c3
6 changed files with 345 additions and 112 deletions

124
gui/Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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,

View File

@ -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<runtime::TokioRuntime>),
BitBox02(Box<PairingBitbox02<runtime::TokioRuntime>>),
Jade(Jade<jade::SerialTransport>),
}
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<Fingerprint, String>,
wallet: Option<Arc<Wallet>>,
connected_supported_hws: Vec<String>,
@ -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<async_hwi::jade::SerialTransport>,
wallet: Option<&Wallet>,
keys_aliases: Option<&HashMap<Fingerprint, String>>,
) -> Result<HardwareWallet, HWIError> {
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,
}

View File

@ -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()),

View File

@ -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<V>,
) -> 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<V>,