Skip to content

Commit bcc273c

Browse files
committed
Add back original PSBT input to payjoin proposal
Unlike Bitcoin Core's walletprocesspsbt RPC, BKD's finalize_psbt only checks if the script in the PSBT input map matches the descriptor and does not check whether it has control of the OutPoint specified in the unsigned_tx's TxIn. So the original_psbt input data needs to be added back into payjoin_psbt without overwriting receiver input. BIP 78 spec clears script data from payjoin proposal.
1 parent 20b743e commit bcc273c

1 file changed

Lines changed: 38 additions & 2 deletions

File tree

src/bitcoin/payment.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ use anyhow::{anyhow, Result};
22
use bdk::{
33
database::AnyDatabase, wallet::tx_builder::TxOrdering, FeeRate, TransactionDetails, Wallet,
44
};
5-
use bitcoin::consensus::serialize;
5+
use bitcoin::{
6+
consensus::serialize,
7+
psbt::{Input, Psbt},
8+
TxIn,
9+
};
610
use payjoin::{PjUri, PjUriExt};
711

812
use crate::{
@@ -60,7 +64,7 @@ pub async fn create_payjoin(
6064

6165
// TODO use fee_rate
6266
let pj_params = payjoin::sender::Configuration::non_incentivizing();
63-
let (req, ctx) = pj_uri.create_pj_request(original_psbt, pj_params)?;
67+
let (req, ctx) = pj_uri.create_pj_request(original_psbt.clone(), pj_params)?;
6468
info!("Built PayJoin request");
6569
let response = reqwest::Client::new()
6670
.post(req.url)
@@ -78,6 +82,7 @@ pub async fn create_payjoin(
7882
}
7983

8084
let payjoin_psbt = ctx.process_response(res.as_bytes())?;
85+
let payjoin_psbt = add_back_original_input(&original_psbt, payjoin_psbt);
8186

8287
debug!(
8388
"Proposed PayJoin PSBT:",
@@ -88,3 +93,34 @@ pub async fn create_payjoin(
8893

8994
Ok(tx)
9095
}
96+
97+
/// Unlike Bitcoin Core's walletprocesspsbt RPC, BDK's finalize_psbt only checks
98+
/// if the script in the PSBT input map matches the descriptor and does not
99+
/// check whether it has control of the OutPoint specified in the unsigned_tx's
100+
/// TxIn. So the original_psbt input data needs to be added back into
101+
/// payjoin_psbt without overwriting receiver input.
102+
fn add_back_original_input(original_psbt: &Psbt, payjoin_psbt: Psbt) -> Psbt {
103+
// input_pairs is only used here. It may be added to payjoin, rust-bitcoin, or BDK in time.
104+
fn input_pairs(psbt: &Psbt) -> Box<dyn Iterator<Item = (TxIn, Input)> + '_> {
105+
Box::new(
106+
psbt.unsigned_tx
107+
.input
108+
.iter()
109+
.cloned() // Clone each TxIn for better ergonomics than &muts
110+
.zip(psbt.inputs.iter().cloned()), // Clone each Input too
111+
)
112+
}
113+
114+
let mut original_inputs = input_pairs(&original_psbt).peekable();
115+
116+
for (proposed_txin, mut proposed_psbtin) in input_pairs(&payjoin_psbt) {
117+
if let Some((original_txin, original_psbtin)) = original_inputs.peek() {
118+
if proposed_txin.previous_output == original_txin.previous_output {
119+
proposed_psbtin.witness_utxo = original_psbtin.witness_utxo.clone();
120+
proposed_psbtin.non_witness_utxo = original_psbtin.non_witness_utxo.clone();
121+
}
122+
original_inputs.next();
123+
}
124+
}
125+
payjoin_psbt
126+
}

0 commit comments

Comments
 (0)