@@ -658,12 +658,16 @@ impl Descriptor<DescriptorPublicKey> {
658658 /// turning it into a *definite* descriptor.
659659 ///
660660 /// # Errors
661- /// - If index ≥ 2^31
661+ /// - If the descriptor contains no wildcards
662+ /// - If index >= 2^31
662663 /// - If the descriptor contains multi-path derivations
663664 pub fn at_derivation_index (
664665 & self ,
665666 index : u32 ,
666667 ) -> Result < Descriptor < DefiniteDescriptorKey > , NonDefiniteKeyError > {
668+ if !self . has_wildcard ( ) {
669+ return Err ( NonDefiniteKeyError :: NoWildcard ) ;
670+ }
667671 struct Derivator ( u32 ) ;
668672
669673 impl Translator < DescriptorPublicKey > for Derivator {
@@ -849,7 +853,34 @@ impl Descriptor<DescriptorPublicKey> {
849853 script_pubkey : & Script ,
850854 range : Range < u32 > ,
851855 ) -> Result < Option < ( u32 , Descriptor < bitcoin:: PublicKey > ) > , NonDefiniteKeyError > {
852- let range = if self . has_wildcard ( ) { range } else { 0 ..1 } ;
856+ if !self . has_wildcard ( ) {
857+ // Non-wildcard descriptor: translate directly to a definite descriptor without
858+ // needing a derivation index.
859+ struct Definitor ;
860+
861+ impl Translator < DescriptorPublicKey > for Definitor {
862+ type TargetPk = DefiniteDescriptorKey ;
863+ type Error = NonDefiniteKeyError ;
864+
865+ fn pk (
866+ & mut self ,
867+ pk : & DescriptorPublicKey ,
868+ ) -> Result < Self :: TargetPk , Self :: Error > {
869+ DefiniteDescriptorKey :: new ( pk. clone ( ) )
870+ }
871+
872+ translate_hash_clone ! ( DescriptorPublicKey ) ;
873+ }
874+
875+ let definite = self
876+ . translate_pk ( & mut Definitor )
877+ . map_err ( |e| e. expect_translator_err ( "No Context errors while translating" ) ) ?;
878+ let concrete = definite. derived_descriptor ( secp) ;
879+ if & concrete. script_pubkey ( ) == script_pubkey {
880+ return Ok ( Some ( ( 0 , concrete) ) ) ;
881+ }
882+ return Ok ( None ) ;
883+ }
853884
854885 for i in range {
855886 let concrete = self . derived_descriptor ( secp, i) ?;
@@ -1868,7 +1899,9 @@ mod tests {
18681899 assert_eq ! ( desc_one. to_string( ) , raw_desc_one) ;
18691900 assert_eq ! ( desc_two. to_string( ) , raw_desc_two) ;
18701901
1871- // Same address
1902+ // Same address. Ranged descriptors must be derived at a specific index first.
1903+ assert ! ( desc_one. has_wildcard( ) , "test helper only accepts ranged descriptors" ) ;
1904+ assert ! ( desc_two. has_wildcard( ) , "test helper only accepts ranged descriptors" ) ;
18721905 let addr_one = desc_one
18731906 . at_derivation_index ( index)
18741907 . unwrap ( )
@@ -1888,19 +1921,45 @@ mod tests {
18881921 assert_eq ! ( addr_two, addr_expected) ;
18891922 }
18901923
1891- // P2SH and pubkeys
1892- _test_sortedmulti (
1893- "sh(sortedmulti(1,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352))#uetvewm2" ,
1894- "sh(sortedmulti(1,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))#7l8smyg9" ,
1895- "3JZJNxvDKe6Y55ZaF5223XHwfF2eoMNnoV" ,
1896- ) ;
1924+ let secp_ctx = secp256k1:: Secp256k1 :: verification_only ( ) ;
18971925
1898- // P2WSH and single-xpub descriptor
1899- _test_sortedmulti (
1900- "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))#7etm7zk7" ,
1901- "wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))#ppmeel9k" ,
1902- "bc1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcspx5y6a" ,
1903- ) ;
1926+ // P2SH and pubkeys (no wildcard — descriptors are fully defined)
1927+ {
1928+ let desc_strs = [
1929+ "sh(sortedmulti(1,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352))#uetvewm2" ,
1930+ "sh(sortedmulti(1,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))#7l8smyg9" ,
1931+ ] ;
1932+ let addr_expected = bitcoin:: Address :: from_str ( "3JZJNxvDKe6Y55ZaF5223XHwfF2eoMNnoV" )
1933+ . unwrap ( )
1934+ . assume_checked ( ) ;
1935+ for desc_str in desc_strs {
1936+ let desc = Descriptor :: < DefiniteDescriptorKey > :: from_str ( desc_str) . unwrap ( ) ;
1937+ assert_eq ! ( desc. to_string( ) , desc_str) ;
1938+ let addr = desc
1939+ . derived_descriptor ( & secp_ctx)
1940+ . address ( bitcoin:: Network :: Bitcoin )
1941+ . unwrap ( ) ;
1942+ assert_eq ! ( addr, addr_expected) ;
1943+ }
1944+ }
1945+
1946+ // P2WSH and xpub descriptor (no wildcard)
1947+ {
1948+ let desc_strs = [
1949+ "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))#7etm7zk7" ,
1950+ "wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))#ppmeel9k" ,
1951+ ] ;
1952+ let addr_expected =
1953+ bitcoin:: Address :: from_str ( "bc1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcspx5y6a" )
1954+ . unwrap ( )
1955+ . assume_checked ( ) ;
1956+ for desc_str in desc_strs {
1957+ let desc = Descriptor :: < DefiniteDescriptorKey > :: from_str ( desc_str) . unwrap ( ) ;
1958+ assert_eq ! ( desc. to_string( ) , desc_str) ;
1959+ let addr = desc. derived_descriptor ( & secp_ctx) . address ( bitcoin:: Network :: Bitcoin ) . unwrap ( ) ;
1960+ assert_eq ! ( addr, addr_expected) ;
1961+ }
1962+ }
19041963
19051964 // P2WSH-P2SH and ranged descriptor
19061965 _test_sortedmulti (
@@ -2698,4 +2757,41 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
26982757 ) ;
26992758 }
27002759 }
2760+
2761+ #[ test]
2762+ fn at_derivation_index_fails_without_wildcard ( ) {
2763+ use crate :: descriptor:: DescriptorPublicKey ;
2764+ // Descriptor with no wildcard — all keys are fully specified
2765+ let desc = Descriptor :: < DescriptorPublicKey > :: from_str (
2766+ "wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1/2)"
2767+ ) . unwrap ( ) ;
2768+ assert ! ( !desc. has_wildcard( ) ) ;
2769+ assert ! ( matches!(
2770+ desc. at_derivation_index( 0 ) ,
2771+ Err ( NonDefiniteKeyError :: NoWildcard )
2772+ ) ) ;
2773+ }
2774+
2775+ #[ test]
2776+ fn at_derivation_index_works_with_mixed_keys ( ) {
2777+ use crate :: descriptor:: DescriptorPublicKey ;
2778+ // One key has wildcard, one doesn't
2779+ let desc = Descriptor :: < DescriptorPublicKey > :: from_str (
2780+ "wsh(multi(1,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/2))"
2781+ ) . unwrap ( ) ;
2782+ assert ! ( desc. has_wildcard( ) ) ;
2783+ assert ! ( desc. at_derivation_index( 0 ) . is_ok( ) ) ;
2784+ }
2785+
2786+ #[ test]
2787+ fn at_derivation_index_works_with_wildcard ( ) {
2788+ use crate :: descriptor:: DescriptorPublicKey ;
2789+ let desc = Descriptor :: < DescriptorPublicKey > :: from_str (
2790+ "wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/*)"
2791+ ) . unwrap ( ) ;
2792+ assert ! ( desc. has_wildcard( ) ) ;
2793+ let d0 = desc. at_derivation_index ( 0 ) . unwrap ( ) ;
2794+ let d1 = desc. at_derivation_index ( 1 ) . unwrap ( ) ;
2795+ assert_ne ! ( d0. to_string( ) , d1. to_string( ) ) ;
2796+ }
27012797}
0 commit comments