musig: Add MuSig2AggregatePubkeys variant that validates the aggregate
What changed, and why it matters
This commit is a straightforward internal code refactor in Bitcoin Core's MuSig2 cryptographic helper module. It adds a new variant of an existing function that lets callers both compute an aggregate public key and optionally check it against an expected value, while making two previously public helper functions private to the source file. There is no bug fix, no security patch, and no disclosed vulnerability.
No security action required. Treat as normal code cleanup/refactoring. Reviewers may verify that the new overload's expected-pubkey comparison uses the correct equality semantics and that making the helpers static does not break any external callers (the diff shows they are no longer declared in the header).
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces an overloaded MuSig2AggregatePubkeys that accepts an output secp256k1_musig_keyagg_cache and an optional expected_aggregate CPubKey. It reuses the existing key aggregation and serialization helpers, returning nullopt if aggregation fails or if the computed aggregate does not match the expected one. The two helpers GetMuSig2KeyAggCache and GetCPubKeyFromMuSig2KeyAggCache are made static and removed from the public header because their functionality is now folded into the new aggregate function. The old one-argument overload remains as a thin wrapper.
Changed components
src/musig.cppsrc/musig.hInspect captured patch +17 / −10
diff --git a/src/musig.cpp b/src/musig.cpp
index c361a7ea..7ebb8e55 100644
--- a/src/musig.cpp
+++ b/src/musig.cpp
@@ -7,7 +7,7 @@
#include <secp256k1_musig.h>
-bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
+static bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
{
// Parse the pubkeys
std::vector<secp256k1_pubkey> secp_pubkeys;
@@ -29,7 +29,7 @@ bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_k
return true;
}
-std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache)
+static std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache)
{
// Get the plain aggregated pubkey
secp256k1_pubkey agg_pubkey;
@@ -44,13 +44,21 @@ std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_ca
return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len);
}
-std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys)
+std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional<CPubKey>& expected_aggregate)
{
- secp256k1_musig_keyagg_cache keyagg_cache;
if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) {
return std::nullopt;
}
- return GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache);
+ std::optional<CPubKey> agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache);
+ if (!agg_key.has_value()) return std::nullopt;
+ if (expected_aggregate.has_value() && expected_aggregate != agg_key) return std::nullopt;
+ return agg_key;
+}
+
+std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys)
+{
+ secp256k1_musig_keyagg_cache keyagg_cache;
+ return MuSig2AggregatePubkeys(pubkeys, keyagg_cache, std::nullopt);
}
CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey)
diff --git a/src/musig.h b/src/musig.h
index 40da1f10..bb469df4 100644
--- a/src/musig.h
+++ b/src/musig.h
@@ -18,11 +18,10 @@ struct secp256k1_musig_secnonce;
using namespace util::hex_literals;
constexpr uint256 MUSIG_CHAINCODE{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8};
-//! Create a secp256k1_musig_keyagg_cache from the pubkeys in their current order. This is necessary for most MuSig2 operations
-bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache);
-//! Retrieve the full aggregate pubkey from the secp256k1_musig_keyagg_cache
-std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& cache);
-//! Compute the full aggregate pubkey from the given participant pubkeys in their current order
+//! Compute the full aggregate pubkey from the given participant pubkeys in their current order.
+//! Outputs the secp256k1_musig_keyagg_cache and validates that the computed aggregate pubkey matches an expected aggregate pubkey.
+//! This is necessary for most MuSig2 operations.
+std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional<CPubKey>& expected_aggregate);
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys);
//! Construct the BIP 328 synthetic xpub for a pubkey
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.