Skip to content

Commit

Permalink
Merge #706: Add bitbox support
Browse files Browse the repository at this point in the history
4be74ad refac hw module and add bitbox support (edouard)

Pull request description:

  - Add bitbox02 as possible signer to liana-gui
  - Introduce global_settings.json in datadir to keep the bitbox noise config.

ACKs for top commit:
  edouardparis:
    Self-ACK 4be74ad

Tree-SHA512: e0615e1903baa7faecc25db717f49ed71c23a6afcec607c41ac2045cfb2cfa9a9aa3d3452cc952aeaf72a735d670c56dbf19b239c8d665466a2ecb09fbe222c5
  • Loading branch information
edouardparis committed Oct 24, 2023
2 parents ec2e2e5 + 4be74ad commit 1a59d03
Show file tree
Hide file tree
Showing 28 changed files with 1,652 additions and 682 deletions.
651 changes: 614 additions & 37 deletions gui/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ name = "liana-gui"
path = "src/main.rs"

[dependencies]
async-hwi = "0.0.11"
async-hwi = "0.0.12"
liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false, features = ["nonblocking_shutdown"] }
liana_ui = { path = "ui" }
backtrace = "0.3"
Expand Down
4 changes: 4 additions & 0 deletions gui/src/app/cache.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
use crate::daemon::model::{Coin, SpendTx};
use liana::miniscript::bitcoin::Network;
use std::path::PathBuf;

#[derive(Debug)]
pub struct Cache {
pub datadir_path: PathBuf,
pub network: Network,
pub blockheight: i32,
pub coins: Vec<Coin>,
pub spend_txs: Vec<SpendTx>,
pub rescan_progress: Option<f64>,
}

/// only used for tests.
impl std::default::Default for Cache {
fn default() -> Self {
Self {
datadir_path: std::path::PathBuf::new(),
network: Network::Bitcoin,
blockheight: 0,
coins: Vec::new(),
Expand Down
4 changes: 2 additions & 2 deletions gui/src/app/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use liana::{
use crate::{
app::{error::Error, view, wallet::Wallet},
daemon::model::*,
hw::HardwareWallet,
hw::HardwareWalletMessage,
};

#[derive(Debug)]
Expand All @@ -32,7 +32,7 @@ pub enum Message {
Updated(Result<(), Error>),
Saved(Result<(), Error>),
StartRescan(Result<(), Error>),
ConnectedHardwareWallets(Vec<HardwareWallet>),
HardwareWallets(HardwareWalletMessage),
HistoryTransactions(Result<Vec<HistoryTransaction>, Error>),
PendingTransactions(Result<Vec<HistoryTransaction>, Error>),
LabelsUpdated(Result<HashMap<String, Option<String>>, Error>),
Expand Down
96 changes: 96 additions & 0 deletions gui/src/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,99 @@ impl std::fmt::Display for SettingsError {
}
}
}

/// global settings.
pub mod global {
use async_hwi::bitbox::{ConfigError, NoiseConfig, NoiseConfigData};
use serde::{Deserialize, Serialize};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};

pub const DEFAULT_FILE_NAME: &str = "global_settings.json";

#[derive(Debug, Deserialize, Serialize)]
pub struct Settings {
pub bitbox: Option<BitboxSettings>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct BitboxSettings {
pub noise_config: NoiseConfigData,
}

pub struct PersistedBitboxNoiseConfig {
file_path: PathBuf,
}

impl async_hwi::bitbox::api::Threading for PersistedBitboxNoiseConfig {}

impl PersistedBitboxNoiseConfig {
/// Creates a new persisting noise config, which stores the pairing information in "bitbox.json"
/// in the provided directory.
pub fn new(global_datadir: &Path) -> PersistedBitboxNoiseConfig {
PersistedBitboxNoiseConfig {
file_path: global_datadir.join(DEFAULT_FILE_NAME),
}
}
}

impl NoiseConfig for PersistedBitboxNoiseConfig {
fn read_config(&self) -> Result<NoiseConfigData, async_hwi::bitbox::api::ConfigError> {
if !self.file_path.exists() {
return Ok(NoiseConfigData::default());
}

let mut file =
std::fs::File::open(&self.file_path).map_err(|e| ConfigError(e.to_string()))?;

let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|e| ConfigError(e.to_string()))?;

let settings = serde_json::from_str::<Settings>(&contents)
.map_err(|e| ConfigError(e.to_string()))?;

Ok(settings
.bitbox
.map(|s| s.noise_config)
.unwrap_or_else(NoiseConfigData::default))
}

fn store_config(&self, conf: &NoiseConfigData) -> Result<(), ConfigError> {
let data = if self.file_path.exists() {
let mut file =
std::fs::File::open(&self.file_path).map_err(|e| ConfigError(e.to_string()))?;

let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|e| ConfigError(e.to_string()))?;

let mut settings = serde_json::from_str::<Settings>(&contents)
.map_err(|e| ConfigError(e.to_string()))?;

settings.bitbox = Some(BitboxSettings {
noise_config: conf.clone(),
});

serde_json::to_string_pretty(&settings).map_err(|e| ConfigError(e.to_string()))?
} else {
serde_json::to_string_pretty(&Settings {
bitbox: Some(BitboxSettings {
noise_config: conf.clone(),
}),
})
.map_err(|e| ConfigError(e.to_string()))?
};

let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&self.file_path)
.map_err(|e| ConfigError(e.to_string()))?;

file.write_all(data.as_bytes())
.map_err(|e| ConfigError(e.to_string()))
}
}
}
82 changes: 49 additions & 33 deletions gui/src/app/state/psbt.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Arc;

