@@ -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 {
@@ -98,6 +98,9 @@ impl PsbtExt for Psbt {
9898// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh-nested-in-bip16-p2sh
9999const 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 ) ]
102105pub ( 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 {
407421impl 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