Merge #1607: Hint user if the key of signing device is not part of the wallet descriptor

c8e455c8ceb2a7b5408380dbe3cd30ce3959ff57 lianad: add a test for LianaDescriptor::contains_fingerprint() (pythcoiner)
aead34cc8eaa3e74d2c76c7e1e8341d756d779a2 installer: hint the user if the plugged signing device is not part of the descriptor (pythcoiner)
5ae13dd025c2e038e17746f5bfc8cf72ec5120c7 liana-ui: make it more clear when a device key is not part of the wallet descriptor (pythcoiner)
fa8fd4782f97b8969f6a655e0511bf8f96924232 liana: implement LianaDescriptor::contains_fingerprint() (pythcoiner)

Pull request description:

  This PR:
  - [x] Add `LianaDescriptor::contains_fingerprint()` method

  - [x] make more explicit when a signing device is not part of the wallet descriptor

  before (user had to hover the button to see the tooltip):
  ![image](https://github.com/user-attachments/assets/c61a95a0-a9b2-4f85-8245-e5490c7e37af)

  after:
  ![image](https://github.com/user-attachments/assets/1d17775e-6c26-4eeb-83a7-c8621d44c54c)

  - [x] Hint the user in the installer if the plugged device is not part of the created/imported descriptor:

  ![image](https://github.com/user-attachments/assets/e22f0180-513e-48d5-b293-00677e328285)

ACKs for top commit:
  jp1ac4:
    tACK c8e455c8ceb2a7b5408380dbe3cd30ce3959ff57.

Tree-SHA512: 092b8cb6efe6b6a6dc4f8505649783cb7c83c231242ab14f479b573e12cd61bab4e6e27a173bee19097d3ad90a8f1e578ecf4b699a4a481b40588f5f60cb9c71
This commit is contained in:
edouardparis 2025-05-06 14:22:18 +02:00
commit 9fc7c093ff
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
5 changed files with 62 additions and 28 deletions

View File

@ -501,6 +501,7 @@ impl super::DescriptorEditModal for EditXpubModal {
hw.fingerprint() == chosen_signer,
self.processing,
hw.fingerprint() == chosen_signer,
None,
self.device_must_support_tapminiscript,
))
}

View File

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

View File

@ -589,7 +589,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<bitcoin::bip32::Fingerprint>,
error: Option<&Error>,
@ -598,8 +598,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::<String>(&descriptor) {
if let Ok((template, keys)) = extract_keys_and_template::<String>(&descriptor_str) {
let mut col = Column::new()
.push(
card::simple(
@ -656,7 +657,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(
@ -666,7 +667,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),
@ -710,6 +711,7 @@ pub fn register_descriptor<'a>(
hw.fingerprint()
.map(|fg| registered.contains(&fg))
.unwrap_or(false),
Some(descriptor),
false,
))
}),
@ -1648,14 +1650,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<Message> {
) -> Element<'a, Message> {
let mut unrelated = false;
let mut bttn = Button::new(match hw {
HardwareWallet::Supported {
kind,
@ -1664,9 +1668,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(), {
@ -1715,7 +1725,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()

View File

@ -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<V>,
fingerprint: F,
) -> Container<'a, T> {
container(
tooltip::Tooltip::new(
container(
column(vec![
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(),
])
.width(Length::Fill),
]);
container(
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),
"This signer does not have a key in this wallet.",
tooltip::Position::Bottom,
)
.padding(10)
.style(theme::card::simple),
)
.width(Length::Fill)

View File

@ -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
@ -2202,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.
}