Skip to content

Commit 9bda234

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 9bda234

1 file changed

Lines changed: 145 additions & 2 deletions

File tree

payjoin/src/core/psbt/mod.rs

Lines changed: 145 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 {
@@ -98,6 +98,9 @@ impl PsbtExt for Psbt {
9898
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh-nested-in-bip16-p2sh
9999
const NESTED_P2WPKH_MAX: InputWeightPrediction = InputWeightPrediction::from_slice(23, &[72, 33]);
100100

101+
const SCHNORR_SIG_DEFAULT_SIZE: usize = 64;
102+
const SCHNORR_SIG_WITH_SIGHASH_SIZE: usize = 65;
103+
101104
#[derive(Clone, Debug)]
102105
pub(crate) struct InternalInputPair<'a> {
103106
pub txin: &'a TxIn,
@@ -230,7 +233,18 @@ impl InternalInputPair<'_> {
230233
.ok_or(InputWeightError::NotSupported)?;
231234
Ok(iwp)
232235
},
233-
P2tr => Ok(InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH),
236+
Ok(InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH),
237+
SCHNORR_SIG_WITH_SIGHASH_SIZE =>
238+
Ok(InputWeightPrediction::P2TR_KEY_NON_DEFAULT_SIGHASH),
239+
_ => Err(InputWeightError::NotSupported),
240+
}
241+
} else {
242+
Err(InputWeightError::NotSupported)
243+
}
244+
} else {
245+
Err(InputWeightError::NotSupported)
246+
}
247+
}
234248
_ => Err(AddressTypeError::UnknownAddressType.into()),
235249
}?;
236250

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

0 commit comments

Comments
 (0)