From fa8fd4782f97b8969f6a655e0511bf8f96924232 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Tue, 25 Mar 2025 06:33:19 +0100 Subject: [PATCH 1/4] liana: implement LianaDescriptor::contains_fingerprint() --- liana/src/descriptors/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/liana/src/descriptors/mod.rs b/liana/src/descriptors/mod.rs index 070d954b..cc66bacf 100644 --- a/liana/src/descriptors/mod.rs +++ b/liana/src/descriptors/mod.rs @@ -1,6 +1,7 @@ use miniscript::{ bitcoin::{ - self, bip32, + self, + bip32::{self, Fingerprint}, constants::WITNESS_SCALE_FACTOR, psbt::{Input as PsbtIn, Output as PsbtOut, Psbt}, secp256k1, @@ -210,6 +211,12 @@ impl LianaDescriptor { }) } + /// Whether a key matching this fingerprint is part of this descriptor + pub fn contains_fingerprint(&self, fg: Fingerprint) -> bool { + self.multi_desc + .for_any_key(|k| k.master_fingerprint() == fg) + } + /// Get the descriptor for receiving addresses. pub fn receive_descriptor(&self) -> &SinglePathLianaDesc { &self.receive_desc From 5ae13dd025c2e038e17746f5bfc8cf72ec5120c7 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Tue, 25 Mar 2025 06:35:11 +0100 Subject: [PATCH 2/4] liana-ui: make it more clear when a device key is not part of the wallet descriptor --- liana-ui/src/component/hw.rs | 39 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/liana-ui/src/component/hw.rs b/liana-ui/src/component/hw.rs index 31f560be..6d99db46 100644 --- a/liana-ui/src/component/hw.rs +++ b/liana-ui/src/component/hw.rs @@ -1,6 +1,7 @@ use crate::{color, component::text, icon, image, theme, widget::*}; use iced::{ - widget::{column, container, row, tooltip}, + alignment::Vertical, + widget::{column, container, row, tooltip, Space}, Alignment, Length, }; use std::borrow::Cow; @@ -129,24 +130,28 @@ pub fn unrelated_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( version: Option, fingerprint: F, ) -> Container<'a, T> { + let key = column(vec![ + text::p1_regular(format!("#{}", fingerprint)).into(), + Row::new() + .spacing(5) + .push(text::caption(kind.to_string())) + .push_maybe(version.map(|v| text::caption(v.to_string()))) + .into(), + ]); container( - tooltip::Tooltip::new( - container( - column(vec![ - text::p1_regular(format!("#{}", fingerprint)).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), - ) - .width(Length::Fill) - .padding(10), - "This signer does not have a key in this wallet.", - tooltip::Position::Bottom, + container( + Row::new() + .push(key) + .push(Space::with_width(15)) + .push(Space::with_width(Length::Fill)) + .push(text::text( + "This signing device is not related to this Liana wallet.", + )) + .push(Space::with_width(Length::Fill)) + .align_y(Vertical::Center), ) + .width(Length::Fill) + .padding(10) .style(theme::card::simple), ) .width(Length::Fill) From aead34cc8eaa3e74d2c76c7e1e8341d756d779a2 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Tue, 25 Mar 2025 06:36:14 +0100 Subject: [PATCH 3/4] installer: hint the user if the plugged signing device is not part of the descriptor --- .../installer/step/descriptor/editor/key.rs | 1 + .../src/installer/step/descriptor/mod.rs | 3 +- liana-gui/src/installer/view/mod.rs | 28 +++++++++++++------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/liana-gui/src/installer/step/descriptor/editor/key.rs b/liana-gui/src/installer/step/descriptor/editor/key.rs index 599d8a77..ceb6a09e 100644 --- a/liana-gui/src/installer/step/descriptor/editor/key.rs +++ b/liana-gui/src/installer/step/descriptor/editor/key.rs @@ -459,6 +459,7 @@ impl super::DescriptorEditModal for EditXpubModal { hw.fingerprint() == chosen_signer, self.processing, hw.fingerprint() == chosen_signer, + None, self.device_must_support_tapminiscript, )) } diff --git a/liana-gui/src/installer/step/descriptor/mod.rs b/liana-gui/src/installer/step/descriptor/mod.rs index 866fb447..7933fb77 100644 --- a/liana-gui/src/installer/step/descriptor/mod.rs +++ b/liana-gui/src/installer/step/descriptor/mod.rs @@ -306,10 +306,11 @@ impl Step for RegisterDescriptor { email: Option<&'a str>, ) -> Element<'a, Message> { let desc = self.descriptor.as_ref().unwrap(); + view::register_descriptor( progress, email, - desc.to_string(), + desc, &hws.list, &self.registered, self.error.as_ref(), diff --git a/liana-gui/src/installer/view/mod.rs b/liana-gui/src/installer/view/mod.rs index 7aea026e..0f23b2ab 100644 --- a/liana-gui/src/installer/view/mod.rs +++ b/liana-gui/src/installer/view/mod.rs @@ -584,7 +584,7 @@ pub fn share_xpubs<'a>( pub fn register_descriptor<'a>( progress: (usize, usize), email: Option<&'a str>, - descriptor: String, + descriptor: &'a LianaDescriptor, hws: &'a [HardwareWallet], registered: &HashSet, error: Option<&Error>, @@ -593,8 +593,9 @@ pub fn register_descriptor<'a>( done: bool, created_desc: bool, ) -> Element<'a, Message> { + let descriptor_str = descriptor.to_string(); let displayed_descriptor = - if let Ok((template, keys)) = extract_keys_and_template::(&descriptor) { + if let Ok((template, keys)) = extract_keys_and_template::(&descriptor_str) { let mut col = Column::new() .push( card::simple( @@ -651,7 +652,7 @@ pub fn register_descriptor<'a>( .push( scrollable( Column::new() - .push(text(descriptor.to_owned()).small()) + .push(text(descriptor_str.to_owned()).small()) .push(Space::with_height(Length::Fixed(5.0))), ) .direction(scrollable::Direction::Horizontal( @@ -661,7 +662,7 @@ pub fn register_descriptor<'a>( .push( Row::new().push(Column::new().width(Length::Fill)).push( button::secondary(Some(icon::clipboard_icon()), "Copy") - .on_press(Message::Clibpboard(descriptor)), + .on_press(Message::Clibpboard(descriptor_str)), ), ) .spacing(10), @@ -705,6 +706,7 @@ pub fn register_descriptor<'a>( hw.fingerprint() .map(|fg| registered.contains(&fg)) .unwrap_or(false), + Some(descriptor), false, )) }), @@ -1642,14 +1644,16 @@ pub fn defined_sequence<'a>( .into() } -pub fn hw_list_view( +pub fn hw_list_view<'a>( i: usize, - hw: &HardwareWallet, + hw: &'a HardwareWallet, chosen: bool, processing: bool, selected: bool, + descriptor: Option<&'a LianaDescriptor>, device_must_support_taproot: bool, -) -> Element { +) -> Element<'a, Message> { + let mut unrelated = false; let mut bttn = Button::new(match hw { HardwareWallet::Supported { kind, @@ -1658,9 +1662,15 @@ pub fn hw_list_view( alias, .. } => { + let device_in_descriptor = descriptor + .map(|d| d.contains_fingerprint(*fingerprint)) + .unwrap_or(true); let not_tapminiscript = device_must_support_taproot && !is_compatible_with_tapminiscript(kind, version.as_ref()); - if chosen && processing { + if !device_in_descriptor { + unrelated = true; + hw::unrelated_hardware_wallet(kind.to_string(), version.as_ref(), fingerprint) + } else if chosen && processing { hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } else if selected { hw::selected_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref(), { @@ -1709,7 +1719,7 @@ pub fn hw_list_view( }) .style(theme::button::secondary) .width(Length::Fill); - if !processing && hw.is_supported() { + if !processing && hw.is_supported() && !unrelated { bttn = bttn.on_press(Message::Select(i)); } bttn.into() From c8e455c8ceb2a7b5408380dbe3cd30ce3959ff57 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Thu, 17 Apr 2025 19:28:21 +0200 Subject: [PATCH 4/4] lianad: add a test for LianaDescriptor::contains_fingerprint() --- liana/src/descriptors/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/liana/src/descriptors/mod.rs b/liana/src/descriptors/mod.rs index cc66bacf..f89577f7 100644 --- a/liana/src/descriptors/mod.rs +++ b/liana/src/descriptors/mod.rs @@ -2209,5 +2209,15 @@ mod tests { LianaDescriptor::from_str("wsh(0)").unwrap_err(); } + #[test] + fn descriptor_contains_fingerprint() { + let descr = LianaDescriptor::from_str("wsh(or_d(multi(3,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/0/<0;1>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/0/<0;1>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/0/<0;1>/*),and_v(v:thresh(2,pkh([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/1/<0;1>/*),a:pkh([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/1/<0;1>/*),a:pkh([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/1/<0;1>/*)),older(26352))))").unwrap(); + + assert!(descr.contains_fingerprint(Fingerprint::from_str("aabb0011").unwrap())); + assert!(descr.contains_fingerprint(Fingerprint::from_str("aabb0012").unwrap())); + assert!(descr.contains_fingerprint(Fingerprint::from_str("aabb0013").unwrap())); + assert!(!descr.contains_fingerprint(Fingerprint::from_str("aabb0014").unwrap())); + } + // TODO: test error conditions of deserialization. }