From b25168aa0946754f6239b7eb256de73292ec0d07 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Thu, 15 Aug 2024 21:53:22 -0300 Subject: [PATCH 1/2] feat: add `HWISigner`, moved from `bdk_hwi` - 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. --- Cargo.toml | 3 ++- src/lib.rs | 43 ++++++++++++++++++++++++++++++++++++++-- src/signer.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 src/signer.rs diff --git a/Cargo.toml b/Cargo.toml index 4acfc6c..fdde1fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,11 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bdk_wallet = { version = "1.0.0-beta.1" } bitcoin = { version = "0.32", features = ["serde", "base64"] } +pyo3 = { version = "0.21.2", features = ["auto-initialize"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } -pyo3 = { version = "0.21.2", features = ["auto-initialize"] } miniscript = { version = "12.0", features = ["serde"], optional = true } diff --git a/src/lib.rs b/src/lib.rs index d549e92..355b5ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,9 @@ -//! Rust wrapper for the [Bitcoin Hardware Wallet Interface](https://github.com/bitcoin-core/HWI/). +//! This crate is contains both: +//! - [`HWIClient`]: A Rust wrapper for the [Bitcoin Hardware Wallet Interface](https://github.com/bitcoin-core/HWI/). +//! - [`HWISigner`]: An implementation of a [`TransactionSigner`] to be used with hardware wallets, that relies on [`HWIClient`]. //! -//! # Example - display address with path +//! # HWIClient Example: +//! ## Display address with path //! ```no_run //! use bitcoin::bip32::{ChildNumber, DerivationPath}; //! use hwi::error::Error; @@ -25,6 +28,40 @@ //! Ok(()) //! } //! ``` +//! +//! # HWISigner Example: +//! ## Add custom HWI signer to [`bdk_wallet`] +//! ```no_run +//! # use bdk_wallet::bitcoin::Network; +//! # use bdk_wallet::descriptor::Descriptor; +//! # use bdk_wallet::signer::SignerOrdering; +//! # use hwi::{HWIClient, HWISigner}; +//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet}; +//! # use std::sync::Arc; +//! # use std::str::FromStr; +//! # +//! # fn main() -> Result<(), Box> { +//! let mut devices = HWIClient::enumerate()?; +//! if devices.is_empty() { +//! panic!("No devices found!"); +//! } +//! let first_device = devices.remove(0)?; +//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?; +//! +//! # let mut wallet = Wallet::create("", "").network(Network::Testnet).create_wallet_no_persist()?; +//! # +//! // Adding the hardware signer to the BDK wallet +//! wallet.add_signer( +//! KeychainKind::External, +//! SignerOrdering(200), +//! Arc::new(custom_signer), +//! ); +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! [`TransactionSigner`]: bdk_wallet::signer::TransactionSigner #[cfg(test)] #[macro_use] @@ -32,11 +69,13 @@ extern crate serial_test; extern crate core; pub use interface::HWIClient; +pub use signer::HWISigner; #[cfg(feature = "doctest")] pub mod doctest; pub mod error; pub mod interface; +pub mod signer; pub mod types; #[cfg(test)] diff --git a/src/signer.rs b/src/signer.rs new file mode 100644 index 0000000..c2854d7 --- /dev/null +++ b/src/signer.rs @@ -0,0 +1,55 @@ +use bdk_wallet::bitcoin::bip32::Fingerprint; +use bdk_wallet::bitcoin::secp256k1::{All, Secp256k1}; +use bdk_wallet::bitcoin::Psbt; + +use crate::error::Error; +use crate::types::{HWIChain, HWIDevice}; +use crate::HWIClient; + +use bdk_wallet::signer::{SignerCommon, SignerError, SignerId, TransactionSigner}; + +#[derive(Debug)] +/// Custom signer for Hardware Wallets +/// +/// This ignores `sign_options` and leaves the decisions up to the hardware wallet. +pub struct HWISigner { + fingerprint: Fingerprint, + client: HWIClient, +} + +impl HWISigner { + /// Create an instance from the specified device and chain + pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result { + let client = HWIClient::get_client(device, false, chain)?; + Ok(HWISigner { + fingerprint: device.fingerprint, + client, + }) + } +} + +impl SignerCommon for HWISigner { + fn id(&self, _secp: &Secp256k1) -> SignerId { + SignerId::Fingerprint(self.fingerprint) + } +} + +impl TransactionSigner for HWISigner { + fn sign_transaction( + &self, + psbt: &mut Psbt, + _sign_options: &bdk_wallet::SignOptions, + _secp: &Secp256k1, + ) -> Result<(), SignerError> { + psbt.combine( + self.client + .sign_tx(psbt) + .map_err(|e| { + SignerError::External(format!("While signing with hardware wallet: {}", e)) + })? + .psbt, + ) + .expect("Failed to combine HW signed psbt with passed PSBT"); + Ok(()) + } +} From 7747b3fec88589b1c4915d9ed2472a550b111ae3 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Fri, 16 Aug 2024 16:00:20 -0300 Subject: [PATCH 2/2] refactor(signer)!: add `signer` behind a non-default feature --- .github/workflows/main.yml | 7 +++-- Cargo.toml | 4 ++- src/lib.rs | 60 +++++++++++++++++++++----------------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a0f4d7..70e88e0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,9 +42,10 @@ jobs: matrix: rust: - version: stable # STABLE - features: miniscript - version: 1.63.0 # MSRV - features: miniscript + features: + - miniscript + - signer emulator: - name: trezor - name: ledger @@ -77,7 +78,7 @@ jobs: - name: Update toolchain run: rustup update - name: Test - run: cargo test --features ${{ matrix.rust.features }} + run: cargo test --features ${{ matrix.features }} - name: Wipe run: cargo test test_wipe_device -- --ignored test-readme-examples: diff --git a/Cargo.toml b/Cargo.toml index fdde1fa..9bd31b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,12 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bdk_wallet = { version = "1.0.0-beta.1" } bitcoin = { version = "0.32", features = ["serde", "base64"] } pyo3 = { version = "0.21.2", features = ["auto-initialize"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } +bdk_wallet = { version = "1.0.0-beta.1", optional = true } miniscript = { version = "12.0", features = ["serde"], optional = true } [dev-dependencies] @@ -24,3 +24,5 @@ serial_test = "0.6.0" [features] doctest = [] +signer = ["dep:bdk_wallet"] +miniscript = ["dep:miniscript"] diff --git a/src/lib.rs b/src/lib.rs index 355b5ae..9cace79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,38 +30,44 @@ //! ``` //! //! # HWISigner Example: -//! ## Add custom HWI signer to [`bdk_wallet`] +//! ## Add custom [`HWISigner`] to [`Wallet`] //! ```no_run -//! # use bdk_wallet::bitcoin::Network; -//! # use bdk_wallet::descriptor::Descriptor; -//! # use bdk_wallet::signer::SignerOrdering; -//! # use hwi::{HWIClient, HWISigner}; -//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet}; -//! # use std::sync::Arc; -//! # use std::str::FromStr; -//! # -//! # fn main() -> Result<(), Box> { -//! let mut devices = HWIClient::enumerate()?; -//! if devices.is_empty() { -//! panic!("No devices found!"); -//! } -//! let first_device = devices.remove(0)?; -//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?; +//! # #[cfg(feature = "signer")] +//! # { +//! use bdk_wallet::bitcoin::Network; +//! use bdk_wallet::descriptor::Descriptor; +//! use bdk_wallet::signer::SignerOrdering; +//! use bdk_wallet::{KeychainKind, SignOptions, Wallet}; +//! use hwi::{HWIClient, HWISigner}; +//! use std::str::FromStr; +//! use std::sync::Arc; //! -//! # let mut wallet = Wallet::create("", "").network(Network::Testnet).create_wallet_no_persist()?; -//! # -//! // Adding the hardware signer to the BDK wallet -//! wallet.add_signer( -//! KeychainKind::External, -//! SignerOrdering(200), -//! Arc::new(custom_signer), -//! ); +//! fn main() -> Result<(), Box> { +//! let mut devices = HWIClient::enumerate()?; +//! if devices.is_empty() { +//! panic!("No devices found!"); +//! } +//! let first_device = devices.remove(0)?; +//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?; //! -//! # Ok(()) +//! let mut wallet = Wallet::create("", "") +//! .network(Network::Testnet) +//! .create_wallet_no_persist()?; +//! +//! // Adding the hardware signer to the BDK wallet +//! wallet.add_signer( +//! KeychainKind::External, +//! SignerOrdering(200), +//! Arc::new(custom_signer), +//! ); +//! +//! Ok(()) +//! } //! # } //! ``` //! -//! [`TransactionSigner`]: bdk_wallet::signer::TransactionSigner +//! [`TransactionSigner`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/signer/trait.TransactionSigner.html +//! [`Wallet`]: https://docs.rs/bdk_wallet/1.0.0-beta.1/bdk_wallet/struct.Wallet.html #[cfg(test)] #[macro_use] @@ -69,12 +75,14 @@ extern crate serial_test; extern crate core; pub use interface::HWIClient; +#[cfg(feature = "signer")] pub use signer::HWISigner; #[cfg(feature = "doctest")] pub mod doctest; pub mod error; pub mod interface; +#[cfg(feature = "signer")] pub mod signer; pub mod types;