Skip to content

Commit c41988d

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

File tree

2 files changed

+80
-5
lines changed

2 files changed

+80
-5
lines changed

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: 73 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,49 @@ 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+
non_witness_utxo: None,
76+
witness_utxo: Some(TxOut { value: txout.value, script_pubkey: txout.script_pubkey }),
77+
partial_sigs: Default::default(),
78+
sighash_type: None,
79+
redeem_script: None,
80+
witness_script: None,
81+
bip32_derivation: Default::default(),
82+
final_script_sig: None,
83+
final_script_witness: None,
84+
ripemd160_preimages: Default::default(),
85+
sha256_preimages: Default::default(),
86+
hash160_preimages: Default::default(),
87+
hash256_preimages: Default::default(),
88+
tap_key_sig: None,
89+
tap_script_sigs: Default::default(),
90+
tap_scripts: Default::default(),
91+
tap_key_origins: Default::default(),
92+
tap_internal_key: None,
93+
tap_merkle_root: None,
94+
proprietary: Default::default(),
95+
unknown: Default::default(),
96+
};
97+
let input_pair = Self { txin, psbtin };
98+
let raw = InternalInputPair::from(&input_pair);
99+
raw.validate_utxo()?;
100+
101+
Ok(input_pair)
102+
}
103+
61104
pub(crate) fn previous_txout(&self) -> TxOut {
62105
InternalInputPair::from(self)
63106
.previous_txout()
@@ -88,3 +131,32 @@ pub(crate) fn parse_payload(
88131

89132
Ok((psbt, params))
90133
}
134+
135+
#[cfg(test)]
136+
mod tests {
137+
use bitcoin::hashes::Hash;
138+
use bitcoin::key::WPubkeyHash;
139+
use bitcoin::{ScriptBuf, ScriptHash, Txid};
140+
141+
use super::*;
142+
use crate::psbt::InternalPsbtInputError::InvalidP2wpkhScriptPubkey;
143+
144+
#[test]
145+
fn create_p2wpkh_input_pair() {
146+
let outpoint = OutPoint { txid: Txid::all_zeros(), vout: 0 };
147+
let p2wpkh_txout = TxOut {
148+
value: Default::default(),
149+
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
150+
};
151+
assert!(InputPair::new_p2wpkh(p2wpkh_txout, outpoint).is_ok());
152+
153+
let p2sh_txout = TxOut {
154+
value: Default::default(),
155+
script_pubkey: ScriptBuf::new_p2sh(&ScriptHash::all_zeros()),
156+
};
157+
assert_eq!(
158+
InputPair::new_p2wpkh(p2sh_txout, outpoint).err().unwrap(),
159+
PsbtInputError::from(InvalidP2wpkhScriptPubkey)
160+
)
161+
}
162+
}

0 commit comments

Comments
 (0)