key: cleanse ChainCode on destruction
What changed, and why it matters
This commit is a defensive hardening change for Bitcoin Core's handling of BIP32 'chain codes'—secret-derived values used when creating child keys from a master key. Previously these values were stored as ordinary 256-bit numbers that could remain in memory after use. The change makes ChainCode a dedicated type that securely wipes its own memory when it goes out of scope, similar to how private keys are already handled. It is a preventive security improvement rather than a fix for an active exploit.
Treat as a low-risk defensive hardening patch. Review that all ChainCode construction paths remain constexpr-compatible where needed and that no unintended copies of chain codes are introduced elsewhere. No urgent deployment action is required beyond normal update cadence.
Security signals we found
memory_cleanse() added to destructor of sensitive cryptographic material
prevents potential information disclosure of BIP32 chain codes via memory scraping or core dumps
defensive hardening consistent with existing CKey memory-cleansing practice
no functional behavior change to key derivation or consensus logic
Evidence from the diff
The patch converts ChainCode from a typedef of uint256 into a base_blob<256> subclass whose destructor calls memory_cleanse(). This ensures chain codes held in CExtKey, CExtPubKey, local variables, and the MuSig2 constant are overwritten when destroyed. It also removes a duplicate typedef in pubkey.h and updates two fuzz-test call sites to construct ChainCode explicitly. The change aligns MUSIG_CHAINCODE’s type with its BIP328 semantic role and removes a GCC-14 consteval-lambda workaround.
Changed components
src/hash.h (ChainCode type definition)src/musig.cpp (MUSIG_CHAINCODE constant)src/pubkey.h (duplicate typedef removal)src/test/fuzz/key.cpp (fuzz test call sites)Inspect captured patch +12 / −9
diff --git a/src/hash.h b/src/hash.h
index 34486af6..b671761f 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -13,12 +13,20 @@
#include <prevector.h>
#include <serialize.h>
#include <span.h>
+#include <support/cleanse.h>
#include <uint256.h>
#include <string>
#include <vector>
-typedef uint256 ChainCode;
+/** A BIP32 chain code. Cleansed on destruction. */
+class ChainCode : public base_blob<256> {
+public:
+ constexpr ChainCode() = default;
+ constexpr explicit ChainCode(std::span<const unsigned char> vch) : base_blob<256>(vch) {}
+ constexpr explicit ChainCode(const base_blob<256>& b) : base_blob<256>(b) {}
+ ~ChainCode() { memory_cleanse(data(), size()); }
+};
/** A hasher class for Bitcoin's 256-bit hash (double SHA-256). */
class CHash256 {
diff --git a/src/musig.cpp b/src/musig.cpp
index 706874be..b04a26db 100644
--- a/src/musig.cpp
+++ b/src/musig.cpp
@@ -9,10 +9,7 @@
//! MuSig2 chaincode as defined by BIP 328
using namespace util::hex_literals;
-constexpr uint256 MUSIG_CHAINCODE{
- // Use immediate lambda to work around GCC-14 bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117966
- []() consteval { return uint256{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8}; }(),
-};
+const ChainCode MUSIG_CHAINCODE{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8};
static bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
{
diff --git a/src/pubkey.h b/src/pubkey.h
index 02ad7371..0391609e 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -27,8 +27,6 @@ public:
explicit CKeyID(const uint160& in) : uint160(in) {}
};
-typedef uint256 ChainCode;
-
/** An encapsulated public key. */
class CPubKey
{
diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp
index e4bedff8..19101ae8 100644
--- a/src/test/fuzz/key.cpp
+++ b/src/test/fuzz/key.cpp
@@ -85,7 +85,7 @@ FUZZ_TARGET(key, .init = initialize_key)
{
CKey child_key;
ChainCode child_chaincode;
- const bool ok = key.Derive(child_key, child_chaincode, 0, random_uint256);
+ const bool ok = key.Derive(child_key, child_chaincode, 0, ChainCode{random_uint256});
assert(ok);
assert(child_key.IsValid());
assert(!(child_key == key));
@@ -275,7 +275,7 @@ FUZZ_TARGET(key, .init = initialize_key)
{
CPubKey child_pubkey;
ChainCode child_chaincode;
- const bool ok = pubkey.Derive(child_pubkey, child_chaincode, 0, random_uint256);
+ const bool ok = pubkey.Derive(child_pubkey, child_chaincode, 0, ChainCode{random_uint256});
assert(ok);
assert(child_pubkey != pubkey);
assert(child_pubkey.IsCompressed());
Why this scored 49/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.