diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92f67cb..66b63bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: matrix: rust: - version: 1.61.0 # STABLE - - version: 1.41.1 # MSRV + - version: 1.56.1 # MSRV emulator: - name: trezor - name: ledger diff --git a/Cargo.toml b/Cargo.toml index 012181a..c2e6b10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ bitcoin = { version = "0.28", features = ["use-serde", "base64"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } pyo3 = { version = "0.15.1", features = ["auto-initialize"]} +bdk = "0.20.0" [dev-dependencies] serial_test = "0.6.0" diff --git a/src/interface.rs b/src/interface.rs index ee85513..180e5e1 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -26,6 +26,7 @@ macro_rules! deserialize_obj { } /// Convenience class containing required Python objects +#[derive(Debug)] struct HWILib { commands: Py, json_dumps: Py, @@ -44,6 +45,7 @@ impl HWILib { } } +#[derive(Debug)] pub struct HWIClient { hwilib: HWILib, hw_client: PyObject, diff --git a/src/lib.rs b/src/lib.rs index c5ba415..7ee6baa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,17 +31,27 @@ pub use interface::HWIClient; pub mod error; pub mod interface; +pub mod signer; pub mod types; #[cfg(test)] mod tests { + use crate::signer::HWISigner; use crate::types; use crate::HWIClient; use std::collections::BTreeMap; use std::str::FromStr; + use std::sync::Arc; + use bdk::database::MemoryDatabase; + use bdk::signer::SignerOrdering; + use bdk::KeychainKind; + use bdk::SignOptions; + use bdk::Wallet; use bitcoin::psbt::{Input, Output}; use bitcoin::util::bip32::{DerivationPath, KeySource}; + use bitcoin::Address; + use bitcoin::Network; use bitcoin::{secp256k1, Transaction}; use bitcoin::{TxIn, TxOut}; @@ -320,4 +330,44 @@ mod tests { client.wipe_device().unwrap(); } } + #[test] + #[serial] + fn test_create_signer() { + let descriptors = get_first_device().get_descriptors(None).unwrap(); + let devices = HWIClient::enumerate().unwrap(); + let custom_signer = + HWISigner::from_device(devices.first().unwrap(), types::HWIChain::Test).unwrap(); + + let mut wallet = Wallet::new( + &descriptors.internal[0], + Some(&descriptors.receive[0]), + Network::Testnet, + MemoryDatabase::default(), + ) + .unwrap(); + wallet.add_signer( + KeychainKind::External, + SignerOrdering(200), + Arc::new(custom_signer), + ); + + let client = + bdk::electrum_client::Client::new("ssl://electrum.blockstream.info:60002").unwrap(); + let blockchain = bdk::blockchain::ElectrumBlockchain::from(client); + wallet + .sync(&blockchain, bdk::SyncOptions::default()) + .unwrap(); + + let balance = wallet.get_balance().unwrap(); + let faucet_address = Address::from_str("mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB").unwrap(); + + let mut tx_builder = wallet.build_tx(); + tx_builder + .add_recipient(faucet_address.script_pubkey(), balance / 2) + .enable_rbf(); + let (mut psbt, _tx_details) = tx_builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); + + assert!(finalized, "Tx has not been finalized"); + } } diff --git a/src/signer.rs b/src/signer.rs new file mode 100644 index 0000000..2a5bf79 --- /dev/null +++ b/src/signer.rs @@ -0,0 +1,51 @@ +use bdk::signer::{SignerCommon, SignerError, SignerId, TransactionSigner}; +use bitcoin::{ + psbt::PartiallySignedTransaction, + secp256k1::{All, Secp256k1}, + util::bip32::Fingerprint, +}; + +use crate::{ + error::Error, + types::{HWIChain, HWIDevice}, + HWIClient, +}; + +#[derive(Debug)] +pub struct HWISigner { + fingerprint: Fingerprint, + client: HWIClient, +} + +impl HWISigner { + pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result { + let client = HWIClient::get_client(device, false, chain)?; + Ok(HWISigner { + fingerprint: device.fingerprint, + client, + }) + } +} + +impl SignerCommon for HWISigner { + fn id(&self, _secp: &Secp256k1) -> SignerId { + SignerId::Fingerprint(self.fingerprint) + } +} + +impl TransactionSigner for HWISigner { + fn sign_transaction( + &self, + psbt: &mut PartiallySignedTransaction, + _secp: &Secp256k1, + ) -> Result<(), SignerError> { + psbt.combine( + self.client + .sign_tx(psbt) + .expect("Hardware Wallet couldn't sign transaction") + .psbt, + ) + .expect("Failed to combine HW signed psbt with passed PSBT"); + Ok(()) + } +}