@@ -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 ,
@@ -207,7 +210,38 @@ impl InternalInputPair<'_> {
207210 }
208211 P2wpkh => Ok ( InputWeightPrediction :: P2WPKH_MAX ) ,
209212 P2wsh => Err ( InputWeightError :: NotSupported ) ,
210- P2tr => Ok ( InputWeightPrediction :: P2TR_KEY_DEFAULT_SIGHASH ) ,
213+ P2tr => {
214+ // For unfinalized psbts, handle via tap_key_sig
215+ if let Some ( sig) = self . psbtin . tap_key_sig {
216+ if !self . psbtin . tap_scripts . is_empty ( )
217+ || !self . psbtin . tap_script_sigs . is_empty ( )
218+ || self . psbtin . tap_merkle_root . is_some ( )
219+ {
220+ return Err ( InputWeightError :: NotSupported ) ;
221+ }
222+ match sig. sighash_type {
223+ TapSighashType :: Default =>
224+ Ok ( InputWeightPrediction :: P2TR_KEY_DEFAULT_SIGHASH ) ,
225+ _ => Ok ( InputWeightPrediction :: P2TR_KEY_NON_DEFAULT_SIGHASH ) ,
226+ }
227+ }
228+ // For finalized psbts, check final_script_witness
229+ else if let Some ( witness) = & self . psbtin . final_script_witness {
230+ if witness. len ( ) == 1 {
231+ match witness[ 0 ] . len ( ) {
232+ SCHNORR_SIG_DEFAULT_SIZE =>
233+ Ok ( InputWeightPrediction :: P2TR_KEY_DEFAULT_SIGHASH ) ,
234+ SCHNORR_SIG_WITH_SIGHASH_SIZE =>
235+ Ok ( InputWeightPrediction :: P2TR_KEY_NON_DEFAULT_SIGHASH ) ,
236+ _ => Err ( InputWeightError :: NotSupported ) ,
237+ }
238+ } else {
239+ Err ( InputWeightError :: NotSupported )
240+ }
241+ } else {
242+ Err ( InputWeightError :: NotSupported )
243+ }
244+ }
211245 _ => Err ( AddressTypeError :: UnknownAddressType . into ( ) ) ,
212246 } ?;
213247
@@ -376,3 +410,132 @@ impl std::error::Error for InputWeightError {
376410impl From < AddressTypeError > for InputWeightError {
377411 fn from ( value : AddressTypeError ) -> Self { Self :: AddressType ( value) }
378412}
413+
414+ #[ cfg( test) ]
415+ mod tests {
416+ use bitcoin:: key:: Secp256k1 ;
417+ use bitcoin:: taproot:: { ControlBlock , LeafVersion } ;
418+ use bitcoin:: { psbt, secp256k1, taproot, PublicKey , ScriptBuf , TapNodeHash , XOnlyPublicKey } ;
419+
420+ use super :: * ;
421+ use crate :: core:: psbt:: InternalInputPair ;
422+ use crate :: receive:: InputPair ;
423+
424+ /// Lengths of txid, index and sequence: (32, 4, 4)
425+ const TXID_INDEX_SEQUENCE_WEIGHT : Weight = Weight :: from_non_witness_data_size ( 32 + 4 + 4 ) ;
426+
427+ #[ test]
428+ fn expected_weight_for_p2tr ( ) {
429+ let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b" ;
430+ let pubkey = pubkey_string. parse :: < PublicKey > ( ) . expect ( "valid pubkey" ) ;
431+ let xonly_pubkey = XOnlyPublicKey :: from ( pubkey. inner ) ;
432+ let p2tr_utxo = TxOut {
433+ value : Default :: default ( ) ,
434+ script_pubkey : ScriptBuf :: new_p2tr ( & Secp256k1 :: new ( ) , xonly_pubkey, None ) ,
435+ } ;
436+ let default_sighash_pair = InputPair {
437+ txin : Default :: default ( ) ,
438+ psbtin : psbt:: Input {
439+ tap_key_sig : Some (
440+ taproot:: Signature :: from_slice (
441+ & [ 0 ; secp256k1:: constants:: SCHNORR_SIGNATURE_SIZE ] ,
442+ )
443+ . unwrap ( ) ,
444+ ) ,
445+ witness_utxo : Some ( p2tr_utxo. clone ( ) ) ,
446+ ..Default :: default ( )
447+ } ,
448+ } ;
449+ assert_eq ! (
450+ InternalInputPair :: from( & default_sighash_pair) . expected_input_weight( ) . unwrap( ) ,
451+ InputWeightPrediction :: P2TR_KEY_DEFAULT_SIGHASH . weight( ) + TXID_INDEX_SEQUENCE_WEIGHT
452+ ) ;
453+
454+ // Add a sighash byte
455+ let mut sig_bytes = [ 0 ; secp256k1:: constants:: SCHNORR_SIGNATURE_SIZE + 1 ] ;
456+ sig_bytes[ sig_bytes. len ( ) - 1 ] = 1 ;
457+ let non_default_sighash_pair = InputPair {
458+ txin : Default :: default ( ) ,
459+ psbtin : psbt:: Input {
460+ tap_key_sig : Some ( taproot:: Signature :: from_slice ( & sig_bytes) . unwrap ( ) ) ,
461+ witness_utxo : Some ( p2tr_utxo) ,
462+ ..Default :: default ( )
463+ } ,
464+ } ;
465+ assert_eq ! (
466+ InternalInputPair :: from( & non_default_sighash_pair) . expected_input_weight( ) . unwrap( ) ,
467+ InputWeightPrediction :: P2TR_KEY_NON_DEFAULT_SIGHASH . weight( )
468+ + TXID_INDEX_SEQUENCE_WEIGHT
469+ ) ;
470+ }
471+
472+ #[ test]
473+ fn not_supported_p2tr_expected_weights ( ) {
474+ let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b" ;
475+ let pubkey = pubkey_string. parse :: < PublicKey > ( ) . expect ( "valid pubkey" ) ;
476+ let xonly_pubkey = XOnlyPublicKey :: from ( pubkey. inner ) ;
477+ let p2tr_script = ScriptBuf :: new_p2tr ( & Secp256k1 :: new ( ) , xonly_pubkey. clone ( ) , None ) ;
478+ let p2tr_utxo = TxOut { value : Default :: default ( ) , script_pubkey : p2tr_script. clone ( ) } ;
479+
480+ let mut tap_scripts = BTreeMap :: new ( ) ;
481+ let leaf_version: u8 = 0xC0 ;
482+ let mut control_block_vec = Vec :: with_capacity ( 33 ) ;
483+ control_block_vec. push ( leaf_version) ;
484+ control_block_vec. extend_from_slice ( & xonly_pubkey. serialize ( ) ) ;
485+ let control_block = ControlBlock :: decode ( control_block_vec. as_slice ( ) ) . unwrap ( ) ;
486+ tap_scripts
487+ . insert ( control_block. clone ( ) , ( p2tr_script. clone ( ) , control_block. leaf_version ) ) ;
488+
489+ let pair_with_tapscripts = InputPair {
490+ txin : Default :: default ( ) ,
491+ psbtin : psbt:: Input {
492+ tap_scripts,
493+ witness_utxo : Some ( p2tr_utxo. clone ( ) ) ,
494+ ..Default :: default ( )
495+ } ,
496+ } ;
497+ assert_eq ! (
498+ InternalInputPair :: from( & pair_with_tapscripts) . expected_input_weight( ) . err( ) . unwrap( ) ,
499+ InputWeightError :: NotSupported
500+ ) ;
501+
502+ let mut tap_script_sigs = BTreeMap :: new ( ) ;
503+ tap_script_sigs. insert (
504+ ( xonly_pubkey. clone ( ) , p2tr_script. tapscript_leaf_hash ( ) ) ,
505+ taproot:: Signature :: from_slice ( & [ 0 ; secp256k1:: constants:: SCHNORR_SIGNATURE_SIZE ] )
506+ . unwrap ( ) ,
507+ ) ;
508+ let pair_with_tap_script_sigs = InputPair {
509+ txin : Default :: default ( ) ,
510+ psbtin : psbt:: Input {
511+ tap_script_sigs,
512+ witness_utxo : Some ( p2tr_utxo. clone ( ) ) ,
513+ ..Default :: default ( )
514+ } ,
515+ } ;
516+ assert_eq ! (
517+ InternalInputPair :: from( & pair_with_tap_script_sigs)
518+ . expected_input_weight( )
519+ . err( )
520+ . unwrap( ) ,
521+ InputWeightError :: NotSupported
522+ ) ;
523+
524+ let tap_merkle_root = TapNodeHash :: from_script ( & p2tr_script, LeafVersion :: TapScript ) ;
525+ let pair_with_tap_merkle_root = InputPair {
526+ txin : Default :: default ( ) ,
527+ psbtin : psbt:: Input {
528+ tap_merkle_root : Some ( tap_merkle_root) ,
529+ witness_utxo : Some ( p2tr_utxo. clone ( ) ) ,
530+ ..Default :: default ( )
531+ } ,
532+ } ;
533+ assert_eq ! (
534+ InternalInputPair :: from( & pair_with_tap_merkle_root)
535+ . expected_input_weight( )
536+ . err( )
537+ . unwrap( ) ,
538+ InputWeightError :: NotSupported
539+ ) ;
540+ }
541+ }
0 commit comments