Skip to content

Commit d8ecb21

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 703cf2d commit d8ecb21

File tree

1 file changed

+44
-3
lines changed

1 file changed

+44
-3
lines changed

src/bitcoin/payment.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
use anyhow::{anyhow, Result};
2-
use bdk::{wallet::tx_builder::TxOrdering, FeeRate, TransactionDetails};
3-
use bitcoin::consensus::serialize;
2+
3+
use bdk::{
4+
database::AnyDatabase, wallet::tx_builder::TxOrdering, FeeRate, TransactionDetails, Wallet,
5+
};
6+
7+
use bitcoin::consensus::{
8+
consensus::serialize,
9+
psbt::{Input, Psbt},
10+
TxIn,
11+
};
12+
413
use payjoin::{PjUri, PjUriExt};
514

615
use crate::{
@@ -59,7 +68,7 @@ pub async fn create_payjoin(
5968

6069
// TODO use fee_rate
6170
let pj_params = payjoin::sender::Configuration::non_incentivizing();
62-
let (req, ctx) = pj_uri.create_pj_request(original_psbt, pj_params)?;
71+
let (req, ctx) = pj_uri.create_pj_request(original_psbt.clone(), pj_params)?;
6372
info!("Built PayJoin request");
6473
let response = reqwest::Client::new()
6574
.post(req.url)
@@ -77,6 +86,7 @@ pub async fn create_payjoin(
7786
}
7887

7988
let payjoin_psbt = ctx.process_response(res.as_bytes())?;
89+
let payjoin_psbt = add_back_original_input(&original_psbt, payjoin_psbt);
8090

8191
debug!(
8292
"Proposed PayJoin PSBT:",
@@ -87,3 +97,34 @@ pub async fn create_payjoin(
8797

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

0 commit comments

Comments
 (0)