Skip to content

Commit 37090d9

Browse files
committed
Fix weight estimation for P2TR inputs
Estimation can only occur if the P2TR input is a key spend, since we can't reliably predict the size of potential unlocking scripts
1 parent eb5ff94 commit 37090d9

File tree

1 file changed

+156
-2
lines changed

1 file changed

+156
-2
lines changed

payjoin/src/core/psbt/mod.rs

Lines changed: 156 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::fmt;
99
use bitcoin::address::FromScriptError;
1010
use bitcoin::psbt::Psbt;
1111
use bitcoin::transaction::InputWeightPrediction;
12-
use bitcoin::{bip32, psbt, Address, AddressType, Network, TxIn, TxOut, Weight};
12+
use bitcoin::{bip32, psbt, Address, AddressType, Network, TapSighashType, TxIn, TxOut, Weight};
1313

1414
#[derive(Debug, PartialEq)]
1515
pub(crate) enum InconsistentPsbt {
@@ -230,7 +230,27 @@ impl InternalInputPair<'_> {
230230
.ok_or(InputWeightError::NotSupported)?;
231231
Ok(iwp)
232232
},
233-
P2tr => Ok(InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH),
233+
P2tr => {
234+
// Check for script-path spend fields first
235+
if !self.psbtin.tap_scripts.is_empty()
236+
|| !self.psbtin.tap_script_sigs.is_empty()
237+
|| self.psbtin.tap_merkle_root.is_some()
238+
{
239+
return Err(InputWeightError::NotSupported);
240+
}
241+
242+
// An input weight can only be predicted for a taproot key spend
243+
match self.psbtin.tap_key_sig {
244+
None => Err(InputWeightError::NotSupported),
245+
Some(signature) => {
246+
// There's a chance that a custom sighash byte is appended to the schnorr sig
247+
match signature.sighash_type {
248+
TapSighashType::Default => Ok(InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH),
249+
_ => Ok(InputWeightPrediction::P2TR_KEY_NON_DEFAULT_SIGHASH),
250+
}
251+
}
252+
}
253+
}
234254
_ => Err(AddressTypeError::UnknownAddressType.into()),
235255
}?;
236256

