signingprovider: Add musig2 secnonces
What changed, and why it matters
This commit adds infrastructure for storing and retrieving MuSig2 secret nonces (temporary cryptographic values used in multi-signature setup) inside Bitcoin Core's signing provider classes. It is a code organization change that introduces storage methods and a deletion method, but does not by itself change how transactions are signed or validated. There is no indication in the commit that it fixes a known security bug.
No immediate action required. Treat as normal feature/refactoring commit. Review future commits that consume these APIs to ensure MuSig2 secret nonces are generated with sufficient randomness, never reused across sessions, and erased promptly after use.
Security signals we found
Adds secret nonce storage plumbing for MuSig2 multi-signature protocol
Uses std::optional<std::reference_wrapper<>> for nonce retrieval, requiring callers to manage lifetime
Merge semantics explicitly overwrite sessions rather than combine them
No validation, bounds, or cryptographic correctness changes visible in diff
Evidence from the diff
The patch extends SigningProvider, HidingSigningProvider, and FlatSigningProvider with SetMuSig2SecNonce, GetMuSig2SecNonce, and DeleteMuSig2Session. FlatSigningProvider stores MuSig2SecNonce objects in a pointer-owned map keyed by a uint256 session id, and Merge handles the pointer with an overwrite rule. The change is purely additive plumbing for MuSig2 nonce lifecycle management; no existing signing logic is altered and no vulnerability is described.
Changed components
src/script/signingprovider.hsrc/script/signingprovider.cppSigningProvider class hierarchyFlatSigningProviderHidingSigningProviderInspect captured patch +51 / −0
diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp
index 792796b0..846f0c98 100644
--- a/src/script/signingprovider.cpp
+++ b/src/script/signingprovider.cpp
@@ -58,6 +58,21 @@ std::vector<CPubKey> HidingSigningProvider::GetMuSig2ParticipantPubkeys(const CP
return m_provider->GetMuSig2ParticipantPubkeys(pubkey);
}
+void HidingSigningProvider::SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const
+{
+ m_provider->SetMuSig2SecNonce(id, std::move(nonce));
+}
+
+std::optional<std::reference_wrapper<MuSig2SecNonce>> HidingSigningProvider::GetMuSig2SecNonce(const uint256& session_id) const
+{
+ return m_provider->GetMuSig2SecNonce(session_id);
+}
+
+void HidingSigningProvider::DeleteMuSig2Session(const uint256& session_id) const
+{
+ m_provider->DeleteMuSig2Session(session_id);
+}
+
bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); }
bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const
@@ -94,6 +109,26 @@ std::vector<CPubKey> FlatSigningProvider::GetMuSig2ParticipantPubkeys(const CPub
return participant_pubkeys;
}
+void FlatSigningProvider::SetMuSig2SecNonce(const uint256& session_id, MuSig2SecNonce&& nonce) const
+{
+ if (!Assume(musig2_secnonces)) return;
+ musig2_secnonces->emplace(session_id, std::move(nonce));
+}
+
+std::optional<std::reference_wrapper<MuSig2SecNonce>> FlatSigningProvider::GetMuSig2SecNonce(const uint256& session_id) const
+{
+ if (!Assume(musig2_secnonces)) return std::nullopt;
+ const auto& it = musig2_secnonces->find(session_id);
+ if (it == musig2_secnonces->end()) return std::nullopt;
+ return it->second;
+}
+
+void FlatSigningProvider::DeleteMuSig2Session(const uint256& session_id) const
+{
+ if (!Assume(musig2_secnonces)) return;
+ musig2_secnonces->erase(session_id);
+}
+
FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
{
scripts.merge(b.scripts);
@@ -102,6 +137,8 @@ FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
origins.merge(b.origins);
tr_trees.merge(b.tr_trees);
aggregate_pubkeys.merge(b.aggregate_pubkeys);
+ // We shouldn't be merging 2 different sessions, just overwrite with b's sessions.
+ if (!musig2_secnonces) musig2_secnonces = b.musig2_secnonces;
return *this;
}
diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h
index 1da58bf6..cc917cc6 100644
--- a/src/script/signingprovider.h
+++ b/src/script/signingprovider.h
@@ -9,11 +9,15 @@
#include <addresstype.h>
#include <attributes.h>
#include <key.h>
+#include <musig.h>
#include <pubkey.h>
#include <script/keyorigin.h>
#include <script/script.h>
#include <sync.h>
+#include <functional>
+#include <optional>
+
struct ShortestVectorFirstComparator
{
bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const
@@ -162,6 +166,9 @@ public:
virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; }
virtual bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; }
virtual std::vector<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const { return {}; }
+ virtual void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const {}
+ virtual std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const { return std::nullopt; }
+ virtual void DeleteMuSig2Session(const uint256& session_id) const {}
bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const
{
@@ -206,6 +213,9 @@ public:
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
std::vector<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override;
+ void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override;
+ std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const override;
+ void DeleteMuSig2Session(const uint256& session_id) const override;
};
struct FlatSigningProvider final : public SigningProvider
@@ -216,6 +226,7 @@ struct FlatSigningProvider final : public SigningProvider
std::map<CKeyID, CKey> keys;
std::map<XOnlyPubKey, TaprootBuilder> tr_trees; /** Map from output key to Taproot tree (which can then make the TaprootSpendData */
std::map<CPubKey, std::vector<CPubKey>> aggregate_pubkeys; /** MuSig2 aggregate pubkeys */
+ std::map<uint256, MuSig2SecNonce>* musig2_secnonces{nullptr};
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
@@ -225,6 +236,9 @@ struct FlatSigningProvider final : public SigningProvider
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
std::vector<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override;
+ void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override;
+ std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const override;
+ void DeleteMuSig2Session(const uint256& session_id) const override;
FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND;
};
Why this scored 11/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.