diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index 767cd5a4..27f48142 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -407,23 +407,19 @@ impl<'a, Cs> TxBuilder<'a, Cs> { satisfaction_weight: Weight, sequence: Sequence, ) -> Result<&mut Self, AddForeignUtxoError> { - if psbt_input.witness_utxo.is_none() { - match psbt_input.non_witness_utxo.as_ref() { - Some(tx) => { - if tx.compute_txid() != outpoint.txid { - return Err(AddForeignUtxoError::InvalidTxid { - input_txid: tx.compute_txid(), - foreign_utxo: outpoint, - }); - } - if tx.output.len() <= outpoint.vout as usize { - return Err(AddForeignUtxoError::InvalidOutpoint(outpoint)); - } - } - None => { - return Err(AddForeignUtxoError::MissingUtxo); - } + // Always validate non_witness_utxo if present + if let Some(tx) = psbt_input.non_witness_utxo.as_ref() { + if tx.compute_txid() != outpoint.txid { + return Err(AddForeignUtxoError::InvalidTxid { + input_txid: tx.compute_txid(), + foreign_utxo: outpoint, + }); + } + if tx.output.len() <= outpoint.vout as usize { + return Err(AddForeignUtxoError::InvalidOutpoint(outpoint)); } + } else if psbt_input.witness_utxo.is_none() { + return Err(AddForeignUtxoError::MissingUtxo); } let mut existing_index: Option = None; diff --git a/tests/add_foreign_utxo.rs b/tests/add_foreign_utxo.rs index 1dd0a8c9..409d71fd 100644 --- a/tests/add_foreign_utxo.rs +++ b/tests/add_foreign_utxo.rs @@ -290,3 +290,36 @@ fn test_taproot_foreign_utxo() { "foreign_utxo should be in there" ); } + +#[test] +fn test_add_foreign_utxo_rejects_wrong_non_witness_utxo_even_with_witness_utxo() { + let (mut wallet1, txid1) = get_funded_wallet_wpkh(); + let (wallet2, _) = + get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); + + let utxo2 = wallet2.list_unspent().next().unwrap(); + let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); + + let satisfaction_weight = wallet2 + .public_descriptor(KeychainKind::External) + .max_weight_to_satisfy() + .unwrap(); + + // Provide both witness_utxo and a non_witness_utxo with wrong txid. + // Previously this was accepted because non_witness_utxo was only validated + // when witness_utxo was None. + let mut builder = wallet1.build_tx(); + let result = builder.add_foreign_utxo( + utxo2.outpoint, + psbt::Input { + witness_utxo: Some(utxo2.txout.clone()), + non_witness_utxo: Some(tx1.as_ref().clone()), + ..Default::default() + }, + satisfaction_weight, + ); + assert!( + matches!(result, Err(AddForeignUtxoError::InvalidTxid { .. })), + "should reject non_witness_utxo with wrong txid even when witness_utxo is present" + ); +}