@@ -9,7 +9,7 @@ use std::fmt;
99use bitcoin:: address:: FromScriptError ;
1010use bitcoin:: psbt:: Psbt ;
1111use 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 ) ]
1515pub ( 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 {
407427impl 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