What changed, and why it matters
This commit adds a new helper function for creating MuSig2 cryptographic nonces in Bitcoin Core. MuSig2 is a multi-signature scheme that lets multiple parties jointly sign a transaction. The change is a feature addition: it introduces a nonce generator, a session ID helper, and wiring for signing code. There is no direct evidence in the commit or supplied references that this fixes a known security vulnerability. It is best treated as a normal code addition that may carry implementation risks rather than a disclosed security patch.
Review the MuSig2 nonce generation for correct use of the libsecp256k1 API, ensure the session ID uniquely identifies each signing session to prevent nonce reuse, and verify that secret nonces are cleared from memory after use. Treat as a feature commit unless additional security context emerges.
Security signals we found
New cryptographic nonce generation path using secp256k1_musig_nonce_gen
Secret nonce stored in SigningProvider keyed by session ID derived from pubkeys and sighash
Uses GetStrongRandBytes for nonce randomness
No explicit security claim or CVE reference in commit message or diff
No supplied verified references indicating vulnerability or incident
Evidence from the diff
The commit adds CKey::CreateMuSig2Nonce and MutableTransactionSignatureCreator::CreateMuSig2Nonce, plus a MuSig2SessionID helper and a MUSIG2_PUBNONCE_SIZE constant. It uses libsecp256k1’s secp256k1_musig_nonce_gen with strong randomness and the signer’s secret key bytes as the ‘extra32’ personalization input. The generated public nonce is serialized and returned, while the secret nonce is stored in the SigningProvider keyed by a session ID derived from script pubkey, participant pubkey, and sighash. A dummy implementation is also added for the unit-test signature creator. The commit does not modify existing behavior; it extends the signing API.
Changed components
src/key.cpp / CKeysrc/key.hsrc/musig.cppsrc/musig.hsrc/psbt.hsrc/script/sign.cpp / MutableTransactionSignatureCreatorsrc/script/sign.hInspect captured patch +88 / −1
diff --git a/src/key.cpp b/src/key.cpp
index 01fa3d27..02398332 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -13,6 +13,7 @@
#include <secp256k1.h>
#include <secp256k1_ellswift.h>
#include <secp256k1_extrakeys.h>
+#include <secp256k1_musig.h>
#include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h>
@@ -349,6 +350,39 @@ KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const
return KeyPair(*this, merkle_root);
}
+std::vector<uint8_t> CKey::CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys)
+{
+ // Get the keyagg cache and aggregate pubkey
+ secp256k1_musig_keyagg_cache keyagg_cache;
+ if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return {};
+
+ // Parse participant pubkey
+ CPubKey our_pubkey = GetPubKey();
+ secp256k1_pubkey pubkey;
+ if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, our_pubkey.data(), our_pubkey.size())) {
+ return {};
+ }
+
+ // Generate randomness for nonce
+ uint256 rand;
+ GetStrongRandBytes(rand);
+
+ // Generate nonce
+ secp256k1_musig_pubnonce pubnonce;
+ if (!secp256k1_musig_nonce_gen(secp256k1_context_sign, secnonce.Get(), &pubnonce, rand.data(), UCharCast(begin()), &pubkey, sighash.data(), &keyagg_cache, nullptr)) {
+ return {};
+ }
+
+ // Serialize pubnonce
+ std::vector<uint8_t> out;
+ out.resize(MUSIG2_PUBNONCE_SIZE);
+ if (!secp256k1_musig_pubnonce_serialize(secp256k1_context_static, out.data(), &pubnonce)) {
+ return {};
+ }
+
+ return out;
+}
+
CKey GenerateRandomKey(bool compressed) noexcept
{
CKey key;
diff --git a/src/key.h b/src/key.h
index 22f96880..97ed27cc 100644
--- a/src/key.h
+++ b/src/key.h
@@ -7,6 +7,7 @@
#ifndef BITCOIN_KEY_H
#define BITCOIN_KEY_H
+#include <musig.h>
#include <pubkey.h>
#include <serialize.h>
#include <support/allocators/secure.h>
@@ -220,6 +221,8 @@ public:
* Merkle root of the script tree).
*/
KeyPair ComputeKeyPair(const uint256* merkle_root) const;
+
+ std::vector<uint8_t> CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys);
};
CKey GenerateRandomKey(bool compressed = true) noexcept;
diff --git a/src/musig.cpp b/src/musig.cpp
index 7ebb8e55..5cf638d5 100644
--- a/src/musig.cpp
+++ b/src/musig.cpp
@@ -111,3 +111,10 @@ bool MuSig2SecNonce::IsValid()
{
return m_impl->IsValid();
}
+
+uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash)
+{
+ HashWriter hasher;
+ hasher << script_pubkey << part_pubkey << sighash;
+ return hasher.GetSHA256();
+}
diff --git a/src/musig.h b/src/musig.h
index bb469df4..03324fc4 100644
--- a/src/musig.h
+++ b/src/musig.h
@@ -18,6 +18,8 @@ struct secp256k1_musig_secnonce;
using namespace util::hex_literals;
constexpr uint256 MUSIG_CHAINCODE{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8};
+constexpr size_t MUSIG2_PUBNONCE_SIZE{66};
+
//! 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.
@@ -58,4 +60,6 @@ public:
bool IsValid();
};
+uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash);
+
#endif // BITCOIN_MUSIG_H
diff --git a/src/psbt.h b/src/psbt.h
index f8098b04..f0de079f 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -794,7 +794,7 @@ struct PSBTInput
std::vector<uint8_t> pubnonce;
s >> pubnonce;
- if (pubnonce.size() != 66) {
+ if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) {
throw std::ios_base::failure("Input musig2 pubnonce value is not 66 bytes");
}
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 8575c0b9..5ca3f988 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -7,8 +7,10 @@
#include <consensus/amount.h>
#include <key.h>
+#include <musig.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
+#include <random.h>
#include <script/keyorigin.h>
#include <script/miniscript.h>
#include <script/script.h>
@@ -100,6 +102,34 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider&
return true;
}
+std::vector<uint8_t> MutableTransactionSignatureCreator::CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const
+{
+ assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
+
+ // Retrieve the private key
+ CKey key;
+ if (!provider.GetKey(part_pubkey.GetID(), key)) return {};
+
+ // Retrieve participant pubkeys
+ auto it = sigdata.musig2_pubkeys.find(aggregate_pubkey);
+ if (it == sigdata.musig2_pubkeys.end()) return {};
+ const std::vector<CPubKey>& pubkeys = it->second;
+ if (std::find(pubkeys.begin(), pubkeys.end(), part_pubkey) == pubkeys.end()) return {};
+
+ // Compute sighash
+ std::optional<uint256> sighash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
+ if (!sighash.has_value()) return {};
+
+ MuSig2SecNonce secnonce;
+ std::vector<uint8_t> out = key.CreateMuSig2Nonce(secnonce, *sighash, aggregate_pubkey, pubkeys);
+ if (out.empty()) return {};
+
+ // Store the secnonce in the SigningProvider
+ provider.SetMuSig2SecNonce(MuSig2SessionID(script_pubkey, part_pubkey, *sighash), std::move(secnonce));
+
+ return out;
+}
+
static bool GetCScript(const SigningProvider& provider, const SignatureData& sigdata, const CScriptID& scriptid, CScript& script)
{
if (provider.GetCScript(scriptid, script)) {
@@ -755,6 +785,12 @@ public:
sig.assign(64, '\000');
return true;
}
+ std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const override
+ {
+ std::vector<uint8_t> out;
+ out.assign(MUSIG2_PUBNONCE_SIZE, '\000');
+ return out;
+ }
};
}
diff --git a/src/script/sign.h b/src/script/sign.h
index 2b4db200..4a0782da 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -23,6 +23,7 @@ class SigningProvider;
struct bilingual_str;
struct CMutableTransaction;
+struct SignatureData;
/** Interface for signature creators. */
class BaseSignatureCreator {
@@ -33,6 +34,7 @@ public:
/** Create a singular (non-script) signature. */
virtual bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0;
virtual bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const =0;
+ virtual std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const =0;
};
/** A signature creator for transactions. */
@@ -53,6 +55,7 @@ public:
const BaseSignatureChecker& Checker() const override { return checker; }
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override;
bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override;
+ std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const override;
};
/** A signature checker that accepts all signatures */
Why this scored 14/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.