sha256: speed up writes using multi-block compression
What changed, and why it matters
This commit is a performance improvement to the SHA-256 hashing code in libsecp256k1. It makes the code compress multiple 64-byte data blocks directly from the caller's input buffer instead of first copying each block into a small internal buffer. The change is purely an optimization and does not alter the final hash output. New tests confirm that hashing the same data in one large write or many small writes produces identical results.
No security action required. Treat as a normal performance optimization; review for correctness and run the added tests.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors secp256k1_sha256_write() in src/hash_impl.h to handle three phases: (1) fill and flush any existing partial internal buffer if the new input completes a block; (2) compress all remaining full 64-byte blocks directly from the supplied data pointer via fn_sha256_compression(hash->s, data, n_blocks); (3) copy the leftover tail (<64 bytes) into the internal buffer. The compression function signature already accepted a block count, so this is a straightforward use of that capability. src/tests.c adds run_sha256_multi_block_compression_tests() with four equivalence checks comparing single large writes against split writes of varying sizes (64, 80, 128, 150 bytes).
Changed components
src/hash_impl.h: secp256k1_sha256_write()src/tests.c: new SHA-256 multi-block compression testsInspect captured patch +110 / −4
diff --git a/src/hash_impl.h b/src/hash_impl.h
index d5f16cd..7c40f82 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 b85b065..7c3a96b 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[] = {
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.