Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.

Commit 4004b0a

Browse files
committed
Merge #104: feat: add HWISigner, moved from bdk_hwi
7747b3f refactor(signer)!: add `signer` behind a non-default feature (Leonardo Lima) b25168a feat: add `HWISigner`, moved from `bdk_hwi` (Leonardo Lima) Pull request description: partially addresses bitcoindevkit/bdk#1516 ## Description - adds a new `signer.rs` that contains the previous implementation of `HWISigner`, which implements `bdk_wallet::signer::{SignerCommon, TransactionSigner}` traits. - expose the new `signer::HWISigner` as public. - updates the crate documentation. - TODO: re-add test that relies on `bdk_wallet::tests::common::get_funded_wallet` helper methods. ## Notes for Reviewers I'm unsure if the documentation covers everything needed, please let me know if I'm missing something. ACKs for top commit: notmandatory: ACK 7747b3f Tree-SHA512: 8be7d681454eba31b5495dcf6cb9b399e2d992760f92f4ffc1d553908872b1272bb2e792ca608b679caf95b66126846bbb4dff37eb6f067a8e28d14454554098
2 parents 5e88904 + 7747b3f commit 4004b0a

4 files changed

Lines changed: 112 additions & 6 deletions

File tree

.github/workflows/main.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ jobs:
4242
matrix:
4343
rust:
4444
- version: stable # STABLE
45-
features: miniscript
4645
- version: 1.63.0 # MSRV
47-
features: miniscript
46+
features:
47+
- miniscript
48+
- signer
4849
emulator:
4950
- name: trezor
5051
- name: ledger
@@ -77,7 +78,7 @@ jobs:
7778
- name: Update toolchain
7879
run: rustup update
7980
- name: Test
80-
run: cargo test --features ${{ matrix.rust.features }}
81+
run: cargo test --features ${{ matrix.features }}
8182
- name: Wipe
8283
run: cargo test test_wipe_device -- --ignored
8384
test-readme-examples:

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ readme = "README.md"
1212

1313
[dependencies]
1414
bitcoin = { version = "0.32", features = ["serde", "base64"] }
15+
pyo3 = { version = "0.21.2", features = ["auto-initialize"] }
1516
serde = { version = "^1.0", features = ["derive"] }
1617
serde_json = { version = "^1.0" }
17-
pyo3 = { version = "0.21.2", features = ["auto-initialize"] }
1818

19+
bdk_wallet = { version = "1.0.0-beta.1", optional = true }
1920
miniscript = { version = "12.0", features = ["serde"], optional = true }
2021

2122
[dev-dependencies]
2223
serial_test = "0.6.0"
2324

2425
[features]
2526
doctest = []
27+
signer = ["dep:bdk_wallet"]
28+
miniscript = ["dep:miniscript"]

src/lib.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
//! Rust wrapper for the [Bitcoin Hardware Wallet Interface](https://github.com/bitcoin-core/HWI/).
1+
//! This crate is contains both:
2+
//! - [`HWIClient`]: A Rust wrapper for the [Bitcoin Hardware Wallet Interface](https://github.com/bitcoin-core/HWI/).
3+
//! - [`HWISigner`]: An implementation of a [`TransactionSigner`] to be used with hardware wallets, that relies on [`HWIClient`].
24
//!
3-
//! # Example - display address with path
5+
//! # HWIClient Example:
6+
//! ## Display address with path
47
//! ```no_run
58
//! use bitcoin::bip32::{ChildNumber, DerivationPath};
69
//! use hwi::error::Error;
@@ -25,18 +28,62 @@
2528
//! Ok(())
2629
//! }
2730
//! ```
31+
//!
32+
//! # HWISigner Example:
33+
//! ## Add custom [`HWISigner`] to [`Wallet`]
34+
//! ```no_run
35+
//! # #[cfg(feature = "signer")]
36+
//! # {
37+
//! use bdk_wallet::bitcoin::Network;
38+
//! use bdk_wallet::descriptor::Descriptor;
39+
//! use bdk_wallet::signer::SignerOrdering;
40+
//! use bdk_wallet::{KeychainKind, SignOptions, Wallet};
41+
//! use hwi::{HWIClient, HWISigner};
42+
//! use std::str::FromStr;
43+
//! use std::sync::Arc;
44+
//!
45+
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
46+
//! let mut devices = HWIClient::enumerate()?;
47+
//! if devices.is_empty() {
48+
//! panic!("No devices found!");
49+
//! }
50+
//! let first_device = devices.remove(0)?;
51+
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
52+
//!
53+
//! let mut wallet = Wallet::create("", "")
54+
//! .network(Network::Testnet)
55+
//! .create_wallet_no_persist()?;
56+
//!
57+
//! // Adding the hardware signer to the BDK wallet
58+
//! wallet.add_signer(
59+
//! KeychainKind::External,
60+
//! SignerOrdering(200),
61+
//! Arc::new(custom_signer),
62+
//! );
63+
//!
64+
//! Ok(())
65+
//! }
66+
//! # }
67+
//! ```
68+
//!
69+
//! [`TransactionSigner`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/signer/trait.TransactionSigner.html
70+
//! [`Wallet`]: https://docs.rs/bdk_wallet/1.0.0-beta.1/bdk_wallet/struct.Wallet.html
2871
2972
#[cfg(test)]
3073
#[macro_use]
3174
extern crate serial_test;
3275
extern crate core;
3376

3477
pub use interface::HWIClient;
78+
#[cfg(feature = "signer")]
79+
pub use signer::HWISigner;
3580

3681
#[cfg(feature = "doctest")]
3782
pub mod doctest;
3883
pub mod error;
3984
pub mod interface;
85+
#[cfg(feature = "signer")]
86+
pub mod signer;
4087
pub mod types;
4188

4289
#[cfg(test)]

src/signer.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use bdk_wallet::bitcoin::bip32::Fingerprint;
2+
use bdk_wallet::bitcoin::secp256k1::{All, Secp256k1};
3+
use bdk_wallet::bitcoin::Psbt;
4+
5+
use crate::error::Error;
6+
use crate::types::{HWIChain, HWIDevice};
7+
use crate::HWIClient;
8+
9+
use bdk_wallet::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 an 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 Psbt,
41+
_sign_options: &bdk_wallet::SignOptions,
42+
_secp: &Secp256k1<All>,
43+
) -> Result<(), SignerError> {
44+
psbt.combine(
45+
self.client
46+
.sign_tx(psbt)
47+
.map_err(|e| {
48+
SignerError::External(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+
}

0 commit comments

Comments
 (0)