Skip to content

Commit 5dd2951

Browse files
Sjorsbigspider
andcommitted
psbt: add MuSig2 fields
Co-Authored-By: Salvatore Ingala <[email protected]>
1 parent 374948b commit 5dd2951

File tree

2 files changed

+120
-3
lines changed

2 files changed

+120
-3
lines changed

hwilib/psbt.py

Lines changed: 92 additions & 0 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_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

Comments
 (0)