@@ -407,3 +427,137 @@ impl std::error::Error for InputWeightError {
407427
impl From<AddressTypeError> for InputWeightError {
408428
fn from(value: AddressTypeError) -> Self { Self::AddressType(value) }
409429
}
430+
431+
#[cfg(test)]
432+
mod tests {
433+
use bitcoin::key::Secp256k1;
434+
use bitcoin::taproot::{ControlBlock, LeafVersion};
435+
use bitcoin::{psbt, secp256k1, taproot, PublicKey, ScriptBuf, TapNodeHash, XOnlyPublicKey};
436+
437+
use super::*;
438+
use crate::core::psbt::InternalInputPair;
439+
use crate::receive::InputPair;
440+
441+
/// Lengths of txid, index and sequence: (32, 4, 4)
442+
const TXID_INDEX_SEQUENCE_WEIGHT: Weight = Weight::from_non_witness_data_size(32 + 4 + 4);
443+
444+
#[test]
445+
fn expected_weight_for_p2tr() {
446+
let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b";
447+
let pubkey = pubkey_string.parse::<PublicKey>().expect("valid pubkey");
448+
let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
449+
let p2tr_utxo = TxOut {
450+
value: Default::default(),
451+
script_pubkey: ScriptBuf::new_p2tr(&Secp256k1::new(), xonly_pubkey, None),
452+
};
453+
let default_sighash_pair = InputPair {
454+
txin: Default::default(),
455+
psbtin: psbt::Input {
456+
tap_key_sig: Some(
457+
taproot::Signature::from_slice(
458+
&[0; secp256k1::constants::SCHNORR_SIGNATURE_SIZE],
459+
)
460+
.unwrap(),
461+
),
462+
witness_utxo: Some(p2tr_utxo.clone()),
463+
..Default::default()
464+
},
465+
expected_weight: Weight::from_wu(0),
466+
};
467+
assert_eq!(
468+
InternalInputPair::from(&default_sighash_pair).expected_input_weight().unwrap(),
469+
InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH.weight() + TXID_INDEX_SEQUENCE_WEIGHT
470+
);
471+
472+
// Add a sighash byte
473+
let mut sig_bytes = [0; secp256k1::constants::SCHNORR_SIGNATURE_SIZE + 1];
474+
sig_bytes[sig_bytes.len() - 1] = 1;
475+
let non_default_sighash_pair = InputPair {
476+
txin: Default::default(),
477+
psbtin: psbt::Input {
478+
tap_key_sig: Some(taproot::Signature::from_slice(&sig_bytes).unwrap()),
479+
witness_utxo: Some(p2tr_utxo),
480+
..Default::default()
481+
},
482+
expected_weight: Weight::from_wu(0),
483+
};
484+
assert_eq!(
485+
InternalInputPair::from(&non_default_sighash_pair).expected_input_weight().unwrap(),
486+
InputWeightPrediction::P2TR_KEY_NON_DEFAULT_SIGHASH.weight()
487+
+ TXID_INDEX_SEQUENCE_WEIGHT
488+
);
489+
}
490+
491+
#[test]
492+
fn not_supported_p2tr_expected_weights() {
493+
let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b";
494+
let pubkey = pubkey_string.parse::<PublicKey>().expect("valid pubkey");
495+
let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
496+
let p2tr_script = ScriptBuf::new_p2tr(&Secp256k1::new(), xonly_pubkey.clone(), None);
497+
let p2tr_utxo = TxOut { value: Default::default(), script_pubkey: p2tr_script.clone() };
498+
499+
let mut tap_scripts = BTreeMap::new();
500+
let leaf_version: u8 = 0xC0;
501+
let mut control_block_vec = Vec::with_capacity(33);
502+
control_block_vec.push(leaf_version);
503+
control_block_vec.extend_from_slice(&xonly_pubkey.serialize());
504+
let control_block = ControlBlock::decode(control_block_vec.as_slice()).unwrap();
505+
tap_scripts
506+
.insert(control_block.clone(), (p2tr_script.clone(), control_block.leaf_version));
507+
508+
let pair_with_tapscripts = InputPair {
509+
txin: Default::default(),
510+
psbtin: psbt::Input {
511+
tap_scripts,
512+
witness_utxo: Some(p2tr_utxo.clone()),
513+
..Default::default()
514+
},
515+
expected_weight: Weight::from_wu(0),
516+
};
517+
assert_eq!(
518+
InternalInputPair::from(&pair_with_tapscripts).expected_input_weight().err().unwrap(),
519+
InputWeightError::NotSupported
520+
);
521+
522+
let mut tap_script_sigs = BTreeMap::new();
523+
tap_script_sigs.insert(
524+
(xonly_pubkey.clone(), p2tr_script.tapscript_leaf_hash()),
525+
taproot::Signature::from_slice(&[0; secp256k1::constants::SCHNORR_SIGNATURE_SIZE])
526+
.unwrap(),
527+
);
528+
let pair_with_tap_script_sigs = InputPair {
529+
txin: Default::default(),
530+
psbtin: psbt::Input {
531+
tap_script_sigs,
532+
witness_utxo: Some(p2tr_utxo.clone()),
533+
..Default::default()
534+
},
535+
expected_weight: Weight::from_wu(0),
536+
};
537+
assert_eq!(
538+
InternalInputPair::from(&pair_with_tap_script_sigs)
539+
.expected_input_weight()
540+
.err()
541+
.unwrap(),
542+
InputWeightError::NotSupported
543+
);
544+
545+
let tap_merkle_root = TapNodeHash::from_script(&p2tr_script, LeafVersion::TapScript);
546+
let pair_with_tap_merkle_root = InputPair {
547+
txin: Default::default(),
548+
psbtin: psbt::Input {
549+
tap_merkle_root: Some(tap_merkle_root),
550+
witness_utxo: Some(p2tr_utxo.clone()),
551+
..Default::default()
552+
},
553+
expected_weight: Weight::from_wu(0),
554+
};
555+
assert_eq!(
556+
InternalInputPair::from(&pair_with_tap_merkle_root)
557+
.expected_input_weight()
558+
.err()
559+
.unwrap(),
560+
InputWeightError::NotSupported
561+
);
562+
}
563+
}

0 commit comments

Comments
 (0)