Skip to content

Commit ea636ff

Browse files
committed
Merge #839: more DefiniteDescriptorKey fixes and cleanups
f09bf48 fuzz: add fuzz test for parsing definite descriptor keys (Andrew Poelstra) 4930ccb key: replace ConversionError with NonDefiniteKeyError (Andrew Poelstra) 627d963 psbt: remove derives from UtxoUpdateError and OutputUpdateError (Andrew Poelstra) c169af0 key.rs: remove a bunch of unwrap()s which are impossible (Andrew Poelstra) 6267818 TranslateErr: add helper function to unwrap impossible case (Andrew Poelstra) e1f0802 macros: make target pk and error type optional for translate_* macros (Andrew Poelstra) 77ed78b key.rs: remove some deprecated methods (Andrew Poelstra) 7b3b3a8 key: fix DefiniteDescriptorKey construction from key with hardened step (Andrew Poelstra) Pull request description: Again this one changes logic in a way that I need to backport, but the backport PRs will be much simpler since they are constrained not to change the API. This one makes it impossible to construct a `DefiniteDescriptorKey` with hardened derivation steps. Previously this was usually impossible, but not always, which led to inconsistent error checks and a proliferance of `unwrap`s and generally hard-to-reason-about behavior. ACKs for top commit: sanket1729: ACK f09bf48. Tree-SHA512: b57016592bc1b41c1ebe9ceba9c68f3feec432f79782aedbb2af0c817648d5af476e28a03968243fcf0f3ecde92bf28ee10414dd6e39a4d7d4119c4f68d1a1fe
2 parents 19eb66f + f09bf48 commit ea636ff

File tree

13 files changed

+143
-116
lines changed

13 files changed

+143
-116
lines changed

.github/workflows/cron-daily-fuzz.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ compile_descriptor,
2222
compile_taproot,
2323
miniscript_satisfy,
2424
parse_descriptor,
25+
parse_descriptor_definite,
2526
parse_descriptor_priv,
2627
parse_descriptor_secret,
2728
regression_descriptor_parse,

