Skip to content

Commit c2aaf86

Browse files
Check fees in multiparty process_proposal
The distinction beetween 1s1r async payjoin and ns1r is that the sender checks the proposal before the receiver signs in the first round of communication, so the receiver inputs won't yet be finalized. Therefore, in order to share functionality, the receiver input finalized check needs to be triggered by a condition. This does that.
1 parent edb4271 commit c2aaf86

2 files changed

Lines changed: 29 additions & 26 deletions

File tree

payjoin/src/core/send/mod.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ fn ensure<T>(condition: bool, error: T) -> Result<(), T> {
8888
impl PsbtContext {
8989
fn process_proposal(self, mut proposal: Psbt) -> InternalResult<Psbt> {
9090
self.basic_checks(&proposal)?;
91-
self.check_inputs(&proposal)?;
91+
self.check_inputs(&proposal, true)?;
9292
let contributed_fee = self.check_outputs(&proposal)?;
9393
self.restore_original_utxos(&mut proposal)?;
9494
self.check_fees(&proposal, contributed_fee)?;
@@ -161,7 +161,11 @@ impl PsbtContext {
161161
Ok(())
162162
}
163163

164-
fn check_inputs(&self, proposal: &Psbt) -> InternalResult<()> {
164+
fn check_inputs(
165+
&self,
166+
proposal: &Psbt,
167+
ensure_receiver_input_finalized: bool,
168+
) -> InternalResult<()> {
165169
let mut original_inputs = self.original_psbt.input_pairs().peekable();
166170

167171
for proposed in proposal.input_pairs() {
@@ -200,12 +204,14 @@ impl PsbtContext {
200204
.input_pairs()
201205
.next()
202206
.ok_or(InternalProposalError::NoInputs)?;
203-
// Verify the PSBT input is finalized
204-
ensure(
205-
proposed.psbtin.final_script_sig.is_some()
206-
|| proposed.psbtin.final_script_witness.is_some(),
207-
InternalProposalError::ReceiverTxinNotFinalized,
208-
)?;
207+
if ensure_receiver_input_finalized {
208+
// Verify the PSBT input is finalized
209+
ensure(
210+
proposed.psbtin.final_script_sig.is_some()
211+
|| proposed.psbtin.final_script_witness.is_some(),
212+
InternalProposalError::ReceiverTxinNotFinalized,
213+
)?;
214+
}
209215
// Verify that non_witness_utxo or witness_utxo are filled in.
210216
ensure(
211217
proposed.psbtin.witness_utxo.is_some()

payjoin/src/core/send/multiparty/mod.rs

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
66
use url::Url;
77

88
use super::v2::{self, extract_request, EncapsulationError, HpkeContext};
9-
use super::{serialize_url, AdditionalFeeContribution, BuildSenderError, InternalResult};
9+
use super::{serialize_url, AdditionalFeeContribution, BuildSenderError};
1010
use crate::hpke::decrypt_message_b;
1111
use crate::ohttp::{process_get_res, process_post_res};
1212
use crate::output_substitution::OutputSubstitution;
@@ -137,7 +137,7 @@ impl GetContext {
137137
ohttp_ctx: ohttp::ClientResponse,
138138
finalize_psbt: impl Fn(&Psbt) -> Result<Psbt, ImplementationError>,
139139
) -> Result<FinalizeContext, FinalizedError> {
140-
let psbt_ctx = PsbtContext { inner: self.0.psbt_ctx.clone() };
140+
let psbt_ctx = self.0.psbt_ctx.clone();
141141
let body = match process_get_res(response, ohttp_ctx)? {
142142
Some(body) => body,
143143
None => return Err(FinalizedError::from(InternalFinalizedError::MissingResponse)),
@@ -150,7 +150,8 @@ impl GetContext {
150150
.map_err(InternalFinalizedError::Hpke)?;
151151

152152
let proposal = Psbt::deserialize(&psbt).map_err(InternalFinalizedError::Psbt)?;
153-
let psbt = psbt_ctx.process_proposal(proposal).map_err(InternalFinalizedError::Proposal)?;
153+
let psbt =
154+
process_proposal(psbt_ctx, proposal).map_err(InternalFinalizedError::Proposal)?;
154155
let finalized_psbt = finalize_psbt(&psbt).map_err(InternalFinalizedError::FinalizePsbt)?;
155156
Ok(FinalizeContext {
156157
hpke_ctx: self.0.hpke_ctx.clone(),
@@ -207,21 +208,17 @@ impl FinalizeContext {
207208
}
208209
}
209210

210-
pub(crate) struct PsbtContext {
211-
inner: crate::send::PsbtContext,
212-
}
213-
214-
impl PsbtContext {
215-
fn process_proposal(self, mut proposal: Psbt) -> InternalResult<Psbt> {
216-
// TODO(armins) add multiparty check fees modeled after crate::send::PsbtContext::check_fees
217-
// The problem with this is that some of the inputs will be missing witness_utxo or non_witness_utxo field in the psbt so the default psbt.fee() will fail
218-
// Similarly we need to implement a check for the inputs. It would be useful to have all the checks as crate::send::PsbtContext::check_inputs
219-
// However that method expects the receiver to have provided witness for their inputs. In a ns1r the receiver will not sign any inputs of the optimistic merged psbt
220-
self.inner.basic_checks(&proposal)?;
221-
self.inner.check_outputs(&proposal)?;
222-
self.inner.restore_original_utxos(&mut proposal)?;
223-
Ok(proposal)
224-
}
211+
/// The same as `crate::send::PsbtContext::process_proposal` but without checking receiver input finalization
212+
fn process_proposal(
213+
psbt_ctx: crate::send::PsbtContext,
214+
mut proposal: Psbt,
215+
) -> crate::send::InternalResult<Psbt> {
216+
psbt_ctx.basic_checks(&proposal)?;
217+
psbt_ctx.check_inputs(&proposal, false)?;
218+
let contributed_fee = psbt_ctx.check_outputs(&proposal)?;
219+
psbt_ctx.restore_original_utxos(&mut proposal)?;
220+
psbt_ctx.check_fees(&proposal, contributed_fee)?;
221+
Ok(proposal)
225222
}
226223

227224
fn append_optimisitic_merge_query_param(url: &mut Url) {

0 commit comments

Comments
 (0)