Load hot signer in Wallet
This commit is contained in:
parent
ecb3e11486
commit
e76459d159
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user