@@ -103,6 +103,9 @@ class PartiallySignedInput:
103103 PSBT_IN_TAP_BIP32_DERIVATION = 0x16
104104 PSBT_IN_TAP_INTERNAL_KEY = 0x17
105105 PSBT_IN_TAP_MERKLE_ROOT = 0x18
106+ PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a
107+ PSBT_IN_MUSIG2_PUB_NONCE = 0x1b
108+ PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c
106109
107110 def __init__ (self , version : int ) -> None :
108111 self .non_witness_utxo : Optional [CTransaction ] = None
@@ -125,6 +128,9 @@ def __init__(self, version: int) -> None:
125128 self .tap_bip32_paths : Dict [bytes , Tuple [Set [bytes ], KeyOriginInfo ]] = {}
126129 self .tap_internal_key = b""
127130 self .tap_merkle_root = b""
131+ self .musig2_participant_pubkeys : Dict [bytes , List [bytes ]] = {}
132+ self .musig2_pub_nonces : Dict [Tuple [bytes , bytes , Optional [bytes ]], bytes ] = {}
133+ self .musig2_partial_sigs : Dict [Tuple [bytes , bytes , Optional [bytes ]], bytes ] = {}
128134 self .unknown : Dict [bytes , bytes ] = {}
129135
130136 self .version : int = version
@@ -153,6 +159,9 @@ def set_null(self) -> None:
153159 self .sequence = None
154160 self .time_locktime = None
155161 self .height_locktime = None
162+ self .musig2_participant_pubkeys .clear ()
163+ self .musig2_pub_nonces .clear ()
164+ self .musig2_partial_sigs .clear ()
156165 self .unknown .clear ()
157166
158167 def deserialize (self , f : Readable ) -> None :
@@ -351,6 +360,51 @@ def deserialize(self, f: Readable) -> None:
351360 self .tap_merkle_root = deser_string (f )
352361 if len (self .tap_merkle_root ) != 32 :
353362 raise PSBTSerializationError ("Input Taproot merkle root is not 32 bytes" )
363+ elif key_type == PartiallySignedInput .PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS :
364+ if key in key_lookup :
365+ raise PSBTSerializationError ("Duplicate key, input Musig2 participant pubkeys already provided" )
366+ elif len (key ) != 1 + 33 :
367+ raise PSBTSerializationError ("Input Musig2 aggregate compressed pubkey is not 33 bytes" )
368+
369+ pubkeys_cat = deser_string (f )
370+ if len (pubkeys_cat ) == 0 :
371+ raise PSBTSerializationError ("The list of compressed pubkeys for Musig2 cannot be empty" )
372+ if (len (pubkeys_cat ) % 33 ) != 0 :
373+ raise PSBTSerializationError ("The compressed pubkeys for Musig2 must be exactly 33 bytes long" )
374+ pubkeys = []
375+ for i in range (0 , len (pubkeys_cat ), 33 ):
376+ pubkeys .append (pubkeys_cat [i : i + 33 ])
377+
378+ self .musig2_participant_pubkeys [key [1 :]] = pubkeys
379+ elif key_type == PartiallySignedInput .PSBT_IN_MUSIG2_PUB_NONCE :
380+ if key in key_lookup :
381+ raise PSBTSerializationError ("Duplicate key, Musig2 public nonce already provided" )
382+ elif len (key ) not in [1 + 33 + 33 , 1 + 33 + 33 + 32 ]:
383+ raise PSBTSerializationError ("Invalid key length for Musig2 public nonce" )
384+
385+ providing_pubkey = key [1 :1 + 33 ]
386+ aggregate_pubkey = key [1 + 33 :1 + 33 + 33 ]
387+ tapleaf_hash = None if len (key ) == 1 + 33 + 33 else key [1 + 33 + 33 :]
388+
389+ public_nonces = deser_string (f )
390+ if len (public_nonces ) != 66 :
391+ raise PSBTSerializationError ("The length of the public nonces in Musig2 must be exactly 66 bytes" )
392+
393+ self .musig2_pub_nonces [(providing_pubkey , aggregate_pubkey , tapleaf_hash )] = public_nonces
394+ elif key_type == PartiallySignedInput .PSBT_IN_MUSIG2_PARTIAL_SIG :
395+ if key in key_lookup :
396+ raise PSBTSerializationError ("Duplicate key, Musig2 partial signature already provided" )
397+ elif len (key ) not in [1 + 33 + 33 , 1 + 33 + 33 + 32 ]:
398+ raise PSBTSerializationError ("Invalid key length for Musig2 partial signature" )
399+
400+ providing_pubkey = key [1 :1 + 33 ]
401+ aggregate_pubkey = key [1 + 33 :1 + 33 + 33 ]
402+ tapleaf_hash = None if len (key ) == 1 + 33 + 33 else key [1 + 33 + 33 :]
403+
404+ partial_sig = deser_string (f )
405+ if len (partial_sig ) != 32 :
406+ raise PSBTSerializationError ("The length of the partial signature in Musig2 must be exactly 32 bytes" )
407+ self .musig2_partial_sigs [(providing_pubkey , aggregate_pubkey , tapleaf_hash )] = partial_sig
354408 else :
355409 if key in self .unknown :
356410 raise PSBTSerializationError ("Duplicate key, key for unknown value already provided" )
@@ -441,6 +495,20 @@ def serialize(self) -> bytes:
441495 witstack = self .final_script_witness .serialize ()
442496 r += ser_string (witstack )
443497
498+ for pk , pubkeys in self .musig2_participant_pubkeys .items ():
499+ r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS ) + pk )
500+ r += ser_string (b'' .join (pubkeys ))
501+
502+ for (pk , aggpk , hash ), pubnonce in self .musig2_pub_nonces .items ():
503+ key_value = pk + aggpk + (hash or b'' )
504+ r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_MUSIG2_PUB_NONCE ) + key_value )
505+ r += ser_string (pubnonce )
506+
507+ for (pk , aggpk , hash ), partial_sig in self .musig2_partial_sigs .items ():
508+ key_value = pk + aggpk + (hash or b'' )
509+ r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_MUSIG2_PARTIAL_SIG ) + key_value )
510+ r += ser_string (partial_sig )
511+
444512 if self .version >= 2 :
445513 if len (self .prev_txid ) != 0 :
446514 r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_PREVIOUS_TXID ))
@@ -483,6 +551,7 @@ class PartiallySignedOutput:
483551 PSBT_OUT_TAP_INTERNAL_KEY = 0x05
484552 PSBT_OUT_TAP_TREE = 0x06
485553 PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
554+ PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08
486555
487556 def __init__ (self , version : int ) -> None :
488557 self .redeem_script = b""
@@ -493,6 +562,7 @@ def __init__(self, version: int) -> None:
493562 self .tap_internal_key = b""
494563 self .tap_tree = b""
495564 self .tap_bip32_paths : Dict [bytes , Tuple [Set [bytes ], KeyOriginInfo ]] = {}
565+ self .musig2_participant_pubkeys : Dict [bytes , List [bytes ]] = {}
496566 self .unknown : Dict [bytes , bytes ] = {}
497567
498568 self .version : int = version
@@ -509,6 +579,7 @@ def set_null(self) -> None:
509579 self .tap_bip32_paths .clear ()
510580 self .amount = None
511581 self .script = b""
582+ self .musig2_participant_pubkeys = {}
512583 self .unknown .clear ()
513584
514585 def deserialize (self , f : Readable ) -> None :
@@ -589,6 +660,22 @@ def deserialize(self, f: Readable) -> None:
589660 for i in range (0 , num_hashes ):
590661 leaf_hashes .add (vs .read (32 ))
591662 self .tap_bip32_paths [xonly ] = (leaf_hashes , KeyOriginInfo .deserialize (vs .read ()))
663+ elif key_type == PartiallySignedOutput .PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS :
664+ if key in key_lookup :
665+ raise PSBTSerializationError ("Duplicate key, output Musig2 participant pubkeys already provided" )
666+ elif len (key ) != 1 + 33 :
667+ raise PSBTSerializationError ("Output Musig2 aggregate compressed pubkey is not 33 bytes" )
668+
669+ pubkeys_cat = deser_string (f )
670+ if len (pubkeys_cat ) == 0 :
671+ raise PSBTSerializationError ("The list of compressed pubkeys for Musig2 cannot be empty" )
672+ if (len (pubkeys_cat ) % 33 ) != 0 :
673+ raise PSBTSerializationError ("The compressed pubkeys for Musig2 must be exactly 33 bytes long" )
674+ pubkeys = []
675+ for i in range (0 , len (pubkeys_cat ), 33 ):
676+ pubkeys .append (pubkeys_cat [i : i + 33 ])
677+
678+ self .musig2_participant_pubkeys [key [1 :]] = pubkeys
592679 else :
593680 if key in self .unknown :
594681 raise PSBTSerializationError ("Duplicate key, key for unknown value already provided" )
@@ -646,6 +733,11 @@ def serialize(self) -> bytes:
646733 value += origin .serialize ()
647734 r += ser_string (value )
648735
736+ for pk , pubkeys in self .musig2_participant_pubkeys .items ():
737+ r += ser_string (ser_compact_size (
738+ PartiallySignedOutput .PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS ) + pk )
739+ r += ser_string (b'' .join (pubkeys ))
740+
649741 for key , value in sorted (self .unknown .items ()):
650742 r += ser_string (key )
651743 r += ser_string (value )
0 commit comments