From 4d92a083bc267b87db654ae75cbcaf2a87854c09 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 14 Jan 2026 12:16:41 -0500 Subject: [PATCH] sha256: speed up writes using multi-block compression Multiple 64-byte blocks can now be compressed directly from the input buffer, without copying them into the internal buffer. --- src/hash_impl.h | 20 ++++++++--- src/tests.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/hash_impl.h b/src/hash_impl.h index d5f16cd1..7c40f82e 100644 --- a/src/hash_impl.h +++ b/src/hash_impl.h @@ -143,22 +143,34 @@ static void secp256k1_hash_ctx_init(secp256k1_hash_ctx *hash_ctx) { } static void secp256k1_sha256_write(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *hash, const unsigned char *data, size_t len) { + size_t chunk_len; size_t bufsize = hash->bytes & 0x3F; hash->bytes += len; VERIFY_CHECK(hash->bytes >= len); VERIFY_CHECK(hash_ctx != NULL); VERIFY_CHECK(hash_ctx->fn_sha256_compression != NULL); - while (len >= 64 - bufsize) { - /* Fill the buffer, and process it. */ - size_t chunk_len = 64 - bufsize; + + /* If we exceed the 64-byte block size with this input, process it and wipe the buffer */ + chunk_len = 64 - bufsize; + if (bufsize && len >= chunk_len) { memcpy(hash->buf + bufsize, data, chunk_len); data += chunk_len; len -= chunk_len; hash_ctx->fn_sha256_compression(hash->s, hash->buf, 1); bufsize = 0; } + + /* If we still have data to process, invoke compression directly on the input */ + if (len >= 64) { + const size_t n_blocks = len / 64; + const size_t advance = n_blocks * 64; + hash_ctx->fn_sha256_compression(hash->s, data, n_blocks); + data += advance; + len -= advance; + } + + /* Fill the buffer with what remains */ if (len) { - /* Fill the buffer with what remains. */ memcpy(hash->buf + bufsize, data, len); } } diff --git a/src/tests.c b/src/tests.c index b85b0652..7c3a96bc 100644 --- a/src/tests.c +++ b/src/tests.c @@ -480,6 +480,99 @@ static void run_plug_sha256_compression_tests(void) { secp256k1_context_destroy(ctx_cloned); } +static void run_sha256_multi_block_compression_tests(void) { + secp256k1_hash_ctx hash_ctx; + secp256k1_sha256 sha256_one; + secp256k1_sha256 sha256_two; + unsigned char out_one[32], out_two[32]; + + hash_ctx.fn_sha256_compression = secp256k1_sha256_transform; + + { /* 1) Writing one 64-byte full block vs two 32-byte blocks */ + const unsigned char data[64] = "totally serious test message to hash, definitely no random data"; + unsigned char data32[32]; + + secp256k1_sha256_initialize(&sha256_one); + secp256k1_sha256_initialize(&sha256_two); + + /* Write the 64-byte block */ + secp256k1_sha256_write(&hash_ctx, &sha256_one, data, 64); + secp256k1_sha256_finalize(&hash_ctx, &sha256_one, out_one); + + /* Write the two 32-byte blocks */ + memcpy(data32, data, 32); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data32, 32); + memcpy(data32, data + 32, 32); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data32, 32); + secp256k1_sha256_finalize(&hash_ctx, &sha256_two, out_two); + + CHECK(secp256k1_memcmp_var(out_one, out_two, 32) == 0); + } + + { /* 2) Writing one 80-byte block vs two 40-byte blocks */ + const unsigned char data[80] = "Genesis: The Times 03/Jan/2009 Chancellor on brink of second bailout for banks "; + unsigned char data40[40]; + + secp256k1_sha256_initialize(&sha256_one); + secp256k1_sha256_initialize(&sha256_two); + + /* Write the 80-byte block */ + secp256k1_sha256_write(&hash_ctx, &sha256_one, data, 80); + secp256k1_sha256_finalize(&hash_ctx, &sha256_one, out_one); + + /* Write the two 40-byte blocks */ + memcpy(data40, data, 40); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data40, 40); + memcpy(data40, data + 40, 40); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data40, 40); + secp256k1_sha256_finalize(&hash_ctx, &sha256_two, out_two); + + CHECK(secp256k1_memcmp_var(out_one, out_two, 32) == 0); + } + + { /* 3) Writing multiple consecutive full blocks in one write (128 bytes) */ + unsigned char data[128]; + unsigned char i; + for (i = 0; i < 128; i++) data[i] = i; + + secp256k1_sha256_initialize(&sha256_one); + secp256k1_sha256_initialize(&sha256_two); + + /* Single write of 128 bytes (two full 64-byte blocks) */ + secp256k1_sha256_write(&hash_ctx, &sha256_one, data, 128); + secp256k1_sha256_finalize(&hash_ctx, &sha256_one, out_one); + + /* Two separate writes of 64 bytes each */ + secp256k1_sha256_write(&hash_ctx, &sha256_two, data, 64); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data + 64, 64); + secp256k1_sha256_finalize(&hash_ctx, &sha256_two, out_two); + + CHECK(secp256k1_memcmp_var(out_one, out_two, 32) == 0); + } + + { /* 4) Mixed small + large writes in sequence */ + unsigned char data[150]; + unsigned char i; + for (i = 0; i < 150; i++) data[i] = i; + + secp256k1_sha256_initialize(&sha256_one); + secp256k1_sha256_initialize(&sha256_two); + + /* Single write of 150 bytes */ + secp256k1_sha256_write(&hash_ctx, &sha256_one, data, 150); + secp256k1_sha256_finalize(&hash_ctx, &sha256_one, out_one); + + /* Split writes: 10, 64, 64, 12 bytes */ + secp256k1_sha256_write(&hash_ctx, &sha256_two, data, 10); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data + 10, 64); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data + 74, 64); + secp256k1_sha256_write(&hash_ctx, &sha256_two, data + 138, 12); + secp256k1_sha256_finalize(&hash_ctx, &sha256_two, out_two); + + CHECK(secp256k1_memcmp_var(out_one, out_two, 32) == 0); + } +} + static void run_ctz_tests(void) { static const uint32_t b32[] = {1, 0xffffffff, 0x5e56968f, 0xe0d63129}; static const uint64_t b64[] = {1, 0xffffffffffffffff, 0xbcd02462139b3fc3, 0x98b5f80c769693ef}; @@ -7819,6 +7912,7 @@ static const struct tf_test_entry tests_general[] = { CASE(deprecated_context_flags_test), CASE(scratch_tests), CASE(plug_sha256_compression_tests), + CASE(sha256_multi_block_compression_tests), }; static const struct tf_test_entry tests_integer[] = {