Skip to content

Commit 5fe2c43

Browse files
committed
Introduce constructor for p2wpkh input pairs
1 parent 3f0b6cc commit 5fe2c43

File tree

3 files changed

+79
-5
lines changed

3 files changed

+79
-5
lines changed

payjoin-test-utils/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,18 @@ pub static PARSED_PAYJOIN_PROPOSAL_WITH_SENDER_INFO: Lazy<Psbt> = Lazy::new(|| {
303303
pub const MULTIPARTY_ORIGINAL_PSBT_ONE: &str = "cHNidP8BAMMCAAAAA9cTcqYjSnJdteUn3Re12vFuVb5xPypYheHze74WBW8EAAAAAAD+////6JHS6JgYdav7tr+Q5bWk95C2aA4AudqZnjeRF0hx33gBAAAAAP7///+d/1mRjJ6snVe9o0EfMM8bIExdlwfaig1j/JnUupqIWwAAAAAA/v///wJO4wtUAgAAABYAFGAEc6cWf7z5a97Xxulg+S7JZyg/kvEFKgEAAAAWABQexWFsGEdXxYNTJLJ1qBCO0nXELwAAAAAAAQCaAgAAAAKa/zFrWjicRDNVCehGj/2jdXGKuZLMzLNUo+uj7PBW8wAAAAAA/v///4HkRlmU9FA3sHckkCQMjGrabNpgAw37XaqUItNFjR5rAAAAAAD+////AgDyBSoBAAAAFgAUGPI/E9xCHmaL6DIsjkaD8LX2mpLg6QUqAQAAABYAFH6MyygysrAmk/nunEFuGGDLf244aAAAAAEBHwDyBSoBAAAAFgAUGPI/E9xCHmaL6DIsjkaD8LX2mpIBCGsCRzBEAiBLJUhV7tja6FdELXDcB6Q3Gd+BMBpkjdHpj3DLnbL6AAIgJmFl3kpWBzkO8yDDl73BMMalSlAOQvfY+hqFRos5DhQBIQLfeq7CCftNEUHcRLZniJOqcgKZEqPc1A40BnEiZHj3FQABAJoCAAAAAp3/WZGMnqydV72jQR8wzxsgTF2XB9qKDWP8mdS6mohbAQAAAAD+////1xNypiNKcl215SfdF7Xa8W5VvnE/KliF4fN7vhYFbwQBAAAAAP7///8CoNkFKgEAAAAWABQchAe9uxzqT1EjLx4jgx9u1Mn6QADyBSoBAAAAFgAUye8yXWX0MvouXYhAFb06xX5kADpoAAAAAQEfAPIFKgEAAAAWABTJ7zJdZfQy+i5diEAVvTrFfmQAOgEIawJHMEQCIF7ihY/YtUPUTOaEdJbg0/HiwKunK398BI67/LknPGqMAiBHBXmL6gTP8PxEGeWswk6T0tCI2Gvwq1zh+wd7h8QCWAEhApM0w2WFlw0eg64Zp3PeyRmxl/7WGHUED8Ul/aX1FiTBAAAAAA==";
304304

305305
pub const MULTIPARTY_ORIGINAL_PSBT_TWO: &str = "cHNidP8BAMMCAAAAA9cTcqYjSnJdteUn3Re12vFuVb5xPypYheHze74WBW8EAAAAAAD+////6JHS6JgYdav7tr+Q5bWk95C2aA4AudqZnjeRF0hx33gBAAAAAP7///+d/1mRjJ6snVe9o0EfMM8bIExdlwfaig1j/JnUupqIWwAAAAAA/v///wJO4wtUAgAAABYAFGAEc6cWf7z5a97Xxulg+S7JZyg/kvEFKgEAAAAWABQexWFsGEdXxYNTJLJ1qBCO0nXELwAAAAAAAAEAmgIAAAACnf9ZkYyerJ1XvaNBHzDPGyBMXZcH2ooNY/yZ1LqaiFsBAAAAAP7////XE3KmI0pyXbXlJ90XtdrxblW+cT8qWIXh83u+FgVvBAEAAAAA/v///wKg2QUqAQAAABYAFByEB727HOpPUSMvHiODH27UyfpAAPIFKgEAAAAWABTJ7zJdZfQy+i5diEAVvTrFfmQAOmgAAAABAR8A8gUqAQAAABYAFMnvMl1l9DL6Ll2IQBW9OsV+ZAA6AQhrAkcwRAIgXuKFj9i1Q9RM5oR0luDT8eLAq6crf3wEjrv8uSc8aowCIEcFeYvqBM/w/EQZ5azCTpPS0IjYa/CrXOH7B3uHxAJYASECkzTDZYWXDR6Drhmnc97JGbGX/tYYdQQPxSX9pfUWJMEAAQCaAgAAAAIKOB8lY4eoEupDnxviz0/nAuR2biNFKbdkvckiW5ioPAAAAAAA/v///w0g/mj67592vy29xhnZMGeVtEXN1jD4lU/SMZM8oStqAAAAAAD+////AgDyBSoBAAAAFgAUC3r4YzVSpsWp3knMxbgWIx2R36/g6QUqAQAAABYAFMGlw3hwcx1b+KQGWIfOUxzwrwWkaAAAAAEBHwDyBSoBAAAAFgAUC3r4YzVSpsWp3knMxbgWIx2R368BCGsCRzBEAiA//JH+jonzbzqnKI0Uti16iJcdsXI+6Zu0IAZKlOq6AwIgP0XawCCH73uXKilFqSXQQQrBvmi/Sx44D/A+/MQ/mJYBIQIekOyEpJKpFQd7eHuY6Vt4Qlf0+00Wp529I23hl/EpcQAAAA==";
306+
307+
/// sha256d of the empty string
308+
#[rustfmt::skip]
309+
pub const DUMMY32: [u8; 32] = [
310+
0x5d, 0xf6, 0xe0, 0xe2, 0x76, 0x13, 0x59, 0xd3,
311+
0x0a, 0x82, 0x75, 0x05, 0x8e, 0x29, 0x9f, 0xcc,
312+
0x03, 0x81, 0x53, 0x45, 0x45, 0xf5, 0x5c, 0xf4,
313+
0x3e, 0x41, 0x98, 0x3f, 0x5d, 0x4c, 0x94, 0x56,
314+
];
315+
/// hash160 of the empty string
316+
#[rustfmt::skip]
317+
pub const DUMMY20: [u8; 20] = [
318+
0xb4, 0x72, 0xa2, 0x66, 0xd0, 0xbd, 0x89, 0xc1, 0x37, 0x06,
319+
0xa4, 0x13, 0x2c, 0xcf, 0xb1, 0x6f, 0x7c, 0x3b, 0x9f, 0xcb,
320+
];

payjoin/src/psbt/mod.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ impl InternalInputPair<'_> {
217217
}
218218
}
219219

