Skip to content

Commit e7c290f

Browse files
authored
Modify try_preserving_privacy function signature (payjoin#377)
Addresses payjoin#32. Instead of having `try_preserving_privacy` modify the PSBT directly as suggested in that issue, we keep the "coin selection" and "input contribution" steps separate but simplify the function interface so that they're easier to chain and reason about. `try_preserving_privacy` now takes a `InputPair` iterator instead of a bespoke `(Amount, Outpoint)` iterator which is lossy and unwieldy. It also returns a single `InputPair` instead of just an `OutPoint`. This is the same type that `contribute_inputs` takes as a parameter.
2 parents 24d5e9f + 7956acf commit e7c290f

File tree

8 files changed

+161
-135
lines changed

8 files changed

+161
-135
lines changed

payjoin-cli/src/app/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bitcoin::TxIn;
77
use bitcoincore_rpc::bitcoin::Amount;
88
use bitcoincore_rpc::RpcApi;
99
use payjoin::bitcoin::psbt::Psbt;
10+
use payjoin::receive::InputPair;
1011
use payjoin::send::Sender;
1112
use payjoin::{bitcoin, PjUri};
1213

@@ -124,8 +125,8 @@ fn read_local_cert() -> Result<Vec<u8>> {
124125
}
125126

126127
pub fn input_pair_from_list_unspent(
127-
utxo: &bitcoincore_rpc::bitcoincore_rpc_json::ListUnspentResultEntry,
128-
) -> (PsbtInput, TxIn) {
128+
utxo: bitcoincore_rpc::bitcoincore_rpc_json::ListUnspentResultEntry,
129+
) -> InputPair {
129130
let psbtin = PsbtInput {
130131
// NOTE: non_witness_utxo is not necessary because bitcoin-cli always supplies
131132
// witness_utxo, even for non-witness inputs
@@ -141,5 +142,5 @@ pub fn input_pair_from_list_unspent(
141142
previous_output: bitcoin::OutPoint { txid: utxo.txid, vout: utxo.vout },
142143
..Default::default()
143144
};
144-
(psbtin, txin)
145+
InputPair::new(txin, psbtin).expect("Input pair should be valid")
145146
}

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-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::collections::HashMap;
21
use std::str::FromStr;
32
use std::sync::Arc;
43

@@ -365,28 +364,18 @@ fn try_contributing_inputs(
365364
payjoin: payjoin::receive::v2::WantsInputs,
366365
bitcoind: &bitcoincore_rpc::Client,
367366
) -> Result<payjoin::receive::v2::ProvisionalProposal> {
368-
use bitcoin::OutPoint;
369-
370-
let available_inputs = bitcoind
367+
let candidate_inputs = bitcoind
371368
.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
369+
.context("Failed to list unspent from bitcoind")?
370+
.into_iter()
371+
.map(input_pair_from_list_unspent);
372+
let selected_input = payjoin
379373
.try_preserving_privacy(candidate_inputs)
380374
.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);
375+
log::debug!("selected input: {:#?}", selected_input);
387376

388377
Ok(payjoin
389-
.contribute_inputs(vec![input_pair])
378+
.contribute_inputs(vec![selected_input])
390379
.expect("This shouldn't happen. Failed to contribute inputs.")
391380
.commit_inputs())
392381
}

payjoin/src/psbt.rs

+51-22
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub(crate) trait PsbtExt: Sized {
3535
) -> &mut BTreeMap<bip32::Xpub, (bip32::Fingerprint, bip32::DerivationPath)>;
3636
fn proprietary_mut(&mut self) -> &mut BTreeMap<psbt::raw::ProprietaryKey, Vec<u8>>;
3737
fn unknown_mut(&mut self) -> &mut BTreeMap<psbt::raw::Key, Vec<u8>>;
38-
fn input_pairs(&self) -> Box<dyn Iterator<Item = InputPair<'_>> + '_>;
38+
fn input_pairs(&self) -> Box<dyn Iterator<Item = InternalInputPair<'_>> + '_>;
3939
// guarantees that length of psbt input matches that of unsigned_tx inputs and same
4040
/// thing for outputs.
4141
fn validate(self) -> Result<Self, InconsistentPsbt>;
@@ -59,13 +59,13 @@ impl PsbtExt for Psbt {
5959

6060
fn unknown_mut(&mut self) -> &mut BTreeMap<psbt::raw::Key, Vec<u8>> { &mut self.unknown }
6161

62-
fn input_pairs(&self) -> Box<dyn Iterator<Item = InputPair<'_>> + '_> {
62+
fn input_pairs(&self) -> Box<dyn Iterator<Item = InternalInputPair<'_>> + '_> {
6363
Box::new(
6464
self.unsigned_tx
6565
.input
6666
.iter()
6767
.zip(&self.inputs)
68-
.map(|(txin, psbtin)| InputPair { txin, psbtin }),
68+
.map(|(txin, psbtin)| InternalInputPair { txin, psbtin }),
6969
)
7070
}
7171

@@ -106,12 +106,13 @@ fn redeem_script(script_sig: &Script) -> Option<&Script> {
106106
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh-nested-in-bip16-p2sh
107107
const NESTED_P2WPKH_MAX: InputWeightPrediction = InputWeightPrediction::from_slice(23, &[72, 33]);
108108

109-
pub(crate) struct InputPair<'a> {
109+
#[derive(Clone, Debug)]
110+
pub(crate) struct InternalInputPair<'a> {
110111
pub txin: &'a TxIn,
111112
pub psbtin: &'a psbt::Input,
112113
}
113114

114-
impl<'a> InputPair<'a> {
115+
impl<'a> InternalInputPair<'a> {
115116
/// Returns TxOut associated with the input
116117
pub fn previous_txout(&self) -> Result<&TxOut, PrevTxOutError> {
117118
match (&self.psbtin.non_witness_utxo, &self.psbtin.witness_utxo) {
@@ -132,10 +133,13 @@ impl<'a> InputPair<'a> {
132133
}
133134
}
134135

135-
pub fn validate_utxo(&self, treat_missing_as_error: bool) -> Result<(), PsbtInputError> {
136+
pub fn validate_utxo(
137+
&self,
138+
treat_missing_as_error: bool,
139+
) -> Result<(), InternalPsbtInputError> {
136140
match (&self.psbtin.non_witness_utxo, &self.psbtin.witness_utxo) {
137141
(None, None) if treat_missing_as_error =>
138-
Err(PsbtInputError::PrevTxOut(PrevTxOutError::MissingUtxoInformation)),
142+
Err(InternalPsbtInputError::PrevTxOut(PrevTxOutError::MissingUtxoInformation)),
139143
(None, None) => Ok(()),
140144
(Some(tx), None) if tx.compute_txid() == self.txin.previous_output.txid => tx
141145
.output
@@ -153,7 +157,7 @@ impl<'a> InputPair<'a> {
153157
.into()
154158
})
155159
.map(drop),
156-
(Some(_), None) => Err(PsbtInputError::UnequalTxid),
160+
(Some(_), None) => Err(InternalPsbtInputError::UnequalTxid),
157161
(None, Some(_)) => Ok(()),
158162
(Some(tx), Some(witness_txout))
159163
if tx.compute_txid() == self.txin.previous_output.txid =>
@@ -173,10 +177,10 @@ impl<'a> InputPair<'a> {
173177
if witness_txout == non_witness_txout {
174178
Ok(())
175179
} else {
176-
Err(PsbtInputError::SegWitTxOutMismatch)
180+
Err(InternalPsbtInputError::SegWitTxOutMismatch)
177181
}
178182
}
179-
(Some(_), Some(_)) => Err(PsbtInputError::UnequalTxid),
183+
(Some(_), Some(_)) => Err(InternalPsbtInputError::UnequalTxid),
180184
}
181185
}
182186

@@ -245,41 +249,66 @@ impl fmt::Display for PrevTxOutError {
245249
impl std::error::Error for PrevTxOutError {}
246250

247251
#[derive(Debug)]
248-
pub(crate) enum PsbtInputError {
252+
pub(crate) enum InternalPsbtInputError {
249253
PrevTxOut(PrevTxOutError),
250254
UnequalTxid,
251255
/// TxOut provided in `segwit_utxo` doesn't match the one in `non_segwit_utxo`
252256
SegWitTxOutMismatch,
257+
AddressType(AddressTypeError),
258+
NoRedeemScript,
253259
}
254260

255-
impl fmt::Display for PsbtInputError {
261+
impl fmt::Display for InternalPsbtInputError {
256262
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257263
match self {
258-
PsbtInputError::PrevTxOut(_) => write!(f, "invalid previous transaction output"),
259-
PsbtInputError::UnequalTxid => write!(f, "transaction ID of previous transaction doesn't match one specified in input spending it"),
260-
PsbtInputError::SegWitTxOutMismatch => write!(f, "transaction output provided in SegWit UTXO field doesn't match the one in non-SegWit UTXO field"),
264+
Self::PrevTxOut(_) => write!(f, "invalid previous transaction output"),
265+
Self::UnequalTxid => write!(f, "transaction ID of previous transaction doesn't match one specified in input spending it"),
266+
Self::SegWitTxOutMismatch => write!(f, "transaction output provided in SegWit UTXO field doesn't match the one in non-SegWit UTXO field"),
267+
Self::AddressType(_) => write!(f, "invalid address type"),
268+
Self::NoRedeemScript => write!(f, "provided p2sh PSBT input is missing a redeem_script"),
261269
}
262270
}
263271
}
264272

265-
impl std::error::Error for PsbtInputError {
273+
impl std::error::Error for InternalPsbtInputError {
266274
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
267275
match self {
268-
PsbtInputError::PrevTxOut(error) => Some(error),
269-
PsbtInputError::UnequalTxid => None,
270-
PsbtInputError::SegWitTxOutMismatch => None,
276+
Self::PrevTxOut(error) => Some(error),
277+
Self::UnequalTxid => None,
278+
Self::SegWitTxOutMismatch => None,
279+
Self::AddressType(error) => Some(error),
280+
Self::NoRedeemScript => None,
271281
}
272282
}
273283
}
274284

275-
impl From<PrevTxOutError> for PsbtInputError {
276-
fn from(value: PrevTxOutError) -> Self { PsbtInputError::PrevTxOut(value) }
285+
impl From<PrevTxOutError> for InternalPsbtInputError {
286+
fn from(value: PrevTxOutError) -> Self { InternalPsbtInputError::PrevTxOut(value) }
287+
}
288+
289+
impl From<AddressTypeError> for InternalPsbtInputError {
290+
fn from(value: AddressTypeError) -> Self { Self::AddressType(value) }
291+
}
292+
293+
#[derive(Debug)]
294+
pub struct PsbtInputError(InternalPsbtInputError);
295+
296+
impl From<InternalPsbtInputError> for PsbtInputError {
297+
fn from(e: InternalPsbtInputError) -> Self { PsbtInputError(e) }
298+
}
299+
300+
impl fmt::Display for PsbtInputError {
301+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
302+
}
303+
304+
impl std::error::Error for PsbtInputError {
305+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
277306
}
278307

279308
#[derive(Debug)]
280309
pub struct PsbtInputsError {
281310
index: usize,
282-
error: PsbtInputError,
311+
error: InternalPsbtInputError,
283312
}
284313

285314
impl fmt::Display for PsbtInputsError {

payjoin/src/receive/error.rs

-4
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,6 @@ pub struct InputContributionError(InternalInputContributionError);
289289

290290
#[derive(Debug)]
291291
pub(crate) enum InternalInputContributionError {
292-
/// Missing previous txout information
293-
PrevTxOut(crate::psbt::PrevTxOutError),
294292
/// The address type could not be determined
295293
AddressType(crate::psbt::AddressTypeError),
296294
/// The original PSBT has no inputs
@@ -304,8 +302,6 @@ pub(crate) enum InternalInputContributionError {
304302
impl fmt::Display for InputContributionError {
305303
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306304
match &self.0 {
307-
InternalInputContributionError::PrevTxOut(e) =>
308-
write!(f, "Missing previous txout information: {}", e),
309305
InternalInputContributionError::AddressType(e) =>
310306
write!(f, "The address type could not be determined: {}", e),
311307
InternalInputContributionError::NoSenderInputs =>

0 commit comments

Comments
 (0)