Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.

Commit 1ce33fa

Browse files
committed
Upgrade bitcoin and miniscript
Upgrade to `miniscript` v0.10.0 and `bitcoin` v0.30.0 API Changes: - `Network` is now `non_exhaustive` so we implement `TryFrom` instead of `From`. - `get_client`: Now takes an `HWIChain` instead of a generic, this is less general, and less usable. Done because `From<Network>` can no longer be implemented on `HWIChain` because of the newly added `non_exhaustive` on `Network`. - `Address` now has a tag and can only be deserialized unchecked. We just call `assume_checked` in the unit tests. Is the error handling for new `TryFrom` acceptable to the project?
1 parent f2fd85c commit 1ce33fa

5 files changed

Lines changed: 45 additions & 38 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ readme = "README.md"
1111
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1212

1313
[dependencies]
14-
bitcoin = { version = "0.29.1", features = ["serde", "base64"] }
14+
bitcoin = { version = "0.30.0", features = ["serde", "base64"] }
1515
serde = { version = "^1.0", features = ["derive"] }
1616
serde_json = { version = "^1.0" }
1717
pyo3 = { version = "0.15.1", features = ["auto-initialize"] }
1818
base64 = "0.13.0"
1919

20-
miniscript = { version = "9.0", features = ["serde"], optional = true }
20+
miniscript = { version = "10.0", features = ["serde"], optional = true }
2121

2222
[dev-dependencies]
2323
serial_test = "0.6.0"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pip install -r requirements.txt
5555

