From 4f7d14dc6f33011553aa9ed176483e7f276260fa Mon Sep 17 00:00:00 2001 From: wszdexdrf Date: Tue, 16 Aug 2022 23:11:20 +0530 Subject: [PATCH] Add "Getting started with rust-hwi" blog --- docs/.vuepress/config.js | 3 +- .../getting_started_with_rust_hwi.md | 155 ++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 docs/tutorials/getting_started_with_rust_hwi.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index cdebf6c271..78314d29bf 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -53,7 +53,8 @@ const tutorialSidebar = [ '/tutorials/descriptors_in_the_wild', '/tutorials/hidden-power-of-bitcoin', '/tutorials/descriptor_based_paper_wallet', - '/tutorials/spending_policy_demo' + '/tutorials/spending_policy_demo', + '/tutorials/getting_started_with_rust_hwi' ], } ] diff --git a/docs/tutorials/getting_started_with_rust_hwi.md b/docs/tutorials/getting_started_with_rust_hwi.md new file mode 100644 index 0000000000..e57f6aac04 --- /dev/null +++ b/docs/tutorials/getting_started_with_rust_hwi.md @@ -0,0 +1,155 @@ +--- +title: "Getting Started with rust-hwi" +description: "This post will help one understand and develop for hardware wallets using BDK" +authors: + - Wszdexdrf +date: "2022-08-16" +tags: ["BDK", "Development", "Hardware Wallets"] +hidden: true +draft: false +--- + +## Introduction + +[bitcoindevkit/rust-hwi](https://github.com/bitcoindevkit/rust-hwi) is a sub-project for [bitcoindevkit](https://bitcoindevkit.org/) (BDK) which is used to interact with hardware wallets using the Rust programming language. It is a wrapper around [bitcoin-core/HWI](https://github.com/bitcoin-core/HWI) and its behaviour is closely linked with the same. + +## Fundamentals + +As mentioned before, rust-hwi is a wrapper around bitcoin-core/HWI. The functions in it, when called, pass on the arguments to related functions in bitcoin-core/HWI. More information about the functions and their arguments is available on rust-hwi [docs](https://docs.rs/hwi/latest/hwi/index.html) and bitcoin-core/HWI [docs](https://hwi.readthedocs.io/en/stable/). + +rust-hwi uses `PyO3` to call the Python functions from Rust. Let us take an example from the documentation: + +```rust +use bitcoin::util::bip32::{ChildNumber, DerivationPath}; +use hwi::error::Error; +use hwi::interface::HWIClient; +use hwi::types; +use std::str::FromStr; + +fn main() -> Result<(), Error> { + // Find information about devices + let devices = HWIClient::enumerate()?; + let device = devices.first().expect("No devices"); + // Create a client for a device + let client = HWIClient::get_client(&device, true, types::HWIChain::Test)?; + // Display the address from path + let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0")?; + let hwi_address = + client.display_address_with_path(&derivation_path, types::HWIAddressType::Tap)?; + println!("{}", hwi_address.address); + Ok(()) +} +``` + +In the first line, we call `HWIClient::enumerate()`. This function is equivalent to HWI's `enumerate` function, which returns a list of connected devices. Here, `HWIClient::enumerate()` returns a `Vec`, where `HWIDevice` is a struct representing a single device and contains all the information related to it, for example, fingerprint, path, etc. + +Then we store the first device available into `device`, which is straightforward. We then call `HWIClient::get_client()` and pass the reference to the device info and the Chain we are going to use that device on. (the boolean is for setting `expert` mode, which allows for some more functions and detailed information. That is implemented in bitcoin-core/HWI, see the [docs](https://hwi.readthedocs.io/en/latest/usage/cli-usage.html#cmdoption-hwi-expert)) There are 4 chains available, as usual, Main, Test, Regtest & Signet. + +HWI contains a python base class known as `HardwareWalletClient`. All the functions and their arguments are defined in it. Hardware Wallet developers create their own implementations of the base class. The function `get_client()` returns an instance of `HWIClient` struct, which contains a reference to the Python object of `HardwareWalletClient` (and also a reference to the Python code of HWI itself). rust-hwi's `HWIClient` struct tries to mimic the base class `HardwareWalletClient` and thus all the functions used for communicating with a hardware wallet belong to `HWIClient`. + +In the next line, we generate a derivation path and use the client instance we created to get an address for the aforementioned path. We then print out the address and return an `Ok`. + +## Integration with BDK + +BDK is an amazing project. It is one of the easiest ways to integrate Bitcoin wallet features into any application. rust-hwi aims to help BDK to work with hardware wallets. One of the ways to do so is to implement a Custom Signer. + +Let us look at a basic example from BDK's docs: + +```rust +use bdk::{FeeRate, Wallet, SyncOptions, SignOptions}; +use bdk::database::MemoryDatabase; +use bdk::blockchain::ElectrumBlockchain; +use bdk::electrum_client::Client; + +use bdk::wallet::AddressIndex::New; + +fn main() -> Result<(), bdk::Error> { + let client = Client::new("ssl://electrum.blockstream.info:60002")?; + let wallet = Wallet::new( + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", + Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), + bdk::bitcoin::Network::Testnet, + MemoryDatabase::default(), + )?; + let blockchain = ElectrumBlockchain::from(client); + + wallet.sync(&blockchain, SyncOptions::default())?; + + let send_to = wallet.get_address(New)?; + let (mut psbt, details) = { + let mut builder = wallet.build_tx(); + builder + .add_recipient(send_to.script_pubkey(), 50_000) + .enable_rbf() + .do_not_spend_change() + .fee_rate(FeeRate::from_sat_per_vb(5.0)); + builder.finish()? + }; + + let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + + Ok(()) +} +``` + +This creates a wallet instance with the given descriptors and uses an electrum backend to sync the wallet. It then creates a new transaction and then signs it using the same wallet. + +If we were to do this using a hardware wallet, how would we do this? + +First, we create a client instance of the device. + +```rust +let devices = HWIClient::enumerate().unwrap(); +let client = HWIClient::get_client( + devices + .first() + .expect("No devices found. Either plug in a hardware wallet, or start a simulator."), + true, + types::HWIChain::Test, +) +.unwrap(); +``` + +We would then need a descriptor from the device for BDK. + +```rust +let descriptors = client.get_descriptors(None).unwrap(); +``` + +We would now need to create an instance of a custom signer. A basic version is provided in `bdk/wallet/hardwaresigner/HWISigner`. The basic signer has no extra features, it just takes a psbt and simply hands it over to the hardware wallet. + +```rust +let custom_signer = HWISigner::from_device(devices.first().unwrap(), types::HWIChain::Test).unwrap(); +``` + +We now create a wallet instance using the descriptor from the hardware wallet and add the custom signer. + +```rust +let mut wallet = Wallet::new( + &descriptors.internal[0], + Some(&descriptors.receive[0]), + Network::Testnet, + MemoryDatabase::default(), +)?; +wallet.add_signer( + KeychainKind::External, + SignerOrdering(200), + Arc::new(custom_signer), +); +``` + +The rest of the PSBT signing process remains the same! + +```rust +let client = bdk::electrum_client::Client::new("ssl://electrum.blockstream.info:60002")?; +let blockchain = bdk::blockchain::ElectrumBlockchain::from(client); +wallet.sync(&blockchain, bdk::SyncOptions::default())?; + +let send_to = wallet.get_address(New)?; +let mut tx_builder = wallet.build_tx(); +tx_builder + .add_recipient(send_to.script_pubkey(), 50_000) + .enable_rbf(); +let (mut psbt, _tx_details) = tx_builder.finish()?; +let finalized = wallet.sign(&mut psbt, SignOptions::default())?; +``` -- 2.49.0