Skip to content

Commit 469cfd0

Browse files
committed
fix(wallet): fix into_descriptor_key for DescriptorPublicKey
..by including the missing match arm for `DescriptorPublicKey::MultiXPub` when initializing `networks`. Test that `into_wallet_descriptor` correctly parses a multipath descriptor and fails if passed a network that is invalid for the descriptor. Note that rust-miniscript doesn't support parsing a multipath descriptor containing extended private keys.
1 parent 606a34d commit 469cfd0

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

wallet/src/descriptor/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,4 +917,48 @@ mod test {
917917
assert_eq!(psbt_input.redeem_script, Some(script.to_p2wsh()));
918918
assert_eq!(psbt_input.witness_script, Some(script));
919919
}
920+
921+
#[test]
922+
fn test_into_wallet_descriptor_multi() -> anyhow::Result<()> {
923+
// See <https://github.com/bitcoindevkit/bdk_wallet/issues/10>
924+
let secp = Secp256k1::new();
925+
926+
// multipath tpub
927+
let descriptor_str = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)";
928+
let (descriptor, _key_map) = descriptor_str
929+
.into_wallet_descriptor(&secp, Network::Testnet)
930+
.expect("should parse multipath tpub");
931+
932+
assert!(descriptor.is_multipath());
933+
934+
// invalid network for descriptor
935+
let descriptor_str = "wpkh([9a6a2580/84'/0'/0']xpub6DEzNop46vmxR49zYWFnMwmEfawSNmAMf6dLH5YKDY463twtvw1XD7ihwJRLPRGZJz799VPFzXHpZu6WdhT29WnaeuChS6aZHZPFmqczR5K/<0;1>/*)";
936+
let res = descriptor_str.into_wallet_descriptor(&secp, Network::Testnet);
937+
938+
assert!(matches!(
939+
res,
940+
Err(DescriptorError::Key(KeyError::InvalidNetwork))
941+
));
942+
943+
// multipath xpub
944+
let descriptor_str = "wpkh([9a6a2580/84'/0'/0']xpub6DEzNop46vmxR49zYWFnMwmEfawSNmAMf6dLH5YKDY463twtvw1XD7ihwJRLPRGZJz799VPFzXHpZu6WdhT29WnaeuChS6aZHZPFmqczR5K/<0;1>/*)";
945+
let (descriptor, _key_map) = descriptor_str
946+
.into_wallet_descriptor(&secp, Network::Bitcoin)
947+
.expect("should parse multipath xpub");
948+
949+
assert!(descriptor.is_multipath());
950+
951+
// Miniscript can't make an extended private key with multiple paths into a public key.
952+
// ref: <https://docs.rs/miniscript/12.3.2/miniscript/descriptor/enum.DescriptorSecretKey.html#method.to_public>
953+
let descriptor_str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/<0;1>/*)";
954+
assert!(matches!(
955+
Descriptor::parse_descriptor(&secp, descriptor_str),
956+
Err(miniscript::Error::Unexpected(..)),
957+
));
958+
let _ = descriptor_str
959+
.into_wallet_descriptor(&secp, Network::Testnet)
960+
.unwrap_err();
961+
962+
Ok(())
963+
}
920964
}

wallet/src/keys/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use bitcoin::secp256k1::{self, Secp256k1, Signing};
2727
use bitcoin::bip32;
2828
use bitcoin::{key::XOnlyPublicKey, Network, PrivateKey, PublicKey};
2929

30-
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
30+
use miniscript::descriptor::{Descriptor, DescriptorMultiXKey, DescriptorXKey, Wildcard};
3131
pub use miniscript::descriptor::{
3232
DescriptorPublicKey, DescriptorSecretKey, KeyMap, SinglePriv, SinglePub, SinglePubKey,
3333
SortedMultiVec,
@@ -880,10 +880,20 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorPublicKey {
880880
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
881881
let networks = match self {
882882
DescriptorPublicKey::Single(_) => any_network(),
883-
DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. }) if xkey.network.is_mainnet() => {
884-
mainnet_network()
883+
DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. }) => {
884+
if xkey.network.is_mainnet() {
885+
mainnet_network()
886+
} else {
887+
test_networks()
888+
}
889+
}
890+
DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { xkey, .. }) => {
891+
if xkey.network.is_mainnet() {
892+
mainnet_network()
893+
} else {
894+
test_networks()
895+
}
885896
}
886-
_ => test_networks(),
887897
};
888898

889899
Ok(DescriptorKey::from_public(self, networks))

0 commit comments

Comments
 (0)