Skip to content

Commit 96f5f08

Browse files
committed
Return error from at_derivation_index when descriptor has no wildcard
1 parent 04f1c58 commit 96f5f08

2 files changed

Lines changed: 115 additions & 15 deletions

File tree

src/descriptor/key.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ pub enum NonDefiniteKeyError {
358358
Wildcard,
359359
Multipath,
360360
HardenedStep,
361+
NoWildcard,
361362
}
362363

363364
impl fmt::Display for NonDefiniteKeyError {
@@ -368,6 +369,9 @@ impl fmt::Display for NonDefiniteKeyError {
368369
Self::HardenedStep => {
369370
f.write_str("key with hardened derivation steps cannot be a DerivedDescriptorKey")
370371
}
372+
Self::NoWildcard => {
373+
f.write_str("descriptor does not have a wildcard, so at_derivation_index does not apply")
374+
}
371375
}
372376
}
373377
}

src/descriptor/mod.rs

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)