Skip to content

Commit 4049c70

Browse files
ref(hwi): Move hwi out of bdk
Fixes #872
1 parent 0a7b60f commit 4049c70

File tree

9 files changed

+154
-56
lines changed

9 files changed

+154
-56
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ members = [
77
"crates/electrum",
88
"crates/esplora",
99
"crates/bitcoind_rpc",
10+
"crates/hwi",
1011
"example-crates/example_cli",
1112
"example-crates/example_electrum",
1213
"example-crates/example_esplora",

crates/bdk/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ serde_json = { version = "^1.0" }
2222
bdk_chain = { path = "../chain", version = "0.6.0", features = ["miniscript", "serde"], default-features = false }
2323

2424
# Optional dependencies
25-
hwi = { version = "0.7.0", optional = true, features = [ "miniscript"] }
2625
bip39 = { version = "1.0.1", optional = true }
2726

2827
[target.'cfg(target_arch = "wasm32")'.dependencies]
@@ -35,8 +34,6 @@ std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
3534
compiler = ["miniscript/compiler"]
3635
all-keys = ["keys-bip39"]
3736
keys-bip39 = ["bip39"]
38-
hardware-signer = ["hwi"]
39-
test-hardware-signer = ["hardware-signer"]
4037

4138
# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
4239
# for libraries to explicitly include the "getrandom/js" feature, so we only do it when

crates/bdk/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ extern crate std;
1717
pub extern crate alloc;
1818

1919
pub extern crate bitcoin;
20-
#[cfg(feature = "hardware-signer")]
21-
pub extern crate hwi;
2220
extern crate log;
2321
pub extern crate miniscript;
2422
extern crate serde;

crates/bdk/src/wallet/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ pub mod signer;
5050
pub mod tx_builder;
5151
pub(crate) mod utils;
5252

53-
#[cfg(feature = "hardware-signer")]
54-
#[cfg_attr(docsrs, doc(cfg(feature = "hardware-signer")))]
55-
pub mod hardwaresigner;
56-
5753
pub use utils::IsDust;
5854

5955
#[allow(deprecated)]

crates/bdk/src/wallet/signer.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
//! ```
8181
8282
use crate::collections::BTreeMap;
83+
use alloc::string::String;
8384
use alloc::sync::Arc;
8485
use alloc::vec::Vec;
8586
use core::cmp::Ordering;
@@ -159,16 +160,8 @@ pub enum SignerError {
159160
InvalidSighash,
160161
/// Error while computing the hash to sign
161162
SighashError(sighash::Error),
162-
/// Error while signing using hardware wallets
163-
#[cfg(feature = "hardware-signer")]
164-
HWIError(hwi::error::Error),
165-
}
166-
167-
#[cfg(feature = "hardware-signer")]
168-
impl From<hwi::error::Error> for SignerError {
169-
fn from(e: hwi::error::Error) -> Self {
170-
SignerError::HWIError(e)
171-
}
163+
/// A generic error
164+
Generic(String),
172165
}
173166

174167
impl From<sighash::Error> for SignerError {
@@ -192,8 +185,7 @@ impl fmt::Display for SignerError {
192185
Self::NonStandardSighash => write!(f, "The psbt contains a non standard sighash"),
193186
Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"),
194187
Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err),
195-
#[cfg(feature = "hardware-signer")]
196-
Self::HWIError(err) => write!(f, "Error while signing using hardware wallets: {}", err),
188+
Self::Generic(err) => write!(f, "{}", err),
197189
}
198190
}
199191
}

crates/bdk/tests/wallet.rs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3461,41 +3461,6 @@ fn test_fee_rate_sign_grinding_low_r() {
34613461
assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate);
34623462
}
34633463

