bitcoin_hwi/
lib.rs

1//! Rust wrapper for the [Bitcoin Hardware Wallet Interface](https://github.com/bitcoin-core/HWI/).
2//!
3//! # Example - display address with path
4//! ```no_run
5//! use bitcoin::bip32::{ChildNumber, DerivationPath};
6//! use hwi::error::Error;
7//! use hwi::interface::HWIClient;
8//! use hwi::types;
9//! use std::str::FromStr;
10//!
11//! fn main() -> Result<(), Error> {
12//!     // Find information about devices
13//!     let mut devices = HWIClient::enumerate()?;
14//!     if devices.is_empty() {
15//!         panic!("No device found!");
16//!     }
17//!     let device = devices.remove(0)?;
18//!     // Create a client for a device
19//!     let client = HWIClient::get_client(&device, true, bitcoin::Network::Testnet.into())?;
20//!     // Display the address from path
21//!     let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
22//!     let hwi_address =
23//!         client.display_address_with_path(&derivation_path, types::HWIAddressType::Tap)?;
24//!     println!("{}", hwi_address.address.assume_checked());
25//!     Ok(())
26//! }
27//! ```
28
29#[cfg(test)]
30#[macro_use]
31extern crate serial_test;
32
33pub use interface::HWIClient;
34
35#[cfg(feature = "doctest")]
36pub mod doctest;
37pub mod error;
38pub mod interface;
39pub mod types;
40
41#[cfg(test)]
42mod tests {
43    use crate::types::{self, HWIDeviceType, TESTNET};
44    use crate::HWIClient;
45    use std::collections::BTreeMap;
46    use std::str::FromStr;
47
48    use bitcoin::util::bip32::{DerivationPath, KeySource};
49    use bitcoin::psbt::{Input, Output};
50    use bitcoin::{LockTime, secp256k1, Transaction};
51    use bitcoin::{Network, TxIn, TxOut};
52
53    #[cfg(feature = "miniscript")]
54    use miniscript::{Descriptor, DescriptorPublicKey};
55
56    #[test]
57    #[serial]
58    fn test_enumerate() {
59        let devices = HWIClient::enumerate().unwrap();
60        assert!(devices.len() > 0);
61    }
62
63    #[test]
64    #[serial]
65    #[ignore]
66    fn test_find_trezor_device() {
67        HWIClient::find_device(
68            None,
69            Some(HWIDeviceType::Trezor),
70            None,
71            false,
72            Network::Testnet,
73        )
74        .unwrap();
75    }
76
77    fn get_first_device() -> HWIClient {
78        let devices = HWIClient::enumerate().unwrap();
79        let device = devices
80            .first()
81            .expect("No devices found. Either plug in a hardware wallet, or start a simulator.")
82            .as_ref()
83            .expect("Error when opening the first device");
84        HWIClient::get_client(&device, true, TESTNET).unwrap()
85    }
86
87    #[test]
88    #[serial]
89    fn test_get_master_xpub() {
90        let client = get_first_device();
91        client
92            .get_master_xpub(types::HWIAddressType::Wit, 0)
93            .unwrap();
94    }
95
96    #[test]
97    #[serial]
98    fn test_get_xpub() {
99        let client = get_first_device();
100        let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
101        client.get_xpub(&derivation_path, false).unwrap();
102    }
103
104    #[test]
105    #[serial]
106    fn test_sign_message() {
107        let client = get_first_device();
108        let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
109        client
110            .sign_message("I love BDK wallet", &derivation_path)
111            .unwrap();
112    }
113
114    #[test]
115    #[serial]
116    fn test_get_string_descriptors() {
117        let client = get_first_device();
118        let account = Some(10);
119        let descriptor = client.get_descriptors::<String>(account).unwrap();
120        assert!(descriptor.internal.len() > 0);
121        assert!(descriptor.receive.len() > 0);
122    }
123
124    #[test]
125    #[serial]
126    fn test_display_address_with_string_desc() {
127        let client = get_first_device();
128        let descriptor = client.get_descriptors::<String>(None).unwrap();
129        let descriptor = descriptor.receive.first().unwrap();
130        client.display_address_with_desc(descriptor).unwrap();
131    }
132
133    #[test]
134    #[serial]
135    #[cfg(feature = "miniscript")]
136    fn test_get_miniscript_descriptors() {
137        let client = get_first_device();
138        let account = Some(10);
139        let descriptor = client
140            .get_descriptors::<Descriptor<DescriptorPublicKey>>(account)
141            .unwrap();
142        assert!(descriptor.internal.len() > 0);
143        assert!(descriptor.receive.len() > 0);
144    }
145
146    #[test]
147    #[serial]
148    #[cfg(feature = "miniscript")]
149    fn test_display_address_with_miniscript_desc() {
150        let client = get_first_device();
151        let descriptor = client
152            .get_descriptors::<Descriptor<DescriptorPublicKey>>(None)
153            .unwrap();
154        let descriptor = descriptor.receive.first().unwrap();
155        client.display_address_with_desc(descriptor).unwrap();
156    }
157
158    #[test]
159    #[serial]
160    fn test_display_address_with_path_legacy() {
161        let client = get_first_device();
162        let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
163        client
164            .display_address_with_path(&derivation_path, types::HWIAddressType::Legacy)
165            .unwrap();
166    }
167
168    #[test]
169    #[serial]
170    fn test_display_address_with_path_nested_segwit() {
171        let client = get_first_device();
172        let derivation_path = DerivationPath::from_str("m/49'/1'/0'/0/0").unwrap();
173
174        client
175            .display_address_with_path(&derivation_path, types::HWIAddressType::Sh_Wit)
176            .unwrap();
177    }
178
179    #[test]
180    #[serial]
181    fn test_display_address_with_path_native_segwit() {
182        let client = get_first_device();
183        let derivation_path = DerivationPath::from_str("m/84'/1'/0'/0/0").unwrap();
184
185        client
186            .display_address_with_path(&derivation_path, types::HWIAddressType::Wit)
187            .unwrap();
188    }
189
190    // TODO: HWI 2.0.2 doesn't support displayaddress with taproot
191    // #[test]
192    // #[serial]
193    // fn test_display_address_with_path_taproot() {}
194
195    #[test]
196    #[serial]
197    fn test_sign_tx() {
198        let devices = HWIClient::enumerate().unwrap();
199        let device = devices.first().unwrap().as_ref().unwrap();
200        let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
201        let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
202
203        let address = client
204            .display_address_with_path(&derivation_path, types::HWIAddressType::Legacy)
205            .unwrap();
206
207        let pk = client.get_xpub(&derivation_path, true).unwrap();
208        let mut hd_keypaths: BTreeMap<secp256k1::PublicKey, KeySource> = Default::default();
209        // Here device fingerprint is same as master xpub fingerprint
210        hd_keypaths.insert(pk.public_key, (device.fingerprint, derivation_path));
211
212        let script_pubkey = address.address.script_pubkey();
213
214        let previous_tx = Transaction {
215            version: 1,
216            lock_time: LockTime::from_consensus(0).into(),
217            input: vec![TxIn::default()],
218            output: vec![TxOut {
219                value: 100,
220                script_pubkey: script_pubkey.clone(),
221            }],
222        };
223
224        let previous_txin = TxIn {
225            previous_output: bitcoin::OutPoint {
226                txid: previous_tx.txid(),
227                vout: Default::default(),
228            },
229            ..Default::default()
230        };
231        let psbt = bitcoin::psbt::PartiallySignedTransaction {
232            unsigned_tx: Transaction {
233                version: 1,
234                lock_time: LockTime::from_consensus(0).into(),
235                input: vec![previous_txin],
236                output: vec![TxOut {
237                    value: 50,
238                    script_pubkey: script_pubkey,
239                }],
240            },
241            xpub: Default::default(),
242            version: Default::default(),
243            proprietary: Default::default(),
244            unknown: Default::default(),
245
246            inputs: vec![Input {
247                non_witness_utxo: Some(previous_tx),
248                witness_utxo: None,
249                bip32_derivation: hd_keypaths,
250                ..Default::default()
251            }],
252            outputs: vec![Output::default()],
253        };
254        let client = get_first_device();
255        client.sign_tx(&psbt).unwrap();
256    }
257
258    #[test]
259    #[serial]
260    fn test_get_keypool() {
261        let client = get_first_device();
262        let keypool = true;
263        let internal = false;
264        let address_type = types::HWIAddressType::Legacy;
265        let account = Some(8);
266        let derivation_path = DerivationPath::from_str("m/44'/1'/0'").unwrap();
267        let start = 1;
268        let end = 5;
269        client
270            .get_keypool(
271                keypool,
272                internal,
273                address_type,
274                false,
275                account,
276                Some(&derivation_path),
277                start,
278                end,
279            )
280            .unwrap();
281
282        let keypool = true;
283        let internal = true;
284        let address_type = types::HWIAddressType::Wit;
285        let account = None;
286        let start = 1;
287        let end = 8;
288        client
289            .get_keypool(
290                keypool,
291                internal,
292                address_type,
293                false,
294                account,
295                None,
296                start,
297                end,
298            )
299            .unwrap();
300
301        let keypool = false;
302        let internal = true;
303        let address_type = types::HWIAddressType::Sh_Wit;
304        let account = Some(1);
305        let start = 0;
306        let end = 10;
307        client
308            .get_keypool(
309                keypool,
310                internal,
311                address_type,
312                false,
313                account,
314                Some(&derivation_path),
315                start,
316                end,
317            )
318            .unwrap();
319    }
320
321    #[test]
322    #[serial]
323    #[ignore]
324    fn test_install_udev_rules() {
325        if cfg!(target_os = "linux") {
326            HWIClient::install_udev_rules(None, None).unwrap()
327        }
328    }
329
330    #[test]
331    #[serial]
332    fn test_set_log_level() {
333        HWIClient::set_log_level(types::LogLevel::DEBUG).unwrap();
334        test_enumerate();
335    }
336
337    #[test]
338    #[serial]
339    fn test_toggle_passphrase() {
340        let devices = HWIClient::enumerate().unwrap();
341        let unsupported = [
342            HWIDeviceType::Ledger,
343            HWIDeviceType::BitBox01,
344            HWIDeviceType::Coldcard,
345            HWIDeviceType::Jade,
346        ];
347        for device in devices {
348            let device = device.unwrap();
349            if unsupported.contains(&device.device_type) {
350                // These devices don't support togglepassphrase
351                continue;
352            }
353            let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
354            client.toggle_passphrase().unwrap();
355            break;
356        }
357    }
358
359    #[test]
360    #[serial]
361    fn test_get_version() {
362        HWIClient::get_version().unwrap();
363    }
364
365    #[test]
366    #[serial]
367    #[ignore]
368    // At the moment (hwi v2.1.1 and trezor-firmware core v2.5.2) work only with physical devices and NOT emulators!
369    fn test_setup_trezor_device() {
370        let client = HWIClient::find_device(
371            None,
372            Some(HWIDeviceType::Trezor),
373            None,
374            false,
375            Network::Testnet,
376        )
377        .unwrap();
378        client.setup_device(Some("My Label"), None).unwrap();
379    }
380
381    #[test]
382    #[serial]
383    #[ignore]
384    // At the moment (hwi v2.1.1 and trezor-firmware core v2.5.2) work only with physical devices and NOT emulators!
385    fn test_restore_trezor_device() {
386        let client = HWIClient::find_device(
387            None,
388            Some(HWIDeviceType::Trezor),
389            None,
390            false,
391            Network::Testnet,
392        )
393        .unwrap();
394        client.restore_device(Some("My Label"), None).unwrap();
395    }
396
397    #[test]
398    #[serial]
399    fn test_backup_device() {
400        let devices = HWIClient::enumerate().unwrap();
401        let supported = [
402            HWIDeviceType::BitBox01,
403            HWIDeviceType::BitBox02,
404            HWIDeviceType::Coldcard,
405        ];
406        for device in devices {
407            let device = device.unwrap();
408            if supported.contains(&device.device_type) {
409                let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
410                client.backup_device(Some("My Label"), None).unwrap();
411            }
412        }
413    }
414
415    #[test]
416    #[serial]
417    #[ignore]
418    fn test_wipe_device() {
419        let devices = HWIClient::enumerate().unwrap();
420        let unsupported = [
421            HWIDeviceType::Ledger,
422            HWIDeviceType::Coldcard,
423            HWIDeviceType::Jade,
424        ];
425        for device in devices {
426            let device = device.unwrap();
427            if unsupported.contains(&device.device_type) {
428                // These devices don't support wipe
429                continue;
430            }
431            let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
432            client.wipe_device().unwrap();
433        }
434    }
435
436    #[test]
437    #[serial]
438    #[ignore]
439    fn test_install_hwi() {
440        HWIClient::install_hwilib(Some("2.1.1")).unwrap();
441    }
442}