diff --git a/gui/Cargo.lock b/gui/Cargo.lock index ed047fa37..2f0d8bdd7 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -77,22 +77,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", ] -[[package]] -name = "aes-ctr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763" -dependencies = [ - "aes-soft", - "aesni", - "cipher 0.2.5", - "ctr 0.6.0", -] - [[package]] name = "aes-gcm" version = "0.10.3" @@ -101,32 +89,12 @@ checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", - "cipher 0.4.4", - "ctr 0.9.2", + "cipher", + "ctr", "ghash", "subtle", ] -[[package]] -name = "aes-soft" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" -dependencies = [ - "cipher 0.2.5", - "opaque-debug", -] - -[[package]] -name = "aesni" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" -dependencies = [ - "cipher 0.2.5", - "opaque-debug", -] - [[package]] name = "ahash" version = "0.7.6" @@ -224,9 +192,9 @@ dependencies = [ [[package]] name = "async-hwi" -version = "0.0.14" +version = "0.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b9ba5d4bd59fea79a70a375da2fe077b2b17ce26ace805d6939275719ad66e" +checksum = "912663643d018301fb5534949e8430515c7cdc9bf453bfefba2dc70dc854dd31" dependencies = [ "async-trait", "bitbox-api", @@ -585,7 +553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", ] @@ -597,7 +565,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20", - "cipher 0.4.4", + "cipher", "poly1305", "zeroize", ] @@ -623,15 +591,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "cipher" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" -dependencies = [ - "generic-array", -] - [[package]] name = "cipher" version = "0.4.4" @@ -736,13 +695,14 @@ dependencies = [ [[package]] name = "coldcard" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a02245612d550816be0d18aaea125372568a2d3d2f673f7b313294543f0fe03d" +checksum = "53b201b4f71707e6330445ed5b6af1024904c6d2c05aca2f74e5fa51d2e56a0f" dependencies = [ - "aes-ctr", + "aes", "base58", - "bitcoin_hashes 0.12.0", + "bitcoin_hashes 0.13.0", + "ctr", "hidapi", "k256", "rand", @@ -968,22 +928,13 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" -dependencies = [ - "cipher 0.2.5", -] - [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] @@ -2105,14 +2056,15 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hidapi" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723777263b0dcc5730aec947496bd8c3940ba63c15f5633b288cc615f4f6af79" +checksum = "9a722fb137d008dbf264f54612457f8eb6a299efbcb0138178964a0809035d74" dependencies = [ "cc", + "cfg-if", "libc", "pkg-config", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -2549,9 +2501,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa", @@ -2656,9 +2608,9 @@ dependencies = [ [[package]] name = "ledger_bitcoin_client" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8946cd76087649eed46851965dd5d06c45b996b6cfbe4436640e0fa6b7c4766f" +checksum = "8606a9c7375fb139e68fc1ca7cf9c6709566eeca448ff33e37632d8a4302eefe" dependencies = [ "async-trait", "bitcoin", @@ -2668,7 +2620,7 @@ dependencies = [ [[package]] name = "liana" version = "4.0.0" -source = "git+https://github.com/wizardsardine/liana?branch=master#16afa3e9925cd016db03dbe954403bfa348b89e7" +source = "git+https://github.com/wizardsardine/liana?branch=master#dd29578c500ea7644154a5923bea9fa1db9b68e5" dependencies = [ "backtrace", "bdk_coin_select", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 7278e79de..872504159 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -14,7 +14,7 @@ name = "liana-gui" path = "src/main.rs" [dependencies] -async-hwi = "0.0.14" +async-hwi = "0.0.16" 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/state/psbt.rs b/gui/src/app/state/psbt.rs index 66c5a3e44..bfbf0b894 100644 --- a/gui/src/app/state/psbt.rs +++ b/gui/src/app/state/psbt.rs @@ -574,6 +574,12 @@ fn merge_signatures(psbt: &mut Psbt, signed_psbt: &Psbt) { psbtin .partial_sigs .extend(&mut signed_psbtin.partial_sigs.iter()); + psbtin + .tap_script_sigs + .extend(&mut signed_psbtin.tap_script_sigs.iter()); + if let Some(sig) = signed_psbtin.tap_key_sig { + psbtin.tap_key_sig = Some(sig); + } } } diff --git a/gui/src/app/view/hw.rs b/gui/src/app/view/hw.rs index 65a914c82..20ab14d37 100644 --- a/gui/src/app/view/hw.rs +++ b/gui/src/app/view/hw.rs @@ -33,11 +33,12 @@ pub fn hw_list_view( alias.as_ref(), ) } else if *registered == Some(false) { - hw::unregistered_hardware_wallet( + hw::warning_hardware_wallet( kind, version.as_ref(), fingerprint, alias.as_ref(), + "The wallet descriptor is not registered on the device.\n You can register it in the settings.", ) } else { hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) diff --git a/gui/src/hw.rs b/gui/src/hw.rs index e2a8f048e..e455e3537 100644 --- a/gui/src/hw.rs +++ b/gui/src/hw.rs @@ -673,3 +673,32 @@ fn ledger_version_supported(version: Option<&Version>) -> bool { false } } + +// Kind and minimal version of devices supporting tapminiscript. +// We cannot use a lazy_static HashMap yet, because DeviceKind does not implement Hash. +const DEVICES_COMPATIBLE_WITH_TAPMINISCRIPT: [(DeviceKind, Option); 1] = [( + DeviceKind::Ledger, + Some(Version { + major: 2, + minor: 2, + patch: 0, + prerelease: None, + }), +)]; + +pub fn is_compatible_with_tapminiscript( + device_kind: &DeviceKind, + version: Option<&Version>, +) -> bool { + DEVICES_COMPATIBLE_WITH_TAPMINISCRIPT + .iter() + .any(|(kind, minimal_version)| { + device_kind == kind + && match (version, minimal_version) { + (Some(v1), Some(v2)) => v1 >= v2, + (None, Some(_)) => false, + (Some(_), None) => true, + (None, None) => true, + } + }) +} diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index d6e8afad4..4c2981017 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -10,7 +10,7 @@ use crate::{ download::Progress, hw::HardwareWalletMessage, }; -use async_hwi::DeviceKind; +use async_hwi::{DeviceKind, Version}; #[derive(Debug, Clone)] pub enum Message { @@ -30,6 +30,7 @@ pub enum Message { UseHotSigner, Installed(Result), Network(Network), + CreateTaprootDescriptor(bool), SelectBitcoindType(SelectBitcoindTypeMsg), InternalBitcoind(InternalBitcoindMsg), DefineBitcoind(DefineBitcoind), @@ -85,12 +86,18 @@ pub enum DefinePath { EditSequence, } +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] pub enum DefineKey { Delete, Edit, Clipboard(String), - Edited(String, DescriptorPublicKey, Option), + Edited( + String, + DescriptorPublicKey, + Option, + Option, + ), } #[derive(Debug, Clone)] diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 2426496e5..960f596e7 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -24,8 +24,9 @@ use liana_ui::{ widget::Element, }; -use async_hwi::DeviceKind; +use async_hwi::{DeviceKind, Version}; +use crate::hw; use crate::{ app::{settings::KeySetting, wallet::wallet_name}, hw::{HardwareWallet, HardwareWallets}, @@ -72,6 +73,7 @@ impl RecoveryPath { &self, aliases: &HashMap, duplicate_name: &HashSet, + incompatible_with_tapminiscript: &HashSet, ) -> Element { view::recovery_path_view( self.sequence, @@ -85,6 +87,7 @@ impl RecoveryPath { view::defined_descriptor_key( aliases.get(key).unwrap().to_string(), duplicate_name.contains(key), + incompatible_with_tapminiscript.contains(key), ) } else { view::undefined_descriptor_key() @@ -99,6 +102,7 @@ impl RecoveryPath { struct Setup { keys: Vec, duplicate_name: HashSet, + incompatible_with_tapminiscript: HashSet, spending_keys: Vec>, spending_threshold: usize, recovery_paths: Vec, @@ -109,6 +113,7 @@ impl Setup { Self { keys: Vec::new(), duplicate_name: HashSet::new(), + incompatible_with_tapminiscript: HashSet::new(), spending_keys: vec![None], spending_threshold: 1, recovery_paths: vec![RecoveryPath::new()], @@ -120,6 +125,7 @@ impl Setup { && !self.spending_keys.iter().any(|k| k.is_none()) && !self.recovery_paths.iter().any(|path| !path.valid()) && self.duplicate_name.is_empty() + && self.incompatible_with_tapminiscript.is_empty() } // Mark as duplicate every defined key that have the same name but not the same fingerprint. @@ -150,6 +156,33 @@ impl Setup { } } + fn check_for_tapminiscript_support(&mut self, must_support_taproot: bool) { + self.incompatible_with_tapminiscript = HashSet::new(); + if must_support_taproot { + for key in &self.keys { + // check if key is used by a path + if !self + .spending_keys + .iter() + .chain(self.recovery_paths.iter().flat_map(|path| &path.keys)) + .any(|k| *k == Some(key.fingerprint)) + { + continue; + } + + // device_kind is none only for HotSigner which is compatible. + if let Some(device_kind) = key.device_kind.as_ref() { + if !hw::is_compatible_with_tapminiscript( + device_kind, + key.device_version.as_ref(), + ) { + self.incompatible_with_tapminiscript.insert(key.fingerprint); + } + } + } + } + } + fn keys_aliases(&self) -> HashMap { let mut map = HashMap::new(); for key in &self.keys { @@ -160,11 +193,13 @@ impl Setup { } pub struct DefineDescriptor { - network: Network, - network_valid: bool, data_dir: Option, setup: HashMap, + network: Network, + network_valid: bool, + use_taproot: bool, + modal: Option>, signer: Arc>, @@ -175,10 +210,10 @@ impl DefineDescriptor { pub fn new(signer: Arc>) -> Self { Self { network: Network::Bitcoin, + use_taproot: false, setup: HashMap::from([(Network::Bitcoin, Setup::new())]), data_dir: None, network_valid: true, - modal: None, signer, error: None, @@ -204,6 +239,14 @@ impl DefineDescriptor { network_datadir.push(self.network.to_string()); self.network_valid = !network_datadir.exists(); } + self.check_setup() + } + + fn check_setup(&mut self) { + self.setup_mut().check_for_duplicate(); + let use_taproot = self.use_taproot; + self.setup_mut() + .check_for_tapminiscript_support(use_taproot); } } @@ -221,6 +264,10 @@ impl Step for DefineDescriptor { hws.set_network(network); self.set_network(network) } + Message::CreateTaprootDescriptor(use_taproot) => { + self.use_taproot = use_taproot; + self.check_setup(); + } Message::DefineDescriptor(message::DefineDescriptor::AddRecoveryPath) => { self.setup_mut().recovery_paths.push(RecoveryPath::new()); } @@ -236,7 +283,7 @@ impl Step for DefineDescriptor { message::DefineKey::Clipboard(key) => { return Command::perform(async move { key }, Message::Clibpboard); } - message::DefineKey::Edited(name, imported_key, kind) => { + message::DefineKey::Edited(name, imported_key, kind, version) => { let fingerprint = imported_key.master_fingerprint(); hws.set_alias(fingerprint, name.clone()); if let Some(key) = self @@ -252,17 +299,20 @@ impl Step for DefineDescriptor { name, key: imported_key, device_kind: kind, + device_version: version, }); } self.setup_mut().spending_keys[i] = Some(fingerprint); self.modal = None; - self.setup_mut().check_for_duplicate(); + self.check_setup(); } message::DefineKey::Edit => { + let use_taproot = self.use_taproot; let setup = self.setup_mut(); let modal = EditXpubModal::new( + use_taproot, HashSet::from_iter(setup.spending_keys.iter().filter_map(|key| { if key.is_some() && key != &setup.spending_keys[i] { *key @@ -293,7 +343,7 @@ impl Step for DefineDescriptor { { self.setup_mut().spending_threshold -= 1; } - self.setup_mut().check_for_duplicate(); + self.check_setup(); } }, _ => {} @@ -327,7 +377,7 @@ impl Step for DefineDescriptor { message::DefineKey::Clipboard(key) => { return Command::perform(async move { key }, Message::Clibpboard); } - message::DefineKey::Edited(name, imported_key, kind) => { + message::DefineKey::Edited(name, imported_key, kind, version) => { let fingerprint = imported_key.master_fingerprint(); hws.set_alias(fingerprint, name.clone()); if let Some(key) = self @@ -343,17 +393,20 @@ impl Step for DefineDescriptor { name, key: imported_key, device_kind: kind, + device_version: version, }); } self.setup_mut().recovery_paths[i].keys[j] = Some(fingerprint); self.modal = None; - self.setup_mut().check_for_duplicate(); + self.check_setup(); } message::DefineKey::Edit => { + let use_taproot = self.use_taproot; let setup = self.setup_mut(); let modal = EditXpubModal::new( + use_taproot, HashSet::from_iter(setup.recovery_paths[i].keys.iter().filter_map( |key| { if key.is_some() && key != &setup.recovery_paths[i].keys[j] { @@ -390,7 +443,7 @@ impl Step for DefineDescriptor { { self.setup_mut().recovery_paths.remove(i); } - self.setup_mut().check_for_duplicate(); + self.check_setup(); } }, }, @@ -490,7 +543,11 @@ impl Step for DefineDescriptor { PathInfo::Multi(self.setup[&self.network].spending_threshold, spending_keys) }; - let policy = match LianaPolicy::new(spending_keys, recovery_paths) { + let policy = match if self.use_taproot { + LianaPolicy::new(spending_keys, recovery_paths) + } else { + LianaPolicy::new_legacy(spending_keys, recovery_paths) + } { Ok(policy) => policy, Err(e) => { self.error = Some(e.to_string()); @@ -513,6 +570,7 @@ impl Step for DefineDescriptor { progress, self.network, self.network_valid, + self.use_taproot, self.setup[&self.network] .spending_keys .iter() @@ -522,6 +580,9 @@ impl Step for DefineDescriptor { view::defined_descriptor_key( aliases.get(key).unwrap().to_string(), self.setup[&self.network].duplicate_name.contains(key), + self.setup[&self.network] + .incompatible_with_tapminiscript + .contains(key), ) } else { view::undefined_descriptor_key() @@ -539,12 +600,14 @@ impl Step for DefineDescriptor { .iter() .enumerate() .map(|(i, path)| { - path.view(&aliases, &self.setup[&self.network].duplicate_name) - .map(move |msg| { - Message::DefineDescriptor(message::DefineDescriptor::RecoveryPath( - i, msg, - )) - }) + path.view( + &aliases, + &self.setup[&self.network].duplicate_name, + &self.setup[&self.network].incompatible_with_tapminiscript, + ) + .map(move |msg| { + Message::DefineDescriptor(message::DefineDescriptor::RecoveryPath(i, msg)) + }) }) .collect(), self.valid(), @@ -583,6 +646,7 @@ fn new_multixkey_from_xpub( #[derive(Clone)] pub struct Key { pub device_kind: Option, + pub device_version: Option, pub name: String, pub fingerprint: Fingerprint, pub key: DescriptorPublicKey, @@ -675,6 +739,7 @@ impl DescriptorEditModal for EditSequenceModal { } pub struct EditXpubModal { + device_must_support_tapminiscript: bool, /// None if path is primary path path_index: Option, key_index: usize, @@ -692,12 +757,13 @@ pub struct EditXpubModal { keys: Vec, hot_signer: Arc>, hot_signer_fingerprint: Fingerprint, - chosen_signer: Option<(Fingerprint, Option)>, + chosen_signer: Option<(Fingerprint, Option, Option)>, } impl EditXpubModal { #[allow(clippy::too_many_arguments)] fn new( + device_must_support_tapminiscript: bool, other_path_keys: HashSet, key: Option, path_index: Option, @@ -708,6 +774,7 @@ impl EditXpubModal { ) -> Self { let hot_signer_fingerprint = hot_signer.lock().unwrap().fingerprint(); Self { + device_must_support_tapminiscript, other_path_keys, form_name: form::Value { valid: true, @@ -740,7 +807,7 @@ impl EditXpubModal { error: None, network, edit_name: false, - chosen_signer: key.map(|k| (k, None)), + chosen_signer: key.map(|k| (k, None, None)), hot_signer_fingerprint, hot_signer, duplicate_master_fg: false, @@ -767,10 +834,11 @@ impl DescriptorEditModal for EditXpubModal { device, fingerprint, kind, + version, .. }) = hws.list.get(i) { - self.chosen_signer = Some((*fingerprint, Some(*kind))); + self.chosen_signer = Some((*fingerprint, Some(*kind), version.clone())); self.processing = true; return Command::perform( get_extended_pubkey(device.clone(), *fingerprint, self.network), @@ -787,7 +855,7 @@ impl DescriptorEditModal for EditXpubModal { } Message::UseHotSigner => { let fingerprint = self.hot_signer.lock().unwrap().fingerprint(); - self.chosen_signer = Some((fingerprint, None)); + self.chosen_signer = Some((fingerprint, None, None)); self.form_xpub.valid = true; if let Some(alias) = self .keys @@ -882,7 +950,12 @@ impl DescriptorEditModal for EditXpubModal { if let Ok(key) = DescriptorPublicKey::from_str(&self.form_xpub.value) { let key_index = self.key_index; let name = self.form_name.value.clone(); - let device_kind = self.chosen_signer.and_then(|(_, kind)| kind); + let (device_kind, device_version) = + if let Some((_, kind, version)) = &self.chosen_signer { + (*kind, version.clone()) + } else { + (None, None) + }; if self.other_path_keys.contains(&key.master_fingerprint()) { self.duplicate_master_fg = true; } else if let Some(path_index) = self.path_index { @@ -893,7 +966,12 @@ impl DescriptorEditModal for EditXpubModal { path_index, message::DefinePath::Key( key_index, - message::DefineKey::Edited(name, key, device_kind), + message::DefineKey::Edited( + name, + key, + device_kind, + device_version, + ), ), ) }, @@ -906,7 +984,12 @@ impl DescriptorEditModal for EditXpubModal { message::DefineDescriptor::PrimaryPath( message::DefinePath::Key( key_index, - message::DefineKey::Edited(name, key, device_kind), + message::DefineKey::Edited( + name, + key, + device_kind, + device_version, + ), ), ) }, @@ -917,7 +1000,8 @@ impl DescriptorEditModal for EditXpubModal { } message::ImportKeyModal::SelectKey(i) => { if let Some(key) = self.keys.get(i) { - self.chosen_signer = Some((key.fingerprint, key.device_kind)); + self.chosen_signer = + Some((key.fingerprint, key.device_kind, key.device_version.clone())); self.form_xpub.value = key.key.to_string(); self.form_xpub.valid = true; self.form_name.value = key.name.clone(); @@ -930,7 +1014,7 @@ impl DescriptorEditModal for EditXpubModal { Command::none() } fn view<'a>(&'a self, hws: &'a HardwareWallets) -> Element<'a, Message> { - let chosen_signer = self.chosen_signer.map(|s| s.0); + let chosen_signer = self.chosen_signer.as_ref().map(|s| s.0); view::edit_key_modal( self.network, hws.list @@ -953,6 +1037,7 @@ impl DescriptorEditModal for EditXpubModal { && hw.fingerprint() == chosen_signer && self.form_xpub.valid && !self.form_xpub.value.is_empty(), + self.device_must_support_tapminiscript, )) } }) @@ -969,13 +1054,15 @@ impl DescriptorEditModal for EditXpubModal { &key.name, &key.fingerprint, key.device_kind.as_ref(), + key.device_version.as_ref(), Some(key.fingerprint) == chosen_signer, + self.device_must_support_tapminiscript, )) } }) .collect(), self.error.as_ref(), - self.chosen_signer.map(|s| s.0), + self.chosen_signer.as_ref().map(|s| s.0), &self.hot_signer_fingerprint, self.keys.iter().find_map(|k| { if k.fingerprint == self.hot_signer_fingerprint { @@ -1668,6 +1755,7 @@ mod tests { "My Specter key".to_string(), DescriptorPublicKey::from_str("[4df3f0e3/84'/0'/0']tpubDDRs9DnRUiJc4hq92PSJKhfzQBgHJUrDo7T2i48smsDfLsQcm3Vh7JhuGqJv8zozVkNFin8YPgpmn2NWNmpRaE3GW2pSxbmAzYf2juy7LeW").unwrap(), Some(DeviceKind::Specter), + None, ), ); diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 60eb4efa9..b44e7b833 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::HardwareWallet, + hw::{is_compatible_with_tapminiscript, HardwareWallet}, installer::{ message::{self, Message}, prompt, @@ -173,16 +173,28 @@ pub fn welcome<'a>() -> Element<'a, Message> { .into() } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DescriptorKind { + P2WSH, + Taproot, +} + +const DESCRIPTOR_KINDS: [DescriptorKind; 2] = [DescriptorKind::P2WSH, DescriptorKind::Taproot]; + +impl std::fmt::Display for DescriptorKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::P2WSH => write!(f, "P2WSH"), + Self::Taproot => write!(f, "Taproot"), + } + } +} + #[allow(clippy::too_many_arguments)] -pub fn define_descriptor<'a>( - progress: (usize, usize), +pub fn define_descriptor_advanced_settings<'a>( network: bitcoin::Network, network_valid: bool, - spending_keys: Vec>, - spending_threshold: usize, - recovery_paths: Vec>, - valid: bool, - error: Option<&String>, + use_taproot: bool, ) -> Element<'a, Message> { let col_network = Column::new() .spacing(10) @@ -192,7 +204,7 @@ pub fn define_descriptor<'a>( Message::Network(net.into()) }) .style(if network_valid { - theme::PickList::Simple + theme::PickList::Secondary } else { theme::PickList::Invalid }) @@ -204,6 +216,58 @@ pub fn define_descriptor<'a>( Some(text("A data directory already exists for this network").style(color::RED)) }); + let col_wallet = Column::new() + .spacing(10) + .push(text("Descriptor type").bold()) + .push(container( + pick_list( + &DESCRIPTOR_KINDS[..], + Some(if use_taproot { + DescriptorKind::Taproot + } else { + DescriptorKind::P2WSH + }), + |kind| Message::CreateTaprootDescriptor(kind == DescriptorKind::Taproot), + ) + .style(theme::PickList::Secondary) + .padding(10), + )); + + container( + Column::new() + .spacing(20) + .push(Space::with_height(0)) + .push(separation().width(500)) + .push( + Row::new() + .push(col_network) + .push(Space::with_width(100)) + .push(col_wallet), + ) + .push_maybe(if use_taproot { + Some( + p1_regular("Taproot is only supported by Liana version 5.0 and above") + .style(color::GREY_2), + ) + } else { + None + }), + ) + .into() +} + +#[allow(clippy::too_many_arguments)] +pub fn define_descriptor<'a>( + progress: (usize, usize), + network: bitcoin::Network, + network_valid: bool, + use_taproot: bool, + spending_keys: Vec>, + spending_threshold: usize, + recovery_paths: Vec>, + valid: bool, + error: Option<&String>, +) -> Element<'a, Message> { let col_spending_keys = Column::new() .push( Row::new() @@ -263,10 +327,32 @@ pub fn define_descriptor<'a>( progress, "Create the wallet", Column::new() + .push(collapse::Collapse::new( + || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .spacing(10) + .push(text("Advanced settings").small().bold()) + .push(icon::collapse_icon()), + ) + .style(theme::Button::Transparent) + }, + || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .spacing(10) + .push(text("Advanced settings").small().bold()) + .push(icon::collapsed_icon()), + ) + .style(theme::Button::Transparent) + }, + move || define_descriptor_advanced_settings(network, network_valid, use_taproot), + )) .push( Column::new() .width(Length::Fill) - .push(col_network) .push( Column::new() .spacing(25) @@ -707,6 +793,7 @@ pub fn register_descriptor<'a>( hw.fingerprint() .map(|fg| registered.contains(&fg)) .unwrap_or(false), + false, )) }), ) @@ -1287,6 +1374,7 @@ pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> { pub fn defined_descriptor_key<'a>( name: String, duplicate_name: bool, + incompatible_with_tapminiscript: bool, ) -> Element<'a, message::DefineKey> { let col = Column::new() .width(Length::Fill) @@ -1336,6 +1424,21 @@ pub fn defined_descriptor_key<'a>( ) .push(text("Duplicate name").small().style(color::RED)) .into() + } else if incompatible_with_tapminiscript { + Column::new() + .align_items(Alignment::Center) + .push( + card::invalid(col) + .padding(5) + .height(Length::Fixed(150.0)) + .width(Length::Fixed(150.0)), + ) + .push( + text("Taproot is not supported\nby this key device") + .small() + .style(color::RED), + ) + .into() } else { card::simple(col) .padding(5) @@ -1594,6 +1697,7 @@ pub fn hw_list_view( chosen: bool, processing: bool, selected: bool, + device_must_support_taproot: bool, ) -> Element { let mut bttn = Button::new(match hw { HardwareWallet::Supported { @@ -1607,6 +1711,16 @@ pub fn hw_list_view( 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()) + } else if device_must_support_taproot + && !is_compatible_with_tapminiscript(kind, version.as_ref()) + { + hw::warning_hardware_wallet( + kind, + version.as_ref(), + fingerprint, + alias.as_ref(), + "Device firmware version does not support taproot miniscript", + ) } else { hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } @@ -1634,19 +1748,31 @@ pub fn key_list_view<'a>( name: &'a str, fingerprint: &'a Fingerprint, kind: Option<&'a DeviceKind>, + version: Option<&'a async_hwi::Version>, chosen: bool, + device_must_support_taproot: bool, ) -> Element<'a, Message> { let bttn = Button::new(if chosen { hw::selected_hardware_wallet( kind.map(|k| k.to_string()).unwrap_or_default(), - None::, + version, + fingerprint, + Some(name), + ) + } else if device_must_support_taproot + && kind.map(|kind| is_compatible_with_tapminiscript(kind, version)) == Some(false) + { + hw::warning_hardware_wallet( + kind.map(|k| k.to_string()).unwrap_or_default(), + version, fingerprint, Some(name), + "Device firmware version does not support taproot miniscript", ) } else { hw::supported_hardware_wallet( kind.map(|k| k.to_string()).unwrap_or_default(), - None::, + version, fingerprint, Some(name), ) diff --git a/gui/src/signer.rs b/gui/src/signer.rs index 4f7009948..dad45dab9 100644 --- a/gui/src/signer.rs +++ b/gui/src/signer.rs @@ -10,7 +10,7 @@ use liana::{ }; pub struct Signer { - curve: secp256k1::Secp256k1, + curve: secp256k1::Secp256k1, key: HotSigner, pub fingerprint: Fingerprint, } @@ -23,7 +23,7 @@ impl std::fmt::Debug for Signer { impl Signer { pub fn new(key: HotSigner) -> Self { - let curve = secp256k1::Secp256k1::signing_only(); + let curve = secp256k1::Secp256k1::new(); let fingerprint = key.fingerprint(&curve); Self { key, diff --git a/gui/ui/src/color.rs b/gui/ui/src/color.rs index e9e8b6691..dcd7726d7 100644 --- a/gui/ui/src/color.rs +++ b/gui/ui/src/color.rs @@ -1,5 +1,6 @@ use iced::Color; pub const BLACK: Color = iced::Color::BLACK; +pub const TRANSPARENT: Color = iced::Color::TRANSPARENT; pub const LIGHT_BLACK: Color = Color::from_rgb( 0x14 as f32 / 255.0, 0x14 as f32 / 255.0, diff --git a/gui/ui/src/component/hw.rs b/gui/ui/src/component/hw.rs index 848935e58..534d4872f 100644 --- a/gui/ui/src/component/hw.rs +++ b/gui/ui/src/component/hw.rs @@ -51,11 +51,12 @@ pub fn supported_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( .padding(10) } -pub fn unregistered_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( +pub fn warning_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( kind: K, version: Option, fingerprint: F, alias: Option>>, + warning: &'static str, ) -> Container<'a, T> { container( row(vec![ @@ -75,7 +76,7 @@ pub fn unregistered_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Displa .into(), column(vec![tooltip::Tooltip::new( icon::warning_icon(), - "The wallet descriptor is not registered on the device.\n You can register it in the settings.", + warning, tooltip::Position::Bottom, ) .style(theme::Container::Card(theme::Card::Simple)) diff --git a/gui/ui/src/theme.rs b/gui/ui/src/theme.rs index e40d9010c..e6a239825 100644 --- a/gui/ui/src/theme.rs +++ b/gui/ui/src/theme.rs @@ -441,6 +441,7 @@ pub enum PickList { #[default] Simple, Invalid, + Secondary, } impl pick_list::StyleSheet for Theme { type Style = PickList; @@ -465,6 +466,15 @@ impl pick_list::StyleSheet for Theme { border_radius: 25.0, text_color: color::RED, }, + PickList::Secondary => pick_list::Appearance { + placeholder_color: color::GREY_3, + handle_color: color::GREY_3, + background: color::TRANSPARENT.into(), + border_width: 1.0, + border_color: color::GREY_3, + border_radius: 25.0, + text_color: color::GREY_2, + }, } }