diff --git a/src/psbt.rs b/src/psbt.rs index 966c635..376691d 100644 --- a/src/psbt.rs +++ b/src/psbt.rs @@ -471,14 +471,17 @@ where } } + let funding_utxo = + funding_utxo(input, txin, i)?.ok_or(Error::MissingInputFundingUtxo { index: i })?; + // Keep validation aligned with bdk_wallet's default signing policy. The - // signer checks every non-finalized input, including inputs that don't - // belong to this wallet, and identifies Taproot inputs by their PSBT - // metadata rather than by inspecting the funding output. + // Taproot fields are untrusted and may be attached to a non-Taproot + // input, so only exempt them when the funding output is actually P2TR. + let has_taproot_metadata = + input.tap_internal_key.is_some() || input.tap_merkle_root.is_some(); if input.final_script_witness.is_none() && input.final_script_sig.is_none() - && input.tap_internal_key.is_none() - && input.tap_merkle_root.is_none() + && !(funding_utxo.script_pubkey.is_p2tr() && has_taproot_metadata) && input.non_witness_utxo.is_none() { return Err(Error::MissingNonWitnessUtxo { index: i }); @@ -508,9 +511,6 @@ where continue; } - let funding_utxo = - funding_utxo(input, txin, i)?.ok_or(Error::MissingInputFundingUtxo { index: i })?; - if funding_utxo.script_pubkey.is_p2tr() { // Only single-sig P2TR supported for now. if input.tap_key_origins.len() != 1 { diff --git a/tests/spend_test.rs b/tests/spend_test.rs index 671bdd7..acd7e2f 100644 --- a/tests/spend_test.rs +++ b/tests/spend_test.rs @@ -256,7 +256,7 @@ mod psbt_security_tests { // non-taproot inputs require non_witness_utxo #[test] - fn psbt_rejects_p2wpkh_without_non_witness_utxo() { + fn psbt_rejects_p2wpkh_with_spurious_taproot_metadata() { let secp = test_secp(); let master = test_master_key(); let (pk, path, fp) = derive_test_key(&secp, "m/84'/1'/0'/0/0"); @@ -271,6 +271,7 @@ mod psbt_security_tests { value: Amount::from_sat(100_000), script_pubkey: address.script_pubkey(), }), + tap_merkle_root: Some(TapNodeHash::all_zeros()), ..Default::default() }; input.bip32_derivation.insert(pk, (fp, path));