Skip to content

Commit b2b63c6

Browse files
committed
Modify try_preserving_privacy function signature
`try_preserving_privacy` now takes a (PsbtInput, TxIn) iterator instead of a bespoke (Amount, Outpoint) iterator which is lossy and unwieldy. It also returns a single (PsbtInput, TxIn) instead of just an OutPoint. This is the same type that `contribute_inputs` takes as a parameter.
1 parent 5f4fe76 commit b2b63c6

File tree

7 files changed

+51
-78
lines changed

7 files changed

+51
-78
lines changed

payjoin-cli/src/app/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ fn read_local_cert() -> Result<Vec<u8>> {
124124
}
125125

126126
pub fn input_pair_from_list_unspent(
127-
utxo: &bitcoincore_rpc::bitcoincore_rpc_json::ListUnspentResultEntry,
127+
utxo: bitcoincore_rpc::bitcoincore_rpc_json::ListUnspentResultEntry,
128128
) -> (PsbtInput, TxIn) {
129129
let psbtin = PsbtInput {
130130
// NOTE: non_witness_utxo is not necessary because bitcoin-cli always supplies

payjoin-cli/src/app/v1.rs

+7-17
Original file line numberDiff line numberDiff line change
@@ -375,28 +375,18 @@ fn try_contributing_inputs(
375375
payjoin: payjoin::receive::WantsInputs,
376376
bitcoind: &bitcoincore_rpc::Client,
377377
) -> Result<payjoin::receive::ProvisionalProposal> {
378-
use bitcoin::OutPoint;
379-
380-
let available_inputs = bitcoind
378+
let candidate_inputs = bitcoind
381379
.list_unspent(None, None, None, None, None)
382-
.context("Failed to list unspent from bitcoind")?;
383-
let candidate_inputs: HashMap<Amount, OutPoint> = available_inputs
384-
.iter()
385-
.map(|i| (i.amount, OutPoint { txid: i.txid, vout: i.vout }))
386-
.collect();
387-
388-
let selected_outpoint = payjoin
380+
.context("Failed to list unspent from bitcoind")?
381+
.into_iter()
382+
.map(input_pair_from_list_unspent);
383+
let selected_input = payjoin
389384
.try_preserving_privacy(candidate_inputs)
390385
.map_err(|e| anyhow!("Failed to make privacy preserving selection: {}", e))?;
391-
let selected_utxo = available_inputs
392-
.iter()
393-
.find(|i| i.txid == selected_outpoint.txid && i.vout == selected_outpoint.vout)
394-
.context("This shouldn't happen. Failed to retrieve the privacy preserving utxo from those we provided to the seclector.")?;
395-
log::debug!("selected utxo: {:#?}", selected_utxo);
396-
let input_pair = input_pair_from_list_unspent(selected_utxo);
386+
log::debug!("selected input: {:#?}", selected_input);
397387

398388
Ok(payjoin
399-
.contribute_inputs(vec![input_pair])
389+
.contribute_inputs(vec![selected_input])
400390
.expect("This shouldn't happen. Failed to contribute inputs.")
401391
.commit_inputs())
402392
}

payjoin-cli/src/app/v2.rs

+7-17
Original file line numberDiff line numberDiff line change
@@ -365,28 +365,18 @@ fn try_contributing_inputs(
365365
payjoin: payjoin::receive::v2::WantsInputs,
366366
bitcoind: &bitcoincore_rpc::Client,
367367
) -> Result<payjoin::receive::v2::ProvisionalProposal> {
368-
use bitcoin::OutPoint;
369-
370-
let available_inputs = bitcoind
368+
let candidate_inputs = bitcoind
371369
.list_unspent(None, None, None, None, None)
372-
.context("Failed to list unspent from bitcoind")?;
373-
let candidate_inputs: HashMap<Amount, OutPoint> = available_inputs
374-
.iter()
375-
.map(|i| (i.amount, OutPoint { txid: i.txid, vout: i.vout }))
376-
.collect();
377-
378-
let selected_outpoint = payjoin
370+
.context("Failed to list unspent from bitcoind")?
371+
.into_iter()
372+
.map(input_pair_from_list_unspent);
373+
let selected_input = payjoin
379374
.try_preserving_privacy(candidate_inputs)
380375
.map_err(|e| anyhow!("Failed to make privacy preserving selection: {}", e))?;
381-
let selected_utxo = available_inputs
382-
.iter()
383-
.find(|i| i.txid == selected_outpoint.txid && i.vout == selected_outpoint.vout)
384-
.context("This shouldn't happen. Failed to retrieve the privacy preserving utxo from those we provided to the seclector.")?;
385-
log::debug!("selected utxo: {:#?}", selected_utxo);
386-
let input_pair = input_pair_from_list_unspent(selected_utxo);
376+
log::debug!("selected input: {:#?}", selected_input);
387377

388378
Ok(payjoin
389-
.contribute_inputs(vec![input_pair])
379+
.contribute_inputs(vec![selected_input])
390380
.expect("This shouldn't happen. Failed to contribute inputs.")
391381
.commit_inputs())
392382
}

payjoin/src/receive/error.rs

+4
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ pub(crate) enum InternalSelectionError {
260260
TooManyOutputs,
261261
/// No selection candidates improve privacy
262262
NotFound,
263+
/// Missing previous txout information
264+
PrevTxOut(crate::psbt::PrevTxOutError),
263265
}
264266

265267
impl fmt::Display for SelectionError {
@@ -272,6 +274,8 @@ impl fmt::Display for SelectionError {
272274
),
273275
InternalSelectionError::NotFound =>
274276
write!(f, "No selection candidates improve privacy"),
277+
InternalSelectionError::PrevTxOut(e) =>
278+
write!(f, "Missing previous txout information: {}", e),
275279
}
276280
}
277281
}

