Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions payjoin/src/receive/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ pub(crate) enum InternalSelectionError {
/// No candidates available for selection
Empty,
/// Current privacy selection implementation only supports 2-output transactions
TooManyOutputs,
UnsupportedOutputLength,
/// No selection candidates improve privacy
NotFound,
}
Expand All @@ -327,7 +327,7 @@ impl fmt::Display for SelectionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.0 {
InternalSelectionError::Empty => write!(f, "No candidates available for selection"),
InternalSelectionError::TooManyOutputs => write!(
InternalSelectionError::UnsupportedOutputLength => write!(
f,
"Current privacy selection implementation only supports 2-output transactions"
),
Expand Down
22 changes: 10 additions & 12 deletions payjoin/src/receive/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ impl WantsInputs {
/// Proper coin selection allows payjoin to resemble ordinary transactions.
/// To ensure the resemblance, a number of heuristics must be avoided.
///
/// UIH "Unnecessary input heuristic" is avoided for multi-output transactions.
/// Attempt to avoid UIH (Unnecessary input heuristic) for 2-output transactions.
/// A simple consolidation is otherwise chosen if available.
pub fn try_preserving_privacy(
&self,
Expand All @@ -419,28 +419,26 @@ impl WantsInputs {
return Err(InternalSelectionError::Empty.into());
}

if self.payjoin_psbt.outputs.len() > 2 {
// This UIH avoidance function supports only
// many-input, n-output transactions such that n <= 2 for now
return Err(InternalSelectionError::TooManyOutputs.into());
}

if self.payjoin_psbt.outputs.len() == 2 {
self.avoid_uih(candidate_inputs)
} else {
self.select_first_candidate(candidate_inputs)
}
self.avoid_uih(&mut candidate_inputs)
.or_else(|_| self.select_first_candidate(&mut candidate_inputs))
}

/// UIH "Unnecessary input heuristic" is one class of heuristics to avoid. We define
/// UIH1 and UIH2 according to the BlockSci practice
/// BlockSci UIH1 and UIH2:
/// if min(in) > min(out) then UIH1 else UIH2
/// <https://eprint.iacr.org/2022/589.pdf>
///
/// This UIH avoidance function supports only
/// many-input, 2-output transactions for now
fn avoid_uih(
&self,
candidate_inputs: impl IntoIterator<Item = InputPair>,
) -> Result<InputPair, SelectionError> {
if self.payjoin_psbt.outputs.len() != 2 {
return Err(InternalSelectionError::UnsupportedOutputLength.into());
}

let min_out_sats = self
.payjoin_psbt
.unsigned_tx
Expand Down
Loading