@@ -50,6 +50,9 @@ static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15;
50
50
static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16 ;
51
51
static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17 ;
52
52
static constexpr uint8_t PSBT_IN_TAP_MERKLE_ROOT = 0x18 ;
53
+ static constexpr uint8_t PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a ;
54
+ static constexpr uint8_t PSBT_IN_MUSIG2_PUB_NONCE = 0x1b ;
55
+ static constexpr uint8_t PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c ;
53
56
static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC ;
54
57
55
58
// Output types
@@ -59,6 +62,7 @@ static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
59
62
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05 ;
60
63
static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06 ;
61
64
static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 ;
65
+ static constexpr uint8_t PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08 ;
62
66
static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC ;
63
67
64
68
// The separator is 0x00. Reading this in means that the unserializer can interpret it
@@ -193,6 +197,49 @@ void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_k
193
197
}
194
198
}
195
199
200
+ // Deserialize a PSBT_{IN/OUT}_MUSIG2_PARTICIPANT_PUBKEYS field
201
+ template <typename Stream>
202
+ void DeserializeMuSig2ParticipantPubkeys (Stream& s, SpanReader& skey, std::map<CPubKey, std::vector<CPubKey>>& out, std::string context)
203
+ {
204
+ std::array<unsigned char , CPubKey::COMPRESSED_SIZE> agg_pubkey_bytes;
205
+ skey >> std::as_writable_bytes (std::span{agg_pubkey_bytes});
206
+ CPubKey agg_pubkey (agg_pubkey_bytes);
207
+
208
+ std::vector<CPubKey> participants;
209
+ std::vector<unsigned char > val;
210
+ s >> val;
211
+ SpanReader s_val{val};
212
+ while (s_val.size () >= CPubKey::COMPRESSED_SIZE) {
213
+ std::array<unsigned char , CPubKey::COMPRESSED_SIZE> part_pubkey_bytes;
214
+ s_val >> std::as_writable_bytes (std::span{part_pubkey_bytes});
215
+ participants.emplace_back (std::span{part_pubkey_bytes});
216
+ }
217
+ if (!s_val.empty ()) {
218
+ throw std::ios_base::failure (context + " musig2 participants pubkeys value size is not a multiple of 33" );
219
+ }
220
+
221
+ out.emplace (agg_pubkey, participants);
222
+ }
223
+
224
+ // Deserialize the MuSig2 participant identifiers from PSBT_MUSIG2_{PUBNONCE/PARTIAL_SIG} fields
225
+ // Both fields contain the same data after the type byte - aggregate pubkey | participant pubkey | leaf script hash
226
+ template <typename Stream>
227
+ void DeserializeMuSig2ParticipantDataIdentifier (Stream& skey, CPubKey& agg_pub, CPubKey& part_pub, uint256& leaf_hash)
228
+ {
229
+ leaf_hash.SetNull ();
230
+
231
+ std::array<unsigned char , CPubKey::COMPRESSED_SIZE> part_pubkey_bytes;
232
+ std::array<unsigned char , CPubKey::COMPRESSED_SIZE> agg_pubkey_bytes;
233
+
234
+ skey >> std::as_writable_bytes (std::span{part_pubkey_bytes}) >> std::as_writable_bytes (std::span{agg_pubkey_bytes});
235
+ agg_pub.Set (agg_pubkey_bytes.begin (), agg_pubkey_bytes.end ());
236
+ part_pub.Set (part_pubkey_bytes.begin (), part_pubkey_bytes.end ());
237
+
238
+ if (!skey.empty ()) {
239
+ skey >> leaf_hash;
240
+ }
241
+ }
242
+
196
243
/* * A structure for PSBTs which contain per-input information */
197
244
struct PSBTInput
198
245
{
@@ -217,6 +264,13 @@ struct PSBTInput
217
264
XOnlyPubKey m_tap_internal_key;
218
265
uint256 m_tap_merkle_root;
219
266
267
+ // MuSig2 fields
268
+ std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
269
+ // Key is the aggregate pubkey and the script leaf hash, value is a map of participant pubkey to pubnonce
270
+ std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, std::vector<uint8_t >>> m_musig2_pubnonces;
271
+ // Key is the aggregate pubkey and the script leaf hash, value is a map of participant pubkey to partial_sig
272
+ std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, uint256>> m_musig2_partial_sigs;
273
+
220
274
std::map<std::vector<unsigned char >, std::vector<unsigned char >> unknown;
221
275
std::set<PSBTProprietary> m_proprietary;
222
276
std::optional<int > sighash_type;
@@ -337,6 +391,43 @@ struct PSBTInput
337
391
SerializeToVector (s, PSBT_IN_TAP_MERKLE_ROOT);
338
392
SerializeToVector (s, m_tap_merkle_root);
339
393
}
394
+
395
+ // Write MuSig2 Participants
396
+ for (const auto & [agg_pubkey, part_pubs] : m_musig2_participants) {
397
+ SerializeToVector (s, CompactSizeWriter (PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS), std::span{agg_pubkey});
398
+ std::vector<unsigned char > value;
399
+ VectorWriter s_value{value, 0 };
400
+ for (auto & pk : part_pubs) {
401
+ s_value << std::span{pk};
402
+ }
403
+ s << value;
404
+ }
405
+
406
+ // Write MuSig2 pubnonces
407
+ for (const auto & [agg_pubkey_leaf_hash, pubnonces] : m_musig2_pubnonces) {
408
+ const auto & [agg_pubkey, leaf_hash] = agg_pubkey_leaf_hash;
409
+ for (const auto & [part_pubkey, pubnonce] : pubnonces) {
410
+ if (leaf_hash.IsNull ()) {
411
+ SerializeToVector (s, CompactSizeWriter (PSBT_IN_MUSIG2_PUB_NONCE), std::span{part_pubkey}, std::span{agg_pubkey});
412
+ } else {
413
+ SerializeToVector (s, CompactSizeWriter (PSBT_IN_MUSIG2_PUB_NONCE), std::span{part_pubkey}, std::span{agg_pubkey}, leaf_hash);
414
+ }
415
+ s << pubnonce;
416
+ }
417
+ }
418
+
419
+ // Write MuSig2 partial signatures
420
+ for (const auto & [agg_pubkey_leaf_hash, psigs] : m_musig2_partial_sigs) {
421
+ const auto & [agg_pubkey, leaf_hash] = agg_pubkey_leaf_hash;
422
+ for (const auto & [pubkey, psig] : psigs) {
423
+ if (leaf_hash.IsNull ()) {
424
+ SerializeToVector (s, CompactSizeWriter (PSBT_IN_MUSIG2_PARTIAL_SIG), std::span{pubkey}, std::span{agg_pubkey});
425
+ } else {
426
+ SerializeToVector (s, CompactSizeWriter (PSBT_IN_MUSIG2_PARTIAL_SIG), std::span{pubkey}, std::span{agg_pubkey}, leaf_hash);
427
+ }
428
+ SerializeToVector (s, psig);
429
+ }
430
+ }
340
431
}
341
432
342
433
// Write script sig
@@ -672,6 +763,53 @@ struct PSBTInput
672
763
UnserializeFromVector (s, m_tap_merkle_root);
673
764
break ;
674
765
}
766
+ case PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS:
767
+ {
768
+ if (!key_lookup.emplace (key).second ) {
769
+ throw std::ios_base::failure (" Duplicate Key, input participant pubkeys for an aggregate key already provided" );
770
+ } else if (key.size () != CPubKey::COMPRESSED_SIZE + 1 ) {
771
+ throw std::ios_base::failure (" Input musig2 participants pubkeys aggregate key is not 34 bytes" );
772
+ }
773
+ DeserializeMuSig2ParticipantPubkeys (s, skey, m_musig2_participants, std::string{" Input" });
774
+ break ;
775
+ }
776
+ case PSBT_IN_MUSIG2_PUB_NONCE:
777
+ {
778
+ if (!key_lookup.emplace (key).second ) {
779
+ throw std::ios_base::failure (" Duplicate Key, input musig2 pubnonce already provided" );
780
+ } else if (key.size () != 2 * CPubKey::COMPRESSED_SIZE + 1 && key.size () != 2 * CPubKey::COMPRESSED_SIZE + CSHA256::OUTPUT_SIZE + 1 ) {
781
+ throw std::ios_base::failure (" Input musig2 pubnonce key is not expected size of 67 or 99 bytes" );
782
+ }
783
+ CPubKey agg_pub, part_pub;
784
+ uint256 leaf_hash;
785
+ DeserializeMuSig2ParticipantDataIdentifier (skey, agg_pub, part_pub, leaf_hash);
786
+
787
+ std::vector<uint8_t > pubnonce;
788
+ s >> pubnonce;
789
+ if (pubnonce.size () != 66 ) {
790
+ throw std::ios_base::failure (" Input musig2 pubnonce value is not 66 bytes" );
791
+ }
792
+
793
+ m_musig2_pubnonces[std::make_pair (agg_pub, leaf_hash)].emplace (part_pub, pubnonce);
794
+ break ;
795
+ }
796
+ case PSBT_IN_MUSIG2_PARTIAL_SIG:
797
+ {
798
+ if (!key_lookup.emplace (key).second ) {
799
+ throw std::ios_base::failure (" Duplicate Key, input musig2 partial sig already provided" );
800
+ } else if (key.size () != 2 * CPubKey::COMPRESSED_SIZE + 1 && key.size () != 2 * CPubKey::COMPRESSED_SIZE + CSHA256::OUTPUT_SIZE + 1 ) {
801
+ throw std::ios_base::failure (" Input musig2 partial sig key is not expected size of 67 or 99 bytes" );
802
+ }
803
+ CPubKey agg_pub, part_pub;
804
+ uint256 leaf_hash;
805
+ DeserializeMuSig2ParticipantDataIdentifier (skey, agg_pub, part_pub, leaf_hash);
806
+
807
+ uint256 partial_sig;
808
+ UnserializeFromVector (s, partial_sig);
809
+
810
+ m_musig2_partial_sigs[std::make_pair (agg_pub, leaf_hash)].emplace (part_pub, partial_sig);
811
+ break ;
812
+ }
675
813
case PSBT_IN_PROPRIETARY:
676
814
{
677
815
PSBTProprietary this_prop;
@@ -719,6 +857,7 @@ struct PSBTOutput
719
857
XOnlyPubKey m_tap_internal_key;
720
858
std::vector<std::tuple<uint8_t , uint8_t , std::vector<unsigned char >>> m_tap_tree;
721
859
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
860
+ std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
722
861
std::map<std::vector<unsigned char >, std::vector<unsigned char >> unknown;
723
862
std::set<PSBTProprietary> m_proprietary;
724
863
@@ -781,6 +920,17 @@ struct PSBTOutput
781
920
s << value;
782
921
}
783
922
923
+ // Write MuSig2 Participants
924
+ for (const auto & [agg_pubkey, part_pubs] : m_musig2_participants) {
925
+ SerializeToVector (s, CompactSizeWriter (PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS), std::span{agg_pubkey});
926
+ std::vector<unsigned char > value;
927
+ VectorWriter s_value{value, 0 };
928
+ for (auto & pk : part_pubs) {
929
+ s_value << std::span{pk};
930
+ }
931
+ s << value;
932
+ }
933
+
784
934
// Write unknown things
785
935
for (auto & entry : unknown) {
786
936
s << entry.first ;
@@ -907,6 +1057,16 @@ struct PSBTOutput
907
1057
m_tap_bip32_paths.emplace (xonly, std::make_pair (leaf_hashes, DeserializeKeyOrigin (s, origin_len)));
908
1058
break ;
909
1059
}
1060
+ case PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS:
1061
+ {
1062
+ if (!key_lookup.emplace (key).second ) {
1063
+ throw std::ios_base::failure (" Duplicate Key, output participant pubkeys for an aggregate key already provided" );
1064
+ } else if (key.size () != CPubKey::COMPRESSED_SIZE + 1 ) {
1065
+ throw std::ios_base::failure (" Output musig2 participants pubkeys aggregate key is not 34 bytes" );
1066
+ }
1067
+ DeserializeMuSig2ParticipantPubkeys (s, skey, m_musig2_participants, std::string{" Output" });
1068
+ break ;
1069
+ }
910
1070
case PSBT_OUT_PROPRIETARY:
911
1071
{
912
1072
PSBTProprietary this_prop;
0 commit comments