From e76459d159630068f2c0bd9799ce908c30e42d6e Mon Sep 17 00:00:00 2001 From: edouard Date: Tue, 31 Jan 2023 14:01:00 +0100 Subject: [PATCH] Load hot signer in Wallet --- gui/src/app/error.rs | 2 + gui/src/app/state/spend/detail.rs | 54 ++++++++---- gui/src/app/view/warning.rs | 1 + gui/src/app/wallet.rs | 28 +++++- gui/src/loader.rs | 140 +++++++++++++++++++----------- gui/src/signer.rs | 6 ++ 6 files changed, 161 insertions(+), 70 deletions(-) diff --git a/gui/src/app/error.rs b/gui/src/app/error.rs index f0e767a6..ff6d6457 100644 --- a/gui/src/app/error.rs +++ b/gui/src/app/error.rs @@ -9,6 +9,7 @@ pub enum Error { Daemon(DaemonError), Unexpected(String), HardwareWallet(async_hwi::Error), + HotSigner(String), } impl std::fmt::Display for Error { @@ -40,6 +41,7 @@ impl std::fmt::Display for Error { }, Self::Unexpected(e) => write!(f, "Unexpected error: {}", e), Self::HardwareWallet(e) => write!(f, "{}", e), + Self::HotSigner(e) => write!(f, "{}", e), } } } diff --git a/gui/src/app/state/spend/detail.rs b/gui/src/app/state/spend/detail.rs index 4c6f45aa..2b7066a8 100644 --- a/gui/src/app/state/spend/detail.rs +++ b/gui/src/app/state/spend/detail.rs @@ -25,12 +25,16 @@ trait Action { fn warning(&self) -> Option<&Error> { None } - fn load(&self, _wallet: &Wallet, _daemon: Arc) -> Command { + fn load( + &self, + _wallet: Arc, + _daemon: Arc, + ) -> Command { Command::none() } fn update( &mut self, - _wallet: &Wallet, + _wallet: Arc, _daemon: Arc, _message: Message, _tx: &mut SpendTx, @@ -61,7 +65,7 @@ impl SpendTxState { pub fn load(&self, daemon: Arc) -> Command { if let Some(action) = &self.action { - action.load(&self.wallet, daemon) + action.load(self.wallet.clone(), daemon) } else { Command::none() } @@ -83,13 +87,13 @@ impl SpendTxState { } view::SpendTxMessage::Sign => { let action = SignAction::new(); - let cmd = action.load(&self.wallet, daemon); + let cmd = action.load(self.wallet.clone(), daemon); self.action = Some(Box::new(action)); return cmd; } view::SpendTxMessage::EditPsbt => { let action = UpdateAction::new(self.tx.psbt.to_string()); - let cmd = action.load(&self.wallet, daemon); + let cmd = action.load(self.wallet.clone(), daemon); self.action = Some(Box::new(action)); return cmd; } @@ -101,19 +105,34 @@ impl SpendTxState { } _ => { if let Some(action) = self.action.as_mut() { - return action.update(&self.wallet, daemon.clone(), message, &mut self.tx); + return action.update( + self.wallet.clone(), + daemon.clone(), + message, + &mut self.tx, + ); } } }, Message::Updated(Ok(_)) => { self.saved = true; if let Some(action) = self.action.as_mut() { - return action.update(&self.wallet, daemon.clone(), message, &mut self.tx); + return action.update( + self.wallet.clone(), + daemon.clone(), + message, + &mut self.tx, + ); } } _ => { if let Some(action) = self.action.as_mut() { - return action.update(&self.wallet, daemon.clone(), message, &mut self.tx); + return action.update( + self.wallet.clone(), + daemon.clone(), + message, + &mut self.tx, + ); } } }; @@ -147,7 +166,7 @@ pub struct SaveAction { impl Action for SaveAction { fn update( &mut self, - _wallet: &Wallet, + _wallet: Arc, daemon: Arc, message: Message, tx: &mut SpendTx, @@ -183,7 +202,7 @@ pub struct BroadcastAction { impl Action for BroadcastAction { fn update( &mut self, - _wallet: &Wallet, + _wallet: Arc, daemon: Arc, message: Message, tx: &mut SpendTx, @@ -227,7 +246,7 @@ pub struct DeleteAction { impl Action for DeleteAction { fn update( &mut self, - _wallet: &Wallet, + _wallet: Arc, daemon: Arc, message: Message, tx: &mut SpendTx, @@ -284,13 +303,16 @@ impl Action for SignAction { self.error.as_ref() } - fn load(&self, wallet: &Wallet, _daemon: Arc) -> Command { - let wallet = wallet.clone(); + fn load( + &self, + wallet: Arc, + _daemon: Arc, + ) -> Command { Command::perform(list_hws(wallet), Message::ConnectedHardwareWallets) } fn update( &mut self, - wallet: &Wallet, + wallet: Arc, daemon: Arc, message: Message, tx: &mut SpendTx, @@ -365,7 +387,7 @@ impl Action for SignAction { } } -async fn list_hws(wallet: Wallet) -> Vec { +async fn list_hws(wallet: Arc) -> Vec { list_hardware_wallets( &wallet.hardware_wallets, Some((&wallet.name, &wallet.main_descriptor.to_string())), @@ -418,7 +440,7 @@ impl Action for UpdateAction { fn update( &mut self, - wallet: &Wallet, + wallet: Arc, daemon: Arc, message: Message, tx: &mut SpendTx, diff --git a/gui/src/app/view/warning.rs b/gui/src/app/view/warning.rs index b121ec9e..4a2845e0 100644 --- a/gui/src/app/view/warning.rs +++ b/gui/src/app/view/warning.rs @@ -37,6 +37,7 @@ impl From<&Error> for WarningMessage { }, Error::Unexpected(_) => WarningMessage("Unknown error".to_string()), Error::HardwareWallet(_) => WarningMessage("Hardware wallet error".to_string()), + Error::HotSigner(_) => WarningMessage("Hot signer error".to_string()), } } } diff --git a/gui/src/app/wallet.rs b/gui/src/app/wallet.rs index 6e8bd4a2..29742695 100644 --- a/gui/src/app/wallet.rs +++ b/gui/src/app/wallet.rs @@ -1,18 +1,19 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use crate::hw::HardwareWalletConfig; +use crate::{hw::HardwareWalletConfig, signer::Signer}; use liana::descriptors::MultipathDescriptor; use liana::miniscript::bitcoin::util::bip32::Fingerprint; pub const DEFAULT_WALLET_NAME: &str = "Liana"; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Wallet { pub name: String, pub main_descriptor: MultipathDescriptor, pub keys_aliases: HashMap, pub hardware_wallets: Vec, + pub signer: Option, } impl Wallet { @@ -22,6 +23,7 @@ impl Wallet { main_descriptor, keys_aliases: HashMap::new(), hardware_wallets: Vec::new(), + signer: None, } } @@ -31,6 +33,7 @@ impl Wallet { main_descriptor, keys_aliases: HashMap::new(), hardware_wallets: Vec::new(), + signer: None, } } @@ -39,8 +42,25 @@ impl Wallet { self } - pub fn with_harware_wallets(mut self, hardware_wallets: Vec) -> Self { + pub fn with_hardware_wallets(mut self, hardware_wallets: Vec) -> Self { self.hardware_wallets = hardware_wallets; self } + + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer = Some(signer); + self + } + + pub fn descriptor_keys(&self) -> HashSet { + let info = self.main_descriptor.info(); + let mut descriptor_keys = HashSet::new(); + for (fingerprint, _) in info.primary_path().thresh_origins().1.iter() { + descriptor_keys.insert(*fingerprint); + } + for (fingerprint, _) in info.recovery_path().1.thresh_origins().1.iter() { + descriptor_keys.insert(*fingerprint); + } + descriptor_keys + } } diff --git a/gui/src/loader.rs b/gui/src/loader.rs index 477bf8e7..67b30a08 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -13,6 +13,7 @@ use log::{debug, info}; use liana::{ config::{Config, ConfigError}, miniscript::bitcoin, + signer::HotSigner, StartupError, }; @@ -24,6 +25,7 @@ use crate::{ wallet::Wallet, }, daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError}, + signer::Signer, ui::{ component::{button, notification, text::*}, icon, @@ -143,47 +145,14 @@ impl Loader { match res { Ok(info) => { if (info.sync - 1.0_f64).abs() < f64::EPSILON { - let daemon = daemon.clone(); - let settings_path = - settings_path(&self.datadir_path, self.network).unwrap(); - let gui_config_hws = self - .gui_config - .hardware_wallets - .as_ref() - .cloned() - .unwrap_or_default(); return Command::perform( - async move { - let coins = daemon.list_coins().map(|res| res.coins)?; - let spend_txs = daemon.list_spend_transactions()?; - let cache = Cache { - network: info.network, - blockheight: info.block_height, - coins, - spend_txs, - ..Default::default() - }; - let wallet = match Settings::from_file(&settings_path) { - Ok(settings) => { - if let Some(wallet_setting) = settings.wallets.first() { - Wallet::legacy(info.descriptors.main) - .with_harware_wallets( - wallet_setting.hardware_wallets.clone(), - ) - .with_key_aliases(wallet_setting.keys_aliases()) - } else { - Wallet::legacy(info.descriptors.main) - .with_harware_wallets(gui_config_hws) - } - } - Err(settings::SettingsError::NotFound) => { - Wallet::legacy(info.descriptors.main) - .with_harware_wallets(gui_config_hws) - } - Err(e) => return Err(e.into()), - }; - Ok((Arc::new(wallet), cache, daemon)) - }, + load_application( + daemon.clone(), + info, + self.gui_config.clone(), + self.datadir_path.clone(), + self.network, + ), Message::Synced, ); } else { @@ -229,6 +198,10 @@ impl Loader { Message::Started(res) => self.on_start(res), Message::Loaded(res) => self.on_load(res), Message::Syncing(res) => self.on_sync(res), + Message::Synced(Err(e)) => { + self.step = Step::Error(Box::new(e)); + Command::none() + } Message::Failure(_) => { self.daemon_started = false; Command::none() @@ -246,6 +219,71 @@ impl Loader { } } +pub async fn load_application( + daemon: Arc, + info: GetInfoResult, + gui_config: GUIConfig, + datadir_path: Option, + network: bitcoin::Network, +) -> Result<(Arc, Cache, Arc), Error> { + let coins = daemon.list_coins().map(|res| res.coins)?; + let spend_txs = daemon.list_spend_transactions()?; + let cache = Cache { + network: info.network, + blockheight: info.block_height, + coins, + spend_txs, + ..Default::default() + }; + let settings_path = settings_path(&datadir_path, network).unwrap(); + let gui_config_hws = gui_config + .hardware_wallets + .as_ref() + .cloned() + .unwrap_or_default(); + + let mut wallet = match Settings::from_file(&settings_path) { + Ok(settings) => { + if let Some(wallet_setting) = settings.wallets.first() { + Wallet::new(wallet_setting.name.clone(), info.descriptors.main) + .with_hardware_wallets(wallet_setting.hardware_wallets.clone()) + .with_key_aliases(wallet_setting.keys_aliases()) + } else { + Wallet::legacy(info.descriptors.main).with_hardware_wallets(gui_config_hws) + } + } + Err(settings::SettingsError::NotFound) => { + Wallet::legacy(info.descriptors.main).with_hardware_wallets(gui_config_hws) + } + Err(e) => return Err(e.into()), + }; + + let hot_signers = match HotSigner::from_datadir(&get_datadir_path(&datadir_path)?, network) { + Ok(signers) => signers, + Err(e) => match e { + liana::signer::SignerError::MnemonicStorage(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Vec::new() + } else { + return Err(Error::HotSigner(e.to_string())); + } + } + _ => return Err(Error::HotSigner(e.to_string())), + }, + }; + + let curve = bitcoin::secp256k1::Secp256k1::signing_only(); + let keys = wallet.descriptor_keys(); + if let Some(hot_signer) = hot_signers + .into_iter() + .find(|s| keys.contains(&s.fingerprint(&curve))) + { + wallet = wallet.with_signer(Signer::new(hot_signer)); + } + + Ok((Arc::new(wallet), cache, daemon)) +} + #[derive(Clone, Debug)] pub enum ViewMessage { Retry, @@ -368,6 +406,7 @@ pub enum Error { Settings(settings::SettingsError), Config(ConfigError), Daemon(DaemonError), + HotSigner(String), } impl std::fmt::Display for Error { @@ -376,6 +415,7 @@ impl std::fmt::Display for Error { Self::Settings(e) => write!(f, "Settings error: {}", e), Self::Config(e) => write!(f, "Config error: {}", e), Self::Daemon(e) => write!(f, "Liana daemon error: {}", e), + Self::HotSigner(e) => write!(f, "Failed to load hot signer: {}", e), } } } @@ -398,16 +438,20 @@ impl From for Error { } } +fn get_datadir_path(datadir_path: &Option) -> Result { + if let Some(ref datadir) = datadir_path { + Ok(datadir.clone()) + } else { + default_datadir().map_err(|_| ConfigError::DatadirNotFound) + } +} + /// default lianad socket path is .liana/bitcoin/lianad_rpc fn socket_path( datadir: &Option, network: bitcoin::Network, ) -> Result { - let mut path = if let Some(ref datadir) = datadir { - datadir.clone() - } else { - default_datadir().map_err(|_| ConfigError::DatadirNotFound)? - }; + let mut path = get_datadir_path(datadir)?; path.push(network.to_string()); path.push("lianad_rpc"); Ok(path) @@ -418,11 +462,7 @@ fn settings_path( datadir: &Option, network: bitcoin::Network, ) -> Result { - let mut path = if let Some(ref datadir) = datadir { - datadir.clone() - } else { - default_datadir().map_err(|_| ConfigError::DatadirNotFound)? - }; + let mut path = get_datadir_path(datadir)?; path.push(network.to_string()); path.push(settings::DEFAULT_FILE_NAME); Ok(path) diff --git a/gui/src/signer.rs b/gui/src/signer.rs index 039fb683..cad4ffc5 100644 --- a/gui/src/signer.rs +++ b/gui/src/signer.rs @@ -18,6 +18,12 @@ pub struct Signer { fingerprint: Fingerprint, } +impl std::fmt::Debug for Signer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Signer").finish() + } +} + impl Signer { pub fn new(key: HotSigner) -> Self { let curve = secp256k1::Secp256k1::signing_only();