Skip to content

Commit d6102ed

Browse files
[Temporary] Remove Vec from MaybeInputsOwned non-blocking interface
1 parent 7a0c45f commit d6102ed

3 files changed

Lines changed: 135 additions & 21 deletions

File tree

payjoin/src/core/receive/mod.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,67 @@ impl<'a> From<&'a InputPair> for InternalInputPair<'a> {
228228
fn from(pair: &'a InputPair) -> Self { Self { psbtin: &pair.psbtin, txin: &pair.txin } }
229229
}
230230

231+
pub struct Validator<T> {
232+
items: Vec<T>,
233+
positives: Vec<T>,
234+
negatives: Vec<T>,
235+
finalized: bool,
236+
}
237+
238+
impl<T> Validator<T>
239+
where
240+
T: Eq + Clone,
241+
{
242+
fn new(items: Vec<T>) -> Self {
243+
Validator { items, positives: vec![], negatives: vec![], finalized: false }
244+
}
245+
246+
pub fn is_finalized(&self) -> bool { self.finalized }
247+
248+
pub fn get_reference(&self) -> UntaggedReference<T> { UntaggedReference(self.items[0].clone()) }
249+
250+
pub fn mark_reference(&mut self, tagged_reference: TaggedReference<T>) -> Result<bool, Error> {
251+
if self.finalized {
252+
return Err(ImplementationError::from(
253+
"Validation state machine already validated, no references left to mark",
254+
)
255+
.into());
256+
} else if self.items[0] != tagged_reference.0 {
257+
return Err(ImplementationError::from(
258+
"Incorrect reference returned for current validation state",
259+
)
260+
.into());
261+
}
262+
263+
if tagged_reference.1 {
264+
self.positives.push(self.items.remove(0));
265+
} else {
266+
self.negatives.push(self.items.remove(0));
267+
}
268+
if self.items.is_empty() {
269+
self.finalized = true
270+
}
271+
Ok(self.finalized)
272+
}
273+
274+
fn get_positives(&self) -> Vec<T> { self.positives.clone() }
275+
}
276+
277+
pub struct UntaggedReference<T>(T);
278+
279+
pub struct TaggedReference<T>(T, bool);
280+
281+
impl<T> UntaggedReference<T>
282+
where
283+
T: Clone,
284+
{
285+
pub fn value(&self) -> T { self.0.clone() }
286+
287+
pub fn mark(&self, is_owned_seen: bool) -> TaggedReference<T> {
288+
TaggedReference(self.0.clone(), is_owned_seen)
289+
}
290+
}
291+
231292
/// Validate the payload of a Payjoin request for PSBT and Params sanity
232293
pub(crate) fn parse_payload(
233294
base64: &str,
@@ -424,6 +485,41 @@ impl OriginalPayload {
424485
Ok(scripts)
425486
}
426487

488+
/// Utility function to check inputs owned using Validator
489+
fn validate_inputs_not_owned(
490+
&self,
491+
is_owned: &mut impl FnMut(&Script) -> Result<bool, ImplementationError>,
492+
) -> Result<Validator<ScriptBuf>, Error> {
493+
let input_scripts = self.extract_input_scripts()?;
494+
let mut validator = Validator::new(input_scripts);
495+
while !validator.is_finalized() {
496+
let untagged_reference = validator.get_reference();
497+
let input_script = untagged_reference.value();
498+
let input_script_owned = is_owned(&input_script)?;
499+
let tagged_reference = untagged_reference.mark(input_script_owned);
500+
validator.mark_reference(tagged_reference)?;
501+
}
502+
Ok(validator)
503+
}
504+
505+
/// Process inputs owned validator
506+
///
507+
/// An attacker can try to spend the receiver's own inputs. This check prevents that.
508+
#[warn(private_interfaces)]
509+
pub fn process_inputs_owned_validator(
510+
&self,
511+
validator: Validator<ScriptBuf>,
512+
) -> Result<(), Error> {
513+
if !validator.is_finalized() {
514+
return Err(ImplementationError::from("Validator has not finished validation").into());
515+
}
516+
let owned_inputs = validator.get_positives();
517+
if !owned_inputs.is_empty() {
518+
return Err(InternalPayloadError::InputOwned(owned_inputs[0].clone()).into());
519+
}
520+
Ok(())
521+
}
522+
427523
/// Utility function to run callback to check if inputs are owned and gather the result
428524
pub fn gather_inputs_owned_result(
429525
&self,

payjoin/src/core/receive/v1/mod.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ impl UncheckedOriginalPayload {
153153
/// to extract the signed original PSBT to schedule a fallback in case the Payjoin process fails.
154154
///
155155
/// Call [`Self::check_inputs_not_owned`] to proceed. If caller needs to use non-blocking calls
156-
/// for checking inputs are not owned call [`Self::extract_input_scripts`] to extract the scripts
157-
/// to be checked and [`Self::process_inputs_owned_result`] to return the result and proceed to the
156+
/// for checking inputs are not owned call [`Self::get_inputs_owned_validator`] to get
157+
/// a Validator instance that will provide the input scripts to be checked
158+
/// and [`Self::process_inputs_owned_validator`] to return a finalized Validator and proceed to the
158159
/// next state.
159160
#[derive(Debug, Clone)]
160161
pub struct MaybeInputsOwned {
@@ -178,25 +179,33 @@ impl MaybeInputsOwned {
178179
self,
179180
is_owned: &mut impl FnMut(&Script) -> Result<bool, ImplementationError>,
180181
) -> Result<MaybeInputsSeen, Error> {
181-
let inputs_owned_result = self.original.gather_inputs_owned_result(is_owned)?;
182-
self.process_inputs_owned_result(inputs_owned_result)
182+
let validator = self.original.validate_inputs_not_owned(is_owned)?;
183+
self.process_inputs_owned_validator(validator)
183184
}
184185

185-
/// Extracts the inputs txout script pubkeys
186+
/// Utility function for extracting the inputs txout script pubkeys
186187
///
187188
/// Use this for using non-blocking calls to check whether inputs are owned
188189
pub fn extract_input_scripts(&self) -> Result<Vec<ScriptBuf>, Error> {
189190
self.original.extract_input_scripts()
190191
}
191192

192-
/// Process result of whether the original PSBT has no receiver-owned inputs.
193+
/// Provides Validator for checking whether input is owned
193194
///
194195
/// Use this for using non-blocking calls to check whether inputs are owned
195-
pub fn process_inputs_owned_result(
196+
pub fn get_inputs_owned_validator(&self) -> Result<Validator<ScriptBuf>, Error> {
197+
let input_scripts = self.extract_input_scripts()?;
198+
Ok(Validator::new(input_scripts))
199+
}
200+
201+
/// Provides Validator for checking whether input is owned
202+
///
203+
/// Use this for using non-blocking calls to check whether inputs are owned
204+
pub fn process_inputs_owned_validator(
196205
self,
197-
inputs_owned_result: Vec<(ScriptBuf, bool)>,
206+
validator: Validator<ScriptBuf>,
198207
) -> Result<MaybeInputsSeen, Error> {
199-
self.original.process_inputs_owned_result(inputs_owned_result)?;
208+
self.original.process_inputs_owned_validator(validator)?;
200209
Ok(MaybeInputsSeen { original: self.original })
201210
}
202211
}

payjoin/src/core/receive/v2/mod.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ use crate::persist::{
5757
MaybeFatalOrSuccessTransition, MaybeFatalTransition, MaybeFatalTransitionWithNoResults,
5858
MaybeSuccessTransition, MaybeTransientTransition, NextStateTransition,
5959
};
60-
use crate::receive::{parse_payload, InputPair, OriginalPayload, PsbtContext};
60+
use crate::receive::{parse_payload, InputPair, OriginalPayload, PsbtContext, Validator};
6161
use crate::time::Time;
6262
use crate::uri::ShortId;
6363
use crate::{ImplementationError, IntoUrl, IntoUrlError, Request, Version};
@@ -665,9 +665,10 @@ pub struct MaybeInputsOwned {
665665
/// to extract the signed original PSBT to schedule a fallback in case the Payjoin process fails.
666666
///
667667
/// Call [`Receiver<MaybeInputsOwned>::check_inputs_not_owned`] to proceed. If caller needs to use
668-
/// non-blocking calls for checking inputs are not owned call [`Receiver<MaybeInputsOwned>::extract_input_scripts`]
669-
/// to extract the scripts to be checked and [`Receiver<MaybeInputsOwned>::process_inputs_owned_result`]
670-
/// to return the result and proceed to the next state.
668+
/// non-blocking calls for checking inputs are not owned call
669+
/// [`Receiver<MaybeInputsOwned>::get_inputs_owned_validator`] to get a Validator instance that
670+
/// will provide the input scripts to be checked and [`Receiver<MaybeInputsOwned>::process_inputs_owned_validator`]
671+
/// to return a finalized Validator and proceed to the next state.
671672
impl Receiver<MaybeInputsOwned> {
672673
/// Extracts the original transaction received from the sender.
673674
///
@@ -690,9 +691,9 @@ impl Receiver<MaybeInputsOwned> {
690691
Error,
691692
Receiver<HasReplyableError>,
692693
> {
693-
let inputs_owned_result = self.original.gather_inputs_owned_result(is_owned);
694-
match inputs_owned_result {
695-
Ok(inputs_owned_result) => self.process_inputs_owned_result(inputs_owned_result),
694+
let validator_result = self.original.validate_inputs_not_owned(is_owned);
695+
match validator_result {
696+
Ok(validator) => self.process_inputs_owned_validator(validator),
696697
Err(e) => match e {
697698
Error::Implementation(_) => MaybeFatalTransition::transient(e),
698699
_ => MaybeFatalTransition::replyable_error(
@@ -707,26 +708,34 @@ impl Receiver<MaybeInputsOwned> {
707708
}
708709
}
709710

710-
/// Extracts the inputs txout script pubkeys
711+
/// Utility function for extracting the inputs txout script pubkeys
711712
///
712713
/// Use this for using non-blocking calls to check whether inputs are owned
713714
pub fn extract_input_scripts(&self) -> Result<Vec<ScriptBuf>, Error> {
714715
self.original.extract_input_scripts()
715716
}
716717

717-
/// Process result of whether the original PSBT inputs are owned.
718+
/// Provides Validator for checking whether input is owned
718719
///
719720
/// Use this for using non-blocking calls to check whether inputs are owned
720-
pub fn process_inputs_owned_result(
721+
pub fn get_inputs_owned_validator(&self) -> Result<Validator<ScriptBuf>, Error> {
722+
let input_scripts = self.extract_input_scripts()?;
723+
Ok(Validator::new(input_scripts))
724+
}
725+
726+
/// Process Validator for whether the original PSBT inputs are owned.
727+
///
728+
/// Use this for using non-blocking calls to check whether inputs are owned
729+
pub fn process_inputs_owned_validator(
721730
self,
722-
inputs_owned_result: Vec<(ScriptBuf, bool)>,
731+
validator: Validator<ScriptBuf>,
723732
) -> MaybeFatalTransition<
724733
SessionEvent,
725734
Receiver<MaybeInputsSeen>,
726735
Error,
727736
Receiver<HasReplyableError>,
728737
> {
729-
match self.state.original.process_inputs_owned_result(inputs_owned_result) {
738+
match self.state.original.process_inputs_owned_validator(validator) {
730739
Ok(inner) => inner,
731740
Err(e) => match e {
732741
Error::Implementation(_) => {

0 commit comments

Comments
 (0)