Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ jobs:
strategy:
matrix:
rust:
- 1.82.0
- 1.70.0 # MSRV
- 1.85.0 # MSRV
- 1.92.0
features:
- ""
- default
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "electrum2descriptors"
version = "0.7.0"
version = "0.8.0"
authors = ["Riccardo Casatta <riccardo@casatta.it>"]
edition = "2018"
edition = "2024"
description = "Converts electrum xpubs (like vpub, ypub...) into output descriptors"
license = "MIT"
homepage = "https://github.com/RCasatta/electrum2descriptors"
Expand Down Expand Up @@ -32,7 +32,7 @@ regex = { version = "1", optional = true }

[dev-dependencies]
miniscript = "12"
bdk_wallet = "1.0.0"
bdk_wallet = "2.3"
rstest = "0.17"
tempfile = "3.5"

Expand Down
11 changes: 5 additions & 6 deletions src/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ use std::str::FromStr;
fn main() -> Result<(), Electrum2DescriptorError> {
let mut args = std::env::args();
args.next(); // first is program name
let err_msg =
"You must specify an extended public or private key or an electrum wallet file as first argument";
let err_msg = "You must specify an extended public or private key or an electrum wallet file as first argument";
let electrum_x = args
.next()
.ok_or_else(|| Electrum2DescriptorError::GenericBorrow(err_msg))?;
.ok_or(Electrum2DescriptorError::GenericBorrow(err_msg))?;
let descriptor = ElectrumExtendedPrivKey::from_str(&electrum_x)
.map(|e| e.to_descriptors())
.or_else(|_| ElectrumExtendedPubKey::from_str(&electrum_x).map(|e| e.to_descriptors()));
.map(|e| e.to_descriptor())

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For private keys this prints a descriptor that BDK can't load. Maybe keep to_descriptors() for xprv until BDK supports it? Also, {:?} at line 32 now prints a quoted string, and the README examples still show the old list.

.or_else(|_| ElectrumExtendedPubKey::from_str(&electrum_x).map(|e| e.to_descriptor()));
#[cfg(feature = "wallet_file")]
let descriptor = descriptor.or_else(|_| {
let wallet_file = Path::new(&electrum_x)
Expand All @@ -27,7 +26,7 @@ fn main() -> Result<(), Electrum2DescriptorError> {
return Err(Electrum2DescriptorError::GenericBorrow(err_msg));
}
let wallet = ElectrumWalletFile::from_file(wallet_file.as_path())?;
wallet.to_descriptors()
wallet.to_descriptor()
});

println!("{:?}", descriptor?);
Expand Down
39 changes: 33 additions & 6 deletions src/electrum_extended_priv_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ impl ElectrumExtendedKey for ElectrumExtendedPrivKey {
self.xprv.to_string()
}

/// Returns multipath descriptor

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

miniscript 12 and BDK 2.x reject multipath xprv. Please note that in the doc comment so callers know.

fn to_descriptor(&self) -> String {
let xprv = self.xprv.to_string();
let closing_parenthesis = if self.kind.contains('(') { ")" } else { "" };
format!("{}({}/<0;1>/*){}", self.kind, xprv, closing_parenthesis)
}

/// Returns internal and external descriptor
fn to_descriptors(&self) -> Descriptors {
let xprv = self.xprv.to_string();
Expand Down Expand Up @@ -151,7 +158,7 @@ impl ElectrumExtendedPrivKey {
let sentinel = sentinels
.iter()
.find(|sent| NetworkKind::from(sent.1) == self.xprv.network && sent.2 == self.kind)
.ok_or_else(|| Electrum2DescriptorError::UnknownType)?;
.ok_or(Electrum2DescriptorError::UnknownType)?;
let mut data = Vec::from(&sentinel.0[..]);
data.push(self.xprv.depth);
data.extend(self.xprv.parent_fingerprint.as_bytes());
Expand Down Expand Up @@ -188,13 +195,30 @@ mod tests {
#[test]
fn test_vprv_from_electrum() {
let electrum_xprv = ElectrumExtendedPrivKey::from_str("yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF").unwrap();
assert_eq!(electrum_xprv.xprv.to_string(),"xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD");
assert_eq!(
electrum_xprv.xprv.to_string(),
"xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD"
);
assert_eq!(electrum_xprv.kind, "sh(wpkh");
let descriptor = electrum_xprv.to_descriptor();
assert_eq!(
descriptor,
"sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/<0;1>/*))"
);
let descriptors = electrum_xprv.to_descriptors();
assert_eq!(descriptors.external, "sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/0/*))");
assert_eq!(descriptors.change, "sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/1/*))");
assert_eq!(
descriptors.external,
"sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/0/*))"
);
assert_eq!(
descriptors.change,
"sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/1/*))"
);
let xprv = electrum_xprv.xprv();
assert_eq!(xprv.to_string(), "xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD");
assert_eq!(
xprv.to_string(),
"xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD"
);
}

#[test]
Expand All @@ -203,7 +227,10 @@ mod tests {
Xpriv::from_str("xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD").unwrap(),
"sh(wpkh".to_string(),
);
assert_eq!(electrum_xprv.electrum_xprv().unwrap(), "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF");
assert_eq!(
electrum_xprv.electrum_xprv().unwrap(),
"yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF"
);
}

#[test]
Expand Down
94 changes: 84 additions & 10 deletions src/electrum_extended_pub_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ impl ElectrumExtendedKey for ElectrumExtendedPubKey {
[0, 1].map(|i| format!("{}({}/{}/*){}", self.kind, xpub, i, closing_parenthesis));
Descriptors { external, change }
}

/// Returns multipath descriptor
fn to_descriptor(&self) -> String {
let xpub = self.xpub.to_string();
let closing_parenthesis = if self.kind.contains('(') { ")" } else { "" };
format!("{}({}/<0;1>/*){}", self.kind, xpub, closing_parenthesis)
}
}

impl ElectrumExtendedPubKey {
Expand All @@ -150,7 +157,7 @@ impl ElectrumExtendedPubKey {
let sentinel = sentinels
.iter()
.find(|sent| NetworkKind::from(sent.1) == self.xpub.network && sent.2 == self.kind)
.ok_or_else(|| Electrum2DescriptorError::UnknownType)?;
.ok_or(Electrum2DescriptorError::UnknownType)?;
let mut data = Vec::from(&sentinel.0[..]);
data.push(self.xpub.depth);
data.extend(self.xpub.parent_fingerprint.as_bytes());
Expand Down Expand Up @@ -188,13 +195,30 @@ mod tests {
#[test]
fn test_vpub_from_electrum() {
let electrum_xpub = ElectrumExtendedPubKey::from_str("vpub5VXaSncXqxLbdmvrC4Y8z9CszPwuEscADoetWhfrxDFzPUbL5nbVtanYDkrVEutkv9n5A5aCcvRC9swbjDKgHjCZ2tAeae8VsBuPbS8KpXv").unwrap();
assert_eq!(electrum_xpub.xpub.to_string(),"tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp");
assert_eq!(
electrum_xpub.xpub.to_string(),
"tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp"
);
assert_eq!(electrum_xpub.kind, "wpkh");
let descriptor = electrum_xpub.to_descriptor();
assert_eq!(
descriptor,
"wpkh(tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp/<0;1>/*)"
);
let descriptors = electrum_xpub.to_descriptors();
assert_eq!(descriptors.external, "wpkh(tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp/0/*)");
assert_eq!(descriptors.change, "wpkh(tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp/1/*)");
assert_eq!(
descriptors.external,
"wpkh(tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp/0/*)"
);
assert_eq!(
descriptors.change,
"wpkh(tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp/1/*)"
);
let xpub = electrum_xpub.xpub();
assert_eq!(xpub.to_string(), "tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp");
assert_eq!(
xpub.to_string(),
"tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp"
);
}

#[test]
Expand All @@ -203,9 +227,15 @@ mod tests {
Xpub::from_str("tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp").unwrap(),
"wpkh".to_string(),
);
assert_eq!(electrum_xpub.xpub.to_string(),"tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp");
assert_eq!(
electrum_xpub.xpub.to_string(),
"tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp"
);
assert_eq!(electrum_xpub.kind, "wpkh");
assert_eq!(electrum_xpub.electrum_xpub().unwrap(), "vpub5VXaSncXqxLbdmvrC4Y8z9CszPwuEscADoetWhfrxDFzPUbL5nbVtanYDkrVEutkv9n5A5aCcvRC9swbjDKgHjCZ2tAeae8VsBuPbS8KpXv");
assert_eq!(
electrum_xpub.electrum_xpub().unwrap(),
"vpub5VXaSncXqxLbdmvrC4Y8z9CszPwuEscADoetWhfrxDFzPUbL5nbVtanYDkrVEutkv9n5A5aCcvRC9swbjDKgHjCZ2tAeae8VsBuPbS8KpXv"
);
}

#[test]
Expand All @@ -219,9 +249,18 @@ mod tests {
#[test]
fn test_slip121_vectors() {
// from https://github.com/satoshilabs/slips/blob/master/slip-0132.md
test_first_address("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj","1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA");
test_first_address("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP","37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf");
test_first_address("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs","bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu");
test_first_address(
"xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
);
test_first_address(
"ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
);
test_first_address(
"zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
);
}

fn test_first_address(electrum_xpub: &str, expected_first_address: &str) {
Expand All @@ -241,4 +280,39 @@ mod tests {
.to_string();
assert_eq!(expected_first_address, first_address);
}

#[test]
fn test_slip121_vectors_multipath() {
// from https://github.com/satoshilabs/slips/blob/master/slip-0132.md
test_first_address_multipath(
"xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
);
test_first_address_multipath(
"ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
);
test_first_address_multipath(
"zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
);
}

fn test_first_address_multipath(electrum_xpub: &str, expected_first_address: &str) {
let electrum_xpub = ElectrumExtendedPubKey::from_str(electrum_xpub).unwrap();
assert_eq!(electrum_xpub.xpub.network, Network::Bitcoin.into());
let descriptor = electrum_xpub.to_descriptor();
let descriptor = descriptor.replace("<0;1>", "0");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing <0;1> with 0 skips multipath parsing. Parse the multipath descriptor and use into_single_descriptors() to check both branches.

let descriptor: miniscript::Descriptor<DescriptorPublicKey> = descriptor.parse().unwrap();
let secp = Secp256k1::verification_only();
let first_address = descriptor
.at_derivation_index(0)
.unwrap()
.derived_descriptor(&secp)
.unwrap()
.address(miniscript::bitcoin::Network::Bitcoin)
.unwrap()
.to_string();
assert_eq!(expected_first_address, first_address);
}
}
22 changes: 15 additions & 7 deletions src/electrum_wallet_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
};
use bitcoin::bip32::{Xpriv, Xpub};
use regex::Regex;
use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de, ser::SerializeMap};
use std::{fmt, io::BufReader, path::Path, str::FromStr, string::ToString};

/// Representation of an electrum wallet file. Has custom serialization and de-serialization routines to more accurately represent what we need, and the electrum wallet file format.
Expand Down Expand Up @@ -82,8 +82,9 @@ impl ElectrumWalletFile {

/// Construct from a single signature output descriptor. Only the external descriptor is needed, the change descriptor is implied.
fn from_descriptor_singlesig(desc: &str) -> Result<Self, Electrum2DescriptorError> {
let re =
Regex::new(r#"(pkh|sh\(wpkh|sh\(wsh|wpkh|wsh)\((([tx]p(ub|rv)[0-9A-Za-z]+)/0/\*)\)+"#)?;
let re = Regex::new(
r#"(pkh|sh\(wpkh|sh\(wsh|wpkh|wsh)\((([tx]p(ub|rv)[0-9A-Za-z]+)/.*/\*)\)+"#,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/.*/\* accepts any path (/1/*, /5/7/*, /0h/*, /<1;0>/*) and silently maps it to Electrum's 0/1. Suggest /(?:0|<0;1>)/\*.

)?;
let captures = re.captures(desc).map(|captures| {
captures
.iter()
Expand All @@ -99,7 +100,7 @@ impl ElectrumWalletFile {
return Err(Electrum2DescriptorError::UnknownDescriptorFormat(format!(
"{:?}",
captures
)))
)));
}
};

Expand All @@ -113,7 +114,7 @@ impl ElectrumWalletFile {
/// Construct from a multisig output descriptor. Only the external descriptor is needed, the change descriptor is implied.
fn from_descriptor_multisig(desc: &str) -> Result<Self, Electrum2DescriptorError> {
let re = Regex::new(
r#"(sh|sh\(wsh|wsh)\(sortedmulti\((\d),([tx]p(ub|rv)[0-9A-Za-z]+/0/\*,?)+\)+"#,
r#"(sh|sh\(wsh|wsh)\(sortedmulti\((\d),([tx]p(ub|rv)[0-9A-Za-z]+/.*/\*,?)+\)+"#,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. A multisig with /3/* and /9/9/* is accepted as a 0/1 wallet. Suggest /(?:0|<0;1>)/\*, ideally the same path for every key.

)?;
let captures = re.captures(desc).map(|captures| {
captures
Expand All @@ -132,7 +133,7 @@ impl ElectrumWalletFile {
_ => {
return Err(Electrum2DescriptorError::UnknownScriptKind(
kind.to_string(),
))
));
}
};
let re = Regex::new(r#"[tx]p[ur][bv][0-9A-Za-z]+"#)?;
Expand Down Expand Up @@ -203,6 +204,12 @@ impl ElectrumWalletFile {
}
}

/// Generate multipath output descriptor matching the electrum wallet
pub fn to_descriptor(&self) -> Result<String, Electrum2DescriptorError> {
let desc = self.to_descriptors()?;
Ok(desc.external.replace("/0/*", "/<0;1>/*"))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: building the string directly would be more robust than replace("/0/*", …).

}

/// validate the internal structure
fn validate(&self) -> Result<(), Electrum2DescriptorError> {
let expected_keystores: usize = match self.wallet_type {
Expand All @@ -217,6 +224,7 @@ impl ElectrumWalletFile {
));
}

#[allow(clippy::collapsible_if)]
if let WalletType::Multisig(x, _y) = self.wallet_type {
if x as usize > expected_keystores {
return Err(Electrum2DescriptorError::NumberSignaturesKeyStores(
Expand Down Expand Up @@ -294,7 +302,7 @@ impl<'de> Deserialize<'de> for ElectrumWalletFile {
{
struct FieldVisitor;

impl<'de> de::Visitor<'de> for FieldVisitor {
impl de::Visitor<'_> for FieldVisitor {
type Value = Field;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pub trait ElectrumExtendedKey {
/// Returns internal and external descriptor
fn to_descriptors(&self) -> Descriptors;

/// Returns multipath descriptor
fn to_descriptor(&self) -> String;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a default method using kind() and xkey_str(). That removes the duplicate in the pub and priv impls and doesn't break implementors.


/// Returns the bitcoin extended key (xpub or xprv) as String
fn xkey_str(&self) -> String;

Expand Down
Loading