Merge bitcoin-core/secp256k1#1777: Make SHA256 compression runtime pluggable
4d92a083bcsha256: speed up writes using multi-block compression (furszy)0753f8b909Add API to override SHA256 compression at runtime (furszy)fdb6a91a5eIntroduce hash context to support pluggable SHA256 compression (furszy) Pull request description: Tackling the long-standing request #702. Right now we ship our own SHA256 implementation, a standard baseline version that does not take advantage of any hardware-optimized instruction, and it cannot be accessed by the embedding application - it is for internal usage only. This means embedding applications often have to implement or include a different version for their use cases, wasting space on constrained environments, and in performance-sensitive setups it forces them to use a slower path than what the platform provides. Many projects already rely on tuned SHA-NI / ARMv8 / or other hardware-optimized code, so always using the baseline implementation we ship within the library is not ideal. These changes allow users to supply their own SHA256 compression function at runtime, while preserving the existing default behavior for everyone else. This is primarily intended for environments where the available SHA256 implementation is detected dynamically and recompiling the library with a different implementation is not feasible (equivalent build-time functionality will come in a follow-up PR). It introduces a new API: ```C89 secp256k1_context_set_sha256_transform_callback(ctx, fn_transform) ``` This function installs the optimized SHA256 compression into the `secp256k1_context`, which is then used by all internal computations. Important: The provided function is verified to be output-equivalent to the original one. As a quick example, using this functionality in Bitcoin-Core will be very straightforward:f68bef06d9ACKs for top commit: real-or-random: ACK4d92a083bcw0xlt: ACK4d92a083bctheStack: ACK4d92a083bcTree-SHA512: 058e2e82071f1ca77254b684458292c621e60d65bbcc5500574429717e7db75bc9f3221129fafd11eb5d33e666a5efec5e9844460d3b194ef3b6b16f2df28fb9
This commit is contained in:
@@ -307,7 +307,7 @@ static int secp256k1_ellswift_xswiftec_inv_var(secp256k1_fe *t, const secp256k1_
|
||||
* hasher is a SHA256 object to which an incrementing 4-byte counter is written to generate randomness.
|
||||
* Writing 13 bytes (4 bytes for counter, plus 9 bytes for the SHA256 padding) cannot cross a
|
||||
* 64-byte block size boundary (to make sure it only triggers a single SHA256 compression). */
|
||||
static void secp256k1_ellswift_prng(unsigned char* out32, const secp256k1_sha256 *hasher, uint32_t cnt) {
|
||||
static void secp256k1_ellswift_prng(const secp256k1_hash_ctx *hash_ctx, unsigned char* out32, const secp256k1_sha256 *hasher, uint32_t cnt) {
|
||||
secp256k1_sha256 hash = *hasher;
|
||||
unsigned char buf4[4];
|
||||
#ifdef VERIFY
|
||||
@@ -317,8 +317,8 @@ static void secp256k1_ellswift_prng(unsigned char* out32, const secp256k1_sha256
|
||||
buf4[1] = cnt >> 8;
|
||||
buf4[2] = cnt >> 16;
|
||||
buf4[3] = cnt >> 24;
|
||||
secp256k1_sha256_write(&hash, buf4, 4);
|
||||
secp256k1_sha256_finalize(&hash, out32);
|
||||
secp256k1_sha256_write(hash_ctx, &hash, buf4, 4);
|
||||
secp256k1_sha256_finalize(hash_ctx, &hash, out32);
|
||||
|
||||
/* Writing and finalizing together should trigger exactly one SHA256 compression. */
|
||||
VERIFY_CHECK(((hash.bytes) >> 6) == (blocks + 1));
|
||||
@@ -330,7 +330,7 @@ static void secp256k1_ellswift_prng(unsigned char* out32, const secp256k1_sha256
|
||||
* needs encoding.
|
||||
*
|
||||
* hasher is a hasher in the secp256k1_ellswift_prng sense, with the same restrictions. */
|
||||
static void secp256k1_ellswift_xelligatorswift_var(unsigned char *u32, secp256k1_fe *t, const secp256k1_fe *x, const secp256k1_sha256 *hasher) {
|
||||
static void secp256k1_ellswift_xelligatorswift_var(const secp256k1_context *ctx, unsigned char *u32, secp256k1_fe *t, const secp256k1_fe *x, const secp256k1_sha256 *hasher) {
|
||||
/* Pool of 3-bit branch values. */
|
||||
unsigned char branch_hash[32];
|
||||
/* Number of 3-bit values in branch_hash left. */
|
||||
@@ -346,14 +346,14 @@ static void secp256k1_ellswift_xelligatorswift_var(unsigned char *u32, secp256k1
|
||||
secp256k1_fe u;
|
||||
/* If the pool of branch values is empty, populate it. */
|
||||
if (branches_left == 0) {
|
||||
secp256k1_ellswift_prng(branch_hash, hasher, cnt++);
|
||||
secp256k1_ellswift_prng(secp256k1_get_hash_context(ctx), branch_hash, hasher, cnt++);
|
||||
branches_left = 64;
|
||||
}
|
||||
/* Take a 3-bit branch value from the branch pool (top bit is discarded). */
|
||||
--branches_left;
|
||||
branch = (branch_hash[branches_left >> 1] >> ((branches_left & 1) << 2)) & 7;
|
||||
/* Compute a new u value by hashing. */
|
||||
secp256k1_ellswift_prng(u32, hasher, cnt++);
|
||||
secp256k1_ellswift_prng(secp256k1_get_hash_context(ctx), u32, hasher, cnt++);
|
||||
/* overflow is not a problem (we prefer uniform u32 over uniform u). */
|
||||
secp256k1_fe_set_b32_mod(&u, u32);
|
||||
/* Since u is the output of a hash, it should practically never be 0. We could apply the
|
||||
@@ -372,8 +372,8 @@ static void secp256k1_ellswift_xelligatorswift_var(unsigned char *u32, secp256k1
|
||||
* as input, and returns an encoding that matches the provided Y coordinate rather than a random
|
||||
* one.
|
||||
*/
|
||||
static void secp256k1_ellswift_elligatorswift_var(unsigned char *u32, secp256k1_fe *t, const secp256k1_ge *p, const secp256k1_sha256 *hasher) {
|
||||
secp256k1_ellswift_xelligatorswift_var(u32, t, &p->x, hasher);
|
||||
static void secp256k1_ellswift_elligatorswift_var(const secp256k1_context *ctx, unsigned char *u32, secp256k1_fe *t, const secp256k1_ge *p, const secp256k1_sha256 *hasher) {
|
||||
secp256k1_ellswift_xelligatorswift_var(ctx, u32, t, &p->x, hasher);
|
||||
secp256k1_fe_normalize_var(t);
|
||||
if (secp256k1_fe_is_odd(t) != secp256k1_fe_is_odd(&p->y)) {
|
||||
secp256k1_fe_negate(t, t, 1);
|
||||
@@ -406,11 +406,11 @@ int secp256k1_ellswift_encode(const secp256k1_context *ctx, unsigned char *ell64
|
||||
* BIP340 tagged hash with tag "secp256k1_ellswift_encode". */
|
||||
secp256k1_ellswift_sha256_init_encode(&hash);
|
||||
secp256k1_eckey_pubkey_serialize33(&p, p64);
|
||||
secp256k1_sha256_write(&hash, p64, sizeof(p64));
|
||||
secp256k1_sha256_write(&hash, rnd32, 32);
|
||||
secp256k1_sha256_write(secp256k1_get_hash_context(ctx), &hash, p64, sizeof(p64));
|
||||
secp256k1_sha256_write(secp256k1_get_hash_context(ctx), &hash, rnd32, 32);
|
||||
|
||||
/* Compute ElligatorSwift encoding and construct output. */
|
||||
secp256k1_ellswift_elligatorswift_var(ell64, &t, &p, &hash); /* puts u in ell64[0..32] */
|
||||
secp256k1_ellswift_elligatorswift_var(ctx, ell64, &t, &p, &hash); /* puts u in ell64[0..32] */
|
||||
secp256k1_fe_get_b32(ell64 + 32, &t); /* puts t in ell64[32..64] */
|
||||
return 1;
|
||||
}
|
||||
@@ -452,13 +452,13 @@ int secp256k1_ellswift_create(const secp256k1_context *ctx, unsigned char *ell64
|
||||
/* Set up hasher state. The used RNG is H(privkey || "\x00"*32 [|| auxrnd32] || cnt++),
|
||||
* using BIP340 tagged hash with tag "secp256k1_ellswift_create". */
|
||||
secp256k1_ellswift_sha256_init_create(&hash);
|
||||
secp256k1_sha256_write(&hash, seckey32, 32);
|
||||
secp256k1_sha256_write(&hash, zero32, sizeof(zero32));
|
||||
secp256k1_sha256_write(secp256k1_get_hash_context(ctx), &hash, seckey32, 32);
|
||||
secp256k1_sha256_write(secp256k1_get_hash_context(ctx), &hash, zero32, sizeof(zero32));
|
||||
secp256k1_declassify(ctx, &hash, sizeof(hash)); /* private key is hashed now */
|
||||
if (auxrnd32) secp256k1_sha256_write(&hash, auxrnd32, 32);
|
||||
if (auxrnd32) secp256k1_sha256_write(secp256k1_get_hash_context(ctx), &hash, auxrnd32, 32);
|
||||
|
||||
/* Compute ElligatorSwift encoding and construct output. */
|
||||
secp256k1_ellswift_elligatorswift_var(ell64, &t, &p, &hash); /* puts u in ell64[0..32] */
|
||||
secp256k1_ellswift_elligatorswift_var(ctx, ell64, &t, &p, &hash); /* puts u in ell64[0..32] */
|
||||
secp256k1_fe_get_b32(ell64 + 32, &t); /* puts t in ell64[32..64] */
|
||||
|
||||
secp256k1_memczero(ell64, 64, !ret);
|
||||
@@ -482,20 +482,24 @@ int secp256k1_ellswift_decode(const secp256k1_context *ctx, secp256k1_pubkey *pu
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ellswift_xdh_hash_function_prefix(unsigned char *output, const unsigned char *x32, const unsigned char *ell_a64, const unsigned char *ell_b64, void *data) {
|
||||
static int ellswift_xdh_hash_function_prefix_impl(const secp256k1_hash_ctx *hash_ctx, unsigned char *output, const unsigned char *x32, const unsigned char *ell_a64, const unsigned char *ell_b64, void *data) {
|
||||
secp256k1_sha256 sha;
|
||||
|
||||
secp256k1_sha256_initialize(&sha);
|
||||
secp256k1_sha256_write(&sha, data, 64);
|
||||
secp256k1_sha256_write(&sha, ell_a64, 64);
|
||||
secp256k1_sha256_write(&sha, ell_b64, 64);
|
||||
secp256k1_sha256_write(&sha, x32, 32);
|
||||
secp256k1_sha256_finalize(&sha, output);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, data, 64);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, ell_a64, 64);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, ell_b64, 64);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, x32, 32);
|
||||
secp256k1_sha256_finalize(hash_ctx, &sha, output);
|
||||
secp256k1_sha256_clear(&sha);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ellswift_xdh_hash_function_prefix(unsigned char *output, const unsigned char *x32, const unsigned char *ell_a64, const unsigned char *ell_b64, void *data) {
|
||||
return ellswift_xdh_hash_function_prefix_impl(secp256k1_get_hash_context(secp256k1_context_static), output, x32, ell_a64, ell_b64, data);
|
||||
}
|
||||
|
||||
/** Set hash state to the BIP340 tagged hash midstate for "bip324_ellswift_xonly_ecdh". */
|
||||
static void secp256k1_ellswift_sha256_init_bip324(secp256k1_sha256* hash) {
|
||||
static const uint32_t midstate[8] = {
|
||||
@@ -505,21 +509,25 @@ static void secp256k1_ellswift_sha256_init_bip324(secp256k1_sha256* hash) {
|
||||
secp256k1_sha256_initialize_midstate(hash, 64, midstate);
|
||||
}
|
||||
|
||||
static int ellswift_xdh_hash_function_bip324(unsigned char* output, const unsigned char *x32, const unsigned char *ell_a64, const unsigned char *ell_b64, void *data) {
|
||||
static int ellswift_xdh_hash_function_bip324_impl(const secp256k1_hash_ctx *hash_ctx, unsigned char* output, const unsigned char *x32, const unsigned char *ell_a64, const unsigned char *ell_b64, void *data) {
|
||||
secp256k1_sha256 sha;
|
||||
|
||||
(void)data;
|
||||
|
||||
secp256k1_ellswift_sha256_init_bip324(&sha);
|
||||
secp256k1_sha256_write(&sha, ell_a64, 64);
|
||||
secp256k1_sha256_write(&sha, ell_b64, 64);
|
||||
secp256k1_sha256_write(&sha, x32, 32);
|
||||
secp256k1_sha256_finalize(&sha, output);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, ell_a64, 64);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, ell_b64, 64);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, x32, 32);
|
||||
secp256k1_sha256_finalize(hash_ctx, &sha, output);
|
||||
secp256k1_sha256_clear(&sha);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ellswift_xdh_hash_function_bip324(unsigned char* output, const unsigned char *x32, const unsigned char *ell_a64, const unsigned char *ell_b64, void *data) {
|
||||
return ellswift_xdh_hash_function_bip324_impl(secp256k1_get_hash_context(secp256k1_context_static), output, x32, ell_a64, ell_b64, data);
|
||||
}
|
||||
|
||||
const secp256k1_ellswift_xdh_hash_function secp256k1_ellswift_xdh_hash_function_prefix = ellswift_xdh_hash_function_prefix;
|
||||
const secp256k1_ellswift_xdh_hash_function secp256k1_ellswift_xdh_hash_function_bip324 = ellswift_xdh_hash_function_bip324;
|
||||
|
||||
@@ -554,8 +562,14 @@ int secp256k1_ellswift_xdh(const secp256k1_context *ctx, unsigned char *output,
|
||||
secp256k1_fe_normalize(&px);
|
||||
secp256k1_fe_get_b32(sx, &px);
|
||||
|
||||
/* Invoke hasher */
|
||||
ret = hashfp(output, sx, ell_a64, ell_b64, data);
|
||||
/* Invoke hasher. Use ctx-aware function by default */
|
||||
if (hashfp == secp256k1_ellswift_xdh_hash_function_bip324) {
|
||||
ret = ellswift_xdh_hash_function_bip324_impl(secp256k1_get_hash_context(ctx), output, sx, ell_a64, ell_b64, data);
|
||||
} else if (hashfp == secp256k1_ellswift_xdh_hash_function_prefix) {
|
||||
ret = ellswift_xdh_hash_function_prefix_impl(secp256k1_get_hash_context(ctx), output, sx, ell_a64, ell_b64, data);
|
||||
} else {
|
||||
ret = hashfp(output, sx, ell_a64, ell_b64, data);
|
||||
}
|
||||
|
||||
secp256k1_memclear_explicit(sx, sizeof(sx));
|
||||
secp256k1_fe_clear(&px);
|
||||
|
||||
@@ -432,9 +432,44 @@ void ellswift_xdh_correctness_tests(void) {
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_SHA256_TRANSFORM_PROBE(sha256_ellswift_xdh)
|
||||
void ellswift_xdh_ctx_sha256_tests(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 skA[32] = {1}, skB[32] = {2};
|
||||
unsigned char keyA[64], keyB[64], data[64] = {0};
|
||||
const secp256k1_ellswift_xdh_hash_function hash_funcs[2] = {secp256k1_ellswift_xdh_hash_function_bip324, secp256k1_ellswift_xdh_hash_function_prefix};
|
||||
int i;
|
||||
|
||||
CHECK(secp256k1_ellswift_create(ctx, keyA, skA, NULL));
|
||||
CHECK(secp256k1_ellswift_create(ctx, keyB, skB, NULL));
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
const secp256k1_ellswift_xdh_hash_function hash_fn = hash_funcs[i];
|
||||
/* Default behavior. No ctx-provided SHA256 compression */
|
||||
CHECK(secp256k1_ellswift_xdh(ctx, out_default, keyA, keyB, skA, 0, hash_fn, data));
|
||||
CHECK(!sha256_ellswift_xdh_called);
|
||||
|
||||
/* Override SHA256 compression directly, bypassing the ctx setter sanity checks */
|
||||
ctx->hash_ctx.fn_sha256_compression = sha256_ellswift_xdh;
|
||||
CHECK(secp256k1_ellswift_xdh(ctx, out_custom, keyA, keyB, skA, 0, hash_fn, data));
|
||||
CHECK(sha256_ellswift_xdh_called);
|
||||
/* Outputs must differ if custom compression was used */
|
||||
CHECK(secp256k1_memcmp_var(out_default, out_custom, 32) != 0);
|
||||
|
||||
/* Restore defaults */
|
||||
sha256_ellswift_xdh_called = 0;
|
||||
secp256k1_context_set_sha256_compression(ctx, NULL);
|
||||
}
|
||||
|
||||
secp256k1_context_destroy(ctx);
|
||||
}
|
||||
|
||||
/* Test hash initializers */
|
||||
void ellswift_hash_init_tests(void) {
|
||||
secp256k1_sha256 sha_optimized;
|
||||
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(CTX);
|
||||
/* "secp256k1_ellswift_encode" */
|
||||
static const unsigned char encode_tag[] = {'s', 'e', 'c', 'p', '2', '5', '6', 'k', '1', '_', 'e', 'l', 'l', 's', 'w', 'i', 'f', 't', '_', 'e', 'n', 'c', 'o', 'd', 'e'};
|
||||
/* "secp256k1_ellswift_create" */
|
||||
@@ -446,19 +481,19 @@ void ellswift_hash_init_tests(void) {
|
||||
* secp256k1_ellswift_sha256_init_encode has the expected
|
||||
* state. */
|
||||
secp256k1_ellswift_sha256_init_encode(&sha_optimized);
|
||||
test_sha256_tag_midstate(&sha_optimized, encode_tag, sizeof(encode_tag));
|
||||
test_sha256_tag_midstate(hash_ctx, &sha_optimized, encode_tag, sizeof(encode_tag));
|
||||
|
||||
/* Check that hash initialized by
|
||||
* secp256k1_ellswift_sha256_init_create has the expected
|
||||
* state. */
|
||||
secp256k1_ellswift_sha256_init_create(&sha_optimized);
|
||||
test_sha256_tag_midstate(&sha_optimized, create_tag, sizeof(create_tag));
|
||||
test_sha256_tag_midstate(hash_ctx, &sha_optimized, create_tag, sizeof(create_tag));
|
||||
|
||||
/* Check that hash initialized by
|
||||
* secp256k1_ellswift_sha256_init_bip324 has the expected
|
||||
* state. */
|
||||
secp256k1_ellswift_sha256_init_bip324(&sha_optimized);
|
||||
test_sha256_tag_midstate(&sha_optimized, bip324_tag, sizeof(bip324_tag));
|
||||
test_sha256_tag_midstate(hash_ctx, &sha_optimized, bip324_tag, sizeof(bip324_tag));
|
||||
}
|
||||
|
||||
void ellswift_xdh_bad_scalar_tests(void) {
|
||||
@@ -499,6 +534,7 @@ static const struct tf_test_entry tests_ellswift[] = {
|
||||
CASE1(ellswift_xdh_correctness_tests),
|
||||
CASE1(ellswift_hash_init_tests),
|
||||
CASE1(ellswift_xdh_bad_scalar_tests),
|
||||
CASE1(ellswift_xdh_ctx_sha256_tests),
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user