Skip to content

Commit 17b1e47

Browse files
Sjorsbigspider
andcommitted
psbt: add MuSig2 fields
Co-authored-by: Salvatore Ingala <[email protected]>
1 parent 5f533aa commit 17b1e47

File tree

1 file changed

+89
-14
lines changed

1 file changed

+89
-14
lines changed

hwilib/psbt.py

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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_nonce = Dict[Tuple[bytes, bytes, Optional[bytes]], bytes] = {}
133+
self.musig2_partial_sig = 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_nonce.clear()
164+
self.musig2_partial_sig.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,10 @@ def serialize(self) -> bytes:
646717
value += origin.serialize()
647718
r += ser_string(value)
648719

720+
if len(self.musig2_participant_pubkeys) != 0:
721+
r += ser_string(ser_compact_size(PartiallySignedOutput.PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS))
722+
r += ser_string(self.musig2_participant_pubkeys)
723+
649724
for key, value in sorted(self.unknown.items()):
650725
r += ser_string(key)
651726
r += ser_string(value)

0 commit comments

Comments
 (0)