From a4a43ea86060fa0a62b47dedc7de820459b3a472 Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Wed, 12 Oct 2022 14:23:42 +0100 Subject: [PATCH 1/4] Re-export HWI if the hardware-signer feature is set --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bed048cf..dcbe1b1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,6 +203,8 @@ pub extern crate miniscript; extern crate serde; #[macro_use] extern crate serde_json; +#[cfg(feature = "hardware-signer")] +pub extern crate hwi; #[cfg(all(feature = "reqwest", feature = "ureq"))] compile_error!("Features reqwest and ureq are mutually exclusive and cannot be enabled together"); From 0695e9fb3e41727e5732561a993411147487afd3 Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Wed, 12 Oct 2022 14:24:09 +0100 Subject: [PATCH 2/4] Bump HWI to 0.2.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e0b73698..72b40f63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ async-trait = { version = "0.1", optional = true } rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true } cc = { version = ">=1.0.64", optional = true } socks = { version = "0.3", optional = true } -hwi = { version = "0.2.2", optional = true } +hwi = { version = "0.2.3", optional = true } bip39 = { version = "1.0.1", optional = true } bitcoinconsensus = { version = "0.19.0-3", optional = true } From 1a71eb1f4736651ad82e0abd64792b6cc7b16c20 Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Wed, 12 Oct 2022 14:24:29 +0100 Subject: [PATCH 3/4] Update the hardwaresigner module documentation Add a little example on how to use the HWISigner, slightly improve the module description --- src/wallet/hardwaresigner.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/wallet/hardwaresigner.rs b/src/wallet/hardwaresigner.rs index 7e4f74bc..58246392 100644 --- a/src/wallet/hardwaresigner.rs +++ b/src/wallet/hardwaresigner.rs @@ -11,7 +11,40 @@ //! HWI Signer //! -//! This module contains a simple implementation of a Custom signer for rust-hwi +//! This module contains HWISigner, an implementation of a [TransactionSigner] to be +//! used with hardware wallets. +//! ```no_run +//! # use bdk::bitcoin::Network; +//! # use bdk::database::MemoryDatabase; +//! # use bdk::signer::SignerOrdering; +//! # use bdk::wallet::hardwaresigner::HWISigner; +//! # use bdk::wallet::AddressIndex::New; +//! # use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet}; +//! # use hwi::{types::HWIChain, HWIClient}; +//! # use std::sync::Arc; +//! # +//! # fn main() -> Result<(), Box> { +//! let devices = HWIClient::enumerate()?; +//! let first_device = devices.first().expect("No devices found!"); +//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?; +//! +//! # let mut wallet = Wallet::new( +//! # "", +//! # None, +//! # Network::Testnet, +//! # MemoryDatabase::default(), +//! # )?; +//! # +//! // Adding the hardware signer to the BDK wallet +//! wallet.add_signer( +//! KeychainKind::External, +//! SignerOrdering(200), +//! Arc::new(custom_signer), +//! ); +//! +//! # Ok(()) +//! # } +//! ``` use bitcoin::psbt::PartiallySignedTransaction; use bitcoin::secp256k1::{All, Secp256k1}; From 1437e1ecfe663b819156d98c5e1975fb357a763f Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Wed, 12 Oct 2022 14:31:17 +0100 Subject: [PATCH 4/4] Add the hardware_signer example --- Cargo.toml | 5 ++ examples/hardware_signer.rs | 103 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 examples/hardware_signer.rs diff --git a/Cargo.toml b/Cargo.toml index 72b40f63..4205237f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,11 @@ name = "psbt_signer" path = "examples/psbt_signer.rs" required-features = ["electrum"] +[[example]] +name = "hardware_signer" +path = "examples/hardware_signer.rs" +required-features = ["electrum", "hardware-signer"] + [workspace] members = ["macros"] [package.metadata.docs.rs] diff --git a/examples/hardware_signer.rs b/examples/hardware_signer.rs new file mode 100644 index 00000000..81343bcf --- /dev/null +++ b/examples/hardware_signer.rs @@ -0,0 +1,103 @@ +use bdk::bitcoin::{Address, Network}; +use bdk::blockchain::{Blockchain, ElectrumBlockchain}; +use bdk::database::MemoryDatabase; +use bdk::hwi::{types::HWIChain, HWIClient}; +use bdk::signer::SignerOrdering; +use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex}; +use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet}; +use electrum_client::Client; +use std::str::FromStr; +use std::sync::Arc; + +// This example shows how to sync a wallet, create a transaction, sign it +// and broadcast it using an external hardware wallet. +// The hardware wallet must be connected to the computer and unlocked before +// running the example. Also, the `hwi` python package should be installed +// and available in the environment. +// +// To avoid loss of funds, consider using an hardware wallet simulator: +// * Coldcard: https://github.com/Coldcard/firmware +// * Ledger: https://github.com/LedgerHQ/speculos +// * Trezor: https://docs.trezor.io/trezor-firmware/core/emulator/index.html +fn main() -> Result<(), Box> { + println!("Hold tight, I'm connecting to your hardware wallet..."); + + // Listing all the available hardware wallet devices... + let devices = HWIClient::enumerate()?; + let first_device = devices + .first() + .expect("No devices found. Either plug in a hardware wallet, or start a simulator."); + // ...and creating a client out of the first one + let client = HWIClient::get_client(first_device, true, HWIChain::Test)?; + println!("Look what I found, a {}!", first_device.model); + + // Getting the HW's public descriptors + let descriptors = client.get_descriptors(None)?; + println!( + "The hardware wallet's descriptor is: {}", + descriptors.receive[0] + ); + + // Creating a custom signer from the device + let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?; + let mut wallet = Wallet::new( + &descriptors.receive[0], + Some(&descriptors.internal[0]), + Network::Testnet, + MemoryDatabase::default(), + )?; + + // Adding the hardware signer to the BDK wallet + wallet.add_signer( + KeychainKind::External, + SignerOrdering(200), + Arc::new(custom_signer), + ); + + // create client for Blockstream's testnet electrum server + let blockchain = + ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); + + println!("Syncing the wallet..."); + wallet.sync(&blockchain, SyncOptions::default())?; + + // get deposit address + let deposit_address = wallet.get_address(AddressIndex::New)?; + + let balance = wallet.get_balance()?; + println!("Wallet balances in SATs: {}", balance); + + if balance.get_total() < 10000 { + println!( + "Send some sats from the u01.net testnet faucet to address '{addr}'.\nFaucet URL: https://bitcoinfaucet.uo1.net/?to={addr}", + addr = deposit_address.address + ); + return Ok(()); + } + + let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?; + let (mut psbt, _details) = { + let mut builder = wallet.build_tx(); + builder + .drain_wallet() + .drain_to(return_address.script_pubkey()) + .enable_rbf() + .fee_rate(FeeRate::from_sat_per_vb(5.0)); + builder.finish()? + }; + + // `sign` will call the hardware wallet asking for a signature + assert!( + wallet.sign(&mut psbt, SignOptions::default())?, + "The hardware wallet couldn't finalize the transaction :(" + ); + + println!("Let's broadcast your tx..."); + let raw_transaction = psbt.extract_tx(); + let txid = raw_transaction.txid(); + + blockchain.broadcast(&raw_transaction)?; + println!("Transaction broadcasted! TXID: {txid}.\nExplorer URL: https://mempool.space/testnet/tx/{txid}", txid = txid); + + Ok(()) +}