From 689f19a4f22009a9043a7308e00c96bf1de97a45 Mon Sep 17 00:00:00 2001 From: edouard Date: Mon, 23 Jan 2023 17:40:00 +0100 Subject: [PATCH] Edit key name in installer --- gui/src/installer/message.rs | 6 +- gui/src/installer/step/descriptor.rs | 188 +++++++-------- gui/src/installer/view.rs | 343 +++++++++++++++------------ 3 files changed, 286 insertions(+), 251 deletions(-) diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 6090308a..2517ea5c 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -44,6 +44,7 @@ pub enum DefineDescriptor { Key(bool, usize, DefineKey), HWXpubImported(Result), XPubEdited(String), + NameEdited(String), SequenceEdited(String), ThresholdEdited(bool, usize), ConfirmXpub, @@ -52,8 +53,7 @@ pub enum DefineDescriptor { #[derive(Debug, Clone)] pub enum DefineKey { Delete, - ImportFromHardware, - ImportFromClipboard, + Edit, Clipboard(String), - Imported(DescriptorPublicKey), + Edited(String, DescriptorPublicKey), } diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index e32816b6..5a4595e8 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -7,7 +7,7 @@ use liana::{ descriptors::{LianaDescKeys, MultipathDescriptor}, miniscript::{ bitcoin::{ - util::bip32::{DerivationPath, ExtendedPubKey, Fingerprint}, + util::bip32::{DerivationPath, Fingerprint}, Network, }, descriptor::{DerivPaths, DescriptorMultiXKey, DescriptorPublicKey, Wildcard}, @@ -48,6 +48,8 @@ pub struct DefineDescriptor { sequence: form::Value, modal: Option>, + name_indexes: (usize, usize), + error: Option, } @@ -61,6 +63,7 @@ impl DefineDescriptor { spending_threshold: 1, recovery_keys: vec![DescriptorKey::new("Recovery key 1".to_string())], recovery_threshold: 1, + name_indexes: (1, 1), sequence: form::Value::default(), modal: None, error: None, @@ -112,12 +115,12 @@ impl DefineDescriptor { for spending_key in self.spending_keys.iter_mut() { spending_key.duplicate_name = duplicate_names.contains(&spending_key.name); if let Some(key) = &spending_key.key { - spending_key.duplicate_key = duplicate_keys.contains(&key); + spending_key.duplicate_key = duplicate_keys.contains(key); } } for recovery_key in self.recovery_keys.iter_mut() { if let Some(key) = &recovery_key.key { - recovery_key.duplicate_key = duplicate_keys.contains(&key); + recovery_key.duplicate_key = duplicate_keys.contains(key); } } } @@ -161,16 +164,16 @@ impl Step for DefineDescriptor { } message::DefineDescriptor::AddKey(is_recovery) => { if is_recovery { + self.name_indexes.0 += 1; self.recovery_keys.push(DescriptorKey::new(format!( "Recovery key {}", - self.recovery_keys.len() + 1 + self.name_indexes.0, ))); self.recovery_threshold += 1; } else { - self.spending_keys.push(DescriptorKey::new(format!( - "Key {}", - self.spending_keys.len() + 1 - ))); + self.name_indexes.1 += 1; + self.spending_keys + .push(DescriptorKey::new(format!("Key {}", self.name_indexes.1,))); self.spending_threshold += 1; } } @@ -178,28 +181,51 @@ impl Step for DefineDescriptor { message::DefineKey::Clipboard(key) => { return Command::perform(async move { key }, Message::Clibpboard); } - message::DefineKey::Imported(imported_key) => { + message::DefineKey::Edited(name, imported_key) => { if is_recovery { if let Some(recovery_key) = self.recovery_keys.get_mut(i) { + recovery_key.name = name; recovery_key.key = Some(imported_key); recovery_key.check_network(self.network); } } else if let Some(spending_key) = self.spending_keys.get_mut(i) { + spending_key.name = name; spending_key.key = Some(imported_key); spending_key.check_network(self.network); } self.modal = None; self.check_for_duplicate(); } - message::DefineKey::ImportFromClipboard => { - let modal = ImportXpubModal::new(i, is_recovery, self.network); - self.modal = Some(Box::new(modal)); - } - message::DefineKey::ImportFromHardware => { - let modal = HardwareXpubModal::new(i, is_recovery, self.network); - let cmd = modal.load(); - self.modal = Some(Box::new(modal)); - return cmd; + message::DefineKey::Edit => { + if is_recovery { + if let Some(recovery_key) = self.recovery_keys.get(i) { + let name = recovery_key.name.clone(); + let key = recovery_key + .key + .as_ref() + .map(|k| { + k.to_string().trim_end_matches("/<0;1>/*").to_string() + }) + .unwrap_or_else(|| "".to_string()); + let modal = + EditXpubModal::new(name, key, i, is_recovery, self.network); + let cmd = modal.load(); + self.modal = Some(Box::new(modal)); + return cmd; + } + } else if let Some(spending_key) = self.spending_keys.get(i) { + let name = spending_key.name.clone(); + let key = spending_key + .key + .as_ref() + .map(|k| k.to_string().trim_end_matches("/<0;1>/*").to_string()) + .unwrap_or_else(|| "".to_string()); + let modal = + EditXpubModal::new(name, key, i, is_recovery, self.network); + let cmd = modal.load(); + self.modal = Some(Box::new(modal)); + return cmd; + } } message::DefineKey::Delete => { if is_recovery { @@ -371,10 +397,13 @@ impl DescriptorKey { pub fn view(&self) -> Element { match &self.key { - None => view::undefined_descriptor_key(), - Some(key) => { - view::defined_descriptor_key(key.to_string(), self.valid, self.duplicate_key) - } + None => view::undefined_descriptor_key(&self.name), + Some(_) => view::defined_descriptor_key( + &self.name, + self.valid, + self.duplicate_key, + self.duplicate_name, + ), } } } @@ -411,20 +440,37 @@ impl From for Box { } } -pub struct HardwareXpubModal { +pub struct EditXpubModal { is_recovery: bool, key_index: usize, network: Network, error: Option, processing: bool, + form_name: form::Value, + form_xpub: form::Value, + chosen_hw: Option, hws: Vec, } -impl HardwareXpubModal { - fn new(key_index: usize, is_recovery: bool, network: Network) -> Self { +impl EditXpubModal { + fn new( + name: String, + key: String, + key_index: usize, + is_recovery: bool, + network: Network, + ) -> Self { Self { + form_name: form::Value { + valid: true, + value: name, + }, + form_xpub: form::Value { + valid: true, + value: key, + }, is_recovery, key_index, chosen_hw: None, @@ -442,7 +488,7 @@ impl HardwareXpubModal { } } -impl DescriptorKeyModal for HardwareXpubModal { +impl DescriptorKeyModal for EditXpubModal { fn processing(&self) -> bool { self.processing } @@ -474,61 +520,18 @@ impl DescriptorKeyModal for HardwareXpubModal { self.processing = false; match res { Ok(key) => { - let key_index = self.key_index; - let is_recovery = self.is_recovery; - return Command::perform( - async move { (is_recovery, key_index, key) }, - |(is_recovery, key_index, key)| { - message::DefineDescriptor::Key( - is_recovery, - key_index, - message::DefineKey::Imported(key), - ) - }, - ) - .map(Message::DefineDescriptor); + self.form_xpub.value = + key.to_string().trim_end_matches("/<0;1>/*").to_string(); } Err(e) => { self.error = Some(e); } } } - _ => {} - }; - Command::none() - } - fn view(&self) -> Element { - view::hardware_wallet_xpubs_modal( - self.is_recovery, - &self.hws, - self.error.as_ref(), - self.processing, - self.chosen_hw, - ) - } -} - -pub struct ImportXpubModal { - is_recovery: bool, - key_index: usize, - form_xpub: form::Value, - network: Network, -} - -impl ImportXpubModal { - fn new(key_index: usize, is_recovery: bool, network: Network) -> Self { - Self { - form_xpub: form::Value::default(), - is_recovery, - key_index, - network, - } - } -} - -impl DescriptorKeyModal for ImportXpubModal { - fn update(&mut self, message: Message) -> Command { - match message { + Message::DefineDescriptor(message::DefineDescriptor::NameEdited(name)) => { + self.form_name.valid = true; + self.form_name.value = name; + } Message::DefineDescriptor(message::DefineDescriptor::XPubEdited(s)) => { self.form_xpub.valid = DescriptorPublicKey::from_str(&format!("{}/<0;1>/*", s)).is_ok(); @@ -540,13 +543,14 @@ impl DescriptorKeyModal for ImportXpubModal { { let key_index = self.key_index; let is_recovery = self.is_recovery; + let name = self.form_name.value.clone(); return Command::perform( async move { (is_recovery, key_index, key) }, |(is_recovery, key_index, key)| { message::DefineDescriptor::Key( is_recovery, key_index, - message::DefineKey::Imported(key), + message::DefineKey::Edited(name, key), ) }, ) @@ -558,29 +562,15 @@ impl DescriptorKeyModal for ImportXpubModal { Command::none() } fn view(&self) -> Element { - view::clipboard_xpub_modal(&self.form_xpub, self.network) - } -} - -pub struct XKey { - origin: Option<(Fingerprint, DerivationPath)>, - key: ExtendedPubKey, -} - -impl std::fmt::Display for XKey { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if let Some((ref master_id, ref master_deriv)) = self.origin { - std::fmt::Formatter::write_str(f, "[")?; - for byte in master_id.into_bytes().iter() { - write!(f, "{:02x}", byte)?; - } - for child in master_deriv { - write!(f, "/{}", child)?; - } - std::fmt::Formatter::write_str(f, "]")?; - } - self.key.fmt(f)?; - Ok(()) + view::edit_key_modal( + self.network, + &self.hws, + self.error.as_ref(), + self.processing, + self.chosen_hw, + &self.form_xpub, + &self.form_name, + ) } } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index e77d6087..a22ab1da 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -162,12 +162,14 @@ pub fn define_descriptor<'a>( Some(card::warning( "A data directory already exists for this network".to_string(), )) - }); + }) + .padding(50); let col_spending_keys = Column::new() .push( Row::new() .spacing(10) + .push(Space::with_width(Length::Units(40))) .push(text("Primary path:").bold()) .push(tooltip( super::prompt::DEFINE_DESCRIPTOR_PRIMATRY_PATH_TOOLTIP, @@ -200,13 +202,13 @@ pub fn define_descriptor<'a>( .push( Button::new( Container::new(icon::plus_icon().size(50)) - .width(Length::Units(250)) - .height(Length::Units(250)) + .width(Length::Units(200)) + .height(Length::Units(200)) .align_y(alignment::Vertical::Center) .align_x(alignment::Horizontal::Center), ) - .width(Length::Units(250)) - .height(Length::Units(250)) + .width(Length::Units(200)) + .height(Length::Units(200)) .style(button::Style::TransparentBorder.into()) .on_press( Message::DefineDescriptor( @@ -225,7 +227,11 @@ pub fn define_descriptor<'a>( .spacing(10); let col_recovery_keys = Column::new() - .push(text("Recovery path:").bold()) + .push( + Row::new() + .push(Space::with_width(Length::Units(50))) + .push(text("Recovery path:").bold()), + ) .push(separation().width(Length::Fill)) .push( Container::new( @@ -253,13 +259,13 @@ pub fn define_descriptor<'a>( .push( Button::new( Container::new(icon::plus_icon().size(50)) - .width(Length::Units(250)) - .height(Length::Units(250)) + .width(Length::Units(200)) + .height(Length::Units(200)) .align_y(alignment::Vertical::Center) .align_x(alignment::Horizontal::Center), ) - .width(Length::Units(250)) - .height(Length::Units(250)) + .width(Length::Units(200)) + .height(Length::Units(200)) .style(button::Style::TransparentBorder.into()) .on_press( Message::DefineDescriptor( @@ -292,7 +298,7 @@ pub fn define_descriptor<'a>( ) .push( Container::new( - form::Form::new("Number of block", sequence, |msg| { + form::Form::new("Number of blocks", sequence, |msg| { Message::DefineDescriptor( message::DefineDescriptor::SequenceEdited(msg), ) @@ -313,6 +319,7 @@ pub fn define_descriptor<'a>( layout( progress, Column::new() + .push(Space::with_height(Length::Units(50))) .push(text("Create the wallet").bold().size(50)) .push( Column::new() @@ -330,9 +337,9 @@ pub fn define_descriptor<'a>( .on_press(Message::Next) }) .push_maybe(error.map(|e| card::error("Failed to create descriptor", e.to_string()))) + .push(Space::with_height(Length::Units(20))) .width(Length::Fill) .height(Length::Fill) - .padding(100) .spacing(50) .align_items(Alignment::Center), ) @@ -451,10 +458,8 @@ pub fn participate_xpub<'a>( .spacing(10) .align_items(Alignment::Center) .push( - Container::new( - text(format!("Select your hardware wallet:")).bold(), - ) - .width(Length::Fill), + Container::new(text("Select your hardware wallet:").bold()) + .width(Length::Fill), ) .push( button::border(Some(icon::reload_icon()), "Refresh") @@ -849,7 +854,7 @@ pub fn install<'a>( layout(progress, col) } -pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> { +pub fn undefined_descriptor_key(name: &str) -> Element { card::simple( Column::new() .width(Length::Fill) @@ -857,7 +862,6 @@ pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> { .push( Row::new() .align_items(Alignment::Center) - .push(icon::key_icon()) .push(Space::with_width(Length::Fill)) .push( Button::new(icon::cross_icon()) @@ -868,39 +872,41 @@ pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> { .push( Container::new( Column::new() - .spacing(5) + .spacing(15) + .align_items(Alignment::Center) .push( - button::border(Some(icon::import_icon()), "from text input") - .on_press(message::DefineKey::ImportFromClipboard), + Scrollable::new(text(name).bold()) + .horizontal_scroll(Properties::new().width(2).scroller_width(2)), ) - .push( - button::border(Some(icon::chip_icon()), "from hardware") - .on_press(message::DefineKey::ImportFromHardware), - ), + .push(icon::circle_check_icon().style(color::FOREGROUND).size(50)), ) .height(Length::Fill) .align_y(alignment::Vertical::Center), - ), + ) + .push( + button::border(Some(icon::pencil_icon()), "Edit") + .on_press(message::DefineKey::Edit), + ) + .push(Space::with_height(Length::Units(5))), ) .padding(5) - .height(Length::Units(250)) - .width(Length::Units(250)) + .height(Length::Units(200)) + .width(Length::Units(200)) .into() } -pub fn defined_descriptor_key<'a>( - key: String, +pub fn defined_descriptor_key( + name: &str, valid: bool, - duplicate: bool, -) -> Element<'a, message::DefineKey> { + duplicate_key: bool, + duplicate_name: bool, +) -> Element { let col = Column::new() - .spacing(40) .width(Length::Fill) .align_items(Alignment::Center) .push( Row::new() .align_items(Alignment::Center) - .push(icon::key_icon()) .push(Space::with_width(Length::Fill)) .push( Button::new(icon::cross_icon()) @@ -914,18 +920,28 @@ pub fn defined_descriptor_key<'a>( .spacing(5) .push( Container::new( - Scrollable::new(Container::new(text(key.clone()))) - .height(Length::Units(50)) - .horizontal_scroll(Properties::new().width(2).scroller_width(2)), + Column::new() + .spacing(15) + .align_items(Alignment::Center) + .push( + Scrollable::new(text(name).bold()).horizontal_scroll( + Properties::new().width(2).scroller_width(2), + ), + ) + .push( + icon::circle_check_icon() + .style(color::SUCCESS) + .size(40) + .width(Length::Units(50)), + ), ) - .width(Length::Fill) - .height(Length::Fill), + .height(Length::Fill) + .align_y(alignment::Vertical::Center), ) - .push( - button::transparent_border(Some(icon::clipboard_icon()), "Copy") - .on_press(message::DefineKey::Clipboard(key)), - ), - ); + .height(Length::Fill), + ) + .push(button::border(Some(icon::pencil_icon()), "Edit").on_press(message::DefineKey::Edit)) + .push(Space::with_height(Length::Units(5))); if !valid { Column::new() @@ -933,8 +949,8 @@ pub fn defined_descriptor_key<'a>( .push( card::invalid(col) .padding(5) - .height(Length::Units(250)) - .width(Length::Units(250)), + .height(Length::Units(200)) + .width(Length::Units(200)), ) .push( text("Key is for a different network") @@ -942,134 +958,164 @@ pub fn defined_descriptor_key<'a>( .style(color::ALERT), ) .into() - } else if duplicate { + } else if duplicate_key { Column::new() .align_items(Alignment::Center) .push( card::invalid(col) .padding(5) - .height(Length::Units(250)) - .width(Length::Units(250)), + .height(Length::Units(200)) + .width(Length::Units(200)), ) .push(text("Key is a duplicate").small().style(color::ALERT)) .into() + } else if duplicate_name { + Column::new() + .align_items(Alignment::Center) + .push( + card::invalid(col) + .padding(5) + .height(Length::Units(200)) + .width(Length::Units(200)), + ) + .push(text("Name is a duplicate").small().style(color::ALERT)) + .into() } else { card::simple(col) .padding(5) - .height(Length::Units(250)) - .width(Length::Units(250)) + .height(Length::Units(200)) + .width(Length::Units(200)) .into() } } -pub fn hardware_wallet_xpubs_modal<'a>( - is_heir: bool, +pub fn edit_key_modal<'a>( + network: bitcoin::Network, hws: &[HardwareWallet], error: Option<&Error>, processing: bool, chosen_hw: Option, + form_xpub: &form::Value, + form_name: &form::Value, ) -> Element<'a, Message> { - card::simple( - Column::new() - .spacing(20) - .push( - text(if is_heir { - "Import the recovery public key:" - } else { - "Import the user public key:" - }) - .bold(), - ) - .push(separation().width(Length::Fill)) - .push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string()))) - .push(if !hws.is_empty() { - Column::new() - .push( + Column::new() + .push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string()))) + .push(card::simple( + Column::new() + .spacing(25) + .push( + Container::new( Row::new() - .spacing(10) - .align_items(Alignment::Center) - .push( - Container::new( - text(format!("{} hardware wallets connected", hws.len())) - .bold(), - ) - .width(Length::Fill), - ) - .push( - button::border(Some(icon::reload_icon()), "Refresh") - .on_press(Message::Reload), - ), + .spacing(5) + .push(icon::pencil_icon()) + .push(text("Edit")), ) - .spacing(10) - .push( - hws.iter() - .enumerate() - .fold(Column::new().spacing(10), |col, (i, hw)| { + .width(Length::Fill) + .align_x(alignment::Horizontal::Center), + ) + .push( + Column::new() + .spacing(5) + .push(text("Edit name:").bold()) + .push( + form::Form::new("Name", form_name, |msg| { + Message::DefineDescriptor(message::DefineDescriptor::NameEdited( + msg, + )) + }) + .warning("Please enter correct name") + .size(20) + .padding(10), + ), + ) + .push( + Column::new() + .spacing(5) + .push(text("Enter an extended public key:").bold()) + .push( + Row::new() + .push( + form::Form::new("Extended public key", form_xpub, |msg| { + Message::DefineDescriptor( + message::DefineDescriptor::XPubEdited(msg), + ) + }) + .warning(if network == bitcoin::Network::Bitcoin { + "Please enter correct xpub" + } else { + "Please enter correct tpub" + }) + .size(20) + .padding(10), + ) + .spacing(10) + .push(Container::new(text("/<0;1>/*")).padding(5)), + ), + ) + .push(if !hws.is_empty() { + Column::new() + .push( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push( + Container::new(text("Or select a hardware wallet:").bold()) + .width(Length::Fill), + ) + .push( + button::border(Some(icon::reload_icon()), "Refresh") + .on_press(Message::Reload), + ), + ) + .spacing(10) + .push(hws.iter().enumerate().fold( + Column::new().spacing(10), + |col, (i, hw)| { col.push(hw_list_view( i, hw, Some(i) == chosen_hw, processing, - false, + !processing + && Some(i) == chosen_hw + && form_xpub.valid + && !form_xpub.value.is_empty(), )) - }), - ) - .width(Length::Fill) - } else { - Column::new() - .push( - Column::new() - .spacing(15) - .width(Length::Fill) - .push("Please connect a hardware wallet") - .push(button::border(None, "Refresh").on_press(Message::Reload)) - .align_items(Alignment::Center), - ) - .width(Length::Fill) - }) - .width(Length::Units(600)), - ) - .into() -} -pub fn clipboard_xpub_modal<'a>( - form_xpub: &form::Value, - network: bitcoin::Network, -) -> Element<'a, Message> { - card::simple( - Column::new() - .spacing(10) - .push(text("Input extended public key:").bold()) - .push( - Row::new() - .push( - form::Form::new("Extended public key", form_xpub, |msg| { - Message::DefineDescriptor(message::DefineDescriptor::XPubEdited(msg)) - }) - .warning(if network == bitcoin::Network::Bitcoin { - "Please enter correct xpub" - } else { - "Please enter correct tpub" - }) - .size(20) - .padding(10), - ) - .spacing(10) - .push(Container::new(text("/<0;1>/*")).padding(5)), - ) - .push( - Row::new() - .push(Space::with_width(Length::Fill)) - .push(if form_xpub.valid { - button::primary(None, "Apply").on_press(Message::DefineDescriptor( - message::DefineDescriptor::ConfirmXpub, + }, )) - } else { + .width(Length::Fill) + } else { + Column::new() + .push( + Row::new() + .spacing(15) + .width(Length::Fill) + .push( + text("Or connect a hardware wallet") + .bold() + .width(Length::Fill), + ) + .push(button::border(None, "Refresh").on_press(Message::Reload)) + .align_items(Alignment::Center), + ) + .width(Length::Fill) + }) + .push( + if form_xpub.valid && !form_xpub.value.is_empty() && !form_name.value.is_empty() + { button::primary(None, "Apply") - }), - ), - ) - .width(Length::Units(600)) - .into() + .on_press(Message::DefineDescriptor( + message::DefineDescriptor::ConfirmXpub, + )) + .width(Length::Units(200)) + } else { + button::primary(None, "Apply").width(Length::Units(100)) + }, + ) + .align_items(Alignment::Center), + )) + .width(Length::Units(600)) + .into() } fn hw_list_view<'a>( @@ -1217,18 +1263,17 @@ mod threshsold_input { }; Column::new() - .height(Length::Units(250)) - .width(Length::Units(200)) - .push(button(icon::up_icon().size(50), Event::IncrementPressed)) + .height(Length::Units(200)) + .width(Length::Units(100)) + .push(button(icon::up_icon().size(40), Event::IncrementPressed)) .push(text("Threshold:").small().bold()) .push( Container::new(text(format!("{}/{}", self.value, self.max)).size(50)) .height(Length::Fill) .align_y(alignment::Vertical::Center), ) - .push(button(icon::down_icon().size(50), Event::DecrementPressed)) + .push(button(icon::down_icon().size(40), Event::DecrementPressed)) .align_items(Alignment::Center) - .spacing(10) .into() } }