Skip to content

Commit 21d0cd6

Browse files
committed
Validate InputPair to rm SelectionErr::PrevTxOut
Validated pairs will not produce a mismatched TxOut error.
1 parent f6b296a commit 21d0cd6

File tree

8 files changed

+32
-29
lines changed

8 files changed

+32
-29
lines changed

payjoin-cli/src/app/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ use bitcoin::TxIn;
77
use bitcoincore_rpc::bitcoin::Amount;
88
use bitcoincore_rpc::RpcApi;
99
use payjoin::bitcoin::psbt::Psbt;
10-
use payjoin::psbt::InputPair;
1110
use payjoin::send::Sender;
12-
use payjoin::{bitcoin, PjUri};
11+
use payjoin::{bitcoin, InputPair, PjUri};
1312

1413
pub mod config;
1514
use crate::app::config::AppConfig;

payjoin-cli/src/app/v2.rs

-1
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

payjoin/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub use crate::ohttp::OhttpKeys;
3838
pub mod io;
3939

4040
#[cfg(any(feature = "send", feature = "receive"))]
41-
pub mod psbt;
41+
pub(crate) mod psbt;
4242
#[cfg(any(feature = "send", all(feature = "receive", feature = "v2")))]
4343
mod request;
4444
#[cfg(any(feature = "send", all(feature = "receive", feature = "v2")))]
@@ -48,5 +48,6 @@ mod uri;
4848

4949
#[cfg(feature = "base64")]
5050
pub use bitcoin::base64;
51+
pub use psbt::InputPair;
5152
pub use uri::{PjParseError, PjUri, PjUriBuilder, Uri, UriExt};
5253
pub use url::{ParseError, Url};

payjoin/src/psbt.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,32 @@ pub struct InputPair {
115115
impl InputPair {
116116
pub fn new(txin: TxIn, psbtin: psbt::Input) -> Result<Self, PsbtInputError> {
117117
let input_pair = Self { txin, psbtin };
118+
// TODO validate and document Input details required for Input Contribution fee estimation
119+
// TODO Validate AddressType will return valid AddressType or an error
120+
// TODO consider whether or not this should live in receive module since it's a baby of that state machine
118121
InternalInputPair::from(&input_pair).validate_utxo(true)?;
119122
Ok(input_pair)
120123
}
124+
125+
pub(crate) fn txin(&self) -> &TxIn { &self.txin }
126+
127+
pub(crate) fn psbtin(&self) -> &psbt::Input { &self.psbtin }
128+
129+
pub(crate) fn address_type(&self) -> Result<AddressType, AddressTypeError> {
130+
let raw = InternalInputPair { txin: &self.txin, psbtin: &self.psbtin };
131+
raw.address_type()
132+
}
133+
134+
pub(crate) fn previous_txout(&self) -> TxOut {
135+
InternalInputPair::from(self)
136+
.previous_txout()
137+
.expect("missing UTXO information should have been validated in InputPair::new")
138+
.clone()
139+
}
121140
}
122141

123142
#[derive(Clone, Debug)]
124-
pub(crate) struct InternalInputPair<'a> {
143+
pub struct InternalInputPair<'a> {
125144
pub txin: &'a TxIn,
126145
pub psbtin: &'a psbt::Input,
127146
}

payjoin/src/receive/error.rs

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

267265
impl fmt::Display for SelectionError {
@@ -274,8 +272,6 @@ impl fmt::Display for SelectionError {
274272
),
275273
InternalSelectionError::NotFound =>
276274
write!(f, "No selection candidates improve privacy"),
277-
InternalSelectionError::PrevTxOut(e) =>
278-
write!(f, "Missing previous txout information: {}", e),
279275
}
280276
}
281277
}
@@ -293,8 +289,6 @@ pub struct InputContributionError(InternalInputContributionError);
293289

