Merge bitcoindevkit/bdk#1161: ref(hwi): Move hwi out of bdk
105d70e9743f651d141c6e0d901d60d583afd4d9 ref(hwi): Move hwi out of bdk (Daniela Brozzoni) Pull request description: Fixes #872 ### Description This commit moves the `hardwaresigner` outside of bdk and inside `bdk_hwi` ### Notes to the reviewers There are currently two issues with the code: - `TransactionSigner` dictates that `sign_transaction` must return a `SignerError` - but being `SignerError` defined inside of bdk, we can't modify it to include an hwi specific error! I don't know how we could fix this (other than getting rid of the trait altogether :)); for now I just added a `SignerError::Generic` variant, lmk if you have better ideas! - The hwi tests used the bdk utils to get a funded wallet for testing, which aren't available in `bdk_hwi`, which made me realize - maybe we should expose them so that we can use them across our crates, and also our users can use them to test their code? For now, I just left the test commented. ### Changelog notice - The old `hardwaresigner` module has been moved out of `bdk` and inside `bdk_hwi`. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: ~~* [ ] I've added tests for the new feature~~ * [x] I've added docs for the new feature ACKs for top commit: notmandatory: reACK 105d70e9743f651d141c6e0d901d60d583afd4d9 Tree-SHA512: 9ae3cd035cc27bd7b2831e89c104f40771c6f165cb3bfe1a9052f820050fca7c19f4dd68f171c630a71a7d18ccdee5f94adbc497c6475bd257c2d01cc08109a1
This commit is contained in:
		
						commit
						6e6bad9223
					
				| @ -7,6 +7,7 @@ members = [ | |||||||
|     "crates/electrum", |     "crates/electrum", | ||||||
|     "crates/esplora", |     "crates/esplora", | ||||||
|     "crates/bitcoind_rpc", |     "crates/bitcoind_rpc", | ||||||
|  |     "crates/hwi", | ||||||
|     "example-crates/example_cli", |     "example-crates/example_cli", | ||||||
|     "example-crates/example_electrum", |     "example-crates/example_electrum", | ||||||
|     "example-crates/example_esplora", |     "example-crates/example_esplora", | ||||||
|  | |||||||
| @ -21,7 +21,6 @@ serde_json = { version = "^1.0" } | |||||||
| bdk_chain = { path = "../chain", version = "0.7.0", features = ["miniscript", "serde"], default-features = false } | bdk_chain = { path = "../chain", version = "0.7.0", features = ["miniscript", "serde"], default-features = false } | ||||||
| 
 | 
 | ||||||
| # Optional dependencies | # Optional dependencies | ||||||
| hwi = { version = "0.7.0", optional = true, features = [ "miniscript"] } |  | ||||||
| bip39 = { version = "2.0", optional = true } | bip39 = { version = "2.0", optional = true } | ||||||
| 
 | 
 | ||||||
| [target.'cfg(target_arch = "wasm32")'.dependencies] | [target.'cfg(target_arch = "wasm32")'.dependencies] | ||||||
| @ -34,8 +33,6 @@ std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"] | |||||||
| compiler = ["miniscript/compiler"] | compiler = ["miniscript/compiler"] | ||||||
| all-keys = ["keys-bip39"] | all-keys = ["keys-bip39"] | ||||||
| keys-bip39 = ["bip39"] | keys-bip39 = ["bip39"] | ||||||
| hardware-signer = ["hwi"] |  | ||||||
| test-hardware-signer = ["hardware-signer"] |  | ||||||
| 
 | 
 | ||||||
| # This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended | # This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended | ||||||
| # for libraries to explicitly include the "getrandom/js" feature, so we only do it when | # for libraries to explicitly include the "getrandom/js" feature, so we only do it when | ||||||
|  | |||||||
| @ -17,8 +17,6 @@ extern crate std; | |||||||
| pub extern crate alloc; | pub extern crate alloc; | ||||||
| 
 | 
 | ||||||
| pub extern crate bitcoin; | pub extern crate bitcoin; | ||||||
| #[cfg(feature = "hardware-signer")] |  | ||||||
| pub extern crate hwi; |  | ||||||
| pub extern crate miniscript; | pub extern crate miniscript; | ||||||
| extern crate serde; | extern crate serde; | ||||||
| extern crate serde_json; | extern crate serde_json; | ||||||
|  | |||||||
| @ -50,10 +50,6 @@ pub mod tx_builder; | |||||||
| pub(crate) mod utils; | pub(crate) mod utils; | ||||||
| 
 | 
 | ||||||
| pub mod error; | pub mod error; | ||||||
| #[cfg(feature = "hardware-signer")] |  | ||||||
| #[cfg_attr(docsrs, doc(cfg(feature = "hardware-signer")))] |  | ||||||
| pub mod hardwaresigner; |  | ||||||
| 
 |  | ||||||
| pub use utils::IsDust; | pub use utils::IsDust; | ||||||
| 
 | 
 | ||||||
| #[allow(deprecated)] | #[allow(deprecated)] | ||||||
|  | |||||||
| @ -80,6 +80,7 @@ | |||||||
| //! ```
 | //! ```
 | ||||||
| 
 | 
 | ||||||
| use crate::collections::BTreeMap; | use crate::collections::BTreeMap; | ||||||
|  | use alloc::string::String; | ||||||
| use alloc::sync::Arc; | use alloc::sync::Arc; | ||||||
| use alloc::vec::Vec; | use alloc::vec::Vec; | ||||||
| use core::cmp::Ordering; | use core::cmp::Ordering; | ||||||
| @ -162,16 +163,10 @@ pub enum SignerError { | |||||||
|     SighashError(sighash::Error), |     SighashError(sighash::Error), | ||||||
|     /// Miniscript PSBT error
 |     /// Miniscript PSBT error
 | ||||||
|     MiniscriptPsbt(MiniscriptPsbtError), |     MiniscriptPsbt(MiniscriptPsbtError), | ||||||
|     /// Error while signing using hardware wallets
 |     /// To be used only by external libraries implementing [`InputSigner`] or
 | ||||||
|     #[cfg(feature = "hardware-signer")] |     /// [`TransactionSigner`], so that they can return their own custom errors, without having to
 | ||||||
|     HWIError(hwi::error::Error), |     /// modify [`SignerError`] in BDK.
 | ||||||
| } |     External(String), | ||||||
| 
 |  | ||||||
| #[cfg(feature = "hardware-signer")] |  | ||||||
| impl From<hwi::error::Error> for SignerError { |  | ||||||
|     fn from(e: hwi::error::Error) -> Self { |  | ||||||
|         SignerError::HWIError(e) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<sighash::Error> for SignerError { | impl From<sighash::Error> for SignerError { | ||||||
| @ -196,8 +191,7 @@ impl fmt::Display for SignerError { | |||||||
|             Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"), |             Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"), | ||||||
|             Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err), |             Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err), | ||||||
|             Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err), |             Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err), | ||||||
|             #[cfg(feature = "hardware-signer")] |             Self::External(err) => write!(f, "{}", err), | ||||||
|             Self::HWIError(err) => write!(f, "Error while signing using hardware wallets: {}", err), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3591,41 +3591,6 @@ fn test_fee_rate_sign_grinding_low_r() { | |||||||
|     assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate); |     assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // #[cfg(feature = "test-hardware-signer")]
 |  | ||||||
| // #[test]
 |  | ||||||
| // fn test_hardware_signer() {
 |  | ||||||
| //     use std::sync::Arc;
 |  | ||||||
| //
 |  | ||||||
| //     use bdk::signer::SignerOrdering;
 |  | ||||||
| //     use bdk::wallet::hardwaresigner::HWISigner;
 |  | ||||||
| //     use hwi::types::HWIChain;
 |  | ||||||
| //     use hwi::HWIClient;
 |  | ||||||
| //
 |  | ||||||
| //     let mut devices = HWIClient::enumerate().unwrap();
 |  | ||||||
| //     if devices.is_empty() {
 |  | ||||||
| //         panic!("No devices found!");
 |  | ||||||
| //     }
 |  | ||||||
| //     let device = devices.remove(0).unwrap();
 |  | ||||||
| //     let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
 |  | ||||||
| //     let descriptors = client.get_descriptors::<String>(None).unwrap();
 |  | ||||||
| //     let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
 |  | ||||||
| //
 |  | ||||||
| //     let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
 |  | ||||||
| //     wallet.add_signer(
 |  | ||||||
| //         KeychainKind::External,
 |  | ||||||
| //         SignerOrdering(200),
 |  | ||||||
| //         Arc::new(custom_signer),
 |  | ||||||
| //     );
 |  | ||||||
| //
 |  | ||||||
| //     let addr = wallet.get_address(LastUnused);
 |  | ||||||
| //     let mut builder = wallet.build_tx();
 |  | ||||||
| //     builder.drain_to(addr.script_pubkey()).drain_wallet();
 |  | ||||||
| //     let (mut psbt, _) = builder.finish().unwrap();
 |  | ||||||
| //
 |  | ||||||
| //     let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
 |  | ||||||
| //     assert!(finalized);
 |  | ||||||
| // }
 |  | ||||||
| 
 |  | ||||||
| #[test] | #[test] | ||||||
| fn test_taproot_load_descriptor_duplicated_keys() { | fn test_taproot_load_descriptor_duplicated_keys() { | ||||||
|     // Added after issue https://github.com/bitcoindevkit/bdk/issues/760
 |     // Added after issue https://github.com/bitcoindevkit/bdk/issues/760
 | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								crates/hwi/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								crates/hwi/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | [package] | ||||||
|  | name = "bdk_hwi" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2021" | ||||||
|  | homepage = "https://bitcoindevkit.org" | ||||||
|  | repository = "https://github.com/bitcoindevkit/bdk" | ||||||
|  | description = "Utilities to use bdk with hardware wallets" | ||||||
|  | license = "MIT OR Apache-2.0" | ||||||
|  | readme = "README.md" | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | bdk = { path = "../bdk" } | ||||||
|  | hwi = { version = "0.7.0", features = [ "miniscript"] } | ||||||
							
								
								
									
										42
									
								
								crates/hwi/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								crates/hwi/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | //! HWI Signer
 | ||||||
|  | //!
 | ||||||
|  | //! This crate contains HWISigner, an implementation of a [`TransactionSigner`] to be
 | ||||||
|  | //! used with hardware wallets.
 | ||||||
|  | //! ```no_run
 | ||||||
|  | //! # use bdk::bitcoin::Network;
 | ||||||
|  | //! # use bdk::signer::SignerOrdering;
 | ||||||
|  | //! # use bdk_hwi::HWISigner;
 | ||||||
|  | //! # use bdk::wallet::AddressIndex::New;
 | ||||||
|  | //! # use bdk::{FeeRate, KeychainKind, SignOptions, Wallet};
 | ||||||
|  | //! # use hwi::HWIClient;
 | ||||||
|  | //! # use std::sync::Arc;
 | ||||||
|  | //! #
 | ||||||
|  | //! # fn main() -> Result<(), Box<dyn std::error::Error>> {
 | ||||||
|  | //! 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::new_no_persist(
 | ||||||
|  | //! #     "",
 | ||||||
|  | //! #     None,
 | ||||||
|  | //! #     Network::Testnet,
 | ||||||
|  | //! # )?;
 | ||||||
|  | //! #
 | ||||||
|  | //! // 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
 | ||||||
|  | 
 | ||||||
|  | mod signer; | ||||||
|  | pub use signer::*; | ||||||
							
								
								
									
										94
									
								
								crates/hwi/src/signer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								crates/hwi/src/signer.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | |||||||
|  | use bdk::bitcoin::bip32::Fingerprint; | ||||||
|  | use bdk::bitcoin::psbt::PartiallySignedTransaction; | ||||||
|  | use bdk::bitcoin::secp256k1::{All, Secp256k1}; | ||||||
|  | 
 | ||||||
|  | use hwi::error::Error; | ||||||
|  | use hwi::types::{HWIChain, HWIDevice}; | ||||||
|  | use hwi::HWIClient; | ||||||
|  | 
 | ||||||
|  | use bdk::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 a instance from the specified device and chain
 | ||||||
|  |     pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result<HWISigner, Error> { | ||||||
|  |         let client = HWIClient::get_client(device, false, chain)?; | ||||||
|  |         Ok(HWISigner { | ||||||
|  |             fingerprint: device.fingerprint, | ||||||
|  |             client, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl SignerCommon for HWISigner { | ||||||
|  |     fn id(&self, _secp: &Secp256k1<All>) -> SignerId { | ||||||
|  |         SignerId::Fingerprint(self.fingerprint) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TransactionSigner for HWISigner { | ||||||
|  |     fn sign_transaction( | ||||||
|  |         &self, | ||||||
|  |         psbt: &mut PartiallySignedTransaction, | ||||||
|  |         _sign_options: &bdk::SignOptions, | ||||||
|  |         _secp: &Secp256k1<All>, | ||||||
|  |     ) -> 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(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO: re-enable this once we have the `get_funded_wallet` test util
 | ||||||
|  | // #[cfg(test)]
 | ||||||
|  | // mod tests {
 | ||||||
|  | //     #[test]
 | ||||||
|  | //     fn test_hardware_signer() {
 | ||||||
|  | //         use std::sync::Arc;
 | ||||||
|  | //
 | ||||||
|  | //         use bdk::tests::get_funded_wallet;
 | ||||||
|  | //         use bdk::signer::SignerOrdering;
 | ||||||
|  | //         use bdk::bitcoin::Network;
 | ||||||
|  | //         use crate::HWISigner;
 | ||||||
|  | //         use hwi::HWIClient;
 | ||||||
|  | //
 | ||||||
|  | //         let mut devices = HWIClient::enumerate().unwrap();
 | ||||||
|  | //         if devices.is_empty() {
 | ||||||
|  | //             panic!("No devices found!");
 | ||||||
|  | //         }
 | ||||||
|  | //         let device = devices.remove(0).unwrap();
 | ||||||
|  | //         let client = HWIClient::get_client(&device, true, Network::Regtest.into()).unwrap();
 | ||||||
|  | //         let descriptors = client.get_descriptors::<String>(None).unwrap();
 | ||||||
|  | //         let custom_signer = HWISigner::from_device(&device, Network::Regtest.into()).unwrap();
 | ||||||
|  | //
 | ||||||
|  | //         let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
 | ||||||
|  | //         wallet.add_signer(
 | ||||||
|  | //             bdk::KeychainKind::External,
 | ||||||
|  | //             SignerOrdering(200),
 | ||||||
|  | //             Arc::new(custom_signer),
 | ||||||
|  | //         );
 | ||||||
|  | //
 | ||||||
|  | //         let addr = wallet.get_address(bdk::wallet::AddressIndex::LastUnused);
 | ||||||
|  | //         let mut builder = wallet.build_tx();
 | ||||||
|  | //         builder.drain_to(addr.script_pubkey()).drain_wallet();
 | ||||||
|  | //         let (mut psbt, _) = builder.finish().unwrap();
 | ||||||
|  | //
 | ||||||
|  | //         let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
 | ||||||
|  | //         assert!(finalized);
 | ||||||
|  | //     }
 | ||||||
|  | // }
 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user