diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 90384ee7..226fb0bc 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -24,6 +24,13 @@ jobs: uses: actions/checkout@v5 with: persist-credentials: false + + # hwi (async-hwi) depends on libudev-sys + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev + - name: Install lcov tools run: sudo apt-get install lcov -y # This action automatically reads and applies rust-toolchain.toml diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index bc467cd4..f41deb94 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -28,6 +28,11 @@ jobs: uses: actions/checkout@v5 with: persist-credentials: false + # hwi (async-hwi) depends on libudev-sys + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev # The 'toolchain' argument on this action overrides the Rust compiler version set in rust-toolchain.toml # in order to test our MSRV. - name: Install Rust toolchain @@ -58,6 +63,11 @@ jobs: uses: actions/checkout@v5 with: persist-credentials: false + # hwi (async-hwi) depends on libudev-sys + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev # This action will honor the Rust compiler version set in rust-toolchain.toml. We aim to keep it in sync with # Rust stable. - name: Install Rust toolchain @@ -77,6 +87,12 @@ jobs: uses: actions/checkout@v5 with: persist-credentials: false + + # hwi (async-hwi) depends on libudev-sys + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev # This action automatically reads and applies rust-toolchain.toml - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 diff --git a/Cargo.toml b/Cargo.toml index cb08925c..fa782ea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ keys-bip39 = ["bip39"] rusqlite = ["bdk_chain/rusqlite"] file_store = ["bdk_file_store"] test-utils = ["std", "anyhow", "tempfile"] +simulator = [] [dev-dependencies] anyhow = "1" @@ -52,6 +53,7 @@ ctrlc = "3.4.6" rand = "0.8" tempfile = "3" tokio = { version = "1.38.1", features = ["rt", "rt-multi-thread", "macros"] } +async-hwi = { git = "https://github.com/wizardsardine/async-hwi", branch = "master"} [[example]] name = "mnemonic_to_descriptors" @@ -74,3 +76,6 @@ name = "esplora_blocking" [[example]] name = "bitcoind_rpc" + +[[example]] +name = "hwi_signer" \ No newline at end of file diff --git a/README.md b/README.md index 2756fedd..f3ec6c54 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ that the `Wallet` can use to update its view of the chain. * [`examples/esplora_blocking`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/esplora_blocking) * [`examples/electrum`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/electrum) * [`examples/bitcoind_rpc`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/bitcoind_rpc) +* [`examples/hwi_signer`](https://github.com/bitcoindevkit/bdk_wallet/tree/master/examples/hwi_signer) + ## Persistence diff --git a/examples/README.md b/examples/README.md index a6dca181..88058ada 100644 --- a/examples/README.md +++ b/examples/README.md @@ -32,3 +32,17 @@ just clean ``` +# Notes about running hwi_signer test + +Download a simulator at `https://github.com/BitBoxSwiss/bitbox02-firmware/releases/`. + +Run the simulator and then run the example with `--features=simulator` enabled. + +```sh + +curl https://github.com/BitBoxSwiss/bitbox02-firmware/releases/download/firmware%2Fv9.19.0/bitbox02-multi-v9.19.0-simulator1.0.0-linux-amd64 + +./bitbox02-multi-v9.19.0-simulator1.0.0-linux-amd64 + +cargo run --example hwi_signer --features simulator +`` diff --git a/examples/hwi_signer.rs b/examples/hwi_signer.rs new file mode 100644 index 00000000..214dbb86 --- /dev/null +++ b/examples/hwi_signer.rs @@ -0,0 +1,70 @@ +use async_hwi::bitbox::api::runtime::TokioRuntime; +use async_hwi::bitbox::api::BitBox; +use async_hwi::bitbox::NoiseConfigNoCache; +use bdk_wallet::bitcoin::absolute::LockTime; +use bdk_wallet::bitcoin::{Amount, FeeRate, Network}; + +use async_hwi::{bitbox::BitBox02, HWI}; +use bdk_wallet::test_utils::get_funded_wallet; +use bdk_wallet::KeychainKind; + +const SEND_AMOUNT: Amount = Amount::from_sat(5000); +const NETWORK: Network = Network::Regtest; +const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdfCLpvozodGytD3gRUa1M5WQz4kNuDZVf1inhcsSHXRpyLWN3k3Qy3nucrzz5hw2iZiEs6spehpee2WxqfSi31ByRJEu4rZ/84h/1h/0h/0/*)"; +const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdfCLpvozodGytD3gRUa1M5WQz4kNuDZVf1inhcsSHXRpyLWN3k3Qy3nucrzz5hw2iZiEs6spehpee2WxqfSi31ByRJEu4rZ/84h/1h/0h/1/*)"; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let (mut wallet, _) = get_funded_wallet(EXTERNAL_DESC, INTERNAL_DESC); + + // Pairing with Bitbox connected Bitbox device + let noise_config = Box::new(NoiseConfigNoCache {}); + + let bitbox = { + #[cfg(feature = "simulator")] + { + BitBox::::from_simulator(None, noise_config).await? + } + + #[cfg(not(feature = "simulator"))] + { + use async_hwi::bitbox::api::usb; + BitBox::::from_hid_device(usb::get_any_bitbox02().unwrap(), noise_config) + .await? + } + }; + + let pairing_device = bitbox.unlock_and_pair().await?; + let paired_device = pairing_device.wait_confirm().await?; + + if (paired_device.restore_from_mnemonic().await).is_ok() { + println!("Initializing device with mnemonic..."); + } else { + println!("Device already initialized proceeding..."); + } + + let bb = BitBox02::from(paired_device); + let bb = bb.with_network(NETWORK); + + let receiving_address = wallet.next_unused_address(KeychainKind::External); + + println!("Wallet balance {}", wallet.balance()); + + let mut tx_builder = wallet.build_tx(); + + tx_builder + .add_recipient(receiving_address.script_pubkey(), SEND_AMOUNT) + .fee_rate(FeeRate::from_sat_per_vb(2).unwrap()) + .nlocktime(LockTime::from_height(0).unwrap()); + + let mut psbt = tx_builder.finish()?; + + // Sign with the connected bitbox or any hardware device + bb.sign_tx(&mut psbt).await?; + + println!( + "Signing with bitbox done. Balance After signing {}", + wallet.balance() + ); + Ok(()) +}