@@ -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 :
@@ -335,22 +344,51 @@ def deserialize(self, f: Readable) -> None:
335344 for i in range (0 , num_hashes ):
336345 leaf_hashes .add (vs .read (32 ))
337346 self .tap_bip32_paths [xonly ] = (leaf_hashes , KeyOriginInfo .deserialize (vs .read ()))
338- elif key_type == PartiallySignedInput .PSBT_IN_TAP_INTERNAL_KEY :
347+ elif key_type == PartiallySignedInput .PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS :
339348 if key in key_lookup :
340- raise PSBTSerializationError ("Duplicate key, input Taproot internal key already provided" )
341- elif len (key ) != 1 :
342- raise PSBTSerializationError ("Input Taproot internal key key is more than one byte type" )
343- self .tap_internal_key = deser_string (f )
344- if len (self .tap_internal_key ) != 32 :
345- raise PSBTSerializationError ("Input Taproot internal key is not 32 bytes" )
346- elif key_type == PartiallySignedInput .PSBT_IN_TAP_MERKLE_ROOT :
349+ raise PSBTSerializationError ("Duplicate key, input Musig2 participant pubkeys already provided" )
350+ elif len (key ) != 1 + 33 :
351+ raise PSBTSerializationError ("Input Musig2 aggregate compressed pubkey is not 33 bytes" )
352+
353+ pubkeys_cat = deser_string (f )
354+ if len (pubkeys_cat ) == 0 :
355+ raise PSBTSerializationError ("The list of compressed pubkeys for Musig2 cannot be empty" )
356+ if (len (pubkeys_cat ) % 33 ) != 0 :
357+ raise PSBTSerializationError ("The compressed pubkeys for Musig2 must be exactly 33 bytes long" )
358+ pubkeys = []
359+ for i in range (0 , len (pubkeys_cat ), 33 ):
360+ pubkeys .append (pubkeys_cat [i : i + 33 ])
361+
362+ self .musig2_participant_pubkeys [key [1 :]] = pubkeys
363+ elif key_type == PartiallySignedInput .PSBT_IN_MUSIG2_PUB_NONCE :
347364 if key in key_lookup :
348- raise PSBTSerializationError ("Duplicate key, input Taproot merkle root already provided" )
349- elif len (key ) != 1 :
350- raise PSBTSerializationError ("Input Taproot merkle root key is more than one byte type" )
351- self .tap_merkle_root = deser_string (f )
352- if len (self .tap_merkle_root ) != 32 :
353- raise PSBTSerializationError ("Input Taproot merkle root is not 32 bytes" )
365+ raise PSBTSerializationError ("Duplicate key, Musig2 public nonce already provided" )
366+ elif len (key ) not in [1 + 33 + 33 , 1 + 33 + 33 + 32 ]:
367+ raise PSBTSerializationError ("Invalid key length for Musig2 public nonce" )
368+
369+ providing_pubkey = key [1 :1 + 33 ]
370+ aggregate_pubkey = key [1 + 33 :1 + 33 + 33 ]
371+ tapleaf_hash = None if len (key ) == 1 + 33 + 33 else key [1 + 33 + 33 :]
372+
373+ public_nonces = deser_string (f )
374+ if len (public_nonces ) != 66 :
375+ raise PSBTSerializationError ("The length of the public nonces in Musig2 must be exactly 66 bytes" )
376+
377+ self .musig2_pub_nonces [(providing_pubkey , aggregate_pubkey , tapleaf_hash )] = public_nonces
378+ elif key_type == PartiallySignedInput .PSBT_IN_MUSIG2_PARTIAL_SIG :
379+ if key in key_lookup :
380+ raise PSBTSerializationError ("Duplicate key, Musig2 partial signature already provided" )
381+ elif len (key ) not in [1 + 33 + 33 , 1 + 33 + 33 + 32 ]:
382+ raise PSBTSerializationError ("Invalid key length for Musig2 partial signature" )
383+
384+ providing_pubkey = key [1 :1 + 33 ]
385+ aggregate_pubkey = key [1 + 33 :1 + 33 + 33 ]
386+ tapleaf_hash = None if len (key ) == 1 + 33 + 33 else key [1 + 33 + 33 :]
387+
388+ partial_sig = deser_string (f )
389+ if len (partial_sig ) != 32 :
390+ raise PSBTSerializationError ("The length of the partial signature in Musig2 must be exactly 32 bytes" )
391+ self .musig2_partial_sigs [(providing_pubkey , aggregate_pubkey , tapleaf_hash )] = partial_sig
354392 else :
355393 if key in self .unknown :
356394 raise PSBTSerializationError ("Duplicate key, key for unknown value already provided" )
@@ -441,6 +479,20 @@ def serialize(self) -> bytes:
441479 witstack = self .final_script_witness .serialize ()
442480 r += ser_string (witstack )
443481
482+ for pk , pubkeys in self .musig2_participant_pubkeys .items ():
483+ r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS ) + pk )
484+ r += ser_string (b'' .join (pubkeys ))
485+
486+ for (pk , aggpk , hash ), pubnonce in self .musig2_pub_nonces .items ():
487+ key_value = pk + aggpk + (hash or b'' )
488+ r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_MUSIG2_PUB_NONCE ) + key_value )
489+ r += ser_string (pubnonce )
490+
491+ for (pk , aggpk , hash ), partial_sig in self .musig2_partial_sigs .items ():
492+ key_value = pk + aggpk + (hash or b'' )
493+ r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_MUSIG2_PARTIAL_SIG ) + key_value )
494+ r += ser_string (partial_sig )
495+
444496 if self .version >= 2 :
445497 if len (self .prev_txid ) != 0 :
446498 r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_PREVIOUS_TXID ))
@@ -483,6 +535,7 @@ class PartiallySignedOutput:
483535 PSBT_OUT_TAP_INTERNAL_KEY = 0x05
484536 PSBT_OUT_TAP_TREE = 0x06
485537 PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
538+ PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08
486539
487540 def __init__ (self , version : int ) -> None :
488541 self .redeem_script = b""
@@ -493,6 +546,7 @@ def __init__(self, version: int) -> None:
493546 self .tap_internal_key = b""
494547 self .tap_tree = b""
495548 self .tap_bip32_paths : Dict [bytes , Tuple [Set [bytes ], KeyOriginInfo ]] = {}
549+ self .musig2_participant_pubkeys : Dict [bytes , List [bytes ]] = {}
496550 self .unknown : Dict [bytes , bytes ] = {}
497551
498552 self .version : int = version
@@ -509,6 +563,7 @@ def set_null(self) -> None:
509563 self .tap_bip32_paths .clear ()
510564 self .amount = None
511565 self .script = b""
566+ self .musig2_participant_pubkeys = {}
512567 self .unknown .clear ()
513568
514569 def deserialize (self , f : Readable ) -> None :
@@ -589,6 +644,22 @@ def deserialize(self, f: Readable) -> None:
589644 for i in range (0 , num_hashes ):
590645 leaf_hashes .add (vs .read (32 ))
591646 self .tap_bip32_paths [xonly ] = (leaf_hashes , KeyOriginInfo .deserialize (vs .read ()))
647+ elif key_type == PartiallySignedOutput .PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS :
648+ if key in key_lookup :
649+ raise PSBTSerializationError ("Duplicate key, output Musig2 participant pubkeys already provided" )
650+ elif len (key ) != 1 + 33 :
651+ raise PSBTSerializationError ("Output Musig2 aggregate compressed pubkey is not 33 bytes" )
652+
653+ pubkeys_cat = deser_string (f )
654+ if len (pubkeys_cat ) == 0 :
655+ raise PSBTSerializationError ("The list of compressed pubkeys for Musig2 cannot be empty" )
656+ if (len (pubkeys_cat ) % 33 ) != 0 :
657+ raise PSBTSerializationError ("The compressed pubkeys for Musig2 must be exactly 33 bytes long" )
658+ pubkeys = []
659+ for i in range (0 , len (pubkeys_cat ), 33 ):
660+ pubkeys .append (pubkeys_cat [i : i + 33 ])
661+
662+ self .musig2_participant_pubkeys [key [1 :]] = pubkeys
592663 else :
593664 if key in self .unknown :
594665 raise PSBTSerializationError ("Duplicate key, key for unknown value already provided" )
@@ -646,6 +717,11 @@ def serialize(self) -> bytes:
646717 value += origin .serialize ()
647718 r += ser_string (value )
648719
720+ for pk , pubkeys in self .musig2_participant_pubkeys .items ():
721+ r += ser_string (ser_compact_size (
722+ PartiallySignedOutput .PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS ) + pk )
723+ r += ser_string (b'' .join (pubkeys ))
724+
649725 for key , value in sorted (self .unknown .items ()):
650726 r += ser_string (key )
651727 r += ser_string (value )
0 commit comments