Ok(result)
}
-/// Returns `true` if `descriptor` is a BIP-389 multipath descriptor (e.g. `.../<0;1>/*`).
+/// Returns `true` if `descriptor` is a supported two-path BIP-389 multipath descriptor
+/// (external and internal), `false` for a normal single-path descriptor.
///
-/// Parses via IntoWalletDescriptor and uses miniscript's `is_multipath`.
-/// Returns an error if the descriptor is unparseable.
+/// Errors if the descriptor is unparseable, or if it's a multipath descriptor with a number
+/// of paths other than two (supports only external/internal two-path multipath).
pub fn is_multipath_descriptor(descriptor: &str, network: Network) -> Result<bool, Error> {
let secp = Secp256k1::new();
let (descriptor, _) = descriptor.into_wallet_descriptor(&secp, network.into())?;
- Ok(descriptor.is_multipath())
+
+ if !descriptor.is_multipath() {
+ return Ok(false);
+ }
+
+ let paths = descriptor.into_single_descriptors()?.len();
+ if paths != 2 {
+ return Err(Error::Generic(format!(
+ "Unsupported multipath descriptor: expected exactly 2 paths (external/internal), found {paths}."
+ )));
+ }
+ Ok(true)
}
#[cfg(test)]
use super::*;
const MULTIPATH: &str = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)";
+ const THREE_PATH: &str = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1;2>/*)";
const SINGLE: &str = "wpkh([07234a14/84'/1'/0']tpubDCSgT6PaVLQH9h2TAxKryhvkEurUBcYRJc9dhTcMDyahhWiMWfEWvQQX89yaw7w7XU8bcVujoALfxq59VkFATri3Cxm5mkp9kfHfRFDckEh/0/*)#429nsxmg";
#[test]
fn rejects_unparseable() {
assert!(is_multipath_descriptor("not a descriptor", Network::Testnet).is_err());
}
+
+ #[test]
+ fn rejects_more_than_two_paths() {
+ assert!(is_multipath_descriptor(THREE_PATH, Network::Testnet).is_err());
+ }
}