Add MuSig2SecNonce class for secure allocation of musig nonces
What changed, and why it matters
This commit adds a new helper class that wraps sensitive MuSig2 signing nonces in locked, non-swappable memory and prevents accidental copying. It is a defensive hardening change: it does not by itself fix an active bug, but it reduces the risk that a secret nonce could leak through memory dumps, swapping, or programmer error. The change is forward-looking and only affects code paths that use MuSig2 multi-signature signing.
Treat as a defensive hardening commit. Review future commits that adopt MuSig2SecNonce to ensure all secret-nonce handling migrates to it and that no raw secp256k1_musig_secnonce copies remain. No immediate patch or incident response is indicated.
Security signals we found
Use of secure_unique_ptr / make_secure_unique for cryptographic secret material
Explicit deletion of copy constructor and copy assignment to prevent nonce reuse/leakage
Move-only semantics for a secret nonce object
Header comment describes private-key leakage risk if nonce is exposed
No direct bug fix or CVE reference in commit message or diff
Evidence from the diff
The patch introduces MuSig2SecNonce/MuSig2SecNonceImpl in src/musig.cpp and src/musig.h. The implementation stores a secp256k1_musig_secnonce in a secure_unique_ptr backed by Bitcoin Core’s secure allocator (LockedPoolAllocator / mlock / zero-on-free semantics). The public wrapper deletes copy construction/assignment, allows only move, exposes Get(), Invalidate(), and IsValid(). The header comment explicitly states the security rationale: secret nonces must be kept secret to avoid private-key leakage, and they must be treated like CKey. No existing callers are changed in this commit; the class is infrastructure for future MuSig2 signing code.
Changed components
src/musig.cppsrc/musig.hMuSig2 signing infrastructure (future callers of MuSig2SecNonce)Inspect captured patch +74 / −0
diff --git a/src/musig.cpp b/src/musig.cpp
index 85074796..c361a7ea 100644
--- a/src/musig.cpp
+++ b/src/musig.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <musig.h>
+#include <support/allocators/secure.h>
#include <secp256k1_musig.h>
@@ -62,3 +63,43 @@ CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey)
extpub.pubkey = pubkey;
return extpub;
}
+
+class MuSig2SecNonceImpl
+{
+private:
+ //! The actual secnonce itself
+ secure_unique_ptr<secp256k1_musig_secnonce> m_nonce;
+
+public:
+ MuSig2SecNonceImpl() : m_nonce{make_secure_unique<secp256k1_musig_secnonce>()} {}
+
+ // Delete copy constructors
+ MuSig2SecNonceImpl(const MuSig2SecNonceImpl&) = delete;
+ MuSig2SecNonceImpl& operator=(const MuSig2SecNonceImpl&) = delete;
+
+ secp256k1_musig_secnonce* Get() const { return m_nonce.get(); }
+ void Invalidate() { m_nonce.reset(); }
+ bool IsValid() { return m_nonce != nullptr; }
+};
+
+MuSig2SecNonce::MuSig2SecNonce() : m_impl{std::make_unique<MuSig2SecNonceImpl>()} {}
+
+MuSig2SecNonce::MuSig2SecNonce(MuSig2SecNonce&&) noexcept = default;
+MuSig2SecNonce& MuSig2SecNonce::operator=(MuSig2SecNonce&&) noexcept = default;
+
+MuSig2SecNonce::~MuSig2SecNonce() = default;
+
+secp256k1_musig_secnonce* MuSig2SecNonce::Get() const
+{
+ return m_impl->Get();
+}
+
+void MuSig2SecNonce::Invalidate()
+{
+ return m_impl->Invalidate();
+}
+
+bool MuSig2SecNonce::IsValid()
+{
+ return m_impl->IsValid();
+}
diff --git a/src/musig.h b/src/musig.h
index 0bc1f5ff..40da1f10 100644
--- a/src/musig.h
+++ b/src/musig.h
@@ -11,6 +11,8 @@
#include <vector>
struct secp256k1_musig_keyagg_cache;
+class MuSig2SecNonceImpl;
+struct secp256k1_musig_secnonce;
//! MuSig2 chaincode as defined by BIP 328
using namespace util::hex_literals;
@@ -26,4 +28,35 @@ std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkey
//! Construct the BIP 328 synthetic xpub for a pubkey
CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey);
+/**
+ * MuSig2SecNonce encapsulates a secret nonce in use in a MuSig2 signing session.
+ * Since this nonce persists outside of libsecp256k1 signing code, we must handle
+ * its construction and destruction ourselves.
+ * The secret nonce must be kept a secret, otherwise the private key may be leaked.
+ * As such, it needs to be treated in the same way that CKeys are treated.
+ * So this class handles the secure allocation of the secp256k1_musig_secnonce object
+ * that libsecp256k1 uses, and only gives out references to this object to avoid
+ * any possibility of copies being made. Furthermore, objects of this class are not
+ * copyable to avoid nonce reuse.
+*/
+class MuSig2SecNonce
+{
+private:
+ std::unique_ptr<MuSig2SecNonceImpl> m_impl;
+
+public:
+ MuSig2SecNonce();
+ MuSig2SecNonce(MuSig2SecNonce&&) noexcept;
+ MuSig2SecNonce& operator=(MuSig2SecNonce&&) noexcept;
+ ~MuSig2SecNonce();
+
+ // Delete copy constructors
+ MuSig2SecNonce(const MuSig2SecNonce&) = delete;
+ MuSig2SecNonce& operator=(const MuSig2SecNonce&) = delete;
+
+ secp256k1_musig_secnonce* Get() const;
+ void Invalidate();
+ bool IsValid();
+};
+
#endif // BITCOIN_MUSIG_H
Why this scored 42/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.