Skip to content

Commit c7b592f

Browse files
committed
Merge bitcoin/bitcoin#31247: psbt: MuSig2 Fields
e261eb8 tests: Add BIP 373 test vectors (Ava Chow) 26370c6 rpc: Include MuSig2 fields in decodepsbt (Ava Chow) ff3d460 psbt: Implement un/ser of musig2 fields (Ava Chow) Pull request description: Implements un/serialization of MuSig2 PSBT fields and prepares PSBT to be able to sign for MuSig2 inputs. Split from #29675 ACKs for top commit: fjahr: re-ACK e261eb8 theStack: re-ACK e261eb8 rkrux: tACK e261eb8 Tree-SHA512: bb852ad074978847ac4dc656332025e2d4d1025d4283537b89618c7cadd61a8ecd2eff24779b8a014bc8d7b431125060449768192fa05ad0577f29e3c64b2374
2 parents 247e9de + e261eb8 commit c7b592f

File tree

3 files changed

+319
-1
lines changed

3 files changed

+319
-1
lines changed

src/psbt.h

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15;
5050
static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16;
5151
static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17;
5252
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;
5356
static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC;
5457

5558
// Output types
@@ -59,6 +62,7 @@ static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
5962
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
6063
static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06;
6164
static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
65+
static constexpr uint8_t PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08;
6266
static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC;
6367

6468
// 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
193197
}
194198
}
195199

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+
196243
/** A structure for PSBTs which contain per-input information */
197244
struct PSBTInput
198245
{
@@ -217,6 +264,13 @@ struct PSBTInput
217264
XOnlyPubKey m_tap_internal_key;
218265
uint256 m_tap_merkle_root;
219266

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+
220274
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
221275
std::set<PSBTProprietary> m_proprietary;
222276
std::optional<int> sighash_type;
@@ -337,6 +391,43 @@ struct PSBTInput
337391
SerializeToVector(s, PSBT_IN_TAP_MERKLE_ROOT);
338392
SerializeToVector(s, m_tap_merkle_root);
339393
}
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+
}
340431
}
341432

342433
// Write script sig
@@ -672,6 +763,53 @@ struct PSBTInput
672763
UnserializeFromVector(s, m_tap_merkle_root);
673764
break;
674765
}
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+
}
675813
case PSBT_IN_PROPRIETARY:
676814
{
677815
PSBTProprietary this_prop;
@@ -719,6 +857,7 @@ struct PSBTOutput
719857
XOnlyPubKey m_tap_internal_key;
720858
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree;
721859
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
860+
std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
722861
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
723862
std::set<PSBTProprietary> m_proprietary;
724863

@@ -781,6 +920,17 @@ struct PSBTOutput
781920
s << value;
782921
}
783922

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+
784934
// Write unknown things
785935
for (auto& entry : unknown) {
786936
s << entry.first;
@@ -907,6 +1057,16 @@ struct PSBTOutput
9071057
m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len)));
9081058
break;
9091059
}
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+
}
9101070
case PSBT_OUT_PROPRIETARY:
9111071
{
9121072
PSBTProprietary this_prop;

src/rpc/rawtransaction.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,37 @@ const RPCResult decodepsbt_inputs{
916916
}},
917917
{RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
918918
{RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"},
919+
{RPCResult::Type::ARR, "musig2_participant_pubkeys", /*optional=*/true, "",
920+
{
921+
{RPCResult::Type::OBJ, "", "",
922+
{
923+
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which the participants create."},
924+
{RPCResult::Type::ARR, "participant_pubkeys", "",
925+
{
926+
{RPCResult::Type::STR_HEX, "pubkey", "The compressed public keys that are aggregated for aggregate_pubkey."},
927+
}},
928+
}},
929+
}},
930+
{RPCResult::Type::ARR, "musig2_pubnonces", /*optional=*/true, "",
931+
{
932+
{RPCResult::Type::OBJ, "", "",
933+
{
934+
{RPCResult::Type::STR_HEX, "participant_pubkey", "The compressed public key of the participant that created this pubnonce."},
935+
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which this pubnonce is for."},
936+
{RPCResult::Type::STR_HEX, "leaf_hash", /*optional=*/true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
937+
{RPCResult::Type::STR_HEX, "pubnonce", "The public nonce itself."},
938+
}},
939+
}},
940+
{RPCResult::Type::ARR, "musig2_partial_sigs", /*optional=*/true, "",
941+
{
942+
{RPCResult::Type::OBJ, "", "",
943+
{
944+
{RPCResult::Type::STR_HEX, "participant_pubkey", "The compressed public key of the participant that created this partial signature."},
945+
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which this partial signature is for."},
946+
{RPCResult::Type::STR_HEX, "leaf_hash", /*optional=*/true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
947+
{RPCResult::Type::STR_HEX, "partial_sig", "The partial signature itself."},
948+
}},
949+
}},
919950
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields",
920951
{
921952
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
@@ -983,6 +1014,17 @@ const RPCResult decodepsbt_outputs{
9831014
}},
9841015
}},
9851016
}},
1017+
{RPCResult::Type::ARR, "musig2_participant_pubkeys", /*optional=*/true, "",
1018+
{
1019+
{RPCResult::Type::OBJ, "", "",
1020+
{
1021+
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which the participants create."},
1022+
{RPCResult::Type::ARR, "participant_pubkeys", "",
1023+
{
1024+
{RPCResult::Type::STR_HEX, "pubkey", "The compressed public keys that are aggregated for aggregate_pubkey."},
1025+
}},
1026+
}},
1027+
}},
9861028
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields",
9871029
{
9881030
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
@@ -1304,6 +1346,52 @@ static RPCHelpMan decodepsbt()
13041346
in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root));
13051347
}
13061348

