From d73894dfa0143f8bdb7fcb884b9c8c6d2154382b Mon Sep 17 00:00:00 2001 From: edouardparis Date: Thu, 26 Sep 2024 15:54:24 +0200 Subject: [PATCH] installer: module editor --- .../editor/mod.rs} | 312 +--------------- gui/src/installer/step/descriptor/mod.rs | 336 ++++++++++++++++++ gui/src/installer/step/mod.rs | 4 +- gui/src/installer/step/share_xpubs.rs | 2 +- 4 files changed, 341 insertions(+), 313 deletions(-) rename gui/src/installer/step/{descriptor.rs => descriptor/editor/mod.rs} (83%) create mode 100644 gui/src/installer/step/descriptor/mod.rs diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor/editor/mod.rs similarity index 83% rename from gui/src/installer/step/descriptor.rs rename to gui/src/installer/step/descriptor/editor/mod.rs index 0816db94..4037ef3a 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor/editor/mod.rs @@ -27,7 +27,7 @@ use async_hwi::{DeviceKind, Version}; use crate::hw; use crate::{ - app::{settings::KeySetting, wallet::wallet_name}, + app::settings::KeySetting, hw::{HardwareWallet, HardwareWallets}, installer::{ message::{self, Message}, @@ -1097,316 +1097,6 @@ pub async fn get_extended_pubkey( })) } -pub struct ImportDescriptor { - network: Network, - imported_descriptor: form::Value, - wrong_network: bool, - error: Option, -} - -impl ImportDescriptor { - pub fn new(network: Network) -> Self { - Self { - network, - imported_descriptor: form::Value::default(), - wrong_network: false, - error: None, - } - } - - fn check_descriptor(&mut self, network: Network) -> Option { - if !self.imported_descriptor.value.is_empty() { - if let Ok(desc) = LianaDescriptor::from_str(&self.imported_descriptor.value) { - if network == Network::Bitcoin { - self.imported_descriptor.valid = desc.all_xpubs_net_is(network); - } else { - self.imported_descriptor.valid = desc.all_xpubs_net_is(Network::Testnet); - } - if self.imported_descriptor.valid { - self.wrong_network = false; - Some(desc) - } else { - self.wrong_network = true; - None - } - } else { - self.imported_descriptor.valid = false; - self.wrong_network = false; - None - } - } else { - self.wrong_network = false; - self.imported_descriptor.valid = true; - None - } - } -} - -impl Step for ImportDescriptor { - // ImportRemoteWallet is used instead - fn skip(&self, ctx: &Context) -> bool { - ctx.remote_backend.is_some() - } - // form value is set as valid each time it is edited. - // Verification of the values is happening when the user click on Next button. - fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { - if let Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(desc)) = - message - { - self.imported_descriptor.value = desc; - self.check_descriptor(self.network); - } - Command::none() - } - - fn apply(&mut self, ctx: &mut Context) -> bool { - ctx.bitcoin_config.network = self.network; - // Set to true in order to force the registration process to be shown to user. - ctx.hw_is_used = true; - // descriptor forms for import or creation cannot be both empty or filled. - if let Some(desc) = self.check_descriptor(self.network) { - ctx.descriptor = Some(desc); - true - } else { - false - } - } - - fn view<'a>( - &'a self, - _hws: &'a HardwareWallets, - progress: (usize, usize), - email: Option<&'a str>, - ) -> Element { - view::import_descriptor( - progress, - email, - &self.imported_descriptor, - self.wrong_network, - self.error.as_ref(), - ) - } -} - -impl From for Box { - fn from(s: ImportDescriptor) -> Box { - Box::new(s) - } -} - -pub struct RegisterDescriptor { - descriptor: Option, - processing: bool, - chosen_hw: Option, - hmacs: Vec<(Fingerprint, DeviceKind, Option<[u8; 32]>)>, - registered: HashSet, - error: Option, - done: bool, - /// Whether this step is part of the descriptor creation process. This is used to detect when - /// it's instead shown as part of the descriptor *import* process, where we can't detect - /// whether a signing device is used, to explicit this step is not required if the user isn't - /// using a signing device. - created_desc: bool, -} - -impl RegisterDescriptor { - fn new(created_desc: bool) -> Self { - Self { - created_desc, - descriptor: Default::default(), - processing: Default::default(), - chosen_hw: Default::default(), - hmacs: Default::default(), - registered: Default::default(), - error: Default::default(), - done: Default::default(), - } - } - - pub fn new_create_wallet() -> Self { - Self::new(true) - } - - pub fn new_import_wallet() -> Self { - Self::new(false) - } -} - -impl Step for RegisterDescriptor { - fn load_context(&mut self, ctx: &Context) { - // we reset device registered set if the descriptor have changed. - if self.descriptor != ctx.descriptor { - self.registered = Default::default(); - self.done = false; - } - self.descriptor.clone_from(&ctx.descriptor); - let mut map = HashMap::new(); - for key in ctx.keys.iter().filter(|k| !k.name.is_empty()) { - map.insert(key.master_fingerprint, key.name.clone()); - } - } - fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command { - match message { - Message::Select(i) => { - if let Some(HardwareWallet::Supported { - device, - fingerprint, - .. - }) = hws.list.get(i) - { - if !self.registered.contains(fingerprint) { - let descriptor = self.descriptor.as_ref().unwrap(); - let name = wallet_name(descriptor); - self.chosen_hw = Some(i); - self.processing = true; - self.error = None; - return Command::perform( - register_wallet( - device.clone(), - *fingerprint, - name, - descriptor.to_string(), - ), - Message::WalletRegistered, - ); - } - } - } - Message::WalletRegistered(res) => { - self.processing = false; - self.chosen_hw = None; - match res { - Ok((fingerprint, hmac)) => { - if let Some(hw_h) = hws - .list - .iter() - .find(|hw_h| hw_h.fingerprint() == Some(fingerprint)) - { - self.registered.insert(fingerprint); - self.hmacs.push((fingerprint, *hw_h.kind(), hmac)); - } - } - Err(e) => { - if !matches!(e, Error::HardwareWallet(async_hwi::Error::UserRefused)) { - self.error = Some(e) - } - } - } - } - Message::Reload => { - return self.load(); - } - Message::UserActionDone(done) => { - self.done = done; - } - _ => {} - }; - Command::none() - } - fn skip(&self, ctx: &Context) -> bool { - !ctx.hw_is_used - } - fn apply(&mut self, ctx: &mut Context) -> bool { - for (fingerprint, kind, token) in &self.hmacs { - ctx.hws.push((*kind, *fingerprint, *token)); - } - true - } - fn subscription(&self, hws: &HardwareWallets) -> Subscription { - hws.refresh().map(Message::HardwareWallets) - } - fn load(&self) -> Command { - Command::none() - } - fn view<'a>( - &'a self, - hws: &'a HardwareWallets, - progress: (usize, usize), - email: Option<&'a str>, - ) -> Element<'a, Message> { - let desc = self.descriptor.as_ref().unwrap(); - view::register_descriptor( - progress, - email, - desc.to_string(), - &hws.list, - &self.registered, - self.error.as_ref(), - self.processing, - self.chosen_hw, - self.done, - self.created_desc, - ) - } -} - -async fn register_wallet( - hw: std::sync::Arc, - fingerprint: Fingerprint, - name: String, - descriptor: String, -) -> Result<(Fingerprint, Option<[u8; 32]>), Error> { - let hmac = hw - .register_wallet(&name, &descriptor) - .await - .map_err(Error::from)?; - Ok((fingerprint, hmac)) -} - -impl From for Box { - fn from(s: RegisterDescriptor) -> Box { - Box::new(s) - } -} - -#[derive(Default)] -pub struct BackupDescriptor { - done: bool, - descriptor: Option, - key_aliases: HashMap, -} - -impl Step for BackupDescriptor { - fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { - if let Message::UserActionDone(done) = message { - self.done = done; - } - Command::none() - } - fn load_context(&mut self, ctx: &Context) { - if self.descriptor != ctx.descriptor { - self.descriptor.clone_from(&ctx.descriptor); - self.done = false; - } - self.key_aliases = ctx - .keys - .iter() - .cloned() - .map(|k| (k.master_fingerprint, k.name)) - .collect() - } - fn view<'a>( - &'a self, - _hws: &'a HardwareWallets, - progress: (usize, usize), - email: Option<&'a str>, - ) -> Element { - view::backup_descriptor( - progress, - email, - self.descriptor.as_ref().expect("Must be a descriptor"), - &self.key_aliases, - self.done, - ) - } -} - -impl From for Box { - fn from(s: BackupDescriptor) -> Box { - Box::new(s) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/gui/src/installer/step/descriptor/mod.rs b/gui/src/installer/step/descriptor/mod.rs new file mode 100644 index 00000000..fe8e27b0 --- /dev/null +++ b/gui/src/installer/step/descriptor/mod.rs @@ -0,0 +1,336 @@ +pub mod editor; + +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; + +use iced::{Command, Subscription}; +use liana::{ + descriptors::LianaDescriptor, + miniscript::bitcoin::{bip32::Fingerprint, Network}, +}; + +use liana_ui::{component::form, widget::Element}; + +use async_hwi::DeviceKind; + +use crate::{ + app::wallet::wallet_name, + hw::{HardwareWallet, HardwareWallets}, + installer::{ + message::{self, Message}, + step::{Context, Step}, + view, Error, + }, +}; + +pub struct ImportDescriptor { + network: Network, + imported_descriptor: form::Value, + wrong_network: bool, + error: Option, +} + +impl ImportDescriptor { + pub fn new(network: Network) -> Self { + Self { + network, + imported_descriptor: form::Value::default(), + wrong_network: false, + error: None, + } + } + + fn check_descriptor(&mut self, network: Network) -> Option { + if !self.imported_descriptor.value.is_empty() { + if let Ok(desc) = LianaDescriptor::from_str(&self.imported_descriptor.value) { + if network == Network::Bitcoin { + self.imported_descriptor.valid = desc.all_xpubs_net_is(network); + } else { + self.imported_descriptor.valid = desc.all_xpubs_net_is(Network::Testnet); + } + if self.imported_descriptor.valid { + self.wrong_network = false; + Some(desc) + } else { + self.wrong_network = true; + None + } + } else { + self.imported_descriptor.valid = false; + self.wrong_network = false; + None + } + } else { + self.wrong_network = false; + self.imported_descriptor.valid = true; + None + } + } +} + +impl Step for ImportDescriptor { + // ImportRemoteWallet is used instead + fn skip(&self, ctx: &Context) -> bool { + ctx.remote_backend.is_some() + } + // form value is set as valid each time it is edited. + // Verification of the values is happening when the user click on Next button. + fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { + if let Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(desc)) = + message + { + self.imported_descriptor.value = desc; + self.check_descriptor(self.network); + } + Command::none() + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + ctx.bitcoin_config.network = self.network; + // Set to true in order to force the registration process to be shown to user. + ctx.hw_is_used = true; + // descriptor forms for import or creation cannot be both empty or filled. + if let Some(desc) = self.check_descriptor(self.network) { + ctx.descriptor = Some(desc); + true + } else { + false + } + } + + fn view<'a>( + &'a self, + _hws: &'a HardwareWallets, + progress: (usize, usize), + email: Option<&'a str>, + ) -> Element { + view::import_descriptor( + progress, + email, + &self.imported_descriptor, + self.wrong_network, + self.error.as_ref(), + ) + } +} + +impl From for Box { + fn from(s: ImportDescriptor) -> Box { + Box::new(s) + } +} + +pub struct RegisterDescriptor { + descriptor: Option, + processing: bool, + chosen_hw: Option, + hmacs: Vec<(Fingerprint, DeviceKind, Option<[u8; 32]>)>, + registered: HashSet, + error: Option, + done: bool, + /// Whether this step is part of the descriptor creation process. This is used to detect when + /// it's instead shown as part of the descriptor *import* process, where we can't detect + /// whether a signing device is used, to explicit this step is not required if the user isn't + /// using a signing device. + created_desc: bool, +} + +impl RegisterDescriptor { + fn new(created_desc: bool) -> Self { + Self { + created_desc, + descriptor: Default::default(), + processing: Default::default(), + chosen_hw: Default::default(), + hmacs: Default::default(), + registered: Default::default(), + error: Default::default(), + done: Default::default(), + } + } + + pub fn new_create_wallet() -> Self { + Self::new(true) + } + + pub fn new_import_wallet() -> Self { + Self::new(false) + } +} + +impl Step for RegisterDescriptor { + fn load_context(&mut self, ctx: &Context) { + // we reset device registered set if the descriptor have changed. + if self.descriptor != ctx.descriptor { + self.registered = Default::default(); + self.done = false; + } + self.descriptor.clone_from(&ctx.descriptor); + let mut map = HashMap::new(); + for key in ctx.keys.iter().filter(|k| !k.name.is_empty()) { + map.insert(key.master_fingerprint, key.name.clone()); + } + } + fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command { + match message { + Message::Select(i) => { + if let Some(HardwareWallet::Supported { + device, + fingerprint, + .. + }) = hws.list.get(i) + { + if !self.registered.contains(fingerprint) { + let descriptor = self.descriptor.as_ref().unwrap(); + let name = wallet_name(descriptor); + self.chosen_hw = Some(i); + self.processing = true; + self.error = None; + return Command::perform( + register_wallet( + device.clone(), + *fingerprint, + name, + descriptor.to_string(), + ), + Message::WalletRegistered, + ); + } + } + } + Message::WalletRegistered(res) => { + self.processing = false; + self.chosen_hw = None; + match res { + Ok((fingerprint, hmac)) => { + if let Some(hw_h) = hws + .list + .iter() + .find(|hw_h| hw_h.fingerprint() == Some(fingerprint)) + { + self.registered.insert(fingerprint); + self.hmacs.push((fingerprint, *hw_h.kind(), hmac)); + } + } + Err(e) => { + if !matches!(e, Error::HardwareWallet(async_hwi::Error::UserRefused)) { + self.error = Some(e) + } + } + } + } + Message::Reload => { + return self.load(); + } + Message::UserActionDone(done) => { + self.done = done; + } + _ => {} + }; + Command::none() + } + fn skip(&self, ctx: &Context) -> bool { + !ctx.hw_is_used + } + fn apply(&mut self, ctx: &mut Context) -> bool { + for (fingerprint, kind, token) in &self.hmacs { + ctx.hws.push((*kind, *fingerprint, *token)); + } + true + } + fn subscription(&self, hws: &HardwareWallets) -> Subscription { + hws.refresh().map(Message::HardwareWallets) + } + fn load(&self) -> Command { + Command::none() + } + fn view<'a>( + &'a self, + hws: &'a HardwareWallets, + progress: (usize, usize), + email: Option<&'a str>, + ) -> Element<'a, Message> { + let desc = self.descriptor.as_ref().unwrap(); + view::register_descriptor( + progress, + email, + desc.to_string(), + &hws.list, + &self.registered, + self.error.as_ref(), + self.processing, + self.chosen_hw, + self.done, + self.created_desc, + ) + } +} + +async fn register_wallet( + hw: std::sync::Arc, + fingerprint: Fingerprint, + name: String, + descriptor: String, +) -> Result<(Fingerprint, Option<[u8; 32]>), Error> { + let hmac = hw + .register_wallet(&name, &descriptor) + .await + .map_err(Error::from)?; + Ok((fingerprint, hmac)) +} + +impl From for Box { + fn from(s: RegisterDescriptor) -> Box { + Box::new(s) + } +} + +#[derive(Default)] +pub struct BackupDescriptor { + done: bool, + descriptor: Option, + key_aliases: HashMap, +} + +impl Step for BackupDescriptor { + fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { + if let Message::UserActionDone(done) = message { + self.done = done; + } + Command::none() + } + fn load_context(&mut self, ctx: &Context) { + if self.descriptor != ctx.descriptor { + self.descriptor.clone_from(&ctx.descriptor); + self.done = false; + } + self.key_aliases = ctx + .keys + .iter() + .cloned() + .map(|k| (k.master_fingerprint, k.name)) + .collect() + } + fn view<'a>( + &'a self, + _hws: &'a HardwareWallets, + progress: (usize, usize), + email: Option<&'a str>, + ) -> Element { + view::backup_descriptor( + progress, + email, + self.descriptor.as_ref().expect("Must be a descriptor"), + &self.key_aliases, + self.done, + ) + } +} + +impl From for Box { + fn from(s: BackupDescriptor) -> Box { + Box::new(s) + } +} diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index c45e56c5..d47c253a 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -9,7 +9,9 @@ pub use node::{ DefineNode, }; -pub use descriptor::{BackupDescriptor, DefineDescriptor, ImportDescriptor, RegisterDescriptor}; +pub use descriptor::{ + editor::DefineDescriptor, BackupDescriptor, ImportDescriptor, RegisterDescriptor, +}; pub use backend::{ChooseBackend, ImportRemoteWallet, RemoteBackendLogin}; pub use mnemonic::{BackupMnemonic, RecoverMnemonic}; diff --git a/gui/src/installer/step/share_xpubs.rs b/gui/src/installer/step/share_xpubs.rs index 27dabe18..b6c1403d 100644 --- a/gui/src/installer/step/share_xpubs.rs +++ b/gui/src/installer/step/share_xpubs.rs @@ -13,7 +13,7 @@ use crate::{ installer::{ message::Message, step::{ - descriptor::{default_derivation_path, get_extended_pubkey}, + descriptor::editor::{default_derivation_path, get_extended_pubkey}, Context, Step, }, view, Error,