payjoin/src/receive/mod.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,8 @@ impl WantsInputs {
459459
/// A simple consolidation is otherwise chosen if available.
460460
pub fn try_preserving_privacy(
461461
&self,
462-
candidate_inputs: impl IntoIterator<Item = (Amount, OutPoint)>,
463-
) -> Result<OutPoint, SelectionError> {
462+
candidate_inputs: impl IntoIterator<Item = (PsbtInput, TxIn)>,
463+
) -> Result<(PsbtInput, TxIn), SelectionError> {
464464
let mut candidate_inputs = candidate_inputs.into_iter().peekable();
465465
if candidate_inputs.peek().is_none() {
466466
return Err(InternalSelectionError::Empty.into());
@@ -486,8 +486,8 @@ impl WantsInputs {
486486
/// https://eprint.iacr.org/2022/589.pdf
487487
fn avoid_uih(
488488
&self,
489-
candidate_inputs: impl IntoIterator<Item = (Amount, OutPoint)>,
490-
) -> Result<OutPoint, SelectionError> {
489+
candidate_inputs: impl IntoIterator<Item = (PsbtInput, TxIn)>,
490+
) -> Result<(PsbtInput, TxIn), SelectionError> {
491491
let min_original_out_sats = self
492492
.payjoin_psbt
493493
.unsigned_tx
@@ -506,15 +506,17 @@ impl WantsInputs {
506506

507507
let prior_payment_sats = self.payjoin_psbt.unsigned_tx.output[self.change_vout].value;
508508

509-
for candidate in candidate_inputs {
510-
let candidate_sats = candidate.0;
509+
for (psbtin, txin) in candidate_inputs {
510+
let input_pair = InputPair { txin: &txin, psbtin: &psbtin };
511+
let candidate_sats =
512+
input_pair.previous_txout().map_err(InternalSelectionError::PrevTxOut)?.value;
511513
let candidate_min_out = min(min_original_out_sats, prior_payment_sats + candidate_sats);
512514
let candidate_min_in = min(min_original_in_sats, candidate_sats);
513515

514516
if candidate_min_in > candidate_min_out {
515517
// The candidate avoids UIH2 but conforms to UIH1: Optimal change heuristic.
516518
// It implies the smallest output is the sender's change address.
517-
return Ok(candidate.1);
519+
return Ok((psbtin, txin));
518520
}
519521
}
520522

@@ -524,12 +526,12 @@ impl WantsInputs {
524526

525527
fn select_first_candidate(
526528
&self,
527-
candidate_inputs: impl IntoIterator<Item = (Amount, OutPoint)>,
528-
) -> Result<OutPoint, SelectionError> {
529+
candidate_inputs: impl IntoIterator<Item = (PsbtInput, TxIn)>,
530+
) -> Result<(PsbtInput, TxIn), SelectionError> {
529531
candidate_inputs
530532
.into_iter()
531533
.next()
532-
.map_or(Err(InternalSelectionError::NotFound.into()), |(_, outpoint)| Ok(outpoint))
534+
.map_or(Err(InternalSelectionError::NotFound.into()), |input_pair| Ok(input_pair))
533535
}
534536

535537
/// Add the provided list of inputs to the transaction.

payjoin/src/receive/v2/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,8 @@ impl WantsInputs {
387387
/// https://eprint.iacr.org/2022/589.pdf
388388
pub fn try_preserving_privacy(
389389
&self,
390-
candidate_inputs: impl IntoIterator<Item = (Amount, OutPoint)>,
391-
) -> Result<OutPoint, SelectionError> {
390+
candidate_inputs: impl IntoIterator<Item = (PsbtInput, TxIn)>,
391+
) -> Result<(PsbtInput, TxIn), SelectionError> {
392392
self.inner.try_preserving_privacy(candidate_inputs)
393393
}
394394

payjoin/tests/integration.rs

+18-31
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ mod integration {
560560
// POST payjoin
561561
let proposal =
562562
session.process_res(response.bytes().await?.to_vec().as_slice(), ctx)?.unwrap();
563-
let inputs = receiver_utxos.iter().map(input_pair_from_list_unspent).collect();
563+
let inputs = receiver_utxos.into_iter().map(input_pair_from_list_unspent).collect();
564564
let mut payjoin_proposal =
565565
handle_directory_proposal(&receiver, proposal, Some(inputs));
566566
assert!(!payjoin_proposal.is_output_substitution_disabled());
@@ -874,24 +874,18 @@ mod integration {
874874
let inputs = match custom_inputs {
875875
Some(inputs) => inputs,
876876
None => {
877-
let available_inputs =
878-
receiver.list_unspent(None, None, None, None, None).unwrap();
879-
let candidate_inputs: HashMap<Amount, OutPoint> = available_inputs
880-
.iter()
881-
.map(|i| (i.amount, OutPoint { txid: i.txid, vout: i.vout }))
882-
.collect();
883-
884-
let selected_outpoint = payjoin
877+
let candidate_inputs = receiver
878+
.list_unspent(None, None, None, None, None)
879+
.unwrap()
880+
.into_iter()
881+
.map(input_pair_from_list_unspent);
882+
let selected_input = payjoin
885883
.try_preserving_privacy(candidate_inputs)
886-
.expect("Failed to make privacy preserving selection");
887-
let selected_utxo = available_inputs
888-
.iter()
889-
.find(|i| {
890-
i.txid == selected_outpoint.txid && i.vout == selected_outpoint.vout
884+
.map_err(|e| {
885+
format!("Failed to make privacy preserving selection: {:?}", e)
891886
})
892887
.unwrap();
893-
let input_pair = input_pair_from_list_unspent(selected_utxo);
894-
vec![input_pair]
888+
vec![selected_input]
895889
}
896890
};
897891
let payjoin = payjoin.contribute_inputs(inputs).unwrap().commit_inputs();
@@ -1043,7 +1037,7 @@ mod integration {
10431037
.script_pubkey(),
10441038
}];
10451039
let drain_script = outputs[0].script_pubkey.clone();
1046-
let inputs = receiver_utxos.iter().map(input_pair_from_list_unspent).collect();
1040+
let inputs = receiver_utxos.into_iter().map(input_pair_from_list_unspent).collect();
10471041
let response = handle_v1_pj_request(
10481042
req,
10491043
headers,
@@ -1309,21 +1303,14 @@ mod integration {
13091303
let inputs = match custom_inputs {
13101304
Some(inputs) => inputs,
13111305
None => {
1312-
let available_inputs = receiver.list_unspent(None, None, None, None, None)?;
1313-
let candidate_inputs: HashMap<Amount, OutPoint> = available_inputs
1314-
.iter()
1315-
.map(|i| (i.amount, OutPoint { txid: i.txid, vout: i.vout }))
1316-
.collect();
1317-
1318-
let selected_outpoint = payjoin
1306+
let candidate_inputs = receiver
1307+
.list_unspent(None, None, None, None, None)?
1308+
.into_iter()
1309+
.map(input_pair_from_list_unspent);
1310+
let selected_input = payjoin
13191311
.try_preserving_privacy(candidate_inputs)
13201312
.map_err(|e| format!("Failed to make privacy preserving selection: {:?}", e))?;
1321-
let selected_utxo = available_inputs
1322-
.iter()
1323-
.find(|i| i.txid == selected_outpoint.txid && i.vout == selected_outpoint.vout)
1324-
.unwrap();
1325-
let input_pair = input_pair_from_list_unspent(selected_utxo);
1326-
vec![input_pair]
1313+
vec![selected_input]
13271314
}
13281315
};
13291316
let payjoin = payjoin
@@ -1388,7 +1375,7 @@ mod integration {
13881375
}
13891376

13901377
fn input_pair_from_list_unspent(
1391-
utxo: &bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::ListUnspentResultEntry,
1378+
utxo: bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::ListUnspentResultEntry,
13921379
) -> (PsbtInput, TxIn) {
13931380
let psbtin = PsbtInput {
13941381
// NOTE: non_witness_utxo is not necessary because bitcoin-cli always supplies

0 commit comments

Comments
 (0)