5656
```rust
5757
use bitcoin::Network;
58-
use bitcoin::util::bip32::DerivationPath;
58+
use bitcoin::bip32::DerivationPath;
5959
use hwi::error::Error;
6060
use hwi::HWIClient;
6161
use std::str::FromStr;
@@ -66,7 +66,7 @@ fn main() -> Result<(), Error> {
6666
panic!("No devices found!");
6767
}
6868
let first_device = devices.remove(0)?;
69-
let client = HWIClient::get_client(&first_device, true, Network::Testnet)?;
69+
let client = HWIClient::get_client(&first_device, true, Network::Bitcoin.into())?;
7070
let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
7171
let s = client.sign_message("I love BDK wallet", &derivation_path)?;
7272
println!("{:?}", s.signature);

src/interface.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@ use std::convert::TryInto;
22
use std::ops::Deref;
33
use std::process::Command;
44

5-
use bitcoin::consensus::encode::serialize;
6-
use bitcoin::util::bip32::DerivationPath;
7-
use bitcoin::util::psbt::PartiallySignedTransaction;
8-
9-
use base64;
5+
use bitcoin::bip32::DerivationPath;
6+
use bitcoin::psbt::PartiallySignedTransaction;
107

118
use serde::de::DeserializeOwned;
129
use serde_json::value::Value;
@@ -101,7 +98,7 @@ impl HWIClient {
10198
/// let devices = HWIClient::enumerate()?;
10299
/// for device in devices {
103100
/// let device = device?;
104-
/// let client = HWIClient::get_client(&device, false, bitcoin::Network::Testnet)?;
101+
/// let client = HWIClient::get_client(&device, false, bitcoin::Network::Testnet.into())?;
105102
/// let xpub = client.get_master_xpub(HWIAddressType::Tap, 0)?;
106103
/// println!(
107104
/// "I can see a {} here, and its xpub is {}",
@@ -112,18 +109,19 @@ impl HWIClient {
112109
/// # Ok(())
113110
/// # }
114111
/// ```
115-
pub fn get_client<C>(device: &HWIDevice, expert: bool, chain: C) -> Result<HWIClient, Error>
116-
where
117-
C: Into<HWIChain>,
118-
{
112+
pub fn get_client(
113+
device: &HWIDevice,
114+
expert: bool,
115+
chain: HWIChain,
116+
) -> Result<HWIClient, Error> {
119117
let libs = HWILib::initialize()?;
120118
Python::with_gil(|py| {
121119
let client_args = (
122120
device.device_type.to_string(),
123121
&device.path,
124122
"",
125123
expert,
126-
chain.into(),
124+
chain,
127125
);
128126
let client = libs
129127
.commands
@@ -211,13 +209,12 @@ impl HWIClient {
211209
&self,
212210
psbt: &PartiallySignedTransaction,
213211
) -> Result<HWIPartiallySignedTransaction, Error> {
214-
let psbt = base64::encode(serialize(psbt));
215212
Python::with_gil(|py| {
216213
let output = self
217214
.hwilib
218215
.commands
219216
.getattr(py, "signtx")?
220-
.call1(py, (&self.hw_client, psbt))?;
217+
.call1(py, (&self.hw_client, psbt.to_string()))?;
221218
let output = self.hwilib.json_dumps.call1(py, (output,))?;
222219
deserialize_obj!(&output.to_string())
223220
})

src/lib.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! # Example - display address with path
44
//! ```no_run
5-
//! use bitcoin::util::bip32::{ChildNumber, DerivationPath};
5+
//! use bitcoin::bip32::{ChildNumber, DerivationPath};
66
//! use hwi::error::Error;
77
//! use hwi::interface::HWIClient;
88
//! use hwi::types;
@@ -16,12 +16,12 @@
1616
//! }
1717
//! let device = devices.remove(0)?;
1818
//! // Create a client for a device
19-
//! let client = HWIClient::get_client(&device, true, bitcoin::Network::Testnet)?;
19+
//! let client = HWIClient::get_client(&device, true, bitcoin::Network::Testnet.into())?;
2020
//! // Display the address from path
2121
//! let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
2222
//! let hwi_address =
2323
//! client.display_address_with_path(&derivation_path, types::HWIAddressType::Tap)?;
24-
//! println!("{}", hwi_address.address);
24+
//! println!("{}", hwi_address.address.assume_checked());
2525
//! Ok(())
2626
//! }
2727
//! ```
@@ -40,13 +40,14 @@ pub mod types;
4040

4141
#[cfg(test)]
4242
mod tests {
43-
use crate::types::{self, HWIDeviceType};
43+
use crate::types::{self, HWIDeviceType, TESTNET};
4444
use crate::HWIClient;
4545
use std::collections::BTreeMap;
4646
use std::str::FromStr;
4747

48+
use bitcoin::bip32::{DerivationPath, KeySource};
49+
use bitcoin::locktime::absolute;
4850
use bitcoin::psbt::{Input, Output};
49-
use bitcoin::util::bip32::{DerivationPath, KeySource};
5051
use bitcoin::{secp256k1, Transaction};
5152
use bitcoin::{Network, TxIn, TxOut};
5253

@@ -81,7 +82,7 @@ mod tests {
8182
.expect("No devices found. Either plug in a hardware wallet, or start a simulator.")
8283
.as_ref()
8384
.expect("Error when opening the first device");
84-
HWIClient::get_client(&device, true, Network::Testnet).unwrap()
85+
HWIClient::get_client(&device, true, TESTNET).unwrap()
8586
}
8687

8788
#[test]
@@ -197,7 +198,7 @@ mod tests {
197198
fn test_sign_tx() {
198199
let devices = HWIClient::enumerate().unwrap();
199200
let device = devices.first().unwrap().as_ref().unwrap();
200-
let client = HWIClient::get_client(&device, true, Network::Testnet).unwrap();
201+
let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
201202
let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
202203

203204
let address = client
@@ -209,13 +210,15 @@ mod tests {
209210
// Here device fingerprint is same as master xpub fingerprint
210211
hd_keypaths.insert(pk.public_key, (device.fingerprint, derivation_path));
211212

213+
let script_pubkey = address.address.assume_checked().script_pubkey();
214+
212215
let previous_tx = Transaction {
213216
version: 1,
214-
lock_time: bitcoin::PackedLockTime(0),
217+
lock_time: absolute::LockTime::from_consensus(0),
215218
input: vec![TxIn::default()],
216219
output: vec![TxOut {
217220
value: 100,
218-
script_pubkey: address.address.script_pubkey(),
221+
script_pubkey: script_pubkey.clone(),
219222
}],
220223
};
221224

@@ -229,11 +232,11 @@ mod tests {
229232
let psbt = bitcoin::psbt::PartiallySignedTransaction {
230233
unsigned_tx: Transaction {
231234
version: 1,
232-
lock_time: bitcoin::PackedLockTime(0),
235+
lock_time: absolute::LockTime::from_consensus(0),
233236
input: vec![previous_txin],
234237
output: vec![TxOut {
235238
value: 50,
236-
script_pubkey: address.address.script_pubkey(),
239+
script_pubkey: script_pubkey,
237240
}],
238241
},
239242
xpub: Default::default(),
@@ -348,7 +351,7 @@ mod tests {
348351
// These devices don't support togglepassphrase
349352
continue;
350353
}
351-
let client = HWIClient::get_client(&device, true, Network::Testnet).unwrap();
354+
let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
352355
client.toggle_passphrase().unwrap();
353356
break;
354357
}
@@ -404,7 +407,7 @@ mod tests {
404407
for device in devices {
405408
let device = device.unwrap();
406409
if supported.contains(&device.device_type) {
407-
let client = HWIClient::get_client(&device, true, Network::Testnet).unwrap();
410+
let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
408411
client.backup_device(Some("My Label"), None).unwrap();
409412
}
410413
}
@@ -426,7 +429,7 @@ mod tests {
426429
// These devices don't support wipe
427430
continue;
428431
}
429-
let client = HWIClient::get_client(&device, true, Network::Testnet).unwrap();
432+
let client = HWIClient::get_client(&device, true, TESTNET).unwrap();
430433
client.wipe_device().unwrap();
431434
}
432435
}

src/types.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::convert::TryFrom;
22
use std::ops::Deref;
3+
use std::str::FromStr;
34

4-
use bitcoin::util::bip32::{ExtendedPubKey, Fingerprint};
5-
use bitcoin::util::{address::Address, psbt::PartiallySignedTransaction};
5+
use bitcoin::address::{Address, NetworkUnchecked};
6+
use bitcoin::bip32::{ExtendedPubKey, Fingerprint};
7+
use bitcoin::psbt::PartiallySignedTransaction;
68
use bitcoin::Network;
79

810
use pyo3::types::PyModule;
@@ -49,7 +51,7 @@ impl Deref for HWISignature {
4951

5052
#[derive(Clone, Eq, PartialEq, Debug, Deserialize)]
5153
pub struct HWIAddress {
52-
pub address: Address,
54+
pub address: Address<NetworkUnchecked>,
5355
}
5456

5557
#[derive(Clone, Eq, PartialEq, Debug, Deserialize)]
@@ -61,10 +63,8 @@ pub struct HWIPartiallySignedTransaction {
6163
fn deserialize_psbt<'de, D: Deserializer<'de>>(
6264
d: D,
6365
) -> Result<PartiallySignedTransaction, D::Error> {
64-
let b64_string = String::deserialize(d)?;
65-
let bytes = base64::decode(b64_string).map_err(serde::de::Error::custom)?;
66-
bitcoin::consensus::deserialize::<PartiallySignedTransaction>(&bytes[..])
67-
.map_err(serde::de::Error::custom)
66+
let s = String::deserialize(d)?;
67+
PartiallySignedTransaction::from_str(&s).map_err(serde::de::Error::custom)
6868
}
6969

7070
impl Deref for HWIPartiallySignedTransaction {
@@ -139,6 +139,10 @@ impl IntoPy<PyObject> for HWIChain {
139139
Testnet => chain.get_item("TEST").unwrap().into(),
140140
Regtest => chain.get_item("REGTEST").unwrap().into(),
141141
Signet => chain.get_item("SIGNET").unwrap().into(),
142+
// This handles non_exhaustive on Network which is only there to future proof
143+
// rust-bitcoin, will need to check this when upgrading rust-bitcoin.
144+
// Sane as of rust-bitcoin v0.30.0
145+
_ => panic!("unknown network"),
142146
}
143147
}
144148
}
@@ -149,6 +153,9 @@ impl From<Network> for HWIChain {
149153
}
150154
}
151155

156+
#[cfg(test)]
157+
pub const TESTNET: HWIChain = HWIChain(Network::Testnet);
158+
152159
// Used internally to deserialize the result of `hwi enumerate`. This might
153160
// contain an `error`, when it does, it might not contain all the fields `HWIDevice`
154161
// is supposed to have - for this reason, they're all Option.

0 commit comments

Comments
 (0)