Skip to content

Commit 4e35ac0

Browse files
authored
Copy fields instead of clearing in prepare_psbt (payjoin#700)
Fix payjoin#693
2 parents beaff5e + b99aaef commit 4e35ac0

File tree

1 file changed

+97
-23
lines changed

1 file changed

+97
-23
lines changed

payjoin/src/receive/v1/mod.rs

Lines changed: 97 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
//! [reference implementation](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-cli)
2626
2727
use std::cmp::{max, min};
28+
use std::collections::BTreeMap;
2829

2930
use bitcoin::psbt::Psbt;
3031
use bitcoin::secp256k1::rand::seq::SliceRandom;
@@ -680,31 +681,42 @@ impl ProvisionalProposal {
680681
output_contribution_weight
681682
}
682683

683-
/// Prepare the PSBT by clearing the fields that the sender expects to be empty
684-
fn prepare_psbt(mut self, processed_psbt: Psbt) -> PayjoinProposal {
685-
self.payjoin_psbt = processed_psbt;
686-
log::trace!("Preparing PSBT {:#?}", self.payjoin_psbt);
687-
for output in self.payjoin_psbt.outputs_mut() {
688-
output.bip32_derivation.clear();
689-
output.tap_key_origins.clear();
690-
output.tap_internal_key = None;
691-
}
692-
for input in self.payjoin_psbt.inputs_mut() {
693-
input.bip32_derivation.clear();
694-
input.tap_key_origins.clear();
695-
input.tap_internal_key = None;
696-
input.partial_sigs.clear();
684+
/// Prepare the PSBT by creating a new PSBT and copying only the fields allowed by the [spec](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#senders-payjoin-proposal-checklist)
685+
fn prepare_psbt(self, processed_psbt: Psbt) -> PayjoinProposal {
686+
log::trace!("Original PSBT from callback: {processed_psbt:#?}");
687+
688+
// Create a new PSBT and copy only the allowed fields
689+
let mut filtered_psbt = Psbt {
690+
unsigned_tx: processed_psbt.unsigned_tx,
691+
version: processed_psbt.version,
692+
xpub: BTreeMap::new(),
693+
proprietary: BTreeMap::new(),
694+
unknown: BTreeMap::new(),
695+
inputs: vec![],
696+
outputs: vec![],
697+
};
698+
699+
for input in &processed_psbt.inputs {
700+
filtered_psbt.inputs.push(bitcoin::psbt::Input {
701+
witness_utxo: input.witness_utxo.clone(),
702+
non_witness_utxo: input.non_witness_utxo.clone(),
703+
sighash_type: input.sighash_type,
704+
final_script_sig: input.final_script_sig.clone(),
705+
final_script_witness: input.final_script_witness.clone(),
706+
tap_key_sig: input.tap_key_sig,
707+
tap_script_sigs: input.tap_script_sigs.clone(),
708+
tap_merkle_root: input.tap_merkle_root,
709+
..Default::default()
710+
});
697711
}
698-
for i in self.sender_input_indexes() {
699-
log::trace!("Clearing sender input {i}");
700-
self.payjoin_psbt.inputs[i].non_witness_utxo = None;
701-
self.payjoin_psbt.inputs[i].witness_utxo = None;
702-
self.payjoin_psbt.inputs[i].final_script_sig = None;
703-
self.payjoin_psbt.inputs[i].final_script_witness = None;
704-
self.payjoin_psbt.inputs[i].tap_key_sig = None;
712+
713+
for _ in &processed_psbt.outputs {
714+
filtered_psbt.outputs.push(bitcoin::psbt::Output::default());
705715
}
706716

707-
PayjoinProposal { payjoin_psbt: self.payjoin_psbt }
717+
log::trace!("Filtered PSBT: {filtered_psbt:#?}");
718+
719+
PayjoinProposal { payjoin_psbt: filtered_psbt }
708720
}
709721

710722
/// Return the indexes of the sender inputs
@@ -775,7 +787,10 @@ impl PayjoinProposal {
775787
pub(crate) mod test {
776788
use std::str::FromStr;
777789

778-
use bitcoin::{Address, Network};
790+
use bitcoin::bip32::{DerivationPath, Fingerprint, Xpriv, Xpub};
791+
use bitcoin::secp256k1::Secp256k1;
792+
use bitcoin::taproot::LeafVersion;
793+
use bitcoin::{Address, Network, ScriptBuf, TapLeafHash};
779794
use payjoin_test_utils::{PARSED_ORIGINAL_PSBT, QUERY_PARAMS, RECEIVER_INPUT_CONTRIBUTION};
780795
use rand::rngs::StdRng;
781796
use rand::SeedableRng;
@@ -1070,4 +1085,63 @@ pub(crate) mod test {
10701085
assert_eq!(original2, vec![1, 5, 4, 2, 6, 3]);
10711086
assert_eq!(original3, vec![4, 5, 1, 2, 6, 3]);
10721087
}
1088+
1089+
/// Add keypath data to psbt to be prepared and verify it is excluded from the final PSBT
1090+
/// See: <https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#senders-payjoin-proposal-checklist>
1091+
#[test]
1092+
fn test_prepare_psbt_excludes_keypaths() {
1093+
let proposal = unchecked_proposal_from_test_vector();
1094+
let mut processed_psbt = proposal.psbt.clone();
1095+
1096+
let secp = Secp256k1::new();
1097+
let (_, pk) = secp.generate_keypair(&mut bitcoin::key::rand::thread_rng());
1098+
let xpriv = Xpriv::new_master(Network::Bitcoin, &[]).expect("Could not generate new xpriv");
1099+
let (x_only, _) = pk.x_only_public_key();
1100+
1101+
processed_psbt.xpub.insert(
1102+
Xpub::from_priv(&secp, &xpriv),
1103+
(Fingerprint::default(), DerivationPath::default()),
1104+
);
1105+
1106+
for input in &mut processed_psbt.inputs {
1107+
input.bip32_derivation.insert(pk, (Fingerprint::default(), DerivationPath::default()));
1108+
input.tap_key_origins.insert(
1109+
x_only,
1110+
(
1111+
vec![TapLeafHash::from_script(&ScriptBuf::new(), LeafVersion::TapScript)],
1112+
(Fingerprint::default(), DerivationPath::default()),
1113+
),
1114+
);
1115+
input.tap_internal_key = Some(x_only);
1116+
}
1117+
1118+
for output in &mut processed_psbt.outputs {
1119+
output.bip32_derivation.insert(pk, (Fingerprint::default(), DerivationPath::default()));
1120+
output.tap_key_origins.insert(
1121+
x_only,
1122+
(
1123+
vec![TapLeafHash::from_script(&ScriptBuf::new(), LeafVersion::TapScript)],
1124+
(Fingerprint::default(), DerivationPath::default()),
1125+
),
1126+
);
1127+
output.tap_internal_key = Some(x_only);
1128+
}
1129+
1130+
let provisional = provisional_proposal_from_test_vector(proposal);
1131+
let payjoin_proposal = provisional.prepare_psbt(processed_psbt);
1132+
1133+
assert!(payjoin_proposal.payjoin_psbt.xpub.is_empty());
1134+
1135+
for input in &payjoin_proposal.payjoin_psbt.inputs {
1136+
assert!(input.bip32_derivation.is_empty());
1137+
assert!(input.tap_key_origins.is_empty());
1138+
assert!(input.tap_internal_key.is_none());
1139+
}
1140+
1141+
for output in &payjoin_proposal.payjoin_psbt.outputs {
1142+
assert!(output.bip32_derivation.is_empty());
1143+
assert!(output.tap_key_origins.is_empty());
1144+
assert!(output.tap_internal_key.is_none());
1145+
}
1146+
}
10731147
}

0 commit comments

Comments
 (0)