Skip to content

Commit f47351f

Browse files
committed
Add a custom signer for hardware wallets
1 parent 7a565e0 commit f47351f

File tree

6 files changed

+133
-0
lines changed

6 files changed

+133
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Change the meaning of the `fee_amount` field inside `CoinSelectionResult`: from now on the `fee_amount` will represent only the fees asociated with the utxos in the `selected` field of `CoinSelectionResult`.
1616
- New `RpcBlockchain` implementation with various fixes.
1717
- Return balance in separate categories, namely `confirmed`, `trusted_pending`, `untrusted_pending` & `immature`.
18+
- Add a custom Signer for Hardware Wallets.
1819

1920
## [v0.20.0] - [v0.19.0]
2021

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ rocksdb = { version = "0.14", default-features = false, features = ["snappy"], o
3333
cc = { version = ">=1.0.64", optional = true }
3434
socks = { version = "0.3", optional = true }
3535
lazy_static = { version = "1.4", optional = true }
36+
hwi = { version = "0.2.2", optional = true }
3637

3738
bip39 = { version = "1.0.1", optional = true }
3839
bitcoinconsensus = { version = "0.19.0-3", optional = true }
@@ -61,6 +62,7 @@ key-value-db = ["sled"]
6162
all-keys = ["keys-bip39"]
6263
keys-bip39 = ["bip39"]
6364
rpc = ["bitcoincore-rpc"]
65+
hardware-signer = ["hwi"]
6466

6567
# We currently provide mulitple implementations of `Blockchain`, all are
6668
# 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
9395
test-rpc-legacy = ["rpc", "electrsd/electrs_0_8_10", "electrsd/bitcoind_0_20_0", "test-blockchains"]
9496
test-esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1", "electrsd/bitcoind_22_0", "test-blockchains"]
9597
test-md-docs = ["electrum"]
98+
test-hardware-signer = ["hardware-signer"]
9699

97100
[dev-dependencies]
98101
lazy_static = "1.4"

src/wallet/hardwaresigner/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Bitcoin Dev Kit
2+
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3+
//
4+
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5+
//
6+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9+
// You may not use this file except in accordance with one or both of these
10+
// licenses.
11+
12+
//! Hardware wallets
13+
//!
14+
//! This module contains modules required for working with Hardware Wallets.
15+
//! This module can contain various vendor specific code, if required.
16+
17+
pub mod signer;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Bitcoin Dev Kit
2+
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3+
//
4+
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5+
//
6+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9+
// You may not use this file except in accordance with one or both of these
10+
// licenses.
11+
12+
//! HWI Signer
13+
//!
14+
//! This module contains a simple implementation of a Custom Signer for rust-hwi
15+
16+
use bitcoin::psbt::PartiallySignedTransaction;
17+
use bitcoin::secp256k1::{All, Secp256k1};
18+
use bitcoin::util::bip32::Fingerprint;
19+
20+
use hwi::error::Error;
21+
use hwi::types::{HWIChain, HWIDevice};
22+
use hwi::HWIClient;
23+
24+
use crate::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
25+
26+
#[derive(Debug)]
27+
/// Custom Signer for Hardware Wallets
28+
/// This ignores `sign_options` and leaves the decisions upto the hardware wallet.
29+
pub struct HWISigner {
30+
fingerprint: Fingerprint,
31+
client: HWIClient,
32+
}
33+
34+
impl HWISigner {
35+
/// Create a instance from the specified device and chain
36+
pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result<HWISigner, Error> {
37+
let client = HWIClient::get_client(device, false, chain)?;
38+
Ok(HWISigner {
39+
fingerprint: device.fingerprint,
40+
client,
41+
})
42+
}
43+
}
44+
45+
impl SignerCommon for HWISigner {
46+
fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
47+
SignerId::Fingerprint(self.fingerprint)
48+
}
49+
}
50+
51+
/// This implementation ignores `sign_options`
52+
impl TransactionSigner for HWISigner {
53+
fn sign_transaction(
54+
&self,
55+
psbt: &mut PartiallySignedTransaction,
56+
_sign_options: &crate::SignOptions,
57+
_secp: &crate::wallet::utils::SecpCtx,
58+
) -> Result<(), SignerError> {
59+
psbt.combine(self.client.sign_tx(psbt)?.psbt)
60+
.expect("Failed to combine HW signed psbt with passed PSBT");
61+
Ok(())
62+
}
63+
}

src/wallet/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ pub(crate) mod utils;
4848
#[cfg_attr(docsrs, doc(cfg(feature = "verify")))]
4949
pub mod verify;
5050

51+
#[cfg(feature = "hardware-signer")]
52+
pub mod hardwaresigner;
53+
5154
pub use utils::IsDust;
5255

5356
#[allow(deprecated)]
@@ -5414,4 +5417,40 @@ pub(crate) mod test {
54145417
// ...and checking that everything is fine
54155418
assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate);
54165419
}
5420+
5421+
#[cfg(feature = "test-hardware-signer")]
5422+
#[test]
5423+
fn test_create_signer() {
5424+
use hwi::{types, HWIClient};
5425+
5426+
use crate::wallet::hardwaresigner::signer::HWISigner;
5427+
5428+
let devices = HWIClient::enumerate().unwrap();
5429+
let client = HWIClient::get_client(
5430+
devices.first().expect(
5431+
"No devices found. Either plug in a hardware wallet, or start a simulator.",
5432+
),
5433+
true,
5434+
types::HWIChain::Regtest,
5435+
)
5436+
.unwrap();
5437+
let descriptors = client.get_descriptors(None).unwrap();
5438+
let custom_signer =
5439+
HWISigner::from_device(devices.first().unwrap(), types::HWIChain::Regtest).unwrap();
5440+
5441+
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
5442+
wallet.add_signer(
5443+
KeychainKind::External,
5444+
SignerOrdering(200),
5445+
Arc::new(custom_signer),
5446+
);
5447+
5448+
let addr = wallet.get_address(LastUnused).unwrap();
5449+
let mut builder = wallet.build_tx();
5450+
builder.drain_to(addr.script_pubkey()).drain_wallet();
5451+
let (mut psbt, _) = builder.finish().unwrap();
5452+
5453+
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
5454+
assert!(finalized);
5455+
}
54175456
}

src/wallet/signer.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ pub enum SignerError {
159159
InvalidSighash,
160160
/// Error while computing the hash to sign
161161
SighashError(sighash::Error),
162+
/// Error while signing using hardware wallets
163+
#[cfg(feature = "hardware-signer")]
164+
HWIError(hwi::error::Error),
165+
}
166+
167+
#[cfg(feature = "hardware-signer")]
168+
impl From<hwi::error::Error> for SignerError {
169+
fn from(e: hwi::error::Error) -> Self {
170+
SignerError::HWIError(e)
171+
}
162172
}
163173

164174
impl From<sighash::Error> for SignerError {

0 commit comments

Comments
 (0)