294290
#[derive(Debug)]
295291
pub(crate) enum InternalInputContributionError {
296-
/// Missing previous txout information
297-
PrevTxOut(crate::psbt::PrevTxOutError),
298292
/// The address type could not be determined
299293
AddressType(crate::psbt::AddressTypeError),
300294
/// The original PSBT has no inputs
@@ -308,8 +302,6 @@ pub(crate) enum InternalInputContributionError {
308302
impl fmt::Display for InputContributionError {
309303
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
310304
match &self.0 {
311-
InternalInputContributionError::PrevTxOut(e) =>
312-
write!(f, "Missing previous txout information: {}", e),
313305
InternalInputContributionError::AddressType(e) =>
314306
write!(f, "The address type could not be determined: {}", e),
315307
InternalInputContributionError::NoSenderInputs =>

payjoin/src/receive/mod.rs

+6-12
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use error::{
4747
};
4848
use optional_parameters::Params;
4949

50-
use crate::psbt::{InputPair, InternalInputPair, PsbtExt};
50+
use crate::psbt::{InputPair, PsbtExt};
5151

5252
pub trait Headers {
5353
fn get_header(&self, key: &str) -> Option<&str>;
@@ -507,16 +507,14 @@ impl WantsInputs {
507507
let prior_payment_sats = self.payjoin_psbt.unsigned_tx.output[self.change_vout].value;
508508

509509
for input_pair in candidate_inputs {
510-
let input_pair = InternalInputPair::from(&input_pair);
511-
let candidate_sats =
512-
input_pair.previous_txout().map_err(InternalSelectionError::PrevTxOut)?.value;
510+
let candidate_sats = input_pair.previous_txout().value;
513511
let candidate_min_out = min(min_original_out_sats, prior_payment_sats + candidate_sats);
514512
let candidate_min_in = min(min_original_in_sats, candidate_sats);
515513

516514
if candidate_min_in > candidate_min_out {
517515
// The candidate avoids UIH2 but conforms to UIH1: Optimal change heuristic.
518516
// It implies the smallest output is the sender's change address.
519-
return Ok(InputPair::from(&input_pair));
517+
return Ok(input_pair);
520518
}
521519
}
522520

@@ -555,7 +553,6 @@ impl WantsInputs {
555553
let mut rng = rand::thread_rng();
556554
let mut receiver_input_amount = Amount::ZERO;
557555
for input_pair in inputs.into_iter() {
558-
let input_pair = InternalInputPair::from(&input_pair);
559556
let input_type =
560557
input_pair.address_type().map_err(InternalInputContributionError::AddressType)?;
561558

@@ -564,16 +561,13 @@ impl WantsInputs {
564561
self.check_mixed_input_types(input_type, uniform_sender_input_type)?;
565562
}
566563

567-
receiver_input_amount += input_pair
568-
.previous_txout()
569-
.map_err(InternalInputContributionError::PrevTxOut)?
570-
.value;
564+
receiver_input_amount += input_pair.previous_txout().value;
571565
let index = rng.gen_range(0..=self.payjoin_psbt.unsigned_tx.input.len());
572-
payjoin_psbt.inputs.insert(index, input_pair.psbtin.clone());
566+
payjoin_psbt.inputs.insert(index, input_pair.psbtin().clone());
573567
payjoin_psbt
574568
.unsigned_tx
575569
.input
576-
.insert(index, TxIn { sequence: original_sequence, ..input_pair.txin.clone() });
570+
.insert(index, TxIn { sequence: original_sequence, ..input_pair.txin().clone() });
577571
}
578572

579573
// Add the receiver change amount to the receiver change output, if applicable

payjoin/src/receive/v2/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::time::{Duration, SystemTime};
33

44
use bitcoin::base64::prelude::BASE64_URL_SAFE_NO_PAD;
55
use bitcoin::base64::Engine;
6-
use bitcoin::psbt::{Input as PsbtInput, Psbt};
7-
use bitcoin::{Address, Amount, FeeRate, OutPoint, Script, TxIn, TxOut};
6+
use bitcoin::psbt::Psbt;
7+
use bitcoin::{Address, FeeRate, OutPoint, Script, TxOut};
88
use serde::de::Deserializer;
99
use serde::{Deserialize, Serialize};
1010
use url::Url;

payjoin/tests/integration.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ mod integration {
1212
use bitcoind::bitcoincore_rpc::{self, RpcApi};
1313
use log::{log_enabled, Level};
1414
use once_cell::sync::{Lazy, OnceCell};
15-
use payjoin::psbt::InputPair;
1615
use payjoin::send::SenderBuilder;
17-
use payjoin::{PjUri, PjUriBuilder, Request, Uri};
16+
use payjoin::{InputPair, PjUri, PjUriBuilder, Request, Uri};
1817
use tracing_subscriber::{EnvFilter, FmtSubscriber};
1918
use url::Url;
2019

0 commit comments

Comments
 (0)