Load hot signer in Wallet

This commit is contained in:
edouard 2023-01-31 14:01:00 +01:00
parent ecb3e11486
commit e76459d159
6 changed files with 161 additions and 70 deletions

View File

@ -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),
}
}
}

View File

@ -25,12 +25,16 @@ trait Action {
fn warning(&self) -> Option<&Error> {
None
}
fn load(&self, _wallet: &Wallet, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
fn load(
&self,
_wallet: Arc<Wallet>,
_daemon: Arc<dyn Daemon + Sync + Send>,
) -> Command<Message> {
Command::none()
}
fn update(
&mut self,
_wallet: &Wallet,
_wallet: Arc<Wallet>,
_daemon: Arc<dyn Daemon + Sync + Send>,
_message: Message,
_tx: &mut SpendTx,
@ -61,7 +65,7 @@ impl SpendTxState {
pub fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
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<Wallet>,
daemon: Arc<dyn Daemon + Sync + Send>,
message: Message,
tx: &mut SpendTx,
@ -183,7 +202,7 @@ pub struct BroadcastAction {
impl Action for BroadcastAction {
fn update(
&mut self,
_wallet: &Wallet,
_wallet: Arc<Wallet>,
daemon: Arc<dyn Daemon + Sync + Send>,
message: Message,
tx: &mut SpendTx,
@ -227,7 +246,7 @@ pub struct DeleteAction {
impl Action for DeleteAction {
fn update(
&mut self,
_wallet: &Wallet,
_wallet: Arc<Wallet>,
daemon: Arc<dyn Daemon + Sync + Send>,
message: Message,
tx: &mut SpendTx,
@ -284,13 +303,16 @@ impl Action for SignAction {
self.error.as_ref()
}
fn load(&self, wallet: &Wallet, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
let wallet = wallet.clone();
fn load(
&self,
wallet: Arc<Wallet>,
_daemon: Arc<dyn Daemon + Sync + Send>,
) -> Command<Message> {
Command::perform(list_hws(wallet), Message::ConnectedHardwareWallets)
}
fn update(
&mut self,
wallet: &Wallet,
wallet: Arc<Wallet>,
daemon: Arc<dyn Daemon + Sync + Send>,
message: Message,
tx: &mut SpendTx,
@ -365,7 +387,7 @@ impl Action for SignAction {
}
}
async fn list_hws(wallet: Wallet) -> Vec<HardwareWallet> {
async fn list_hws(wallet: Arc<Wallet>) -> Vec<HardwareWallet> {
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<Wallet>,
daemon: Arc<dyn Daemon + Sync + Send>,
message: Message,
tx: &mut SpendTx,

View File

@ -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()),
}
}
}

View File

@ -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<Fingerprint, String>,
pub hardware_wallets: Vec<HardwareWalletConfig>,
pub signer: Option<Signer>,
}
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<HardwareWalletConfig>) -> Self {
pub fn with_hardware_wallets(mut self, hardware_wallets: Vec<HardwareWalletConfig>) -> 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<Fingerprint> {
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
}
}

View File

@ -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<dyn Daemon + Sync + Send>,
info: GetInfoResult,
gui_config: GUIConfig,
datadir_path: Option<PathBuf>,
network: bitcoin::Network,
) -> Result<(Arc<Wallet>, Cache, Arc<dyn Daemon + Sync + Send>), 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<DaemonError> for Error {
}
}
fn get_datadir_path(datadir_path: &Option<PathBuf>) -> Result<PathBuf, ConfigError> {
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<PathBuf>,
network: bitcoin::Network,
) -> Result<PathBuf, ConfigError> {
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<PathBuf>,
network: bitcoin::Network,
) -> Result<PathBuf, ConfigError> {
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)

View File

@ -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();