hashes: write padding directly into the buffer
What changed, and why it matters
This commit is a performance optimization for the finalization step of several cryptographic hash functions (RIPEMD-160, SHA-1, SHA-256, SHA-512). It rewrites how padding bytes are added so the code writes directly into an internal buffer instead of calling the general input() function multiple times. The commit message and diff show only speed improvements; there is no indication of a security fix or behavior change in the hash output.
No security action required. Treat as a normal performance refactor. If reviewing, verify the sha512 zero-fill range is intentional or consider a follow-up cleanup for consistency, but it does not alter correctness.
Security signals we found
No security-related keywords in commit title or message
No CVE, advisory, or bug-report references present
Change is framed purely as a performance optimization
Functional behavior of padding preserved across all four hash algorithms
Potential cosmetic inconsistency in sha512 zero-fill range (BLOCK_SIZE - 8 vs BLOCK_SIZE - 16) but no observable effect on output
Evidence from the diff
The change refactors hash finalization in rust-bitcoin’s hashes crate. Previously, padding was appended through repeated calls to HashEngine::input() (0x80, zeroes, length block). The new code computes the current buffer index, writes the 0x80 byte and trailing zeroes directly into e.buffer, processes an extra block if needed, writes the message-length field, and calls process_blocks once. The logic mirrors the standard Merkle-Damgård padding and appears functionally equivalent. A potential typo exists in sha512/mod.rs where the second zero-fill uses BLOCK_SIZE - 8 instead of BLOCK_SIZE - 16, but because the length field is written immediately afterward into the last 8 bytes and the whole buffer is then processed as a single block, the extra zeroes do not affect the hash output. No security relevance is claimed by the vendor.
Changed components
hashes/src/ripemd160/mod.rshashes/src/sha1/mod.rshashes/src/sha256/mod.rshashes/src/sha512/mod.rsInspect captured patch +41 / −44
diff --git a/hashes/src/ripemd160/mod.rs b/hashes/src/ripemd160/mod.rs
index 4dcb23b5..d3f855ae 100644
--- a/hashes/src/ripemd160/mod.rs
+++ b/hashes/src/ripemd160/mod.rs
@@ -7,7 +7,7 @@ mod crypto;
#[cfg(test)]
mod tests;
-use crate::{incomplete_block_len, HashEngine as _};
+use crate::incomplete_block_len;
crate::internal_macros::general_hash_type! {
160,
@@ -19,20 +19,19 @@ impl Hash {
/// Finalize a hash engine to produce a hash.
#[cfg(not(hashes_fuzz))]
pub fn from_engine(mut e: HashEngine) -> Self {
- // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining
let n_bytes_hashed = e.bytes_hashed;
+ let buf_idx = incomplete_block_len(&e);
- let zeroes = [0; BLOCK_SIZE - 8];
- e.input(&[0x80]);
- if crate::incomplete_block_len(&e) > zeroes.len() {
- e.input(&zeroes);
+ e.buffer[buf_idx] = 0x80;
+ e.buffer[buf_idx + 1..].fill(0);
+
+ if buf_idx >= BLOCK_SIZE - 8 {
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
+ e.buffer[..BLOCK_SIZE - 8].fill(0);
}
- let pad_length = zeroes.len() - incomplete_block_len(&e);
- e.input(&zeroes[..pad_length]);
- debug_assert_eq!(incomplete_block_len(&e), zeroes.len());
- e.input(&(8 * n_bytes_hashed).to_le_bytes());
- debug_assert_eq!(incomplete_block_len(&e), 0);
+ e.buffer[BLOCK_SIZE - 8..].copy_from_slice(&(8 * n_bytes_hashed).to_le_bytes());
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
Self(e.midstate())
}
diff --git a/hashes/src/sha1/mod.rs b/hashes/src/sha1/mod.rs
index 7267ef5a..13c8b1bc 100644
--- a/hashes/src/sha1/mod.rs
+++ b/hashes/src/sha1/mod.rs
@@ -7,7 +7,7 @@ mod crypto;
#[cfg(test)]
mod tests;
-use crate::{incomplete_block_len, HashEngine as _};
+use crate::incomplete_block_len;
crate::internal_macros::general_hash_type! {
160,
@@ -18,20 +18,19 @@ crate::internal_macros::general_hash_type! {
impl Hash {
/// Finalize a hash engine to produce a hash.
pub fn from_engine(mut e: HashEngine) -> Self {
- // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining
let n_bytes_hashed = e.bytes_hashed;
+ let buf_idx = incomplete_block_len(&e);
- let zeroes = [0; BLOCK_SIZE - 8];
- e.input(&[0x80]);
- if incomplete_block_len(&e) > zeroes.len() {
- e.input(&zeroes);
+ e.buffer[buf_idx] = 0x80;
+ e.buffer[buf_idx + 1..].fill(0);
+
+ if buf_idx >= BLOCK_SIZE - 8 {
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
+ e.buffer[..BLOCK_SIZE - 8].fill(0);
}
- let pad_length = zeroes.len() - incomplete_block_len(&e);
- e.input(&zeroes[..pad_length]);
- debug_assert_eq!(incomplete_block_len(&e), zeroes.len());
- e.input(&(8 * n_bytes_hashed).to_be_bytes());
- debug_assert_eq!(incomplete_block_len(&e), 0);
+ e.buffer[BLOCK_SIZE - 8..].copy_from_slice(&(8 * n_bytes_hashed).to_be_bytes());
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
Self(e.midstate())
}
diff --git a/hashes/src/sha256/mod.rs b/hashes/src/sha256/mod.rs
index c80b212d..02aa2fd7 100644
--- a/hashes/src/sha256/mod.rs
+++ b/hashes/src/sha256/mod.rs
@@ -10,7 +10,7 @@ use core::{convert, fmt};
use internals::slice::SliceExt;
-use crate::{incomplete_block_len, sha256d, HashEngine as _};
+use crate::{incomplete_block_len, sha256d};
#[cfg(doc)]
use crate::{sha256t, sha256t_tag};
@@ -131,17 +131,18 @@ impl Hash {
// pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining
let n_bytes_hashed = e.bytes_hashed;
- let zeroes = [0; BLOCK_SIZE - 8];
- e.input(&[0x80]);
- if incomplete_block_len(&e) > zeroes.len() {
- e.input(&zeroes);
+ let buf_idx = incomplete_block_len(&e);
+
+ e.buffer[buf_idx] = 0x80;
+ e.buffer[buf_idx+1..].fill(0);
+
+ if buf_idx >= BLOCK_SIZE - 8 {
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
+ e.buffer[..BLOCK_SIZE - 8].fill(0);
}
- let pad_length = zeroes.len() - incomplete_block_len(&e);
- e.input(&zeroes[..pad_length]);
- debug_assert_eq!(incomplete_block_len(&e), zeroes.len());
- e.input(&(8 * n_bytes_hashed).to_be_bytes());
- debug_assert_eq!(incomplete_block_len(&e), 0);
+ e.buffer[BLOCK_SIZE - 8..].copy_from_slice(&(8 * n_bytes_hashed).to_be_bytes());
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
Self(e.midstate_unchecked().bytes)
}
diff --git a/hashes/src/sha512/mod.rs b/hashes/src/sha512/mod.rs
index fdbc6ee1..0e70c405 100644
--- a/hashes/src/sha512/mod.rs
+++ b/hashes/src/sha512/mod.rs
@@ -8,7 +8,7 @@ mod crypto;
#[cfg(test)]
mod tests;
-use crate::{incomplete_block_len, HashEngine as _};
+use crate::incomplete_block_len;
crate::internal_macros::general_hash_type! {
512,
@@ -20,21 +20,19 @@ impl Hash {
/// Finalize a hash engine to produce a hash.
#[cfg(not(hashes_fuzz))]
pub fn from_engine(mut e: HashEngine) -> Self {
- // pad buffer with a single 1-bit then all 0s, until there are exactly 16 bytes remaining
let n_bytes_hashed = e.bytes_hashed;
+ let buf_idx = incomplete_block_len(&e);
- let zeroes = [0; BLOCK_SIZE - 16];
- e.input(&[0x80]);
- if incomplete_block_len(&e) > zeroes.len() {
- e.input(&zeroes);
+ e.buffer[buf_idx] = 0x80;
+ e.buffer[buf_idx + 1..].fill(0);
+
+ if buf_idx >= BLOCK_SIZE - 16 {
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
+ e.buffer[..BLOCK_SIZE - 8].fill(0);
}
- let pad_length = zeroes.len() - incomplete_block_len(&e);
- e.input(&zeroes[..pad_length]);
- debug_assert_eq!(incomplete_block_len(&e), zeroes.len());
- e.input(&[0; 8]);
- e.input(&(8 * n_bytes_hashed).to_be_bytes());
- debug_assert_eq!(incomplete_block_len(&e), 0);
+ e.buffer[BLOCK_SIZE - 8..].copy_from_slice(&(8 * n_bytes_hashed).to_be_bytes());
+ HashEngine::process_blocks(&mut e.h, &e.buffer);
Self(e.midstate())
}
Why this scored 12/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.