From a9130697fea8f742416d87e8bdf010c157bb691d Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 16 Jun 2026 10:50:27 -0500 Subject: [PATCH 1/2] feat: bump bdk_wallet to 3.1.0 --- bdk-ffi/Cargo.lock | 4 ++-- bdk-ffi/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bdk-ffi/Cargo.lock b/bdk-ffi/Cargo.lock index 6da4c677..5cd2a13e 100644 --- a/bdk-ffi/Cargo.lock +++ b/bdk-ffi/Cargo.lock @@ -208,9 +208,9 @@ dependencies = [ [[package]] name = "bdk_wallet" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67f3c4f9526d22374fca5b7ff1d6bf8d921ab56db2dac8df66a2c5561b31d4ef" +checksum = "1284fb23acc3e3022673712b55f4d5ce7e38aadc2c49bbef830dc3935f0a3289" dependencies = [ "bdk_chain", "bip39", diff --git a/bdk-ffi/Cargo.toml b/bdk-ffi/Cargo.toml index 60d02c82..3f9fdc8f 100644 --- a/bdk-ffi/Cargo.toml +++ b/bdk-ffi/Cargo.toml @@ -15,7 +15,7 @@ name = "uniffi-bindgen" path = "uniffi-bindgen.rs" [dependencies] -bdk_wallet = { version = "=3.0.0", features = ["all-keys", "keys-bip39", "rusqlite"] } +bdk_wallet = { version = "=3.1.0", features = ["all-keys", "keys-bip39", "rusqlite"] } bdk_esplora = { version = "0.22.2", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] } bdk_electrum = { version = "0.24.0", default-features = false, features = ["use-rustls-ring"] } bdk_kyoto = { version = "0.17.0" } From bfbe2abde0abbbab18928ddeb1d72fcdf4368e99 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 16 Jun 2026 18:14:30 -0500 Subject: [PATCH 2/2] fix: align foreign utxo validation with upstream --- bdk-ffi/src/tests/tx_builder.rs | 96 ++++++++++++++++++++++++++++++++- bdk-ffi/src/tx_builder.rs | 46 +++++++--------- 2 files changed, 114 insertions(+), 28 deletions(-) diff --git a/bdk-ffi/src/tests/tx_builder.rs b/bdk-ffi/src/tests/tx_builder.rs index 76880f15..902fbb5d 100644 --- a/bdk-ffi/src/tests/tx_builder.rs +++ b/bdk-ffi/src/tests/tx_builder.rs @@ -1,6 +1,6 @@ -use crate::bitcoin::{Amount, Input, Network, NetworkKind, OutPoint, Script, TxOut}; +use crate::bitcoin::{Amount, Input, Network, NetworkKind, OutPoint, Script, Transaction, TxOut}; use crate::descriptor::Descriptor; -use crate::error::SighashParseError; +use crate::error::{AddForeignUtxoError, SighashParseError}; use crate::esplora::EsploraClient; use crate::store::Persister; use crate::tx_builder::TxBuilder; @@ -8,6 +8,10 @@ use crate::types::FullScanScriptInspector; use crate::wallet::Wallet; use bdk_wallet::bitcoin::hashes::hex::FromHex; +use bdk_wallet::bitcoin::{ + absolute, consensus::serialize, transaction, Amount as BdkAmount, ScriptBuf as BdkScriptBuf, + Transaction as BdkTransaction, TxIn as BdkTxIn, TxOut as BdkTxOut, +}; use std::collections::HashMap; use std::sync::Arc; @@ -301,6 +305,94 @@ fn test_add_foreign_utxo_missing_witness_data() { ); } +#[test] +fn test_add_foreign_utxo_validates_non_witness_utxo_when_witness_utxo_is_present() { + let outpoint = OutPoint { + txid: Arc::new( + crate::bitcoin::Txid::from_string( + "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456".to_string(), + ) + .unwrap(), + ), + vout: 0, + }; + + let witness_utxo = TxOut { + value: Arc::new(Amount::from_sat(50_000)), + script_pubkey: Arc::new(Script::new( + Vec::from_hex("0014d85c2b71d0060b09c9886aeb815e50991dda124d").unwrap(), + )), + }; + + let non_witness_tx = BdkTransaction { + version: transaction::Version::TWO, + lock_time: absolute::LockTime::ZERO, + input: vec![BdkTxIn::default()], + output: vec![BdkTxOut { + value: BdkAmount::from_sat(50_000), + script_pubkey: BdkScriptBuf::new(), + }], + }; + let non_witness_utxo = Arc::new(Transaction::new(serialize(&non_witness_tx)).unwrap()); + + let psbt_input = Input { + non_witness_utxo: Some(non_witness_utxo.clone()), + witness_utxo: Some(witness_utxo.clone()), + partial_sigs: HashMap::new(), + sighash_type: None, + redeem_script: None, + witness_script: None, + bip32_derivation: HashMap::new(), + final_script_sig: None, + final_script_witness: None, + ripemd160_preimages: HashMap::new(), + sha256_preimages: HashMap::new(), + hash160_preimages: HashMap::new(), + hash256_preimages: HashMap::new(), + tap_key_sig: None, + tap_script_sigs: HashMap::new(), + tap_scripts: HashMap::new(), + tap_key_origins: HashMap::new(), + tap_internal_key: None, + tap_merkle_root: None, + proprietary: HashMap::new(), + unknown: HashMap::new(), + }; + + let result = TxBuilder::new().add_foreign_utxo(outpoint.clone(), psbt_input, 68); + + assert!(matches!(result, Err(AddForeignUtxoError::InvalidTxid))); + + let psbt_input_with_sequence = Input { + non_witness_utxo: Some(non_witness_utxo), + witness_utxo: Some(witness_utxo), + partial_sigs: HashMap::new(), + sighash_type: None, + redeem_script: None, + witness_script: None, + bip32_derivation: HashMap::new(), + final_script_sig: None, + final_script_witness: None, + ripemd160_preimages: HashMap::new(), + sha256_preimages: HashMap::new(), + hash160_preimages: HashMap::new(), + hash256_preimages: HashMap::new(), + tap_key_sig: None, + tap_script_sigs: HashMap::new(), + tap_scripts: HashMap::new(), + tap_key_origins: HashMap::new(), + tap_internal_key: None, + tap_merkle_root: None, + proprietary: HashMap::new(), + unknown: HashMap::new(), + }; + + let result = + TxBuilder::new().add_foreign_utxo_with_sequence(outpoint, psbt_input_with_sequence, 68, 0); + + assert!(matches!(result, Err(AddForeignUtxoError::InvalidTxid))); +} + #[test] #[ignore = "requires live MutinyNet Esplora access"] fn test_add_foreign_utxo_with_witness_utxo_succeeds() { diff --git a/bdk-ffi/src/tx_builder.rs b/bdk-ffi/src/tx_builder.rs index d242f82d..c5e5a608 100644 --- a/bdk-ffi/src/tx_builder.rs +++ b/bdk-ffi/src/tx_builder.rs @@ -496,20 +496,17 @@ impl TxBuilder { let bdk_outpoint: BdkOutPoint = outpoint.into(); let bdk_input: BdkInput = psbt_input.try_into()?; - if bdk_input.witness_utxo.is_none() { - match bdk_input.non_witness_utxo.as_ref() { - Some(tx) => { - if tx.compute_txid() != bdk_outpoint.txid { - return Err(AddForeignUtxoError::InvalidTxid); - } - if tx.output.len() <= bdk_outpoint.vout as usize { - return Err(AddForeignUtxoError::InvalidOutpoint { - outpoint: bdk_outpoint.to_string(), - }); - } - } - None => return Err(AddForeignUtxoError::MissingUtxo), + if let Some(tx) = bdk_input.non_witness_utxo.as_ref() { + if tx.compute_txid() != bdk_outpoint.txid { + return Err(AddForeignUtxoError::InvalidTxid); } + if tx.output.len() <= bdk_outpoint.vout as usize { + return Err(AddForeignUtxoError::InvalidOutpoint { + outpoint: bdk_outpoint.to_string(), + }); + } + } else if bdk_input.witness_utxo.is_none() { + return Err(AddForeignUtxoError::MissingUtxo); } let bdk_weight = BdkWeight::from_wu(satisfaction_weight); @@ -535,20 +532,17 @@ impl TxBuilder { let bdk_outpoint: BdkOutPoint = outpoint.into(); let bdk_input: BdkInput = psbt_input.try_into()?; - if bdk_input.witness_utxo.is_none() { - match bdk_input.non_witness_utxo.as_ref() { - Some(tx) => { - if tx.compute_txid() != bdk_outpoint.txid { - return Err(AddForeignUtxoError::InvalidTxid); - } - if tx.output.len() <= bdk_outpoint.vout as usize { - return Err(AddForeignUtxoError::InvalidOutpoint { - outpoint: bdk_outpoint.to_string(), - }); - } - } - None => return Err(AddForeignUtxoError::MissingUtxo), + if let Some(tx) = bdk_input.non_witness_utxo.as_ref() { + if tx.compute_txid() != bdk_outpoint.txid { + return Err(AddForeignUtxoError::InvalidTxid); + } + if tx.output.len() <= bdk_outpoint.vout as usize { + return Err(AddForeignUtxoError::InvalidOutpoint { + outpoint: bdk_outpoint.to_string(), + }); } + } else if bdk_input.witness_utxo.is_none() { + return Err(AddForeignUtxoError::MissingUtxo); } let bdk_weight = BdkWeight::from_wu(satisfaction_weight);