From 562cccfbebf58e6937d7f2e3bbe77673d229ef24 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 15 May 2026 11:28:10 +0200 Subject: [PATCH 1/2] refactor: generalize PSBT pruning Trezor will use it as well. --- liana-gui/src/app/state/psbt.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/liana-gui/src/app/state/psbt.rs b/liana-gui/src/app/state/psbt.rs index a6353f512..065df97c0 100644 --- a/liana-gui/src/app/state/psbt.rs +++ b/liana-gui/src/app/state/psbt.rs @@ -659,10 +659,10 @@ async fn sign_psbt( hw: std::sync::Arc, mut psbt: Psbt, ) -> Result { - // The BitBox02 is only going to produce a signature for a single key in the Script. In order - // to make sure it doesn't sign for a public key from another spending path we remove the BIP32 + // Some devices are only going to produce a signature for a single key in the Script. In order + // to make sure they don't sign for a public key from another spending path we remove the BIP32 // derivation for the other paths. - if matches!(hw.device_kind(), async_hwi::DeviceKind::BitBox02) { + if hw.device_kind().requires_psbt_pruning() { // We need to make sure we don't prune the BIP32 derivations from the original PSBT (which // would end up being updated in the daemon's database and erase the previously unpruned // one). To this end we create a new, pruned, psbt we use for signing and then merge its From 2428fabce2d7d6e868e27a1cfb350ee41e11f97c Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 14 Dec 2025 16:27:26 +0100 Subject: [PATCH 2/2] feat: support Trezor connection --- Cargo.lock | 108 ++++++++++++++++++++++++++++++++++++++++--- liana-gui/Cargo.toml | 2 +- liana-gui/src/hw.rs | 79 ++++++++++++++++++++++++++++++- 3 files changed, 180 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b69f648b6..f34b03f92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,7 +301,7 @@ dependencies = [ "hidapi", "ledger-apdu", "ledger-transport-hidapi", - "ledger_bitcoin_client", + "ledger_bitcoin_client 0.5.0", "regex", "reqwest", "serde", @@ -313,6 +313,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "async-hwi" +version = "0.0.31" +source = "git+https://github.com/romanz/async-hwi?branch=trezor-miniscript#8da18fc2cefdeff54f8387e8362165341d23589b" +dependencies = [ + "async-trait", + "bitbox-api", + "bitcoin", + "coldcard", + "crossbeam", + "futures", + "hex", + "hidapi", + "ledger-apdu", + "ledger-transport-hidapi", + "ledger_bitcoin_client 0.6.2", + "regex", + "reqwest", + "serde", + "serde_bytes", + "serde_cbor", + "serialport", + "tokio", + "tokio-serial", + "tracing", + "trezor-client", +] + [[package]] name = "async-io" version = "2.4.0" @@ -569,7 +597,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes 0.12.0", "serde", "unicode-normalization", ] @@ -800,7 +828,7 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" name = "business-installer" version = "1.0.0" dependencies = [ - "async-hwi", + "async-hwi 0.0.31 (registry+https://github.com/rust-lang/crates.io-index)", "chrono", "crossbeam", "email_address", @@ -3171,6 +3199,17 @@ dependencies = [ "miniscript", ] +[[package]] +name = "ledger_bitcoin_client" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8007335257021d1d294df3714fe4648e47fd2467bb43e43566e2d4d04ce61a" +dependencies = [ + "async-trait", + "bitcoin", + "miniscript", +] + [[package]] name = "liana" version = "14.0.0" @@ -3188,7 +3227,7 @@ dependencies = [ name = "liana-business" version = "1.0.0" dependencies = [ - "async-hwi", + "async-hwi 0.0.31 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace", "business-installer", "iced", @@ -3238,7 +3277,7 @@ name = "liana-gui" version = "14.0.0" dependencies = [ "async-fd-lock", - "async-hwi", + "async-hwi 0.0.31 (git+https://github.com/romanz/async-hwi?branch=trezor-miniscript)", "async-trait", "backtrace", "base64 0.21.7", @@ -3381,6 +3420,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libusb1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "lilt" version = "0.8.1" @@ -4608,6 +4659,26 @@ dependencies = [ "prost 0.11.9", ] +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "qoi" version = "0.4.1" @@ -5012,6 +5083,16 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rusb" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" +dependencies = [ + "libc", + "libusb1-sys", +] + [[package]] name = "rusqlite" version = "0.30.0" @@ -5249,7 +5330,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes 0.14.0", + "bitcoin_hashes 0.12.0", "secp256k1-sys", "serde", ] @@ -6179,6 +6260,21 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trezor-client" +version = "0.1.5" +source = "git+https://github.com/trezor/trezor-firmware?branch=romanz%2F2605%2Frust-miniscript#d47a1f09573ba3463b1d6512a7cfba6fd0f017b6" +dependencies = [ + "bitcoin", + "byteorder", + "hex", + "protobuf", + "rusb", + "thiserror 2.0.17", + "tracing", + "unicode-normalization", +] + [[package]] name = "try-lock" version = "0.2.5" diff --git a/liana-gui/Cargo.toml b/liana-gui/Cargo.toml index 2c9a94243..ad291fbe1 100644 --- a/liana-gui/Cargo.toml +++ b/liana-gui/Cargo.toml @@ -18,7 +18,7 @@ path = "src/main.rs" [dependencies] async-trait = { workspace = true } -async-hwi = { workspace = true } +async-hwi = { git = "https://github.com/romanz/async-hwi", branch = "trezor-miniscript" } liana = { path = "../liana" } liana-connect = { workspace = true } lianad = { path = "../lianad", default-features = false, features = ["nonblocking_shutdown"] } diff --git a/liana-gui/src/hw.rs b/liana-gui/src/hw.rs index cee7f64ba..ee74997a9 100644 --- a/liana-gui/src/hw.rs +++ b/liana-gui/src/hw.rs @@ -12,12 +12,14 @@ use async_hwi::{ bitbox::{api::runtime, BitBox02, PairingBitbox02}, coldcard, jade::{self, Jade}, - ledger, specter, DeviceKind, Error as HWIError, Version, HWI, + ledger, specter, + trezor::{self, TrezorClient, WalletPolicy}, + DeviceKind, Error as HWIError, Version, HWI, }; use iced::futures::{SinkExt, Stream}; use liana::miniscript::bitcoin::{bip32::Fingerprint, hashes::hex::FromHex, Network}; use serde::{Deserialize, Serialize}; -use tracing::{debug, warn}; +use tracing::{debug, error, warn}; #[derive(Debug, Clone)] pub enum UnsupportedReason { @@ -413,6 +415,35 @@ fn refresh(mut state: State) -> impl Stream { let mut hws: Vec = Vec::new(); let mut still: Vec = Vec::new(); + + for device in TrezorClient::find_devices() { + let id = format!("{:?}", device); + if state.connected_supported_hws.contains(&id) { + still.push(id); + continue; + } + let client = match trezor::TrezorClient::connect(device, state.network) { + Ok(client) => client, + Err(err) => { + error!("{} connection failed: {}", id, err); + continue; + } + }; + match handle_trezor_device( + id, + client, + state.wallet.as_ref().map(|w| w.as_ref()), + &state.keys_aliases, + ) + .await + { + Ok(hw) => hws.push(hw), + Err(e) => { + error!("Failed to create a wallet: {}", e); + } + } + } + match specter::SpecterSimulator::try_connect().await { Ok(device) => { let id = "specter-simulator".to_string(); @@ -714,6 +745,50 @@ fn refresh(mut state: State) -> impl Stream { }) } +async fn handle_trezor_device( + id: String, + mut device: TrezorClient, + wallet: Option<&Wallet>, + keys_aliases: &HashMap, +) -> Result { + // TODO: handle the case where the device is not supported + match ( + device.get_master_fingerprint().await, + device.get_version().await, + ) { + (Ok(fingerprint), Ok(version)) => { + let mut registered = false; + if let Some(w) = &wallet { + if let Some(cfg) = w + .hardware_wallets + .iter() + .find(|cfg| cfg.fingerprint == fingerprint) + { + let policy = w.main_descriptor.to_string(); + let wallet = WalletPolicy::new(&w.name, &policy, cfg.token()); + device = device.with_wallet(wallet)?; + registered = true; + } + } + Ok(HardwareWallet::Supported { + id, + device: Arc::new(device), + kind: DeviceKind::Trezor, + fingerprint, + version: Some(version), + registered: Some(registered), + alias: keys_aliases.get(&fingerprint).cloned(), + }) + } + (_, _) => Ok(HardwareWallet::Unsupported { + id, + kind: DeviceKind::Trezor, + version: None, + reason: UnsupportedReason::AppIsNotOpen, + }), + } +} + async fn handle_ledger_device<'a, T: async_hwi::ledger::Transport + Sync + Send + 'static>( id: String, mut device: ledger::Ledger,