220-
#[derive(Debug, PartialEq)]
220+
#[derive(Debug, PartialEq, Eq)]
221221
pub(crate) enum PrevTxOutError {
222222
MissingUtxoInformation,
223223
IndexOutOfBounds { output_count: usize, index: u32 },
@@ -236,14 +236,15 @@ impl fmt::Display for PrevTxOutError {
236236

237237
impl std::error::Error for PrevTxOutError {}
238238

239-
#[derive(Debug, PartialEq)]
239+
#[derive(Debug, PartialEq, Eq)]
240240
pub(crate) enum InternalPsbtInputError {
241241
PrevTxOut(PrevTxOutError),
242242
UnequalTxid,
243243
/// TxOut provided in `segwit_utxo` doesn't match the one in `non_segwit_utxo`
244244
SegWitTxOutMismatch,
245245
AddressType(AddressTypeError),
246246
NoRedeemScript,
247+
InvalidP2wpkhScriptPubkey,
247248
}
248249

249250
impl fmt::Display for InternalPsbtInputError {
@@ -254,6 +255,7 @@ impl fmt::Display for InternalPsbtInputError {
254255
Self::SegWitTxOutMismatch => write!(f, "transaction output provided in SegWit UTXO field doesn't match the one in non-SegWit UTXO field"),
255256
Self::AddressType(_) => write!(f, "invalid address type"),
256257
Self::NoRedeemScript => write!(f, "provided p2sh PSBT input is missing a redeem_script"),
258+
Self::InvalidP2wpkhScriptPubkey => write!(f, "provided script pubkey is invalid for P2WPKH")
257259
}
258260
}
259261
}
@@ -266,6 +268,7 @@ impl std::error::Error for InternalPsbtInputError {
266268
Self::SegWitTxOutMismatch => None,
267269
Self::AddressType(error) => Some(error),
268270
Self::NoRedeemScript => None,
271+
Self::InvalidP2wpkhScriptPubkey => None,
269272
}
270273
}
271274
}
@@ -278,7 +281,7 @@ impl From<AddressTypeError> for InternalPsbtInputError {
278281
fn from(value: AddressTypeError) -> Self { Self::AddressType(value) }
279282
}
280283