use iced::Subscription;

use iced::Command;
use liana::{
descriptors::LianaPolicy,
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt},
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt, Network},
};

use liana_ui::{
Expand All @@ -25,7 +28,7 @@ use crate::{
model::{LabelItem, Labelled, SpendStatus, SpendTx},
Daemon,
},
hw::{list_hardware_wallets, HardwareWallet},
hw::{HardwareWallet, HardwareWallets},
};

pub trait Action {
Expand All @@ -35,6 +38,9 @@ pub trait Action {
fn load(&self, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
Subscription::none()
}
fn update(
&mut self,
_daemon: Arc<dyn Daemon + Sync + Send>,
Expand Down Expand Up @@ -69,6 +75,14 @@ impl PsbtState {
}
}

pub fn subscription(&self) -> Subscription<Message> {
if let Some(action) = &self.action {
action.subscription()
} else {
Subscription::none()
}
}

pub fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
if let Some(action) = &self.action {
action.load(daemon)
Expand All @@ -80,7 +94,7 @@ impl PsbtState {
pub fn update(
&mut self,
daemon: Arc<dyn Daemon + Sync + Send>,
_cache: &Cache,
cache: &Cache,
message: Message,
) -> Command<Message> {
match &message {
Expand All @@ -92,7 +106,12 @@ impl PsbtState {
self.action = Some(Box::<DeleteAction>::default());
}
view::SpendTxMessage::Sign => {
let action = SignAction::new(self.tx.signers(), self.wallet.clone());
let action = SignAction::new(
self.tx.signers(),
self.wallet.clone(),
cache.datadir_path.clone(),
cache.network,
);
let cmd = action.load(daemon);
self.action = Some(Box::new(action));
return cmd;
Expand Down Expand Up @@ -296,18 +315,23 @@ pub struct SignAction {
wallet: Arc<Wallet>,
chosen_hw: Option<usize>,
processing: bool,
hws: Vec<HardwareWallet>,
hws: HardwareWallets,
error: Option<Error>,
signed: HashSet<Fingerprint>,
}

impl SignAction {
pub fn new(signed: HashSet<Fingerprint>, wallet: Arc<Wallet>) -> Self {
pub fn new(
signed: HashSet<Fingerprint>,
wallet: Arc<Wallet>,
datadir_path: PathBuf,
network: Network,
) -> Self {
Self {
wallet,
chosen_hw: None,
processing: false,
hws: Vec::new(),
hws: HardwareWallets::new(datadir_path, network).with_wallet(wallet.clone()),
wallet,
error: None,
signed,
}
Expand All @@ -319,13 +343,10 @@ impl Action for SignAction {
self.error.as_ref()
}

fn load(&self, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
let wallet = self.wallet.clone();
Command::perform(
async move { list_hardware_wallets(&wallet).await },
Message::ConnectedHardwareWallets,
)
fn subscription(&self) -> Subscription<Message> {
self.hws.refresh().map(Message::HardwareWallets)
}

fn update(
&mut self,
daemon: Arc<dyn Daemon + Sync + Send>,
Expand All @@ -338,7 +359,7 @@ impl Action for SignAction {
fingerprint,
device,
..
}) = self.hws.get(i)
}) = self.hws.list.get(i)
{
self.chosen_hw = Some(i);
self.processing = true;
Expand Down Expand Up @@ -372,28 +393,23 @@ impl Action for SignAction {
Message::Updated(res) => match res {
Ok(()) => {
self.processing = false;
tx.sigs = self
.wallet
.main_descriptor
.partial_spend_info(&tx.psbt)
.unwrap();
match self.wallet.main_descriptor.partial_spend_info(&tx.psbt) {
Ok(sigs) => tx.sigs = sigs,
Err(e) => self.error = Some(Error::Unexpected(e.to_string())),
}
}
Err(e) => self.error = Some(e),
},
// We add the new hws without dropping the reference of the previous ones.
Message::ConnectedHardwareWallets(hws) => {
for h in hws {
if !self
.hws
.iter()
.any(|hw| hw.fingerprint() == hw.fingerprint() && hw.kind() == h.kind())
{
self.hws.push(h);
}

Message::HardwareWallets(msg) => match self.hws.update(msg) {
Ok(cmd) => {
return cmd.map(Message::HardwareWallets);
}
}
Err(e) => {
self.error = Some(e.into());
}
},
Message::View(view::Message::Reload) => {
self.hws = Vec::new();
self.chosen_hw = None;
self.error = None;
return self.load(daemon);
Expand All @@ -405,7 +421,7 @@ impl Action for SignAction {
fn view(&self) -> Element<view::Message> {
view::psbt::sign_action(
self.error.as_ref(),
&self.hws,
&self.hws.list,
self.wallet.signer.as_ref().map(|s| s.fingerprint()),
self.wallet
.signer
Expand Down
10 changes: 9 additions & 1 deletion gui/src/app/state/psbts.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Arc;

use iced::Command;
use iced::{Command, Subscription};

use liana::miniscript::bitcoin::psbt::Psbt;
use liana_ui::{
Expand Down Expand Up @@ -109,6 +109,14 @@ impl State for PsbtsPanel {
Command::none()
}

fn subscription(&self) -> Subscription<Message> {
if let Some(psbt) = &self.selected_tx {
psbt.subscription()
} else {
Subscription::none()
}
}

fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
let daemon = daemon.clone();
Command::perform(
Expand Down
18 changes: 9 additions & 9 deletions gui/src/app/state/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ impl RecoveryPanel {
}

impl State for RecoveryPanel {
fn subscription(&self) -> iced::Subscription<Message> {
if let Some(psbt) = &self.generated {
psbt.subscription()
} else {
iced::Subscription::none()
}
}

fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
if let Some(generated) = &self.generated {
generated.view(cache)
Expand Down Expand Up @@ -154,15 +162,7 @@ impl State for RecoveryPanel {
.any(|input| input.previous_output == coin.outpoint)
})
.collect();
let sigs = desc.partial_spend_info(&psbt).unwrap();
Ok(SpendTx::new(
None,
psbt,
coins,
sigs,
desc.max_sat_vbytes(),
network,
))
Ok(SpendTx::new(None, psbt, coins, &desc, network))
},
Message::Recovery,
);
Expand Down
8 changes: 8 additions & 0 deletions gui/src/app/state/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ impl State for SettingsState {
}
}

fn subscription(&self) -> iced::Subscription<Message> {
if let Some(setting) = &self.setting {
setting.subscription()
} else {
iced::Subscription::none()
}
}

fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
if let Some(setting) = &self.setting {
setting.view(cache)
Expand Down
Loading

0 comments on commit 1a59d03

Please sign in to comment.