11use std:: collections:: { BTreeMap , BTreeSet } ;
2+ use std:: mem;
23use std:: sync:: LazyLock ;
34
45use bdk_wallet:: bitcoin:: amount:: CheckedSum as _;
@@ -10,6 +11,7 @@ use bdk_wallet::bitcoin::{
1011 Address , Amount , FeeRate , Network , OutPoint , Psbt , ScriptBuf , Sequence , TapSighashType ,
1112 Transaction , TxIn , TxOut , Weight , Witness , XOnlyPublicKey , absolute, psbt, script, secp256k1,
1213} ;
14+ use bdk_wallet:: miniscript:: psbt:: PsbtExt as _;
1315use bdk_wallet:: miniscript:: { Descriptor , ToPublicKey as _} ;
1416use bdk_wallet:: { KeychainKind , SignOptions , TxOrdering , Wallet } ;
1517use rand:: { RngCore , SeedableRng as _} ;
@@ -54,11 +56,10 @@ struct MockTradeWallet<Cs: Iterator<Item=TxOutput>, As: Iterator<Item=Address>>
5456 new_addresses : As ,
5557 signature_map : BTreeMap < OutPoint , Signature > ,
5658 internal_key : Option < XOnlyPublicKey > ,
59+ script_sigs : BTreeMap < XOnlyPublicKey , Vec < Signature > > ,
5760}
5861
59- impl < Cs : Iterator < Item = TxOutput > , As : Iterator < Item = Address > > TradeWallet
60- for MockTradeWallet < Cs , As >
61- {
62+ impl < Cs : Iterator < Item =TxOutput > , As : Iterator < Item =Address > > TradeWallet for MockTradeWallet < Cs , As > {
6263 fn network ( & self ) -> Network { Network :: Regtest }
6364
6465 fn new_address ( & mut self ) -> Result < Address > {
@@ -140,12 +141,38 @@ impl<Cs: Iterator<Item = TxOutput>, As: Iterator<Item = Address>> TradeWallet
140141 }
141142
142143 fn sign_selected_inputs ( & self , psbt : & mut Psbt , is_selected : & dyn Fn ( & OutPoint ) -> bool ) -> Result < ( ) > {
144+ let mut script_sigs = self . script_sigs . clone ( ) ;
145+
143146 for ( input, TxIn { previous_output, .. } )
144147 in psbt. inputs . iter_mut ( ) . zip ( & psbt. unsigned_tx . input ) {
145148 if is_selected ( previous_output) {
146- let signature = self . signature_map . get ( previous_output)
147- . ok_or ( TransactionErrorKind :: MissingSignature ) ?;
148- input. final_script_witness = Some ( Witness :: p2tr_key_spend ( signature) ) ;
149+ for ( key, ( leaf_hashes, _) ) in & input. tap_key_origins {
150+ if let Some ( sig) = script_sigs. get_mut ( key) . and_then ( Vec :: pop) {
151+ for leaf_hash in leaf_hashes {
152+ input. tap_script_sigs . insert ( ( * key, * leaf_hash) , sig) ;
153+ }
154+ }
155+ }
156+ if let Some ( signature) = self . signature_map . get ( previous_output) {
157+ // Mock keyspend:
158+ input. final_script_witness = Some ( Witness :: p2tr_key_spend ( signature) ) ;
159+ input. redact_sensitive_fields ( ) ;
160+ } else if input. tap_key_origins . len ( ) == input. tap_script_sigs . len ( ) + 1 {
161+ // Mock script spend (assumes only one path):
162+ if let Some ( ( control_block, ( script, _) ) ) = input. tap_scripts . first_key_value ( ) {
163+ let mut wit = Witness :: new ( ) ;
164+ // For the purpose of the mock, assume that (pubkey, leaf-hash) -> signature
165+ // mappings occur in the opposite order that the signatures need to be added
166+ // to the witness. This won't be true in general.
167+ for sig in input. tap_script_sigs . values ( ) . rev ( ) {
168+ wit. push ( sig. serialize ( ) ) ;
169+ }
170+ wit. push ( script. as_bytes ( ) ) ;
171+ wit. push ( control_block. serialize ( ) ) ;
172+ input. final_script_witness = Some ( wit) ;
173+ input. redact_sensitive_fields ( ) ;
174+ }
175+ }
149176 }
150177 }
151178 Ok ( ( ) )
@@ -169,9 +196,15 @@ pub fn mock_buyer_trade_wallet() -> impl TradeWallet {
169196 ] . map ( |a| a. parse :: < Address < _ > > ( )
170197 . expect ( "hardcoded addresses should be valid" ) . assume_checked ( ) ) . into_iter ( ) ;
171198 let internal_key =
172- "0000000000000000000000000000000000000000000000000000000000000001" . parse ( ) . ok ( ) ;
199+ "51494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295" . parse ( ) . ok ( ) ;
200+ let script_sigs = script_sigs ( internal_key. as_slice ( ) , & [
201+ "5564448d3c5f024eaf2c65024a0c6e7a9066eb0390f8ffaeee2feacde310fabf\
202+ 87f3a8d8ad7fb125d7a6f68a282cfab8cd3178262a1fd0c2d06a598c8c454af8",
203+ "652d0abaa3b4f8c7dd85ac9d523d44f768c8e1541aded79165c3cdfb3ba35d62\
204+ eef114e89becb490a80cfdab946d2d91748ccea501ceb4f08655dcc2868c0463",
205+ ] ) ;
173206
174- MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key }
207+ MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key, script_sigs }
175208}
176209
177210//noinspection SpellCheckingInspection
@@ -195,17 +228,30 @@ pub fn mock_seller_trade_wallet() -> impl TradeWallet {
195228 ] . map ( |a| a. parse :: < Address < _ > > ( )
196229 . expect ( "hardcoded addresses should be valid" ) . assume_checked ( ) ) . into_iter ( ) ;
197230 let internal_key =
198- "0000000000000000000000000000000000000000000000000000000000000002" . parse ( ) . ok ( ) ;
231+ "fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f" . parse ( ) . ok ( ) ;
232+ let script_sigs = script_sigs ( internal_key. as_slice ( ) , & [
233+ "87790f7eb3e98eb1b4dadc55ff5762275c4e3c02c6491abb26c8eabfada55b4b\
234+ 3f2627f627919d667be8f191a1b275b01549ab24e5eeda0019f83c658840500e",
235+ "52fe2e44a4789a0f9bc406da144dcacca2621d2c1286e2d8f9913425c9927288\
236+ 13d658a3334a4070ce585cb907a67604fc74578e84e714c38c6547377fac133e",
237+ ] ) ;
199238
200- MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key }
239+ MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key, script_sigs }
201240}
202241
203- fn signature_map ( funding_coins : & [ TxOutput ] , signatures : & [ & ' static str ] ) -> BTreeMap < OutPoint , Signature > {
204- let signatures = signatures. iter ( ) . map ( |s| Signature {
242+ fn signature_vec ( signatures : & [ & ' static str ] ) -> Vec < Signature > {
243+ signatures. iter ( ) . map ( |s| Signature {
205244 signature : s. parse ( ) . expect ( "hardcoded signatures should be valid" ) ,
206245 sighash_type : TapSighashType :: Default ,
207- } ) ;
208- funding_coins. iter ( ) . map ( |o| o. outpoint ) . zip ( signatures) . collect ( )
246+ } ) . collect ( )
247+ }
248+
249+ fn signature_map ( funding_coins : & [ TxOutput ] , signatures : & [ & ' static str ] ) -> BTreeMap < OutPoint , Signature > {
250+ funding_coins. iter ( ) . map ( |o| o. outpoint ) . zip ( signature_vec ( signatures) ) . collect ( )
251+ }
252+
253+ fn script_sigs ( iks : & [ XOnlyPublicKey ] , signatures : & [ & ' static str ] ) -> BTreeMap < XOnlyPublicKey , Vec < Signature > > {
254+ iks. iter ( ) . map ( |k| ( * k, signature_vec ( signatures) ) ) . collect ( )
209255}
210256
211257impl TradeWallet for Wallet {
@@ -238,19 +284,19 @@ impl TradeWallet for Wallet {
238284 ) -> Result < Psbt > {
239285 let mut builder = self . build_tx ( ) ;
240286 builder
241- . ordering ( TxOrdering :: Untouched )
242- . nlocktime ( absolute:: LockTime :: ZERO )
243- . fee_rate ( fee_rate)
244- . add_recipient ( half_deposit_placeholder_spk ( rng) , deposit_amount) ;
287+ . ordering ( TxOrdering :: Untouched )
288+ . nlocktime ( absolute:: LockTime :: ZERO )
289+ . fee_rate ( fee_rate)
290+ . add_recipient ( half_deposit_placeholder_spk ( rng) , deposit_amount) ;
245291 for receiver in trade_fee_receivers {
246292 builder. add_recipient ( receiver. address . script_pubkey ( ) , receiver. amount ) ;
247293 }
248294 let mut psbt = builder. finish ( ) ?;
249295 // Calculate tx fee overpay unconditionally, as this performs additional checks on the PSBT:
250296 let overpay_msat: u64 = half_psbt_fee_overpay_msat ( & psbt, fee_rate)
251- . ok_or ( TransactionErrorKind :: Overflow ) ?
252- . try_into ( )
253- . map_err ( |_| TransactionErrorKind :: InvalidPsbt ) ?;
297+ . ok_or ( TransactionErrorKind :: Overflow ) ?
298+ . try_into ( )
299+ . map_err ( |_| TransactionErrorKind :: InvalidPsbt ) ?;
254300 let change_output_index = 1 + trade_fee_receivers. len ( ) ;
255301 if psbt. unsigned_tx . output . len ( ) > change_output_index {
256302 // Correct any tx fee overpay due to overly conservative input witness size estimation
@@ -274,6 +320,13 @@ impl TradeWallet for Wallet {
274320 if is_selected ( & psbt. unsigned_tx . input [ i] . previous_output ) {
275321 psbt. inputs [ i] . final_script_sig = psbt_copy. inputs [ i] . final_script_sig . take ( ) ;
276322 psbt. inputs [ i] . final_script_witness = psbt_copy. inputs [ i] . final_script_witness . take ( ) ;
323+ psbt. inputs [ i] . tap_script_sigs = mem:: take ( & mut psbt_copy. inputs [ i] . tap_script_sigs ) ;
324+
325+ if !psbt. inputs [ i] . tap_script_sigs . is_empty ( ) {
326+ // BDK couldn't finalize the selected input. Try to finalize it ourselves using
327+ // the `miniscript` lib, ignoring any errors that might occur.
328+ let _ = psbt. finalize_inp_mut ( & * LIBSECP256K1_CTX , i) ;
329+ }
277330 }
278331 }
279332 Ok ( ( ) )
@@ -301,9 +354,9 @@ impl TradeWallet for MemWallet {
301354 rng : & mut dyn RngCore ,
302355 ) -> Result < Psbt > {
303356 let mut res: Vec < ( ScriptBuf , Amount ) > = trade_fee_receivers
304- . iter ( )
305- . map ( |receiver| ( receiver. address . script_pubkey ( ) , deposit_amount) )
306- . collect ( ) ;
357+ . iter ( )
358+ . map ( |receiver| ( receiver. address . script_pubkey ( ) , deposit_amount) )
359+ . collect ( ) ;
307360 res. push ( ( half_deposit_placeholder_spk ( rng) , deposit_amount) ) ;
308361 let mut psbt = self . create_psbt ( res, fee_rate) ?;
309362 // Calculate tx fee overpay unconditionally, as this performs additional checks on the PSBT:
@@ -348,6 +401,7 @@ impl Redact for psbt::Input {
348401 fn redact_sensitive_fields ( & mut self ) {
349402 self . tap_key_origins . clear ( ) ;
350403 self . tap_scripts . clear ( ) ;
404+ self . tap_script_sigs . clear ( ) ;
351405 self . tap_internal_key = None ;
352406 self . tap_merkle_root = None ;
353407 }
@@ -463,7 +517,7 @@ pub(crate) fn merge_psbt_halves(
463517 fn re < T : Clone > ( dest : & mut Vec < T > , src : & [ T ] ) -> Vec < T > {
464518 let mut cloned_src = Vec :: with_capacity ( src. len ( ) + dest. len ( ) ) ;
465519 cloned_src. extend ( src. iter ( ) . cloned ( ) ) ;
466- std :: mem:: replace ( dest, cloned_src)
520+ mem:: replace ( dest, cloned_src)
467521 }
468522 use std:: convert:: identity as id;
469523
@@ -544,6 +598,17 @@ pub(crate) fn set_payouts_and_shuffle(psbt: &mut Psbt, buyer_payout: &mut TxOutp
544598 [ buyer_payout. outpoint . txid , seller_payout. outpoint . txid ] = [ txid; 2 ] ;
545599}
546600
601+ pub ( crate ) fn extract_signed_tx ( psbt : & Psbt ) -> Result < Transaction > {
602+ if !is_well_formed ( psbt) || psbt. inputs . iter ( ) . any ( |input| input. final_script_sig . is_some ( ) ) {
603+ return Err ( TransactionErrorKind :: InvalidPsbt ) ;
604+ }
605+ if psbt. inputs . iter ( ) . any ( |input| input. final_script_witness . is_none ( ) ) {
606+ return Err ( TransactionErrorKind :: MissingSignature ) ;
607+ }
608+ // TODO: Report undocumented panics in `Psbt::extract_tx` & `Psbt::fee` if the PSBT is malformed.
609+ Ok ( psbt. clone ( ) . extract_tx ( ) ?)
610+ }
611+
547612#[ cfg( test) ]
548613mod tests {
549614 use bdk_wallet:: psbt:: PsbtUtils as _;
0 commit comments