Add API to override SHA256 compression at runtime

This introduces `secp256k1_context_set_sha256_compression()`,
which allows users to provide their own SHA256 block-compression
function at runtime.

This is useful in setups where the fastest implementation can only
be determined dynamically based on the available CPU features, and
rebuilding the library is not possible.

The callback is installed on the `secp256k1_context` and is then used
by all operations that compute SHA256 hashes. As part of the setup,
the library performs sanity checks to ensure that the supplied
function is equivalent to the default transform.

Passing NULL to the callback setter restores the built-in
implementation.
This commit is contained in:
furszy
2026-01-23 13:41:55 -05:00
parent fdb6a91a5e
commit 0753f8b909
11 changed files with 247 additions and 21 deletions

View File

@@ -45,10 +45,6 @@ int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *output, const se
ARG_CHECK(point != NULL);
ARG_CHECK(scalar != NULL);
if (hashfp == NULL) {
hashfp = secp256k1_ecdh_hash_function_default;
}
secp256k1_pubkey_load(ctx, &pt, point);
secp256k1_scalar_set_b32(&s, scalar, &overflow);
@@ -64,7 +60,12 @@ int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *output, const se
secp256k1_fe_get_b32(x, &pt.x);
secp256k1_fe_get_b32(y, &pt.y);
ret = hashfp(output, x, y, data);
if (hashfp == NULL) {
/* Use ctx-aware function by default */
ret = ecdh_hash_function_sha256_impl(secp256k1_get_hash_context(ctx), output, x, y, data);
} else {
ret = hashfp(output, x, y, data);
}
secp256k1_memclear_explicit(x, sizeof(x));
secp256k1_memclear_explicit(y, sizeof(y));

View File

@@ -8,6 +8,7 @@
#define SECP256K1_MODULE_ECDH_TESTS_H
#include "../../unit_test.h"
#include "../../testutil.h"
static int ecdh_hash_function_test_xpassthru(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) {
(void)y;
@@ -90,6 +91,30 @@ static void test_ecdh_generator_basepoint(void) {
}
}
DEFINE_SHA256_TRANSFORM_PROBE(sha256_ecdh)
static void test_ecdh_ctx_sha256(void) {
/* Check ctx-provided SHA256 compression override takes effect */
secp256k1_context *ctx = secp256k1_context_clone(CTX);
unsigned char out_default[65], out_custom[65];
const unsigned char sk[32] = {1};
secp256k1_pubkey pubkey;
CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, sk) == 1);
/* Default behavior */
CHECK(secp256k1_ecdh(ctx, out_default, &pubkey, sk, NULL, NULL) == 1);
CHECK(!sha256_ecdh_called);
/* Override SHA256 compression directly, bypassing the ctx setter sanity checks */
ctx->hash_ctx.fn_sha256_compression = sha256_ecdh;
CHECK(secp256k1_ecdh(ctx, out_custom, &pubkey, sk, NULL, NULL) == 1);
/* Outputs must differ if custom compression was used */
CHECK(secp256k1_memcmp_var(out_default, out_custom, 32) != 0);
CHECK(sha256_ecdh_called);
secp256k1_context_destroy(ctx);
}
static void test_bad_scalar(void) {
unsigned char s_zero[32] = { 0 };
unsigned char s_overflow[32] = { 0 };
@@ -187,6 +212,7 @@ static const struct tf_test_entry tests_ecdh[] = {
CASE1(test_bad_scalar),
CASE1(test_result_basepoint),
CASE1(test_ecdh_wycheproof),
CASE1(test_ecdh_ctx_sha256),
};
#endif /* SECP256K1_MODULE_ECDH_TESTS_H */