@@ -208,10 +208,8 @@ impl WantsInputs {
208208 & self ,
209209 candidate_inputs : impl IntoIterator < Item = InputPair > ,
210210 ) -> Result < InputPair , CoinSelectionError > {
211- let mut candidate_inputs = candidate_inputs. into_iter ( ) . peekable ( ) ;
212-
213- self . avoid_uih ( & mut candidate_inputs)
214- . or_else ( |_| self . select_first_candidate ( & mut candidate_inputs) )
211+ let candidates: Vec < _ > = candidate_inputs. into_iter ( ) . collect ( ) ;
212+ self . avoid_uih ( & candidates) . or_else ( |_| self . select_first_candidate ( & candidates) )
215213 }
216214
217215 /// Returns the candidate input which avoids the UIH2 defined in [Unnecessary Input
@@ -226,7 +224,7 @@ impl WantsInputs {
226224 /// Errors if the transaction does not have exactly 2 outputs.
227225 pub ( super ) fn avoid_uih (
228226 & self ,
229- candidate_inputs : impl IntoIterator < Item = InputPair > ,
227+ candidate_inputs : & [ InputPair ] ,
230228 ) -> Result < InputPair , CoinSelectionError > {
231229 if self . payjoin_psbt . outputs . len ( ) != 2 {
232230 return Err ( InternalCoinSelectionError :: UnsupportedOutputLength . into ( ) ) ;
@@ -258,7 +256,7 @@ impl WantsInputs {
258256 if candidate_min_in > candidate_min_out {
259257 // The candidate avoids UIH2 but conforms to UIH1: Optimal change heuristic.
260258 // It implies the smallest output is the sender's change address.
261- return Ok ( input_pair) ;
259+ return Ok ( input_pair. clone ( ) ) ;
262260 }
263261 }
264262
@@ -269,9 +267,9 @@ impl WantsInputs {
269267 /// Returns the first candidate input in the provided list or errors if the list is empty.
270268 fn select_first_candidate (
271269 & self ,
272- candidate_inputs : impl IntoIterator < Item = InputPair > ,
270+ candidate_inputs : & [ InputPair ] ,
273271 ) -> Result < InputPair , CoinSelectionError > {
274- candidate_inputs. into_iter ( ) . next ( ) . ok_or ( InternalCoinSelectionError :: Empty . into ( ) )
272+ candidate_inputs. first ( ) . cloned ( ) . ok_or ( InternalCoinSelectionError :: Empty . into ( ) )
275273 }
276274
277275 /// Contributes the provided list of inputs to the transaction at random indices. If the total input
@@ -584,6 +582,53 @@ mod tests {
584582 ) ;
585583 }
586584
585+ fn candidate_input_from_test_vector ( value : Amount ) -> InputPair {
586+ let txout = TxOut {
587+ value,
588+ script_pubkey : ScriptBuf :: new_p2pkh ( & PubkeyHash :: from_byte_array ( DUMMY20 ) ) ,
589+ } ;
590+ let tx = Transaction {
591+ version : bitcoin:: transaction:: Version :: TWO ,
592+ lock_time : LockTime :: Seconds ( Time :: MIN ) ,
593+ input : vec ! [ ] ,
594+ output : vec ! [ txout. clone( ) ] ,
595+ } ;
596+ let outpoint = OutPoint { txid : tx. compute_txid ( ) , vout : 0 } ;
597+ InputPair :: new (
598+ TxIn { previous_output : outpoint, sequence : Sequence :: MAX , ..Default :: default ( ) } ,
599+ Input { witness_utxo : Some ( txout) , ..Default :: default ( ) } ,
600+ None ,
601+ )
602+ . unwrap ( )
603+ }
604+
605+ #[ test]
606+ fn try_preserving_privacy_falls_back_when_avoid_uih_not_found ( ) {
607+ let original = original_from_test_vector ( ) ;
608+ let wants_inputs = WantsOutputs :: new ( original, vec ! [ 0 ] ) . commit_outputs ( ) ;
609+ let candidates = vec ! [
610+ candidate_input_from_test_vector( Amount :: ONE_SAT ) ,
611+ candidate_input_from_test_vector( Amount :: from_sat( 2 ) ) ,
612+ ] ;
613+
614+ let selected = wants_inputs. try_preserving_privacy ( candidates. clone ( ) ) . unwrap ( ) ;
615+
616+ assert_eq ! ( selected, candidates[ 0 ] ) ;
617+ }
618+
619+ #[ test]
620+ fn try_preserving_privacy_falls_back_when_avoid_uih_unsupported ( ) {
621+ let original = original_from_test_vector ( ) ;
622+ let mut wants_inputs = WantsOutputs :: new ( original, vec ! [ 0 ] ) . commit_outputs ( ) ;
623+ wants_inputs. payjoin_psbt . unsigned_tx . output . pop ( ) ;
624+ wants_inputs. payjoin_psbt . outputs . pop ( ) ;
625+ let candidate = candidate_input_from_test_vector ( Amount :: ONE_SAT ) ;
626+
627+ let selected = wants_inputs. try_preserving_privacy ( [ candidate. clone ( ) ] ) . unwrap ( ) ;
628+
629+ assert_eq ! ( selected, candidate) ;
630+ }
631+
587632 #[ test]
588633 fn test_avoid_uih_one_output ( ) {
589634 let original = original_from_test_vector ( ) ;
@@ -594,14 +639,13 @@ mod tests {
594639 None ,
595640 )
596641 . unwrap ( ) ;
597- let input_iter = [ input] . into_iter ( ) ;
598642 let mut payjoin = WantsOutputs :: new ( original, vec ! [ 0 ] )
599643 . commit_outputs ( )
600- . contribute_inputs ( input_iter . clone ( ) )
644+ . contribute_inputs ( [ input . clone ( ) ] )
601645 . expect ( "Failed to contribute inputs" ) ;
602646
603647 payjoin. payjoin_psbt . outputs . pop ( ) ;
604- let avoid_uih = payjoin. avoid_uih ( input_iter ) ;
648+ let avoid_uih = payjoin. avoid_uih ( std :: slice :: from_ref ( & input ) ) ;
605649 assert_eq ! (
606650 avoid_uih. unwrap_err( ) ,
607651 CoinSelectionError :: from( InternalCoinSelectionError :: UnsupportedOutputLength ) ,
0 commit comments