1349+
// Write MuSig2 fields
1350+
if (!input.m_musig2_participants.empty()) {
1351+
UniValue musig_pubkeys(UniValue::VARR);
1352+
for (const auto& [agg, parts] : input.m_musig2_participants) {
1353+
UniValue musig_part(UniValue::VOBJ);
1354+
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
1355+
UniValue part_pubkeys(UniValue::VARR);
1356+
for (const auto& pub : parts) {
1357+
part_pubkeys.push_back(HexStr(pub));
1358+
}
1359+
musig_part.pushKV("participant_pubkeys", part_pubkeys);
1360+
musig_pubkeys.push_back(musig_part);
1361+
}
1362+
in.pushKV("musig2_participant_pubkeys", musig_pubkeys);
1363+
}
1364+
if (!input.m_musig2_pubnonces.empty()) {
1365+
UniValue musig_pubnonces(UniValue::VARR);
1366+
for (const auto& [agg_lh, part_pubnonce] : input.m_musig2_pubnonces) {
1367+
const auto& [agg, lh] = agg_lh;
1368+
for (const auto& [part, pubnonce] : part_pubnonce) {
1369+
UniValue info(UniValue::VOBJ);
1370+
info.pushKV("participant_pubkey", HexStr(part));
1371+
info.pushKV("aggregate_pubkey", HexStr(agg));
1372+
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
1373+
info.pushKV("pubnonce", HexStr(pubnonce));
1374+
musig_pubnonces.push_back(info);
1375+
}
1376+
}
1377+
in.pushKV("musig2_pubnonces", musig_pubnonces);
1378+
}
1379+
if (!input.m_musig2_partial_sigs.empty()) {
1380+
UniValue musig_partial_sigs(UniValue::VARR);
1381+
for (const auto& [agg_lh, part_psig] : input.m_musig2_partial_sigs) {
1382+
const auto& [agg, lh] = agg_lh;
1383+
for (const auto& [part, psig] : part_psig) {
1384+
UniValue info(UniValue::VOBJ);
1385+
info.pushKV("participant_pubkey", HexStr(part));
1386+
info.pushKV("aggregate_pubkey", HexStr(agg));
1387+
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
1388+
info.pushKV("partial_sig", HexStr(psig));
1389+
musig_partial_sigs.push_back(info);
1390+
}
1391+
}
1392+
in.pushKV("musig2_partial_sigs", musig_partial_sigs);
1393+
}
1394+
13071395
// Proprietary
13081396
if (!input.m_proprietary.empty()) {
13091397
UniValue proprietary(UniValue::VARR);
@@ -1399,6 +1487,22 @@ static RPCHelpMan decodepsbt()
13991487
out.pushKV("taproot_bip32_derivs", std::move(keypaths));
14001488
}
14011489

1490+
// Write MuSig2 fields
1491+
if (!output.m_musig2_participants.empty()) {
1492+
UniValue musig_pubkeys(UniValue::VARR);
1493+
for (const auto& [agg, parts] : output.m_musig2_participants) {
1494+
UniValue musig_part(UniValue::VOBJ);
1495+
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
1496+
UniValue part_pubkeys(UniValue::VARR);
1497+
for (const auto& pub : parts) {
1498+
part_pubkeys.push_back(HexStr(pub));
1499+
}
1500+
musig_part.pushKV("participant_pubkeys", part_pubkeys);
1501+
musig_pubkeys.push_back(musig_part);
1502+
}
1503+
out.pushKV("musig2_participant_pubkeys", musig_pubkeys);
1504+
}
1505+
14021506
// Proprietary
14031507
if (!output.m_proprietary.empty()) {
14041508
UniValue proprietary(UniValue::VARR);

0 commit comments

Comments
 (0)