diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2905cd6..e579c73 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,8 +17,8 @@ jobs: strategy: matrix: rust: - - 1.82.0 - - 1.70.0 # MSRV + - 1.85.0 # MSRV + - 1.92.0 features: - "" - default diff --git a/Cargo.toml b/Cargo.toml index e7c7b3a..10c06a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "electrum2descriptors" -version = "0.7.0" +version = "0.8.0" authors = ["Riccardo Casatta "] -edition = "2018" +edition = "2024" description = "Converts electrum xpubs (like vpub, ypub...) into output descriptors" license = "MIT" homepage = "https://github.com/RCasatta/electrum2descriptors" @@ -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" diff --git a/src/bin.rs b/src/bin.rs index 4b532dd..4d677d8 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -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()) + .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) @@ -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?); diff --git a/src/electrum_extended_priv_key.rs b/src/electrum_extended_priv_key.rs index b0a98e0..e70ac24 100644 --- a/src/electrum_extended_priv_key.rs +++ b/src/electrum_extended_priv_key.rs @@ -124,6 +124,13 @@ impl ElectrumExtendedKey for ElectrumExtendedPrivKey { self.xprv.to_string() } + /// Returns multipath descriptor + 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(); @@ -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()); @@ -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] @@ -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] diff --git a/src/electrum_extended_pub_key.rs b/src/electrum_extended_pub_key.rs index 68e4ae2..e3cc3b9 100644 --- a/src/electrum_extended_pub_key.rs +++ b/src/electrum_extended_pub_key.rs @@ -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 { @@ -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()); @@ -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] @@ -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] @@ -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) { @@ -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"); + let descriptor: miniscript::Descriptor = 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); + } } diff --git a/src/electrum_wallet_file.rs b/src/electrum_wallet_file.rs index e121e48..f22b58f 100644 --- a/src/electrum_wallet_file.rs +++ b/src/electrum_wallet_file.rs @@ -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. @@ -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 { - 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]+)/.*/\*)\)+"#, + )?; let captures = re.captures(desc).map(|captures| { captures .iter() @@ -99,7 +100,7 @@ impl ElectrumWalletFile { return Err(Electrum2DescriptorError::UnknownDescriptorFormat(format!( "{:?}", captures - ))) + ))); } }; @@ -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 { 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]+/.*/\*,?)+\)+"#, )?; let captures = re.captures(desc).map(|captures| { captures @@ -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]+"#)?; @@ -203,6 +204,12 @@ impl ElectrumWalletFile { } } + /// Generate multipath output descriptor matching the electrum wallet + pub fn to_descriptor(&self) -> Result { + let desc = self.to_descriptors()?; + Ok(desc.external.replace("/0/*", "/<0;1>/*")) + } + /// validate the internal structure fn validate(&self) -> Result<(), Electrum2DescriptorError> { let expected_keystores: usize = match self.wallet_type { @@ -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( @@ -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 { diff --git a/src/lib.rs b/src/lib.rs index f7dc75c..9752698 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; + /// Returns the bitcoin extended key (xpub or xprv) as String fn xkey_str(&self) -> String; diff --git a/tests/wallets2descriptors.rs b/tests/wallets2descriptors.rs index ff17ab4..46f7c3c 100644 --- a/tests/wallets2descriptors.rs +++ b/tests/wallets2descriptors.rs @@ -1,5 +1,5 @@ #![cfg(feature = "wallet_file")] -use bdk_wallet::{bitcoin::Network, KeychainKind, Wallet}; +use bdk_wallet::{KeychainKind, Wallet, bitcoin::Network}; use libelectrum2descriptors::{Descriptors, ElectrumWalletFile}; use rstest::rstest; use std::{ @@ -9,27 +9,41 @@ use std::{ use tempfile::tempdir; #[rstest] -#[case::default_legacy("default_legacy", +#[case::default_legacy( + "default_legacy", "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/0/*)", - "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/1/*)")] -#[case::default_legacy_watch("default_legacy_watch", + "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/1/*)" +)] +#[case::default_legacy_watch( + "default_legacy_watch", "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*)", - "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/1/*)")] -#[case::default_segwit("default_segwit", + "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/1/*)" +)] +#[case::default_segwit( + "default_segwit", "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/0/*)", - "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/1/*)")] -#[case::multisig_hw_segwit("multisig_hw_segwit", + "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/1/*)" +)] +#[case::multisig_hw_segwit( + "multisig_hw_segwit", "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/0/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/0/*))", - "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/1/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/1/*))")] -#[case::multisig_legacy("multisig_legacy", + "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/1/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/1/*))" +)] +#[case::multisig_legacy( + "multisig_legacy", "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/0/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*))", - "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/1/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/1/*))")] -#[case::multisig_segwit("multisig_segwit", + "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/1/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/1/*))" +)] +#[case::multisig_segwit( + "multisig_segwit", "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/0/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/0/*))", - "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/1/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/1/*))")] -#[case::multisig_wrapped_watch("multisig_wrapped_watch", + "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/1/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/1/*))" +)] +#[case::multisig_wrapped_watch( + "multisig_wrapped_watch", "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))", - "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/1/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/1/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/1/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/1/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/1/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/1/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/1/*)))")] + "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/1/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/1/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/1/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/1/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/1/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/1/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/1/*)))" +)] fn parse_wallet( #[case] wallet_name: &str, #[case] expected_descriptor_ext: &str, @@ -41,6 +55,9 @@ fn parse_wallet( let addr = first_address_from_descriptor(&desc.external, Network::Testnet); let exp = first_address_from_wallet_file(wallet_name); assert_eq!(addr, exp); + + let desc = wallet_name_to_descriptor(wallet_name); + assert_eq!(desc, expected_descriptor_ext.replace("/0/*", "/<0;1>/*")); } fn wallet_name_to_descriptors(wallet_name: &str) -> Descriptors { @@ -49,6 +66,12 @@ fn wallet_name_to_descriptors(wallet_name: &str) -> Descriptors { wallet.to_descriptors().unwrap() } +fn wallet_name_to_descriptor(wallet_name: &str) -> String { + let wallet_file = get_test_wallet_file(wallet_name); + let wallet = ElectrumWalletFile::from_file(wallet_file.as_path()).unwrap(); + wallet.to_descriptor().unwrap() +} + fn get_test_wallet_file(wallet_name: &str) -> PathBuf { let test_dir = Path::new(file!()).canonicalize().unwrap(); let test_dir = test_dir.as_path().parent().unwrap(); @@ -62,10 +85,14 @@ fn get_test_wallet_file(wallet_name: &str) -> PathBuf { } fn first_address_from_descriptor(desc: &str, network: Network) -> String { - let mut wallet = Wallet::create_single(desc.to_string()) - .network(network) - .create_wallet_no_persist() - .unwrap(); + let mut wallet = if desc.contains("<0;1>") { + Wallet::create_from_two_path_descriptor(desc.to_string()) + } else { + Wallet::create_single(desc.to_string()) + } + .network(network) + .create_wallet_no_persist() + .unwrap(); wallet .reveal_next_address(KeychainKind::External) .address @@ -89,20 +116,34 @@ fn parse_imported(#[case] wallet_name: &str) { } #[rstest] -#[case::default_legacy("default_legacy", - "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/0/*)")] -#[case::default_legacy_watch("default_legacy_watch", - "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*)")] -#[case::default_segwit("default_segwit", - "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/0/*)")] -#[case::multisig_hw_segwit("multisig_hw_segwit", - "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/0/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/0/*))")] -#[case::multisig_legacy("multisig_legacy", - "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/0/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*))")] -#[case::multisig_segwit("multisig_segwit", - "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/0/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/0/*))")] -#[case::multisig_wrapped_watch("multisig_wrapped_watch", - "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))")] +#[case::default_legacy( + "default_legacy", + "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/0/*)" +)] +#[case::default_legacy_watch( + "default_legacy_watch", + "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*)" +)] +#[case::default_segwit( + "default_segwit", + "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/0/*)" +)] +#[case::multisig_hw_segwit( + "multisig_hw_segwit", + "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/0/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/0/*))" +)] +#[case::multisig_legacy( + "multisig_legacy", + "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/0/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*))" +)] +#[case::multisig_segwit( + "multisig_segwit", + "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/0/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/0/*))" +)] +#[case::multisig_wrapped_watch( + "multisig_wrapped_watch", + "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))" +)] fn descriptor_electrum_wallet_roundtrip(#[case] wallet_name: &str, #[case] descriptor: &str) { let wallet = ElectrumWalletFile::from_descriptor(descriptor).unwrap(); @@ -120,19 +161,73 @@ fn descriptor_electrum_wallet_roundtrip(#[case] wallet_name: &str, #[case] descr #[rstest] #[case::default_legacy( - "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/0/*)")] + "default_legacy", + "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/<0;1>/*)" +)] +#[case::default_legacy_watch( + "default_legacy_watch", + "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/<0;1>/*)" +)] +#[case::default_segwit( + "default_segwit", + "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/<0;1>/*)" +)] +#[case::multisig_hw_segwit( + "multisig_hw_segwit", + "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/<0;1>/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/<0;1>/*))" +)] +#[case::multisig_legacy( + "multisig_legacy", + "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/<0;1>/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/<0;1>/*))" +)] +#[case::multisig_segwit( + "multisig_segwit", + "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/<0;1>/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/<0;1>/*))" +)] +#[case::multisig_wrapped_watch( + "multisig_wrapped_watch", + "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/<0;1>/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/<0;1>/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/<0;1>/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/<0;1>/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/<0;1>/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/<0;1>/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/<0;1>/*)))" +)] +fn descriptor_electrum_wallet_roundtrip_multipath( + #[case] wallet_name: &str, + #[case] descriptor: &str, +) { + let wallet = ElectrumWalletFile::from_descriptor(descriptor).unwrap(); + + let tempdir = tempdir().unwrap(); + let filename = tempdir.path().join(wallet_name); + wallet.to_file(&filename).unwrap(); + // Testing that electrum can load the files was done manually. + + let imported = ElectrumWalletFile::from_file(&filename).unwrap(); + assert_eq!(wallet, imported); + + let desc = wallet.to_descriptor().unwrap(); + assert_eq!(desc, descriptor); +} + +#[rstest] +#[case::default_legacy( + "pkh(tprv8ZgxMBicQKsPeYnCHtn5QZqhTgkkDmXebfQMXWmX7ThXJFCbzDTKFNRsB43GUmHzu2pdGcnnegFy175kFcgZQYC5BFPnRdYDPQyqetpyjb5/0/*)" +)] #[case::default_legacy_watch( - "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*)")] + "pkh(tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*)" +)] #[case::default_segwit( - "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/0/*)")] + "wpkh(tprv8cvkZzx9zA7EfFDbH945mK23r7hg6EHXUk79wVUSRukwyctFS1AdpSpkZcykAMDveCj8RA3R4jwFTKMwMbWexJox8NMqq7YphJLDumfCSfu/0/*)" +)] #[case::multisig_hw_segwit( - "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/0/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/0/*))")] + "wsh(sortedmulti(2,tpubDEcw4ooTbmw62zBKdkYepoP3z4WWugdeRzPHHAbk8XVsPfBE9AAZMNghiqwtdFgtabaeppBTPmezUkRkQZidLcSJp3XTASbMakHcYauWehZ/0/*,tpubDEbkvhmJoZMq3SUNqEf3aEsubvqsCUPc7rroHkGERgS7qA1gQVMxUPrgzth6x43odirLohwf4aMHpvcnWi3jCB2xkizv8T4B2KqLRZVLC6K/0/*))" +)] #[case::multisig_legacy( - "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/0/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*))")] + "sh(sortedmulti(2,tprv8ZgxMBicQKsPeLPWr5WbJDAhANr6irc1Yf7eUNCYjGYap27HU4bDBXWGMT3X75FhDyxNXr6pK4QeHcCBvkqchQzK8wZ4JbGv5X5MWtXQtqy/0/*,tpubD6NzVbkrYhZ4Y1ozBYSfoyVp2iGgP6iZAy18p2opXjVv8jTNccGuRs3jMCMe4ncfwy2RUJsoZLSXsGiFhN47xFbJgtRvCuV3RP3UnxpsrZt/0/*))" +)] #[case::multisig_segwit( - "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/0/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/0/*))")] + "wsh(sortedmulti(2,tprv8dNybiDsdyms39SAWTxyiNHABTTgiqmJpScmxGrdKEuZ7TwXcaYXT4f4ddVjWiiQs9zowHqyDmvaebN6fU2Lu6iAYnYuepiLkvzGdcZZi8D/0/*,tpubD9cniQzQ8XnuagyP9Xwg3sWCX77wQPWoLPW7jqzcPn37r8hq2X86uztCEyFbMY16amzwdJ1CcNRXhF3vykn1wuDv2ULzryRtaCcN5Cr8F9y/0/*))" +)] #[case::multisig_wrapped_watch( - "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))")] + "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))" +)] fn descriptor_string_roundtrip(#[case] descriptor: &str) { let wallet = ElectrumWalletFile::from_descriptor(descriptor).unwrap();