diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 2304e388..784df2c9 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -21,6 +21,7 @@ pub enum Message { Close, Reload, Select(usize), + UseHotSigner, Installed(Result), Network(Network), DefineBitcoind(DefineBitcoind), @@ -45,7 +46,6 @@ pub enum DefineDescriptor { HWXpubImported(Result), XPubEdited(String), EditName, - UseHotSigner, NameEdited(String), SequenceEdited(String), ThresholdEdited(bool, usize), diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 1840d856..073d930e 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -658,7 +658,7 @@ impl DescriptorKeyModal for EditXpubModal { self.hws = Vec::new(); return self.load(); } - Message::DefineDescriptor(message::DefineDescriptor::UseHotSigner) => { + Message::UseHotSigner => { self.chosen_hw = None; self.chosen_signer = true; self.form_xpub.valid = true; @@ -841,6 +841,12 @@ impl HardwareWalletXpubs { } } + fn reset(&mut self) { + self.error = None; + self.next_account = ChildNumber::from_hardened_idx(0).unwrap(); + self.xpubs = Vec::new(); + } + fn select(&mut self, i: usize, network: Network) -> Command { if let HardwareWallet::Supported { device, @@ -878,6 +884,42 @@ impl HardwareWalletXpubs { } } +pub struct SignerXpubs { + signer: Arc, + xpubs: Vec, + next_account: ChildNumber, +} + +impl SignerXpubs { + fn new(signer: Arc) -> Self { + Self { + signer, + xpubs: Vec::new(), + next_account: ChildNumber::from_hardened_idx(0).unwrap(), + } + } + + fn reset(&mut self) { + self.xpubs = Vec::new(); + self.next_account = ChildNumber::from_hardened_idx(0).unwrap(); + } + + fn select(&mut self, network: Network) { + let derivation_path = generate_derivation_path(network, self.next_account); + self.next_account = self.next_account.increment().unwrap(); + self.xpubs.push(format!( + "[{}{}]{}/<0;1>/*", + self.signer.fingerprint(), + derivation_path.to_string().trim_start_matches('m'), + self.signer.get_extended_pubkey(&derivation_path) + )); + } + + pub fn view(&self) -> Element { + view::signer_xpubs(&self.xpubs) + } +} + pub struct ParticipateXpub { network: Network, network_valid: bool, @@ -886,6 +928,7 @@ pub struct ParticipateXpub { shared: bool, xpubs_hw: Vec, + xpubs_signer: SignerXpubs, } impl ParticipateXpub { @@ -896,8 +939,30 @@ impl ParticipateXpub { data_dir: None, xpubs_hw: Vec::new(), shared: false, + xpubs_signer: SignerXpubs::new(Arc::new(Signer::generate(Network::Bitcoin).unwrap())), } } + + fn set_network(&mut self, network: Network) { + if network != self.network { + self.xpubs_hw.iter_mut().for_each(|hw| hw.reset()); + self.xpubs_signer.reset(); + } + self.network = network; + if let Some(signer) = Arc::get_mut(&mut self.xpubs_signer.signer) { + signer.set_network(network); + } + if let Some(mut network_datadir) = self.data_dir.clone() { + network_datadir.push(self.network.to_string()); + self.network_valid = !network_datadir.exists(); + } + } +} + +impl Default for ParticipateXpub { + fn default() -> Self { + Self::new() + } } impl Step for ParticipateXpub { @@ -906,10 +971,7 @@ impl Step for ParticipateXpub { fn update(&mut self, message: Message) -> Command { match message { Message::Network(network) => { - self.network = network; - let mut network_datadir = self.data_dir.clone().unwrap(); - network_datadir.push(self.network.to_string()); - self.network_valid = !network_datadir.exists(); + self.set_network(network); } Message::UserActionDone(shared) => self.shared = shared, Message::ImportXpub(i, res) => { @@ -917,6 +979,9 @@ impl Step for ParticipateXpub { hw.update(res); } } + Message::UseHotSigner => { + self.xpubs_signer.select(self.network); + } Message::Select(i) => { if let Some(hw) = self.xpubs_hw.get_mut(i) { return hw.select(i, self.network); @@ -943,11 +1008,8 @@ impl Step for ParticipateXpub { } fn load_context(&mut self, ctx: &Context) { - self.network = ctx.bitcoin_config.network; self.data_dir = Some(ctx.data_dir.clone()); - let mut network_datadir = ctx.data_dir.clone(); - network_datadir.push(self.network.to_string()); - self.network_valid = !network_datadir.exists(); + self.set_network(ctx.bitcoin_config.network); } fn load(&self) -> Command { @@ -961,6 +1023,11 @@ impl Step for ParticipateXpub { ctx.bitcoin_config.network = self.network; // Drop connections to hardware wallets. self.xpubs_hw = Vec::new(); + if !self.xpubs_signer.xpubs.is_empty() { + ctx.signer = Some(self.xpubs_signer.signer.clone()); + } else { + ctx.signer = None; + } true } @@ -974,17 +1041,12 @@ impl Step for ParticipateXpub { .enumerate() .map(|(i, hw)| hw.view(i)) .collect(), + self.xpubs_signer.view(), self.shared, ) } } -impl Default for ParticipateXpub { - fn default() -> Self { - Self::new() - } -} - impl From for Box { fn from(s: ParticipateXpub) -> Box { Box::new(s) diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 6d13faae..6e8cede5 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -411,6 +411,76 @@ pub fn import_descriptor<'a>( ) } +pub fn signer_xpubs(xpubs: &Vec) -> Element { + Container::new( + Column::new() + .push( + Button::new( + Row::new().align_items(Alignment::Center).push( + Column::new() + .push(text("This computer").bold()) + .push( + text("Derive a key from a mnemonic stored on this computer") + .small(), + ) + .spacing(5) + .width(Length::Fill), + ), + ) + .on_press(Message::UseHotSigner) + .padding(10) + .style(button::Style::TransparentBorder.into()) + .width(Length::Fill), + ) + .push_maybe(if xpubs.is_empty() { + None + } else { + Some(separation().width(Length::Fill)) + }) + .push_maybe(if xpubs.is_empty() { + None + } else { + Some(xpubs.iter().fold(Column::new().padding(15), |col, xpub| { + col.push( + Row::new() + .spacing(5) + .align_items(Alignment::Center) + .push( + Container::new( + Scrollable::new(Container::new(text(xpub).small()).padding(10)) + .horizontal_scroll( + Properties::new().width(2).scroller_width(2), + ), + ) + .width(Length::Fill), + ) + .push( + Container::new( + button::border(Some(icon::clipboard_icon()), "Copy") + .on_press(Message::Clibpboard(xpub.clone())) + .width(Length::Shrink), + ) + .padding(10), + ), + ) + })) + }) + .push_maybe(if !xpubs.is_empty() { + Some( + Container::new( + button::border(Some(icon::plus_icon()), "New public key") + .on_press(Message::UseHotSigner), + ) + .padding(10), + ) + } else { + None + }), + ) + .style(card::SimpleCardStyle) + .into() +} + pub fn hardware_wallet_xpubs<'a>( i: usize, xpubs: &'a Vec, @@ -531,13 +601,14 @@ pub fn hardware_wallet_xpubs<'a>( .into() } -pub fn participate_xpub( +pub fn participate_xpub<'a>( progress: (usize, usize), network: bitcoin::Network, network_valid: bool, - hws: Vec>, + hws: Vec>, + signer: Element<'a, Message>, shared: bool, -) -> Element { +) -> Element<'a, Message> { let row_network = Row::new() .spacing(10) .align_items(Alignment::Center) @@ -583,6 +654,7 @@ pub fn participate_xpub( ) .spacing(10) .push(Column::with_children(hws).spacing(10)) + .push(signer) .width(Length::Fill), ) .push(Checkbox::new( @@ -1138,7 +1210,7 @@ pub fn edit_key_modal<'a>( .spacing(5) .push(text("This computer").bold()) .push( - text("Derive a key from a mnemonic stored on the computer").small(), + text("Derive a key from a mnemonic stored on this computer").small(), ) .width(Length::Fill), ) @@ -1150,9 +1222,7 @@ pub fn edit_key_modal<'a>( .spacing(10), ) .width(Length::Fill) - .on_press(Message::DefineDescriptor( - message::DefineDescriptor::UseHotSigner, - )) + .on_press(Message::UseHotSigner) .style(button::Style::Border.into()), ) .width(Length::Fill),