From b3a3f88346dfd218a049acec6a77166f319c70e8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 10 May 2026 12:49:50 +0200 Subject: [PATCH 1/2] crypto: cleanse HMAC stack buffers after use CHMAC_SHA256 and CHMAC_SHA512 leave two stack buffers populated on return: rkey[] holds K' XOR ipad after the constructor, and temp[] holds the inner-hash output after Finalize(). When the HMAC is keyed with sensitive material (chain code in BIP32Hash() in hash.cpp for BIP32 child key derivation; PRK in HKDF-Expand in hkdf_sha256_32.cpp, used for BIP324 transport keying), rkey is one constant XOR from that key, and temp is a one-way digest covering it. Cleanse both buffers with memory_cleanse(), matching the convention in chacha20.cpp and chacha20poly1305.cpp. No observable change for callers. --- src/crypto/hmac_sha256.cpp | 4 ++++ src/crypto/hmac_sha512.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/crypto/hmac_sha256.cpp b/src/crypto/hmac_sha256.cpp index a95ef70849b..0796bbeb327 100644 --- a/src/crypto/hmac_sha256.cpp +++ b/src/crypto/hmac_sha256.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -26,6 +27,8 @@ CHMAC_SHA256::CHMAC_SHA256(const unsigned char* key, size_t keylen) for (int n = 0; n < 64; n++) rkey[n] ^= 0x5c ^ 0x36; inner.Write(rkey, 64); + + memory_cleanse(rkey, sizeof(rkey)); } void CHMAC_SHA256::Finalize(unsigned char hash[OUTPUT_SIZE]) @@ -33,4 +36,5 @@ void CHMAC_SHA256::Finalize(unsigned char hash[OUTPUT_SIZE]) unsigned char temp[32]; inner.Finalize(temp); outer.Write(temp, 32).Finalize(hash); + memory_cleanse(temp, sizeof(temp)); } diff --git a/src/crypto/hmac_sha512.cpp b/src/crypto/hmac_sha512.cpp index f37e709d13c..0a9d1041a67 100644 --- a/src/crypto/hmac_sha512.cpp +++ b/src/crypto/hmac_sha512.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -26,6 +27,8 @@ CHMAC_SHA512::CHMAC_SHA512(const unsigned char* key, size_t keylen) for (int n = 0; n < 128; n++) rkey[n] ^= 0x5c ^ 0x36; inner.Write(rkey, 128); + + memory_cleanse(rkey, sizeof(rkey)); } void CHMAC_SHA512::Finalize(unsigned char hash[OUTPUT_SIZE]) @@ -33,4 +36,5 @@ void CHMAC_SHA512::Finalize(unsigned char hash[OUTPUT_SIZE]) unsigned char temp[64]; inner.Finalize(temp); outer.Write(temp, 64).Finalize(hash); + memory_cleanse(temp, sizeof(temp)); } From 21a1380c13470d28f53cc0b85d902693365d39d3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 12 May 2026 21:24:19 +0200 Subject: [PATCH 2/2] key: cleanse ChainCode on destruction HMAC primitives cleanse their internal stack buffers, but a caller's ChainCode remains populated in memory after use. Promote ChainCode from `typedef uint256` to a `base_blob<256>` subclass with a memory_cleanse() destructor, so chain codes in CExtKey, CExtPubKey, and local variables are cleansed on scope exit. Retype MUSIG_CHAINCODE from `constexpr uint256` to `const ChainCode` to match its BIP328 semantic role. Dropping `constexpr` (ChainCode is no longer a literal type) also removes the GCC-14 consteval lambda workaround. Remove the duplicate typedef in pubkey.h (which includes hash.h transitively). Two fuzz-test call sites in test/fuzz/key.cpp now construct the chain-code argument explicitly rather than relying on the typedef. --- src/hash.h | 10 +++++++++- src/musig.cpp | 5 +---- src/pubkey.h | 2 -- src/test/fuzz/key.cpp | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/hash.h b/src/hash.h index 34486af64a1..b671761fbb6 100644 --- a/src/hash.h +++ b/src/hash.h @@ -13,12 +13,20 @@ #include #include #include +#include #include #include #include -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 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 706874be2cf..b04a26db123 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& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache) { diff --git a/src/pubkey.h b/src/pubkey.h index 02ad7371a79..0391609ebcc 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 e4bedff8b51..19101ae81c2 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());