Skip to content

Commit e291408

Browse files
ref(hwi): Move hwi out of bdk
Fixes #872
1 parent 9e681b3 commit e291408

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
@@ -21,7 +21,6 @@ serde_json = { version = "^1.0" }
2121
bdk_chain = { path = "../chain", version = "0.6.0", features = ["miniscript", "serde"], default-features = false }
2222

2323
# Optional dependencies
24-
hwi = { version = "0.7.0", optional = true, features = [ "miniscript"] }
2524
bip39 = { version = "1.0.1", optional = true }
2625

2726
[target.'cfg(target_arch = "wasm32")'.dependencies]
@@ -34,8 +33,6 @@ std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
3433
compiler = ["miniscript/compiler"]
3534
all-keys = ["keys-bip39"]
3635
keys-bip39 = ["bip39"]
37-
hardware-signer = ["hwi"]
38-
test-hardware-signer = ["hardware-signer"]
3936

4037
# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
4138
# 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
pub extern crate miniscript;
2321
extern crate serde;
2422
extern crate serde_json;

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 tx_builder;
5050
pub(crate) mod utils;
5151

5252
pub mod error;
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;
@@ -162,16 +163,8 @@ pub enum SignerError {
162163
SighashError(sighash::Error),
163164
/// Miniscript PSBT error
164165
MiniscriptPsbt(MiniscriptPsbtError),
165-
/// Error while signing using hardware wallets
166-
#[cfg(feature = "hardware-signer")]
167-
HWIError(hwi::error::Error),
168-
}
169-
170-
#[cfg(feature = "hardware-signer")]
171-
impl From<hwi::error::Error> for SignerError {
172-
fn from(e: hwi::error::Error) -> Self {
173-
SignerError::HWIError(e)
174-
}
166+
/// A generic error
167+
Generic(String),
175168
}
176169

177170
impl From<sighash::Error> for SignerError {
@@ -196,8 +189,7 @@ impl fmt::Display for SignerError {
196189
Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"),
197190
Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err),
198191
Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err),
199-
#[cfg(feature = "hardware-signer")]
200-
Self::HWIError(err) => write!(f, "Error while signing using hardware wallets: {}", err),
192+
Self::Generic(err) => write!(f, "{}", err),
201193
}
202194
}
203195
}

crates/bdk/tests/wallet.rs

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

3580-
// #[cfg(feature = "test-hardware-signer")]
3581-
// #[test]
3582-
// fn test_hardware_signer() {
3583-
// use std::sync::Arc;
3584-
//
3585-
// use bdk::signer::SignerOrdering;
3586-
// use bdk::wallet::hardwaresigner::HWISigner;
3587-
// use hwi::types::HWIChain;
3588-
// use hwi::HWIClient;
3589-
//
3590-
// let mut devices = HWIClient::enumerate().unwrap();
3591-
// if devices.is_empty() {
3592-
// panic!("No devices found!");
3593-
// }
3594-
// let device = devices.remove(0).unwrap();
3595-
// let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
3596-
// let descriptors = client.get_descriptors::<String>(None).unwrap();
3597-
// let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
3598-
//
3599-
// let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
3600-
// wallet.add_signer(
3601-
// KeychainKind::External,
3602-
// SignerOrdering(200),
3603-
// Arc::new(custom_signer),
3604-
// );
3605-
//
3606-
// let addr = wallet.get_address(LastUnused);
3607-
// let mut builder = wallet.build_tx();
3608-
// builder.drain_to(addr.script_pubkey()).drain_wallet();
3609-
// let (mut psbt, _) = builder.finish().unwrap();
3610-
//
3611-
// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
3612-
// assert!(finalized);
3613-
// }
3614-
36153580
#[test]
36163581
fn test_taproot_load_descriptor_duplicated_keys() {
36173582
// 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)