@@ -103,6 +103,9 @@ class PartiallySignedInput:
103
103
PSBT_IN_TAP_BIP32_DERIVATION = 0x16
104
104
PSBT_IN_TAP_INTERNAL_KEY = 0x17
105
105
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
106
109
107
110
def __init__ (self , version : int ) -> None :
108
111
self .non_witness_utxo : Optional [CTransaction ] = None
@@ -125,6 +128,9 @@ def __init__(self, version: int) -> None:
125
128
self .tap_bip32_paths : Dict [bytes , Tuple [Set [bytes ], KeyOriginInfo ]] = {}
126
129
self .tap_internal_key = b""
127
130
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 ] = {}
128
134
self .unknown : Dict [bytes , bytes ] = {}
129
135
130
136
self .version : int = version
@@ -153,6 +159,9 @@ def set_null(self) -> None:
153
159
self .sequence = None
154
160
self .time_locktime = None
155
161
self .height_locktime = None
162
+ self .musig2_participant_pubkeys .clear ()
163
+ self .musig2_pub_nonces .clear ()
164
+ self .musig2_partial_sigs .clear ()
156
165
self .unknown .clear ()
157
166
158
167
def deserialize (self , f : Readable ) -> None :
@@ -351,6 +360,51 @@ def deserialize(self, f: Readable) -> None:
351
360
self .tap_merkle_root = deser_string (f )
352
361
if len (self .tap_merkle_root ) != 32 :
353
362
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
354
408
else :
355
409
if key in self .unknown :
356
410
raise PSBTSerializationError ("Duplicate key, key for unknown value already provided" )
@@ -441,6 +495,20 @@ def serialize(self) -> bytes:
441
495
witstack = self .final_script_witness .serialize ()
442
496
r += ser_string (witstack )
443
497
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
+
444
512
if self .version >= 2 :
445
513
if len (self .prev_txid ) != 0 :
446
514
r += ser_string (ser_compact_size (PartiallySignedInput .PSBT_IN_PREVIOUS_TXID ))
@@ -483,6 +551,7 @@ class PartiallySignedOutput:
483
551
PSBT_OUT_TAP_INTERNAL_KEY = 0x05
484
552
PSBT_OUT_TAP_TREE = 0x06
485
553
PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
554
+ PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08
486
555
487
556
def __init__ (self , version : int ) -> None :
488
557
self .redeem_script = b""
@@ -493,6 +562,7 @@ def __init__(self, version: int) -> None:
493
562
self .tap_internal_key = b""
494
563
self .tap_tree = b""
495
564
self .tap_bip32_paths : Dict [bytes , Tuple [Set [bytes ], KeyOriginInfo ]] = {}
565
+ self .musig2_participant_pubkeys : Dict [bytes , List [bytes ]] = {}
496
566
self .unknown : Dict [bytes , bytes ] = {}
497
567
498
568
self .version : int = version
@@ -509,6 +579,7 @@ def set_null(self) -> None:
509
579
self .tap_bip32_paths .clear ()
510
580
self .amount = None
511
581
self .script = b""
582
+ self .musig2_participant_pubkeys = {}
512
583
self .unknown .clear ()
513
584
514
585
def deserialize (self , f : Readable ) -> None :
@@ -589,6 +660,22 @@ def deserialize(self, f: Readable) -> None:
589
660
for i in range (0 , num_hashes ):
590
661
leaf_hashes .add (vs .read (32 ))
591
662
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
592
679
else :
593
680
if key in self .unknown :
594
681
raise PSBTSerializationError ("Duplicate key, key for unknown value already provided" )
@@ -646,6 +733,11 @@ def serialize(self) -> bytes:
646
733
value += origin .serialize ()
647
734
r += ser_string (value )
648
735
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
+
649
741
for key , value in sorted (self .unknown .items ()):
650
742
r += ser_string (key )
651
743
r += ser_string (value )
0 commit comments