Skip to content

Commit a20e84d

Browse files
committed
Fall back to first candidate if avoid_uih fails
try_preserving_privacy is about a best-effort attempt, not a guaranteed success. This implementation is what was already implied by the docstring. So it's a fix. Making avoid_uih and select_first_candidate public later can be public to allow for more granular downstream control.
1 parent 03c160a commit a20e84d

File tree

1 file changed

+10
-12
lines changed

1 file changed

+10
-12
lines changed

payjoin/src/receive/v1/mod.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ impl WantsInputs {
408408
/// Proper coin selection allows payjoin to resemble ordinary transactions.
409409
/// To ensure the resemblance, a number of heuristics must be avoided.
410410
///
411-
/// UIH "Unnecessary input heuristic" is avoided for multi-output transactions.
411+
/// Attempt to avoid UIH (Unnecessary input heuristic) for 2-output transactions.
412412
/// A simple consolidation is otherwise chosen if available.
413413
pub fn try_preserving_privacy(
414414
&self,
@@ -419,28 +419,26 @@ impl WantsInputs {
419419
return Err(InternalSelectionError::Empty.into());
420420
}
421421

422-
if self.payjoin_psbt.outputs.len() > 2 {
423-
// This UIH avoidance function supports only
424-
// many-input, n-output transactions such that n <= 2 for now
425-
return Err(InternalSelectionError::TooManyOutputs.into());
426-
}
427-
428-
if self.payjoin_psbt.outputs.len() == 2 {
429-
self.avoid_uih(candidate_inputs)
430-
} else {
431-
self.select_first_candidate(candidate_inputs)
432-
}
422+
self.avoid_uih(&mut candidate_inputs)
423+
.or_else(|_| self.select_first_candidate(&mut candidate_inputs))
433424
}
434425

435426
/// UIH "Unnecessary input heuristic" is one class of heuristics to avoid. We define
436427
/// UIH1 and UIH2 according to the BlockSci practice
437428
/// BlockSci UIH1 and UIH2:
438429
/// if min(in) > min(out) then UIH1 else UIH2
439430
/// <https://eprint.iacr.org/2022/589.pdf>
431+
///
432+
/// This UIH avoidance function supports only
433+
/// many-input, 2-output transactions for now
440434
fn avoid_uih(
441435
&self,
442436
candidate_inputs: impl IntoIterator<Item = InputPair>,
443437
) -> Result<InputPair, SelectionError> {
438+
if self.payjoin_psbt.outputs.len() != 2 {
439+
return Err(InternalSelectionError::TooManyOutputs.into());
440+
}
441+
444442
let min_out_sats = self
445443
.payjoin_psbt
446444
.unsigned_tx

0 commit comments

Comments
 (0)