key: use static context for libsecp256k1 calls where applicable
What changed, and why it matters
This commit is a code-quality and performance cleanup, not a security fix. It changes many Bitcoin Core calls to the libsecp256k1 cryptography library so they use a shared, read-only 'static context' instead of a special signing context. The signing context is only needed for operations that actually multiply a secret key with a generator point, such as creating public keys or signing. Using the static context elsewhere is harmless and matches the library's documented recommendations. There is no indication this fixes an active vulnerability.
No immediate security action is required. Treat as a normal code-quality/refactoring commit. Reviewers may verify that the list of functions still using secp256k1_context_sign matches the commit message and upstream libsecp256k1 documentation.
Security signals we found
Change is framed by the author as an API-correctness/performance optimization, not a security fix
No secret-key material is exposed or mishandled in the diff
Functions still requiring the signing context are explicitly preserved as such
No bounds-check, null-check, or validation changes are present
No incident, CVE, or advisory references are supplied
Evidence from the diff
The patch switches secp256k1_context_sign to secp256k1_context_static for functions that do not perform secret-key-dependent generator multiplication: secp256k1_ec_seckey_verify, secp256k1_ec_pubkey_serialize, secp256k1_ecdsa_signature_serialize_compact/der, secp256k1_ecdsa_recoverable_signature_serialize_compact, ec_seckey_import_der, secp256k1_ec_seckey_tweak_add, secp256k1_ellswift_xdh, secp256k1_keypair_xonly_pub, and secp256k1_xonly_pubkey_serialize. The commit message explicitly lists the only calls that still require the signing context. The change is consistent with upstream libsecp256k1 API documentation and reduces unnecessary context dependence. No memory-safety, correctness, or cryptographic flaws are introduced or fixed by the diff itself.
Changed components
src/key.cppsrc/test/fuzz/secp256k1_ec_seckey_import_export_der.cppsrc/test/key_tests.cppInspect captured patch +16 / −16
diff --git a/src/key.cpp b/src/key.cpp
index 01fa3d27..9504468f 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -155,7 +155,7 @@ int ec_seckey_export_der(const secp256k1_context *ctx, unsigned char *seckey, si
}
bool CKey::Check(const unsigned char *vch) {
- return secp256k1_ec_seckey_verify(secp256k1_context_sign, vch);
+ return secp256k1_ec_seckey_verify(secp256k1_context_static, vch);
}
void CKey::MakeNewKey(bool fCompressedIn) {
@@ -186,7 +186,7 @@ CPubKey CKey::GetPubKey() const {
CPubKey result;
int ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, UCharCast(begin()));
assert(ret);
- secp256k1_ec_pubkey_serialize(secp256k1_context_sign, (unsigned char*)result.begin(), &clen, &pubkey, fCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
+ secp256k1_ec_pubkey_serialize(secp256k1_context_static, (unsigned char*)result.begin(), &clen, &pubkey, fCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
assert(result.size() == clen);
assert(result.IsValid());
return result;
@@ -196,7 +196,7 @@ CPubKey CKey::GetPubKey() const {
bool SigHasLowR(const secp256k1_ecdsa_signature* sig)
{
unsigned char compact_sig[64];
- secp256k1_ecdsa_signature_serialize_compact(secp256k1_context_sign, compact_sig, sig);
+ secp256k1_ecdsa_signature_serialize_compact(secp256k1_context_static, compact_sig, sig);
// In DER serialization, all values are interpreted as big-endian, signed integers. The highest bit in the integer indicates
// its signed-ness; 0 is positive, 1 is negative. When the value is interpreted as a negative integer, it must be converted
@@ -222,7 +222,7 @@ bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& vchSig, bool gr
ret = secp256k1_ecdsa_sign(secp256k1_context_sign, &sig, hash.begin(), UCharCast(begin()), secp256k1_nonce_function_rfc6979, extra_entropy);
}
assert(ret);
- secp256k1_ecdsa_signature_serialize_der(secp256k1_context_sign, vchSig.data(), &nSigLen, &sig);
+ secp256k1_ecdsa_signature_serialize_der(secp256k1_context_static, vchSig.data(), &nSigLen, &sig);
vchSig.resize(nSigLen);
// Additional verification step to prevent using a potentially corrupted signature
secp256k1_pubkey pk;
@@ -254,7 +254,7 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig)
secp256k1_ecdsa_recoverable_signature rsig;
int ret = secp256k1_ecdsa_sign_recoverable(secp256k1_context_sign, &rsig, hash.begin(), UCharCast(begin()), secp256k1_nonce_function_rfc6979, nullptr);
assert(ret);
- ret = secp256k1_ecdsa_recoverable_signature_serialize_compact(secp256k1_context_sign, &vchSig[1], &rec, &rsig);
+ ret = secp256k1_ecdsa_recoverable_signature_serialize_compact(secp256k1_context_static, &vchSig[1], &rec, &rsig);
assert(ret);
assert(rec != -1);
vchSig[0] = 27 + rec + (fCompressed ? 4 : 0);
@@ -277,7 +277,7 @@ bool CKey::SignSchnorr(const uint256& hash, std::span<unsigned char> sig, const
bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) {
MakeKeyData();
- if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size())) {
+ if (!ec_seckey_import_der(secp256k1_context_static, (unsigned char*)begin(), seckey.data(), seckey.size())) {
ClearKeyData();
return false;
}
@@ -303,7 +303,7 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
}
memcpy(ccChild.begin(), vout.data()+32, 32);
keyChild.Set(begin(), begin() + 32, true);
- bool ret = secp256k1_ec_seckey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), vout.data());
+ bool ret = secp256k1_ec_seckey_tweak_add(secp256k1_context_static, (unsigned char*)keyChild.begin(), vout.data());
if (!ret) keyChild.ClearKeyData();
return ret;
}
@@ -331,7 +331,7 @@ ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, c
ECDHSecret output;
// BIP324 uses the initiator as party A, and the responder as party B. Remap the inputs
// accordingly:
- bool success = secp256k1_ellswift_xdh(secp256k1_context_sign,
+ bool success = secp256k1_ellswift_xdh(secp256k1_context_static,
UCharCast(output.data()),
UCharCast(initiating ? our_ellswift.data() : their_ellswift.data()),
UCharCast(initiating ? their_ellswift.data() : our_ellswift.data()),
@@ -415,8 +415,8 @@ KeyPair::KeyPair(const CKey& key, const uint256* merkle_root)
if (success && merkle_root) {
secp256k1_xonly_pubkey pubkey;
unsigned char pubkey_bytes[32];
- assert(secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, keypair));
- assert(secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey));
+ assert(secp256k1_keypair_xonly_pub(secp256k1_context_static, &pubkey, nullptr, keypair));
+ assert(secp256k1_xonly_pubkey_serialize(secp256k1_context_static, pubkey_bytes, &pubkey));
uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root);
success = secp256k1_keypair_xonly_tweak_add(secp256k1_context_static, keypair, tweak.data());
}
diff --git a/src/test/fuzz/secp256k1_ec_seckey_import_export_der.cpp b/src/test/fuzz/secp256k1_ec_seckey_import_export_der.cpp
index 9f84ac97..585cfcd1 100644
--- a/src/test/fuzz/secp256k1_ec_seckey_import_export_der.cpp
+++ b/src/test/fuzz/secp256k1_ec_seckey_import_export_der.cpp
@@ -17,22 +17,22 @@ int ec_seckey_export_der(const secp256k1_context* ctx, unsigned char* seckey, si
FUZZ_TARGET(secp256k1_ec_seckey_import_export_der)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
- secp256k1_context* secp256k1_context_sign = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
{
std::vector<uint8_t> out32(32);
- (void)ec_seckey_import_der(secp256k1_context_sign, out32.data(), ConsumeFixedLengthByteVector(fuzzed_data_provider, CKey::SIZE).data(), CKey::SIZE);
+ (void)ec_seckey_import_der(secp256k1_context_static, out32.data(), ConsumeFixedLengthByteVector(fuzzed_data_provider, CKey::SIZE).data(), CKey::SIZE);
}
{
std::vector<uint8_t> seckey(CKey::SIZE);
const std::vector<uint8_t> key32 = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
size_t seckeylen = CKey::SIZE;
const bool compressed = fuzzed_data_provider.ConsumeBool();
+ secp256k1_context* secp256k1_context_sign = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
const bool exported = ec_seckey_export_der(secp256k1_context_sign, seckey.data(), &seckeylen, key32.data(), compressed);
+ secp256k1_context_destroy(secp256k1_context_sign);
if (exported) {
std::vector<uint8_t> out32(32);
- const bool imported = ec_seckey_import_der(secp256k1_context_sign, out32.data(), seckey.data(), seckey.size()) == 1;
+ const bool imported = ec_seckey_import_der(secp256k1_context_static, out32.data(), seckey.data(), seckey.size()) == 1;
assert(imported && key32 == out32);
}
}
- secp256k1_context_destroy(secp256k1_context_sign);
}
diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp
index 1b60a9c9..acced716 100644
--- a/src/test/key_tests.cpp
+++ b/src/test/key_tests.cpp
@@ -376,9 +376,9 @@ BOOST_AUTO_TEST_CASE(key_schnorr_tweak_smoke_test)
secp256k1_keypair keypair;
BOOST_CHECK(secp256k1_keypair_create(secp256k1_context_sign, &keypair, UCharCast(key.begin())));
secp256k1_xonly_pubkey xonly_pubkey;
- BOOST_CHECK(secp256k1_keypair_xonly_pub(secp256k1_context_sign, &xonly_pubkey, nullptr, &keypair));
+ BOOST_CHECK(secp256k1_keypair_xonly_pub(secp256k1_context_static, &xonly_pubkey, nullptr, &keypair));
unsigned char xonly_bytes[32];
- BOOST_CHECK(secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, xonly_bytes, &xonly_pubkey));
+ BOOST_CHECK(secp256k1_xonly_pubkey_serialize(secp256k1_context_static, xonly_bytes, &xonly_pubkey));
uint256 tweak_old = XOnlyPubKey(xonly_bytes).ComputeTapTweakHash(&merkle_root);
// CPubKey
Why this scored 18/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.