Skip to content

Commit 36867f6

Browse files
committed
fix: avoid panic on hardened derivation paths in PSBT key origins
The verify_key closure in derive_from_psbt_key_origins called .expect() on xpub.derive_pub(), which panics when the derivation path contains hardened steps. Since PSBT data is untrusted, a maliciously crafted bip32_derivation entry with hardened steps could crash the application. Replace the panic with graceful error handling by returning false on derivation failure. Co-Authored-By: HAL 9000
1 parent fb7681a commit 36867f6

1 file changed

Lines changed: 41 additions & 5 deletions

File tree

src/descriptor/mod.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,11 +449,10 @@ impl DescriptorMeta for ExtendedDescriptor {
449449
// Ensure that deriving `xpub` with `path` yields `expected`.
450450
let verify_key =
451451
|xpub: &DescriptorXKey<Xpub>, path: &DerivationPath, expected: &SinglePubKey| {
452-
let derived = xpub
453-
.xkey
454-
.derive_pub(secp, path)
455-
.expect("The path should never contain hardened derivation steps")
456-
.public_key;
452+
let derived = match xpub.xkey.derive_pub(secp, path) {
453+
Ok(derived) => derived.public_key,
454+
Err(_) => return false,
455+
};
457456

458457
match expected {
459458
SinglePubKey::FullKey(pk) if &PublicKey::new(derived) == pk => true,
@@ -982,4 +981,41 @@ mod test {
982981

983982
Ok(())
984983
}
984+
985+
#[test]
986+
fn test_derive_from_psbt_input_with_hardened_key_origin_does_not_panic() {
987+
let secp = Secp256k1::new();
988+
989+
// Create a descriptor with an xpub that has a wildcard.
990+
let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
991+
"pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)",
992+
)
993+
.unwrap();
994+
995+
// Craft a PSBT input with a bip32_derivation entry that contains hardened
996+
// derivation steps. This simulates untrusted/malicious PSBT data.
997+
let fingerprint = bip32::Fingerprint::from_str("0f056943").unwrap();
998+
let hardened_path = bip32::DerivationPath::from_str("m/44h/0h/0h/10/0h").unwrap();
999+
1000+
// Derive the actual public key from the xpub in the descriptor so the
1001+
// fingerprint matches and we exercise the verify_key code path.
1002+
let xpub = bip32::Xpub::from_str(
1003+
"tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd",
1004+
)
1005+
.unwrap();
1006+
let dummy_pubkey = xpub.public_key;
1007+
1008+
let mut psbt_input = psbt::Input::default();
1009+
psbt_input
1010+
.bip32_derivation
1011+
.insert(dummy_pubkey, (fingerprint, hardened_path));
1012+
1013+
// Previously, this would panic with "The path should never contain hardened
1014+
// derivation steps". Now it should gracefully return None.
1015+
let result = descriptor.derive_from_psbt_input(&psbt_input, None, &secp);
1016+
assert!(
1017+
result.is_none(),
1018+
"should return None rather than panicking on hardened derivation in PSBT key origins"
1019+
);
1020+
}
9851021
}

0 commit comments

Comments
 (0)