diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 54020bb90..933f4a78b 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -27,7 +27,7 @@ jobs: uses: Swatinem/rust-cache@v2.2.1 - name: Install grcov run: if [[ ! -e ~/.cargo/bin/grcov ]]; then cargo install grcov; fi - # TODO: re-enable the hwi tests + # ==== hwi emulator setup ==== - name: Build simulator image run: docker build -t hwi/ledger_emulator ./ci -f ci/Dockerfile.ledger - name: Run simulator image @@ -37,7 +37,28 @@ jobs: with: python-version: '3.9' - name: Install python dependencies - run: pip install hwi==2.1.1 protobuf==3.20.1 + run: pip install hwi==2.1.1 protobuf==3.20.1 requests + - name: Patch openssl.cnf for ripemd160 support + run: | + cat << 'EOF' > /tmp/openssl.cnf + openssl_conf = openssl_init + + [openssl_init] + providers = provider_sect + + [provider_sect] + default = default_sect + legacy = legacy_sect + + [default_sect] + activate = 1 + + [legacy_sect] + activate = 1 + EOF + - name: Setup OPENSSL_CONF environment variable + run: echo "OPENSSL_CONF=/tmp/openssl.cnf" >> $GITHUB_ENV + # ==== End hwi emulator setup ==== - name: Test run: cargo test --all-features - name: Make coverage directory diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 4bab2af8f..5095947a2 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -38,6 +38,38 @@ jobs: cargo update -p cc --precise "1.0.105" - name: Build run: cargo build ${{ matrix.features }} + # ==== hwi emulator setup ==== + - name: Build simulator image + run: docker build -t hwi/ledger_emulator ./ci -f ci/Dockerfile.ledger + - name: Run simulator image + run: docker run --name simulator --network=host hwi/ledger_emulator & + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install python dependencies + run: pip install hwi==2.1.1 protobuf==3.20.1 requests + - name: Patch openssl.cnf for ripemd160 support + run: | + cat << 'EOF' > /tmp/openssl.cnf + openssl_conf = openssl_init + + [openssl_init] + providers = provider_sect + + [provider_sect] + default = default_sect + legacy = legacy_sect + + [default_sect] + activate = 1 + + [legacy_sect] + activate = 1 + EOF + - name: Setup OPENSSL_CONF environment variable + run: echo "OPENSSL_CONF=/tmp/openssl.cnf" >> $GITHUB_ENV + # ==== End hwi emulator setup ==== - name: Test run: cargo test ${{ matrix.features }} diff --git a/ci/Dockerfile.ledger b/ci/Dockerfile.ledger index fb4c6bc41..291405441 100644 --- a/ci/Dockerfile.ledger +++ b/ci/Dockerfile.ledger @@ -3,7 +3,7 @@ FROM ghcr.io/ledgerhq/speculos RUN apt-get update RUN apt-get install wget -y -RUN wget "https://github.com/LedgerHQ/speculos/blob/master/apps/nanos%23btc%232.1%231c8db8da.elf?raw=true" -O /speculos/btc.elf +RUN wget "https://github.com/LedgerHQ/speculos/blob/master/apps/nanox%23btc%232.0.2%231c8db8da.elf?raw=true" -O /speculos/btc.elf ADD automation.json /speculos/automation.json -ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--model", "nanos", "--display", "headless", "--vnc-port", "41000", "btc.elf"] +ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--model", "nanox", "--display", "headless", "--vnc-port", "41000", "btc.elf"] diff --git a/crates/hwi/Cargo.toml b/crates/hwi/Cargo.toml index 4f7e5fa50..582bac2e4 100644 --- a/crates/hwi/Cargo.toml +++ b/crates/hwi/Cargo.toml @@ -11,3 +11,6 @@ readme = "README.md" [dependencies] bdk_wallet = { path = "../wallet", version = "1.0.0-alpha.13" } hwi = { version = "0.9.0", features = [ "miniscript"] } + +[dev-dependencies] +bdk_wallet = { path = "../wallet", version = "1.0.0-alpha.13", features = ["test-util"] } diff --git a/crates/hwi/src/signer.rs b/crates/hwi/src/signer.rs index bbb626619..5dc059ccc 100644 --- a/crates/hwi/src/signer.rs +++ b/crates/hwi/src/signer.rs @@ -1,13 +1,11 @@ use bdk_wallet::bitcoin::bip32::Fingerprint; use bdk_wallet::bitcoin::secp256k1::{All, Secp256k1}; use bdk_wallet::bitcoin::Psbt; - +use bdk_wallet::signer::{SignerCommon, SignerError, SignerId, TransactionSigner}; use hwi::error::Error; use hwi::types::{HWIChain, HWIDevice}; use hwi::HWIClient; -use bdk_wallet::signer::{SignerCommon, SignerError, SignerId, TransactionSigner}; - #[derive(Debug)] /// Custom signer for Hardware Wallets /// @@ -41,54 +39,87 @@ impl TransactionSigner for HWISigner { _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"); + let signed_psbt = self + .client + .sign_tx(psbt) + .map_err(|e| { + SignerError::External(format!("While signing with hardware wallet: {}", e)) + })? + .psbt; + + psbt.combine(signed_psbt).map_err(|e| { + SignerError::External(format!( + "Failed to combine HW signed PSBT with passed PSBT: {}", + e + )) + })?; + 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_wallet::tests::get_funded_wallet; -// use bdk_wallet::signer::SignerOrdering; -// use bdk_wallet::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::(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_wallet::KeychainKind::External, -// SignerOrdering(200), -// Arc::new(custom_signer), -// ); -// -// let addr = wallet.get_address(bdk_wallet::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); -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use bdk_wallet::bitcoin::Network; + use bdk_wallet::signer::SignerOrdering; + // use bdk_wallet::wallet::common::get_funded_wallet; + + use bdk_wallet::wallet::test_util::get_funded_wallet; + // use bdk_wallet::wallet::AddressIndex; + use bdk_wallet::KeychainKind; + use std::sync::Arc; + + #[test] + fn test_hardware_signer() { + let mut devices = match HWIClient::enumerate() { + Ok(devices) => devices, + Err(e) => panic!("Failed to enumerate devices: {}", e), + }; + + if devices.is_empty() { + panic!("No devices found!"); + } + + let device = match devices.remove(0) { + Ok(device) => device, + Err(e) => panic!("Failed to remove device: {}", e), + }; + + let client = match HWIClient::get_client(&device, true, Network::Regtest.into()) { + Ok(client) => client, + Err(e) => panic!("Failed to get client: {}", e), + }; + + let descriptors = match client.get_descriptors::(None) { + Ok(descriptors) => descriptors, + Err(e) => panic!("Failed to get descriptors: {}", e), + }; + + let custom_signer = match HWISigner::from_device(&device, Network::Regtest.into()) { + Ok(signer) => signer, + Err(e) => panic!("Failed to create HWISigner: {}", e), + }; + + let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]); + + wallet.add_signer( + KeychainKind::External, + SignerOrdering(200), + Arc::new(custom_signer), + ); + + let addr = wallet + // ,(AddressIndex::LastUnused) + .peek_address(KeychainKind::External, 0); + + let mut builder = wallet.build_tx(); + builder.drain_to(addr.script_pubkey()).drain_wallet(); + let mut psbt = builder.finish().expect("Failed to build transaction"); + + let finalized = wallet + .sign(&mut psbt, Default::default()) + .expect("Failed to sign transaction"); + assert!(finalized); + } +} diff --git a/crates/wallet/Cargo.toml b/crates/wallet/Cargo.toml index 9c141336d..a2a2e813b 100644 --- a/crates/wallet/Cargo.toml +++ b/crates/wallet/Cargo.toml @@ -29,6 +29,7 @@ std = ["bitcoin/std", "bitcoin/rand-std", "miniscript/std", "bdk_chain/std"] compiler = ["miniscript/compiler"] all-keys = ["keys-bip39"] keys-bip39 = ["bip39"] +test-util = [] [dev-dependencies] lazy_static = "1.4" diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 9db21ac71..611aeded3 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -55,6 +55,8 @@ use bdk_chain::tx_graph::CalculateFeeError; pub mod coin_selection; pub mod export; pub mod signer; +#[cfg(any(test, feature = "test-util"))] +pub mod test_util; pub mod tx_builder; pub(crate) mod utils; diff --git a/crates/wallet/tests/common.rs b/crates/wallet/src/wallet/test_util.rs similarity index 90% rename from crates/wallet/tests/common.rs rename to crates/wallet/src/wallet/test_util.rs index 9774ec985..1fdd7f3f2 100644 --- a/crates/wallet/tests/common.rs +++ b/crates/wallet/src/wallet/test_util.rs @@ -1,9 +1,25 @@ +// Bitcoin Dev Kit +// Written in 2020 by Alekos Filini +// +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Test util +//! +//! Test utilities for the wallet module. Only enabled while testing or when the +//! `test-utils` feature is enabled. + #![allow(unused)] -use bdk_chain::{BlockId, ConfirmationBlockTime, ConfirmationTime, TxGraph}; -use bdk_wallet::{ +use crate::{ wallet::{Update, Wallet}, KeychainKind, LocalOutput, }; +use bdk_chain::{BlockId, ConfirmationBlockTime, ConfirmationTime, TxGraph}; use bitcoin::{ hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut, Txid, @@ -121,14 +137,17 @@ pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) { get_funded_wallet_with_change(descriptor, change) } +#[allow(missing_docs)] pub fn get_funded_wallet_wpkh() -> (Wallet, bitcoin::Txid) { get_funded_wallet_with_change(get_test_wpkh(), get_test_wpkh_change()) } +#[allow(missing_docs)] pub fn get_test_wpkh() -> &'static str { "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)" } +#[allow(missing_docs)] pub fn get_test_wpkh_with_change_desc() -> (&'static str, &'static str) { ( "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)", @@ -140,50 +159,61 @@ fn get_test_wpkh_change() -> &'static str { "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/0)" } +#[allow(missing_docs)] pub fn get_test_single_sig_csv() -> &'static str { // and(pk(Alice),older(6)) "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))" } +#[allow(missing_docs)] pub fn get_test_a_or_b_plus_csv() -> &'static str { // or(pk(Alice),and(pk(Bob),older(144))) "wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),and_v(v:pk(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(144))))" } +#[allow(missing_docs)] pub fn get_test_single_sig_cltv() -> &'static str { // and(pk(Alice),after(100000)) "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))" } +#[allow(missing_docs)] pub fn get_test_tr_single_sig() -> &'static str { "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)" } +#[allow(missing_docs)] pub fn get_test_tr_with_taptree() -> &'static str { "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})" } +#[allow(missing_docs)] pub fn get_test_tr_with_taptree_both_priv() -> &'static str { "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(cNaQCDwmmh4dS9LzCgVtyy1e1xjCJ21GUDHe9K98nzb689JvinGV)})" } +#[allow(missing_docs)] pub fn get_test_tr_repeated_key() -> &'static str { "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100)),and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(200))})" } +#[allow(missing_docs)] pub fn get_test_tr_single_sig_xprv() -> &'static str { "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)" } +#[allow(missing_docs)] pub fn get_test_tr_single_sig_xprv_with_change_desc() -> (&'static str, &'static str) { ("tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/0/*)", "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/1/*)") } +#[allow(missing_docs)] pub fn get_test_tr_with_taptree_xprv() -> &'static str { "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})" } +#[allow(missing_docs)] pub fn get_test_tr_dup_keys() -> &'static str { "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})" } diff --git a/crates/wallet/tests/psbt.rs b/crates/wallet/tests/psbt.rs index 155bb143a..6d74510e9 100644 --- a/crates/wallet/tests/psbt.rs +++ b/crates/wallet/tests/psbt.rs @@ -1,8 +1,8 @@ use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, TxIn}; use bdk_wallet::{psbt, KeychainKind, SignOptions}; use core::str::FromStr; -mod common; -use common::*; + +use bdk_wallet::wallet::test_util::*; // from bip 174 const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA"; diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 15e73958f..58dd55518 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -29,8 +29,7 @@ use bitcoin::{ use rand::rngs::StdRng; use rand::SeedableRng; -mod common; -use common::*; +use bdk_wallet::wallet::test_util::*; fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) -> OutPoint { let addr = wallet.next_unused_address(KeychainKind::External).address;