bitcoind-tests/tests/test_cpp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) {
145145
// Sign the transactions with all keys
146146
// AKA the signer role of psbt
147147
for i in 0..psbts.len() {
148-
let wsh_derived = desc_vec[i].derived_descriptor(&secp).unwrap();
148+
let wsh_derived = desc_vec[i].derived_descriptor(&secp);
149149
let ms = if let Descriptor::Wsh(wsh) = &wsh_derived {
150150
match wsh.as_inner() {
151151
miniscript::descriptor::WshInner::Ms(ms) => ms,

bitcoind-tests/tests/test_desc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub fn test_desc_satisfy(
8989
.at_derivation_index(0)
9090
.unwrap();
9191

92-
let derived_desc = definite_desc.derived_descriptor(&secp).unwrap();
92+
let derived_desc = definite_desc.derived_descriptor(&secp);
9393
let desc_address = derived_desc.address(bitcoin::Network::Regtest);
9494
let desc_address = desc_address.map_err(|_x| DescError::AddressComputationError)?;
9595

examples/xpub_descriptors.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ fn p2wsh<C: Verification>(secp: &Secp256k1<C>) -> Address {
3131
let address = Descriptor::<DefiniteDescriptorKey>::from_str(&s)
3232
.unwrap()
3333
.derived_descriptor(secp)
34-
.unwrap()
3534
.address(Network::Bitcoin)
3635
.unwrap();
3736

fuzz/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ path = "fuzz_targets/miniscript_satisfy.rs"
3434
name = "parse_descriptor"
3535
path = "fuzz_targets/parse_descriptor.rs"
3636

37+
[[bin]]
38+
name = "parse_descriptor_definite"
39+
path = "fuzz_targets/parse_descriptor_definite.rs"
40+
3741
[[bin]]
3842
name = "parse_descriptor_priv"
3943
path = "fuzz_targets/parse_descriptor_priv.rs"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![allow(unexpected_cfgs)]
2+
3+
use honggfuzz::fuzz;
4+
use miniscript::{DefiniteDescriptorKey, Descriptor};
5+
6+
fn do_test(data: &[u8]) {
7+
let data_str = String::from_utf8_lossy(data);
8+
9+
if let Ok(desc) = data_str.parse::<Descriptor<DefiniteDescriptorKey>>() {
10+
let _ = desc.to_string();
11+
let _ = desc.address(miniscript::bitcoin::Network::Bitcoin);
12+
}
13+
}
14+
15+
fn main() {
16+
loop {
17+
fuzz!(|data| {
18+
do_test(data);
19+
});
20+
}
21+
}

src/descriptor/key.rs

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -328,19 +328,23 @@ impl DescriptorMultiXKey<bip32::Xpriv> {
328328
}
329329

330330
/// Kinds of malformed key data
331-
#[derive(Debug, PartialEq, Clone)]
331+
#[derive(Debug, PartialEq, Eq, Clone)]
332332
#[non_exhaustive]
333333
#[allow(missing_docs)]
334334
pub enum NonDefiniteKeyError {
335335
Wildcard,
336336
Multipath,
337+
HardenedStep,
337338
}
338339

339340
impl fmt::Display for NonDefiniteKeyError {
340341
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341342
match *self {
342343
Self::Wildcard => f.write_str("key with a wildcard cannot be a DerivedDescriptorKey"),
343344
Self::Multipath => f.write_str("multipath key cannot be a DerivedDescriptorKey"),
345+
Self::HardenedStep => {
346+
f.write_str("key with hardened derivation steps cannot be a DerivedDescriptorKey")
347+
}
344348
}
345349
}
346350
}
@@ -349,7 +353,7 @@ impl fmt::Display for NonDefiniteKeyError {
349353
impl error::Error for NonDefiniteKeyError {}
350354

351355
/// Kinds of malformed key data
352-
#[derive(Debug, PartialEq, Clone)]
356+
#[derive(Debug, PartialEq, Eq, Clone)]
353357
#[non_exhaustive]
354358
#[allow(missing_docs)]
355359
pub enum MalformedKeyDataKind {
@@ -393,7 +397,7 @@ impl fmt::Display for MalformedKeyDataKind {
393397
}
394398

395399
/// Descriptor Key parsing errors
396-
#[derive(Debug, PartialEq, Clone)]
400+
#[derive(Debug, PartialEq, Eq, Clone)]
397401
#[non_exhaustive]
398402
pub enum DescriptorKeyParseError {
399403
/// Error while parsing a BIP32 extended private key
@@ -685,33 +689,6 @@ impl From<PublicKey> for DescriptorPublicKey {
685689
}
686690
}
687691

688-
/// Descriptor key conversion error
689-
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
690-
pub enum ConversionError {
691-
/// Attempted to convert a key with hardened derivations to a bitcoin public key
692-
HardenedChild,
693-
/// Attempted to convert a key with multiple derivation paths to a bitcoin public key
694-
MultiKey,
695-
}
696-
697-
impl fmt::Display for ConversionError {
698-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
699-
f.write_str(match *self {
700-
Self::HardenedChild => "hardened child step in bip32 path",
701-
Self::MultiKey => "multiple existing keys",
702-
})
703-
}
704-
}
705-
706-
#[cfg(feature = "std")]
707-
impl error::Error for ConversionError {
708-
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
709-
match self {
710-
Self::HardenedChild | Self::MultiKey => None,
711-
}
712-
}
713-
}
714-
715692
impl DescriptorPublicKey {
716693
/// The fingerprint of the master key associated with this key, `0x00000000` if none.
717694
pub fn master_fingerprint(&self) -> bip32::Fingerprint {
@@ -806,10 +783,6 @@ impl DescriptorPublicKey {
806783
}
807784
}
808785

809-
/// Whether or not the key has a wildcard
810-
#[deprecated(note = "use has_wildcard instead")]
811-
pub fn is_deriveable(&self) -> bool { self.has_wildcard() }
812-
813786
/// Whether or not the key has a wildcard
814787
pub fn has_wildcard(&self) -> bool {
815788
match *self {
@@ -819,10 +792,21 @@ impl DescriptorPublicKey {
819792
}
820793
}
821794

822-
#[deprecated(note = "use at_derivation_index instead")]
823-
/// Deprecated name for [`Self::at_derivation_index`].
824-
pub fn derive(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
825-
self.at_derivation_index(index)
795+
/// Whether or not the key has a wildcard
796+
pub fn has_hardened_step(&self) -> bool {
797+
let paths = match self {
798+
DescriptorPublicKey::Single(..) => &[],
799+
DescriptorPublicKey::XPub(xpub) => core::slice::from_ref(&xpub.derivation_path),
800+
DescriptorPublicKey::MultiXPub(xpub) => &xpub.derivation_paths.paths()[..],
801+
};
802+
for p in paths {
803+
for step in p.into_iter() {
804+
if step.is_hardened() {
805+
return true;
806+
}
807+
}
808+
}
809+
false
826810
}
827811

828812
/// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a
@@ -838,7 +822,10 @@ impl DescriptorPublicKey {
838822
///
839823
/// - If `index` is hardened.
840824
/// - If the key contains multi-path derivations
841-
pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
825+
pub fn at_derivation_index(
826+
self,
827+
index: u32,
828+
) -> Result<DefiniteDescriptorKey, NonDefiniteKeyError> {
842829
let definite = match self {
843830
DescriptorPublicKey::Single(_) => self,
844831
DescriptorPublicKey::XPub(xpub) => {
@@ -847,12 +834,12 @@ impl DescriptorPublicKey {
847834
Wildcard::Unhardened => xpub.derivation_path.into_child(
848835
bip32::ChildNumber::from_normal_idx(index)
849836
.ok()
850-
.ok_or(ConversionError::HardenedChild)?,
837+
.ok_or(NonDefiniteKeyError::HardenedStep)?,
851838
),
852839
Wildcard::Hardened => xpub.derivation_path.into_child(
853840
bip32::ChildNumber::from_hardened_idx(index)
854841
.ok()
855-
.ok_or(ConversionError::HardenedChild)?,
842+
.ok_or(NonDefiniteKeyError::HardenedStep)?,
856843
),
857844
};
858845
DescriptorPublicKey::XPub(DescriptorXKey {
@@ -862,7 +849,7 @@ impl DescriptorPublicKey {
862849
wildcard: Wildcard::None,
863850
})
864851
}
865-
DescriptorPublicKey::MultiXPub(_) => return Err(ConversionError::MultiKey),
852+
DescriptorPublicKey::MultiXPub(_) => return Err(NonDefiniteKeyError::Multipath),
866853
};
867854

868855
Ok(DefiniteDescriptorKey::new(definite)
@@ -1243,29 +1230,26 @@ impl DefiniteDescriptorKey {
12431230
///
12441231
/// Will return an error if the descriptor key has any hardened derivation steps in its path. To
12451232
/// avoid this error you should replace any such public keys first with [`crate::Descriptor::translate_pk`].
1246-
pub fn derive_public_key<C: Verification>(
1247-
&self,
1248-
secp: &Secp256k1<C>,
1249-
) -> Result<bitcoin::PublicKey, ConversionError> {
1233+
pub fn derive_public_key<C: Verification>(&self, secp: &Secp256k1<C>) -> bitcoin::PublicKey {
12501234
match self.0 {
12511235
DescriptorPublicKey::Single(ref pk) => match pk.key {
1252-
SinglePubKey::FullKey(pk) => Ok(pk),
1253-
SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
1236+
SinglePubKey::FullKey(pk) => pk,
1237+
SinglePubKey::XOnly(xpk) => xpk.to_public_key(),
12541238
},
12551239
DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard {
12561240
Wildcard::Unhardened | Wildcard::Hardened => {
1257-
unreachable!("we've excluded this error case")
1241+
unreachable!("impossible by construction of DefiniteDescriptorKey")
12581242
}
12591243
Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) {
1260-
Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
1244+
Ok(xpub) => bitcoin::PublicKey::new(xpub.public_key),
12611245
Err(bip32::Error::CannotDeriveFromHardenedKey) => {
1262-
Err(ConversionError::HardenedChild)
1246+
unreachable!("impossible by construction of DefiniteDescriptorKey")
12631247
}
12641248
Err(e) => unreachable!("cryptographically unreachable: {}", e),
12651249
},
12661250
},
12671251
DescriptorPublicKey::MultiXPub(_) => {
1268-
unreachable!("A definite key cannot contain a multipath key.")
1252+
unreachable!("impossible by construction of DefiniteDescriptorKey")
12691253
}
12701254
}
12711255
}
@@ -1276,6 +1260,8 @@ impl DefiniteDescriptorKey {
12761260
pub fn new(key: DescriptorPublicKey) -> Result<Self, NonDefiniteKeyError> {
12771261
if key.has_wildcard() {
12781262
Err(NonDefiniteKeyError::Wildcard)
1263+
} else if key.has_hardened_step() {
1264+
Err(NonDefiniteKeyError::HardenedStep)
12791265
} else if key.is_multipath() {
12801266
Err(NonDefiniteKeyError::Multipath)
12811267
} else {
@@ -1333,7 +1319,7 @@ impl MiniscriptKey for DefiniteDescriptorKey {
13331319
impl ToPublicKey for DefiniteDescriptorKey {
13341320
fn to_public_key(&self) -> bitcoin::PublicKey {
13351321
let secp = Secp256k1::verification_only();
1336-
self.derive_public_key(&secp).unwrap()
1322+
self.derive_public_key(&secp)
13371323
}
13381324

13391325
fn to_sha256(hash: &sha256::Hash) -> sha256::Hash { *hash }
@@ -1785,5 +1771,13 @@ mod test {
17851771
.parse::<DescriptorPublicKey>()
17861772
.unwrap();
17871773
assert!(matches!(DefiniteDescriptorKey::new(desc), Err(NonDefiniteKeyError::Multipath)));
1774+
// xpub with hardened path
1775+
let desc = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1'/2"
1776+
.parse::<DescriptorPublicKey>()
1777+
.unwrap();
1778+
assert!(matches!(
1779+
DefiniteDescriptorKey::new(desc),
1780+
Err(NonDefiniteKeyError::HardenedStep)
1781+
));
17881782
}
17891783
}

0 commit comments

Comments
 (0)