281-
#[derive(Debug)]
284+
#[derive(Debug, PartialEq, Eq)]
282285
pub struct PsbtInputError(InternalPsbtInputError);
283286

284287
impl From<InternalPsbtInputError> for PsbtInputError {
@@ -309,7 +312,7 @@ impl std::error::Error for PsbtInputsError {
309312
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) }
310313
}
311314

312-
#[derive(Debug, PartialEq)]
315+
#[derive(Debug, PartialEq, Eq)]
313316
pub(crate) enum AddressTypeError {
314317
PrevTxOut(PrevTxOutError),
315318
InvalidScript(FromScriptError),

payjoin/src/receive/mod.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
1212
use std::str::FromStr;
1313

14-
use bitcoin::{psbt, AddressType, Psbt, TxIn, TxOut};
14+
use bitcoin::{psbt, AddressType, OutPoint, Psbt, TxIn, TxOut};
1515
pub(crate) use error::InternalPayloadError;
1616
pub use error::{
1717
Error, InputContributionError, JsonReply, OutputSubstitutionError, PayloadError,
@@ -58,6 +58,30 @@ impl InputPair {
5858
Ok(input_pair)
5959
}
6060

61+
/// Constructs a new ['InputPair'] for spending a native SegWit P2WPKH output
62+
pub fn new_p2wpkh(txout: TxOut, outpoint: OutPoint) -> Result<Self, PsbtInputError> {
63+
if !txout.script_pubkey.is_p2wpkh() {
64+
return Err(InternalPsbtInputError::InvalidP2wpkhScriptPubkey.into());
65+
}
66+
67+
let txin = TxIn {
68+
previous_output: OutPoint { txid: outpoint.txid, vout: outpoint.vout },
69+
script_sig: Default::default(),
70+
sequence: Default::default(),
71+
witness: Default::default(),
72+
};
73+
74+
let psbtin = psbt::Input {
75+
witness_utxo: Some(TxOut { value: txout.value, script_pubkey: txout.script_pubkey }),
76+
..psbt::Input::default()
77+
};
78+
let input_pair = Self { txin, psbtin };
79+
let raw = InternalInputPair::from(&input_pair);
80+
raw.validate_utxo()?;
81+
82+
Ok(input_pair)
83+
}
84+
6185
pub(crate) fn previous_txout(&self) -> TxOut {
6286
InternalInputPair::from(self)
6387
.previous_txout()
@@ -88,3 +112,35 @@ pub(crate) fn parse_payload(
88112

89113
Ok((psbt, params))
90114
}
115+
116+
#[cfg(test)]
117+
mod tests {
118+
use bitcoin::hashes::Hash;
119+
use bitcoin::key::WPubkeyHash;
120+
use bitcoin::{Amount, ScriptBuf, ScriptHash, Txid};
121+
use payjoin_test_utils::{DUMMY20, DUMMY32};
122+
123+
use super::*;
124+
use crate::psbt::InternalPsbtInputError::InvalidP2wpkhScriptPubkey;
125+
126+
#[test]
127+
fn create_p2wpkh_input_pair() {
128+
let outpoint = OutPoint { txid: Txid::from_byte_array(DUMMY32), vout: 31 };
129+
let p2wpkh_txout = TxOut {
130+
value: Amount::from_sat(12345),
131+
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_byte_array(DUMMY20)),
132+
};
133+
let p2wpkh_pair = InputPair::new_p2wpkh(p2wpkh_txout.clone(), outpoint).unwrap();
134+
assert_eq!(p2wpkh_pair.txin.previous_output, outpoint);
135+
assert_eq!(p2wpkh_pair.psbtin.witness_utxo.unwrap(), p2wpkh_txout);
136+
137+
let p2sh_txout = TxOut {
138+
value: Default::default(),
139+
script_pubkey: ScriptBuf::new_p2sh(&ScriptHash::all_zeros()),
140+
};
141+
assert_eq!(
142+
InputPair::new_p2wpkh(p2sh_txout, outpoint).err().unwrap(),
143+
PsbtInputError::from(InvalidP2wpkhScriptPubkey)
144+
)
145+
}
146+
}

0 commit comments

Comments
 (0)