hashes: bypass copying into the buffer
What changed, and why it matters
This commit is a performance optimization for the SHA-256 hashing code in the rust-bitcoin library. It changes how input data is fed into the hashing engine so that complete blocks of data are processed directly from the input slice instead of being copied into an internal buffer first. The commit message and diff show only benchmark improvements and no security-related claims.
No security action required. Treat as a normal performance improvement. Standard code review for correctness of the new indexing and buffer handling is sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors engine_input_impl! in hashes/src/internal_macros.rs to reduce memory copies. The new logic: (1) if the input plus any existing partial buffer cannot complete a block, it copies into the engine buffer and returns; (2) if there is a partial buffer, it completes that block, processes it, and advances the input; (3) it passes any remaining full blocks directly to process_blocks from the input slice (zero-copy); (4) it copies the final remainder into the buffer. The change is purely an optimization and does not alter the cryptographic output or the public API.
Changed components
hashes/src/internal_macros.rsHashEngine input implementationInspect captured patch +25 / −12
diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs
index 35c2aba3..e52e24c6 100644
--- a/hashes/src/internal_macros.rs
+++ b/hashes/src/internal_macros.rs
@@ -172,19 +172,32 @@ macro_rules! engine_input_impl(
#[cfg(not(hashes_fuzz))]
fn input(&mut self, mut inp: &[u8]) {
- while !inp.is_empty() {
- let buf_idx = $crate::incomplete_block_len(self);
- let rem_len = <Self as crate::HashEngine>::BLOCK_SIZE - buf_idx;
- let write_len = cmp::min(rem_len, inp.len());
-
- self.buffer[buf_idx..buf_idx + write_len]
- .copy_from_slice(&inp[..write_len]);
- self.bytes_hashed += write_len as u64;
- if $crate::incomplete_block_len(self) == 0 {
- Self::process_blocks(&mut self.h, &self.buffer);
- }
- inp = &inp[write_len..];
+ let buf_idx = $crate::incomplete_block_len(self);
+ let block_size = <Self as crate::HashEngine>::BLOCK_SIZE;
+ self.bytes_hashed += inp.len() as u64;
+
+ // we know we won't complete a block, so just copy into the buffer and return
+ if buf_idx + inp.len() < block_size {
+ return self.buffer[buf_idx..buf_idx + inp.len()].copy_from_slice(&inp)
+ }
+
+ // we'll process at least one block.
+ // if there's a partial buffer, complete it and process it
+ if buf_idx > 0 {
+ let needed = block_size - buf_idx;
+ self.buffer[buf_idx..buf_idx+needed].copy_from_slice(&inp[..needed]);
+ Self::process_blocks(&mut self.h, &self.buffer);
+ inp = &inp[needed..]
}
+
+ // pass remaining full blocks directly to process_blocks from the input (zero copy)
+ let full_blocks = inp.len() / block_size * block_size;
+ if full_blocks > 0 {
+ Self::process_blocks(&mut self.h, &inp[..full_blocks])
+ }
+
+ // buffer the remainder
+ self.buffer[..inp.len() - full_blocks].copy_from_slice(&inp[full_blocks..])
}
#[cfg(hashes_fuzz)]
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.