diff --git a/gui/Cargo.lock b/gui/Cargo.lock index a4833a42..ec0ec9d5 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -125,7 +125,7 @@ dependencies = [ [[package]] name = "async-hwi" version = "0.0.1" -source = "git+https://github.com/revault/async-hwi?branch=add-ledger#de5615ba2e2e2d3ded6a058f0193f8787d3873dc" +source = "git+https://github.com/revault/async-hwi?branch=master#14b29f820910132d9b8f799d030bfed69ac67239" dependencies = [ "async-trait", "base64", @@ -1380,7 +1380,7 @@ dependencies = [ [[package]] name = "ledger_bitcoin_client" version = "0.1.0" -source = "git+https://github.com/edouardparis/app-bitcoin-new/?branch=bitcoin_client_rs#95ec68b546d49a77eee274880d2450fdb08f840c" +source = "git+https://github.com/edouardparis/app-bitcoin-new/?branch=bitcoin_client_rs#efe26ed9fcfe0676c33141ad4777fd3786f7d539" dependencies = [ "async-trait", "bitcoin", @@ -1597,7 +1597,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minisafe" version = "0.0.1" -source = "git+https://github.com/revault/minisafe?branch=master#790d283e77063c019f54690eb1c483562e12338c" +source = "git+https://github.com/revault/minisafe?branch=master#8b129fe3e51ac41a78c282a018e70bd86e2ab24f" dependencies = [ "backtrace", "base64", @@ -1636,8 +1636,7 @@ dependencies = [ [[package]] name = "miniscript" version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4975078076f0b7b914a3044ad7432d2a7fcec38edb855afdc672e24ca35b69" +source = "git+https://github.com/darosior/rust-miniscript?branch=multipath_descriptors_on_8.0#a63d5a263a9006b4d29342012133a3bc919765ba" dependencies = [ "bitcoin", "serde", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 1c597ec3..a8b34dcd 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -14,7 +14,7 @@ name = "minisafe-gui" path = "src/main.rs" [dependencies] -async-hwi = { git = "https://github.com/revault/async-hwi", branch = "add-ledger" } +async-hwi = { git = "https://github.com/revault/async-hwi", branch = "master" } minisafe = { git = "https://github.com/revault/minisafe", branch = "master", default-features = false } backtrace = "0.3" diff --git a/gui/src/app/config.rs b/gui/src/app/config.rs index bbfc1970..c0467ed1 100644 --- a/gui/src/app/config.rs +++ b/gui/src/app/config.rs @@ -1,3 +1,4 @@ +use crate::hw::HardwareWalletConfig; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; @@ -9,16 +10,23 @@ pub struct Config { pub log_level: Option, /// Use iced debug feature if true. pub debug: Option, + /// hardware wallets config. + #[serde(default)] + pub hardware_wallets: Vec, } pub const DEFAULT_FILE_NAME: &str = "gui.toml"; impl Config { - pub fn new(minisafed_config_path: PathBuf) -> Self { + pub fn new( + minisafed_config_path: PathBuf, + hardware_wallets: Vec, + ) -> Self { Self { minisafed_config_path, log_level: None, debug: None, + hardware_wallets, } } diff --git a/gui/src/hw.rs b/gui/src/hw.rs index cdbe26d4..ee6eb3cb 100644 --- a/gui/src/hw.rs +++ b/gui/src/hw.rs @@ -1,7 +1,12 @@ +use std::sync::Arc; + use async_hwi::{ledger, specter, DeviceKind, Error as HWIError, HWI}; use log::debug; -use minisafe::miniscript::bitcoin::util::bip32::Fingerprint; -use std::sync::Arc; +use minisafe::miniscript::bitcoin::{ + hashes::hex::{FromHex, ToHex}, + util::bip32::Fingerprint, +}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct HardwareWallet { @@ -22,7 +27,33 @@ impl HardwareWallet { } } -pub async fn list_hardware_wallets() -> Vec { +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct HardwareWalletConfig { + pub kind: String, + pub fingerprint: String, + pub token: String, +} + +impl HardwareWalletConfig { + pub fn new(kind: &async_hwi::DeviceKind, fingerprint: &Fingerprint, token: &[u8; 32]) -> Self { + Self { + kind: kind.to_string(), + fingerprint: fingerprint.to_string(), + token: token.to_hex(), + } + } + + fn token(&self) -> [u8; 32] { + let mut res = [0x00; 32]; + res.copy_from_slice(&Vec::from_hex(&self.token).unwrap()); + res + } +} + +pub async fn list_hardware_wallets( + cfg: &[HardwareWalletConfig], + wallet: Option<(&str, &str)>, +) -> Vec { let mut hws: Vec = Vec::new(); match specter::SpecterSimulator::try_connect().await { Ok(device) => match HardwareWallet::new(Arc::new(device)).await { @@ -49,8 +80,26 @@ pub async fn list_hardware_wallets() -> Vec { } } match ledger::LedgerSimulator::try_connect().await { - Ok(device) => match HardwareWallet::new(Arc::new(device)).await { - Ok(hw) => hws.push(hw), + Ok(mut device) => match device.get_master_fingerprint().await { + Ok(fingerprint) => { + if let Some((name, descriptor)) = wallet { + device + .load_wallet( + name, + descriptor, + cfg.iter() + .find(|cfg| cfg.fingerprint == fingerprint.to_string()) + .map(|cfg| cfg.token()), + ) + .expect("Configuration must be correct"); + } + + hws.push(HardwareWallet { + kind: device.device_kind(), + fingerprint, + device: Arc::new(device), + }); + } Err(e) => { debug!("{}", e); } diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index f75a9304..60d52124 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -12,7 +12,9 @@ use std::convert::TryInto; use std::io::Write; use std::path::PathBuf; -use crate::{app::config as gui_config, installer::config::DEFAULT_FILE_NAME}; +use crate::{ + app::config as gui_config, hw::HardwareWalletConfig, installer::config::DEFAULT_FILE_NAME, +}; pub use message::Message; use step::{Context, DefineBitcoind, DefineDescriptor, Final, RegisterDescriptor, Step, Welcome}; @@ -133,6 +135,12 @@ impl Installer { } pub async fn install(ctx: Context) -> Result { + let hardware_wallets = ctx + .hw_tokens + .iter() + .map(|(kind, fingerprint, token)| HardwareWalletConfig::new(kind, fingerprint, token)) + .collect(); + let mut cfg: minisafe::config::Config = ctx .try_into() .expect("Everything should be checked at this point"); @@ -179,6 +187,7 @@ pub async fn install(ctx: Context) -> Result { e )) })?, + hardware_wallets, )) .unwrap() .as_bytes(), diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 286a63f5..daf02b1d 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -2,10 +2,10 @@ use std::str::FromStr; use iced::{pure::Element, Command}; use minisafe::{ - descriptors::InheritanceDescriptor, + descriptors::MultipathDescriptor, miniscript::{ bitcoin::util::bip32::{DerivationPath, Fingerprint}, - descriptor::{Descriptor, DescriptorPublicKey, DescriptorXKey, Wildcard}, + descriptor::{Descriptor, DescriptorMultiXKey, DescriptorPublicKey, Wildcard}, }, }; @@ -123,7 +123,7 @@ impl Step for DefineDescriptor { } false } else if !self.imported_descriptor.value.is_empty() { - if let Ok(desc) = InheritanceDescriptor::from_str(&self.imported_descriptor.value) { + if let Ok(desc) = MultipathDescriptor::from_str(&self.imported_descriptor.value) { ctx.descriptor = Some(desc); true } else { @@ -144,7 +144,7 @@ impl Step for DefineDescriptor { return false; } - let desc = match InheritanceDescriptor::new( + let desc = match MultipathDescriptor::new( user_key.unwrap(), heir_key.unwrap(), sequence.unwrap(), @@ -207,7 +207,10 @@ impl GetHardwareWalletXpubModal { } } fn load(&self) -> Command { - Command::perform(list_hardware_wallets(), Message::ConnectedHardwareWallets) + Command::perform( + list_hardware_wallets(&[], None), + Message::ConnectedHardwareWallets, + ) } fn update(&mut self, message: Message) -> Command { match message { @@ -276,9 +279,12 @@ async fn get_extended_pubkey( .get_extended_pubkey(&derivation_path, true) .await .map_err(Error::from)?; - Ok(DescriptorPublicKey::XPub(DescriptorXKey { + Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { origin: Some((fingerprint, derivation_path)), - derivation_path: DerivationPath::master(), + derivation_paths: vec![ + DerivationPath::from_str("m/0").unwrap(), + DerivationPath::from_str("m/1").unwrap(), + ], xkey, wildcard: Wildcard::Unhardened, })) @@ -286,7 +292,7 @@ async fn get_extended_pubkey( #[derive(Default)] pub struct RegisterDescriptor { - descriptor: Option, + descriptor: Option, processing: bool, chosen_hw: Option, hws: Vec<(HardwareWallet, Option<[u8; 32]>)>, @@ -348,11 +354,21 @@ impl Step for RegisterDescriptor { }; Command::none() } - fn apply(&mut self, _ctx: &mut Context) -> bool { + fn apply(&mut self, ctx: &mut Context) -> bool { + for (hw, token) in &self.hws { + if let Some(token) = token { + if *token != [0x00; 32] { + ctx.hw_tokens.push((hw.kind, hw.fingerprint, *token)); + } + } + } true } fn load(&self) -> Command { - Command::perform(list_hardware_wallets(), Message::ConnectedHardwareWallets) + Command::perform( + list_hardware_wallets(&[], None), + Message::ConnectedHardwareWallets, + ) } fn view(&self) -> Element { let desc = self.descriptor.as_ref().unwrap(); diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index e9e738d8..f8add589 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -5,10 +5,11 @@ use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; +use async_hwi::DeviceKind; use iced::{pure::Element, Command}; use minisafe::{ config::{BitcoinConfig, BitcoindConfig}, - descriptors::InheritanceDescriptor, + descriptors::MultipathDescriptor, miniscript::bitcoin, }; @@ -40,7 +41,8 @@ pub trait Step { pub struct Context { pub bitcoin_config: BitcoinConfig, pub bitcoind_config: Option, - pub descriptor: Option, + pub descriptor: Option, + pub hw_tokens: Vec<(DeviceKind, bitcoin::util::bip32::Fingerprint, [u8; 32])>, pub data_dir: Option, } @@ -51,6 +53,7 @@ impl Context { network, poll_interval_secs: Duration::from_secs(30), }, + hw_tokens: Vec::new(), bitcoind_config: None, descriptor: None, data_dir, diff --git a/gui/src/utils/mock.rs b/gui/src/utils/mock.rs index 43e9e06b..453703ec 100644 --- a/gui/src/utils/mock.rs +++ b/gui/src/utils/mock.rs @@ -81,7 +81,7 @@ pub fn fake_daemon_config() -> Config { toml::from_str( r#" data_dir = "/home/edouard/code/revault/demo/minisafe/datadir" -main_descriptor = "wsh(or_d(pk(tpubDCbK3Ysvk8HjcF6mPyrgMu3KgLiaaP19RjKpNezd8GrbAbNg6v5BtWLaCt8FNm6QkLseopKLf5MNYQFtochDTKHdfgG6iqJ8cqnLNAwtXuP/*),and_v(v:pkh(tpubDDtb2WPYwEWw2WWDV7reLV348iJHw2HmhzvPysKKrJw3hYmvrd4jasyoioVPdKGQqjyaBMEvTn1HvHWDSVqQ6amyyxRZ5YjpPBBGjJ8yu8S/*),older(100))))#459t6xxr" +main_descriptor = "wsh(or_d(pk(tpubDCbK3Ysvk8HjcF6mPyrgMu3KgLiaaP19RjKpNezd8GrbAbNg6v5BtWLaCt8FNm6QkLseopKLf5MNYQFtochDTKHdfgG6iqJ8cqnLNAwtXuP/<0;1>/*),and_v(v:pkh(tpubDDtb2WPYwEWw2WWDV7reLV348iJHw2HmhzvPysKKrJw3hYmvrd4jasyoioVPdKGQqjyaBMEvTn1HvHWDSVqQ6amyyxRZ5YjpPBBGjJ8yu8S/<0;1>/*),older(100))))#9sx3g3pv" [bitcoin_config] network = "regtest"