]> Untitled Git - bdk/commitdiff
Add a custom signer for hardware wallets
authorwszdexdrf <piyushkumar2k02@kgpian.iitkgp.ac.in>
Mon, 25 Jul 2022 18:37:04 +0000 (00:07 +0530)
committerwszdexdrf <piyushkumar2k02@kgpian.iitkgp.ac.in>
Mon, 29 Aug 2022 08:23:56 +0000 (13:53 +0530)
Also add function to get funded wallet with coinbase

CHANGELOG.md
Cargo.toml
src/wallet/hardwaresigner.rs [new file with mode: 0644]
src/wallet/mod.rs
src/wallet/signer.rs

index bfbb180498065caf36c77b6bb3838a61b0ab588c..2859b7ee528f567fbb55a2932b1ad8c0b013083b 100644 (file)
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 - Add capacity to create FeeRate from sats/kvbytes and sats/kwu.
 - Rename `as_sat_vb` to `as_sat_per_vb`. Move all `FeeRate` test to `types.rs`.
+- Add custom Harware Wallet Signer `HwiSigner` in `src/wallet/harwaresigner/` module.
 
 ## [v0.21.0] - [v0.20.0]
 
index d61e4f3720dd99520aafe14e96e856ee49edf1fd..5fcea94257341d5b7de35f9e1cdd2e144b9d6caf 100644 (file)
@@ -33,6 +33,7 @@ rocksdb = { version = "0.14", default-features = false, features = ["snappy"], o
 cc = { version = ">=1.0.64", optional = true }
 socks = { version = "0.3", optional = true }
 lazy_static = { version = "1.4", optional = true }
+hwi = { version = "0.2.2", optional = true }
 
 bip39 = { version = "1.0.1", optional = true }
 bitcoinconsensus = { version = "0.19.0-3", optional = true }
@@ -61,6 +62,7 @@ key-value-db = ["sled"]
 all-keys = ["keys-bip39"]
 keys-bip39 = ["bip39"]
 rpc = ["bitcoincore-rpc"]
+hardware-signer = ["hwi"]
 
 # We currently provide mulitple implementations of `Blockchain`, all are
 # blocking except for the `EsploraBlockchain` which can be either async or
@@ -93,6 +95,7 @@ test-rpc = ["rpc", "electrsd/electrs_0_8_10", "electrsd/bitcoind_22_0", "test-bl
 test-rpc-legacy = ["rpc", "electrsd/electrs_0_8_10", "electrsd/bitcoind_0_20_0", "test-blockchains"]
 test-esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1", "electrsd/bitcoind_22_0", "test-blockchains"]
 test-md-docs = ["electrum"]
+test-hardware-signer = ["hardware-signer"]
 
 [dev-dependencies]
 lazy_static = "1.4"
diff --git a/src/wallet/hardwaresigner.rs b/src/wallet/hardwaresigner.rs
new file mode 100644 (file)
index 0000000..7e4f74b
--- /dev/null
@@ -0,0 +1,64 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! HWI Signer
+//!
+//! This module contains a simple implementation of a Custom signer for rust-hwi
+
+use bitcoin::psbt::PartiallySignedTransaction;
+use bitcoin::secp256k1::{All, Secp256k1};
+use bitcoin::util::bip32::Fingerprint;
+
+use hwi::error::Error;
+use hwi::types::{HWIChain, HWIDevice};
+use hwi::HWIClient;
+
+use crate::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
+
+#[derive(Debug)]
+/// Custom signer for Hardware Wallets
+///
+/// This ignores `sign_options` and leaves the decisions up to the hardware wallet.
+pub struct HWISigner {
+    fingerprint: Fingerprint,
+    client: HWIClient,
+}
+
+impl HWISigner {
+    /// Create a instance from the specified device and chain
+    pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result<HWISigner, Error> {
+        let client = HWIClient::get_client(device, false, chain)?;
+        Ok(HWISigner {
+            fingerprint: device.fingerprint,
+            client,
+        })
+    }
+}
+
+impl SignerCommon for HWISigner {
+    fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
+        SignerId::Fingerprint(self.fingerprint)
+    }
+}
+
+/// This implementation ignores `sign_options`
+impl TransactionSigner for HWISigner {
+    fn sign_transaction(
+        &self,
+        psbt: &mut PartiallySignedTransaction,
+        _sign_options: &crate::SignOptions,
+        _secp: &crate::wallet::utils::SecpCtx,
+    ) -> Result<(), SignerError> {
+        psbt.combine(self.client.sign_tx(psbt)?.psbt)
+            .expect("Failed to combine HW signed psbt with passed PSBT");
+        Ok(())
+    }
+}
index 3506b1f5c896725e0336d6e28c5b05b420687731..7bd2d6baaa65eb38d6845392daf7bbad1e1bda68 100644 (file)
@@ -48,6 +48,9 @@ pub(crate) mod utils;
 #[cfg_attr(docsrs, doc(cfg(feature = "verify")))]
 pub mod verify;
 
+#[cfg(feature = "hardware-signer")]
+pub mod hardwaresigner;
+
 pub use utils::IsDust;
 
 #[allow(deprecated)]
@@ -5414,4 +5417,33 @@ pub(crate) mod test {
         // ...and checking that everything is fine
         assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate);
     }
+
+    #[cfg(feature = "test-hardware-signer")]
+    #[test]
+    fn test_create_signer() {
+        use crate::wallet::hardwaresigner::HWISigner;
+        use hwi::types::HWIChain;
+        use hwi::HWIClient;
+
+        let devices = HWIClient::enumerate().unwrap();
+        let device = devices.first().expect("No devices found");
+        let client = HWIClient::get_client(device, true, HWIChain::Regtest).unwrap();
+        let descriptors = client.get_descriptors(None).unwrap();
+        let custom_signer = HWISigner::from_device(device, HWIChain::Regtest).unwrap();
+
+        let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
+        wallet.add_signer(
+            KeychainKind::External,
+            SignerOrdering(200),
+            Arc::new(custom_signer),
+        );
+
+        let addr = wallet.get_address(LastUnused).unwrap();
+        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);
+    }
 }
index 7548b32179ce01b16cf60122b39736b9717275c2..1704a9532f54488e680779ded19c9e44fa4d3bfd 100644 (file)
@@ -159,6 +159,16 @@ pub enum SignerError {
     InvalidSighash,
     /// Error while computing the hash to sign
     SighashError(sighash::Error),
+    /// Error while signing using hardware wallets
+    #[cfg(feature = "hardware-signer")]
+    HWIError(hwi::error::Error),
+}
+
+#[cfg(feature = "hardware-signer")]
+impl From<hwi::error::Error> for SignerError {
+    fn from(e: hwi::error::Error) -> Self {
+        SignerError::HWIError(e)
+    }
 }
 
 impl From<sighash::Error> for SignerError {