From ccc4c64ce072723a3921942771ba5f470e3d5218 Mon Sep 17 00:00:00 2001 From: Amir Khordadi Date: Fri, 28 Aug 2026 10:11:25 -0400 Subject: [PATCH] Cache P-256 curve parameters per thread in p256_verify_impl DL_GroupParameters_EC construction decodes the secp256r1 domain parameters (OID lookup, hex-decoding the curve constants, Montgomery conversion) on every call to p256_verify_impl, costing ~11.6us per invocation. Cache the parameters in a thread_local instead. thread_local rather than a shared static because Crypto++'s ECP writes mutable scratch state under Add and the small-scalar Multiply fallback, execution is fiber-parallel across OS threads, and the small-scalar path is reachable from attacker-controlled r and s; a single shared object would be a data race. This matches the thread_local secp256k1_context already used by ecrecover_impl. Also check for the (0, 0) infinity public key before evaluating the curve equation: both orders reject identically ((0, 0) is not on the curve since b != 0 for P-256), and doing the cheap check first skips the more expensive point validation. Measured with all four variants compiled into one binary over the 782 geth/wycheproof vectors (identical verdicts on every vector): full valid verify 695.3 -> 684.2us (-1.6%), early-reject inputs 14.3 -> 2.7us (5.3x), infinity inputs 3.25 -> 3.04us from the reorder. No consensus-visible behavior change. Raised by @guidovranken in #1646. Co-Authored-By: Claude Fable 5 --- category/execution/ethereum/precompiles_impl.hpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/category/execution/ethereum/precompiles_impl.hpp b/category/execution/ethereum/precompiles_impl.hpp index 4e673c8c79..6e4358f4b7 100644 --- a/category/execution/ethereum/precompiles_impl.hpp +++ b/category/execution/ethereum/precompiles_impl.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -383,7 +384,8 @@ p256_verify_impl(byte_string_view const input, std::span const out) Integer qx(input.data() + 96, 32); Integer qy(input.data() + 128, 32); - DL_GroupParameters_EC params(ASN1::secp256r1()); + MONAD_THREAD_LOCAL DL_GroupParameters_EC const params( + ASN1::secp256r1()); auto const &ec = params.GetCurve(); auto const &n = params.GetSubgroupOrder(); auto const p_mod = ec.FieldSize(); @@ -407,13 +409,14 @@ p256_verify_impl(byte_string_view const input, std::span const out) return PrecompileImplResult::failure(); } - // if qy^2 ≢ qx^3 + a*qx + b (mod p): return - if (!ec.VerifyPoint({qx, qy})) { + // if (qx, qy) == (0, 0): return + // (cheaper, check first) + if (qx.IsZero() && qy.IsZero()) { return PrecompileImplResult::failure(); } - // if (qx, qy) == (0, 0): return - if (qx.IsZero() && qy.IsZero()) { + // if qy^2 ≢ qx^3 + a*qx + b (mod p): return + if (!ec.VerifyPoint({qx, qy})) { return PrecompileImplResult::failure(); }