3464-
// #[cfg(feature = "test-hardware-signer")]
3465-
// #[test]
3466-
// fn test_hardware_signer() {
3467-
// use std::sync::Arc;
3468-
//
3469-
// use bdk::signer::SignerOrdering;
3470-
// use bdk::wallet::hardwaresigner::HWISigner;
3471-
// use hwi::types::HWIChain;
3472-
// use hwi::HWIClient;
3473-
//
3474-
// let mut devices = HWIClient::enumerate().unwrap();
3475-
// if devices.is_empty() {
3476-
// panic!("No devices found!");
3477-
// }
3478-
// let device = devices.remove(0).unwrap();
3479-
// let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
3480-
// let descriptors = client.get_descriptors::<String>(None).unwrap();
3481-
// let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
3482-
//
3483-
// let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
3484-
// wallet.add_signer(
3485-
// KeychainKind::External,
3486-
// SignerOrdering(200),
3487-
// Arc::new(custom_signer),
3488-
// );
3489-
//
3490-
// let addr = wallet.get_address(LastUnused);
3491-
// let mut builder = wallet.build_tx();
3492-
// builder.drain_to(addr.script_pubkey()).drain_wallet();
3493-
// let (mut psbt, _) = builder.finish().unwrap();
3494-
//
3495-
// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
3496-
// assert!(finalized);
3497-
// }
3498-
34993464
#[test]
35003465
fn test_taproot_load_descriptor_duplicated_keys() {
35013466
// Added after issue https://github.com/bitcoindevkit/bdk/issues/760

crates/hwi/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "bdk_hwi"
3+
version = "0.1.0"
4+
edition = "2021"
5+
homepage = "https://bitcoindevkit.org"
6+
repository = "https://github.com/bitcoindevkit/bdk"
7+
description = "Utilities to use bdk with hardware wallets"
8+
license = "MIT OR Apache-2.0"
9+
readme = "README.md"
10+
11+
[dependencies]
12+
bdk = { path = "../bdk" }
13+
hwi = { version = "0.7.0", features = [ "miniscript"] }

crates/hwi/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! HWI Signer
2+
//!
3+
//! This crate contains HWISigner, an implementation of a [`TransactionSigner`] to be
4+
//! used with hardware wallets.
5+
//! ```no_run
6+
//! # use bdk::bitcoin::Network;
7+
//! # use bdk::signer::SignerOrdering;
8+
//! # use bdk_hwi::HWISigner;
9+
//! # use bdk::wallet::AddressIndex::New;
10+
//! # use bdk::{FeeRate, KeychainKind, SignOptions, Wallet};
11+
//! # use hwi::HWIClient;
12+
//! # use std::sync::Arc;
13+
//! #
14+
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15+
//! let mut devices = HWIClient::enumerate()?;
16+
//! if devices.is_empty() {
17+
//! panic!("No devices found!");
18+
//! }
19+
//! let first_device = devices.remove(0)?;
20+
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
21+
//!
22+
//! # let mut wallet = Wallet::new_no_persist(
23+
//! # "",
24+
//! # None,
25+
//! # Network::Testnet,
26+
//! # )?;
27+
//! #
28+
//! // Adding the hardware signer to the BDK wallet
29+
//! wallet.add_signer(
30+
//! KeychainKind::External,
31+
//! SignerOrdering(200),
32+
//! Arc::new(custom_signer),
33+
//! );
34+
//!
35+
//! # Ok(())
36+
//! # }
37+
//! ```
38+
//!
39+
//! [`TransactionSigner`]: bdk::wallet::signer::TransactionSigner
40+
41+
mod signer;
42+
pub use signer::*;

crates/hwi/src/signer.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use bdk::bitcoin::bip32::Fingerprint;
2+
use bdk::bitcoin::psbt::PartiallySignedTransaction;
3+
use bdk::bitcoin::secp256k1::{All, Secp256k1};
4+
5+
use hwi::error::Error;
6+
use hwi::types::{HWIChain, HWIDevice};
7+
use hwi::HWIClient;
8+
9+
use bdk::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
10+
11+
#[derive(Debug)]
12+
/// Custom signer for Hardware Wallets
13+
///
14+
/// This ignores `sign_options` and leaves the decisions up to the hardware wallet.
15+
pub struct HWISigner {
16+
fingerprint: Fingerprint,
17+
client: HWIClient,
18+
}
19+
20+
impl HWISigner {
21+
/// Create a instance from the specified device and chain
22+
pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result<HWISigner, Error> {
23+
let client = HWIClient::get_client(device, false, chain)?;
24+
Ok(HWISigner {
25+
fingerprint: device.fingerprint,
26+
client,
27+
})
28+
}
29+
}
30+
31+
impl SignerCommon for HWISigner {
32+
fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
33+
SignerId::Fingerprint(self.fingerprint)
34+
}
35+
}
36+
37+
impl TransactionSigner for HWISigner {
38+
fn sign_transaction(
39+
&self,
40+
psbt: &mut PartiallySignedTransaction,
41+
_sign_options: &bdk::SignOptions,
42+
_secp: &Secp256k1<All>,
43+
) -> Result<(), SignerError> {
44+
psbt.combine(
45+
self.client
46+
.sign_tx(psbt)
47+
.map_err(|e| {
48+
SignerError::Generic(format!("While signing with hardware wallet: {}", e))
49+
})?
50+
.psbt,
51+
)
52+
.expect("Failed to combine HW signed psbt with passed PSBT");
53+
Ok(())
54+
}
55+
}
56+
57+
// TODO: re-enable this once we have the `get_funded_wallet` test util
58+
// #[cfg(test)]
59+
// mod tests {
60+
// #[test]
61+
// fn test_hardware_signer() {
62+
// use std::sync::Arc;
63+
//
64+
// use bdk::tests::get_funded_wallet;
65+
// use bdk::signer::SignerOrdering;
66+
// use bdk::bitcoin::Network;
67+
// use crate::HWISigner;
68+
// use hwi::HWIClient;
69+
//
70+
// let mut devices = HWIClient::enumerate().unwrap();
71+
// if devices.is_empty() {
72+
// panic!("No devices found!");
73+
// }
74+
// let device = devices.remove(0).unwrap();
75+
// let client = HWIClient::get_client(&device, true, Network::Regtest.into()).unwrap();
76+
// let descriptors = client.get_descriptors::<String>(None).unwrap();
77+
// let custom_signer = HWISigner::from_device(&device, Network::Regtest.into()).unwrap();
78+
//
79+
// let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
80+
// wallet.add_signer(
81+
// bdk::KeychainKind::External,
82+
// SignerOrdering(200),
83+
// Arc::new(custom_signer),
84+
// );
85+
//
86+
// let addr = wallet.get_address(bdk::wallet::AddressIndex::LastUnused);
87+
// let mut builder = wallet.build_tx();
88+
// builder.drain_to(addr.script_pubkey()).drain_wallet();
89+
// let (mut psbt, _) = builder.finish().unwrap();
90+
//
91+
// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
92+
// assert!(finalized);
93+
// }
94+
// }

0 commit comments

Comments
 (0)