Rename and clear var containing k or -k
What changed, and why it matters
This commit fixes a small but real security hygiene issue in the Schnorr signature code. A 32-byte buffer that holds the secret signing nonce (the random-like value 'k') was not being wiped from memory after use. The patch renames the buffer to make its sensitive role obvious and adds an explicit clear operation so the nonce does not linger in stack memory after the function returns. If an attacker could read leftover process memory, the un-cleared nonce could help forge signatures for that message/key. The fix is straightforward and aligns the Schnorr code with the existing ECDSA behavior.
Treat as a low-to-moderate defensive-security fix. Review whether any released versions shipped with the uncleared buffer and consider a backport or advisory if the library guarantees memory scrubbing of sensitive intermediates. No immediate emergency response is warranted because exploitation requires an attacker to read process memory, but the patch should be included in the next maintenance release.
Security signals we found
Sensitive cryptographic material (Schnorr nonce k/-k) left uncleared in stack memory
Addition of explicit memory clearing via secp256k1_memclear
Variable renamed to nonce32 to signal sensitive contents
Alignment with existing ECDSA sign_inner cleanup pattern
Potential information disclosure / nonce reuse risk if memory is exposed
Evidence from the diff
In secp256k1_schnorrsig_sign_internal, the local 32-byte array previously named buf stored the output of noncefp (the nonce k, or its negation -k). After converting it into scalar k, the buffer was never scrubbed. The commit renames buf to nonce32 and adds secp256k1_memclear(nonce32, sizeof(nonce32)) in the cleanup block alongside existing clears for k, sk, and seckey. This prevents the raw nonce bytes from remaining in the stack frame after the function returns. The change mirrors the handling in the ECDSA sign_inner implementation.
Changed components
src/modules/schnorrsig/main_impl.hsecp256k1_schnorrsig_sign_internal functionSchnorr BIP-340 signing pathInspect captured patch +4 / −3
diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h
index 2ed7be6..13d9244 100644
--- a/src/modules/schnorrsig/main_impl.h
+++ b/src/modules/schnorrsig/main_impl.h
@@ -139,7 +139,7 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
secp256k1_gej rj;
secp256k1_ge pk;
secp256k1_ge r;
- unsigned char buf[32] = { 0 };
+ unsigned char nonce32[32] = { 0 };
unsigned char pk_buf[32];
unsigned char seckey[32];
int ret = 1;
@@ -164,8 +164,8 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
secp256k1_scalar_get_b32(seckey, &sk);
secp256k1_fe_get_b32(pk_buf, &pk.x);
- ret &= !!noncefp(buf, msg, msglen, seckey, pk_buf, bip340_algo, sizeof(bip340_algo), ndata);
- secp256k1_scalar_set_b32(&k, buf, NULL);
+ ret &= !!noncefp(nonce32, msg, msglen, seckey, pk_buf, bip340_algo, sizeof(bip340_algo), ndata);
+ secp256k1_scalar_set_b32(&k, nonce32, NULL);
ret &= !secp256k1_scalar_is_zero(&k);
secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);
@@ -191,6 +191,7 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
secp256k1_scalar_clear(&k);
secp256k1_scalar_clear(&sk);
secp256k1_memclear(seckey, sizeof(seckey));
+ secp256k1_memclear(nonce32, sizeof(nonce32));
secp256k1_gej_clear(&rj);
return ret;
Why this scored 44/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.