]> Untitled Git - bdk-cli/commitdiff
feat(multipath): limit multipath desc to two paths
authorVihiga Tyonum <withtvpeter@gmail.com>
Wed, 29 Jul 2026 22:24:19 +0000 (23:24 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Wed, 29 Jul 2026 22:24:19 +0000 (23:24 +0100)
- Add a check for a two-paths multipath descriptors
as supported by the wallet api.

src/utils/descriptors.rs

index d6c5e40115421f570922ff181c5aecdee73f9d85..924041dd65dcef39aac84cfb03f8af05d6746f91 100644 (file)
@@ -199,14 +199,26 @@ pub fn generate_descriptor_from_mnemonic(
     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)]
@@ -214,6 +226,7 @@ mod multipath_tests {
     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]
@@ -230,4 +243,9 @@ mod multipath_tests {
     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());
+    }
 }