crate::ix!();
impl Sha256 {
pub fn finalize(&mut self, hash: &mut [u8; SHA256_OUTPUT_SIZE]) {
finalize_inner(self, hash.as_mut_ptr(), false);
}
}
#[inline]
pub unsafe fn sha256_finalize(hash: *mut Sha256, out32: *mut u8) {
debug_assert!(!hash.is_null() && !out32.is_null());
finalize_inner(&mut *hash, out32, true);
}
#[inline(always)]
pub(crate) fn finalize_inner(ctx: &mut Sha256, out: *mut u8, wipe_state: bool) {
const PAD: [u8; 64] = [
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let bytes = *ctx.bytes();
let pad_len = 1 + ((119 - (bytes % 64)) % 64) as usize;
let mut sizedesc = [0u8; 8];
beio::u64_into(&mut sizedesc, bytes << 3);
ctx.write_ptr(PAD.as_ptr(), pad_len);
ctx.write_ptr(sizedesc.as_ptr(), 8);
let mut tmp = [0u32; 8];
for (dst, src) in tmp.iter_mut().zip(ctx.s_mut().iter_mut()) {
*dst = (*src).to_be();
if wipe_state {
*src = 0;
}
}
unsafe {
std::ptr::copy_nonoverlapping(tmp.as_ptr() as *const u8, out, 32);
}
}
#[cfg(test)]
mod finalize_tests {
use super::*;
use hex_literal::hex;
use rand::{rngs::StdRng, RngCore, SeedableRng};
use std::io::Write;
fn digest_safe(data: &[u8]) -> [u8; SHA256_OUTPUT_SIZE] {
let mut ctx = Sha256::new();
ctx.write_all(data).unwrap();
let mut out = [0u8; SHA256_OUTPUT_SIZE];
ctx.finalize(&mut out);
out
}
fn digest_ffi(data: &[u8]) -> [u8; SHA256_OUTPUT_SIZE] {
let mut ctx = Sha256::new();
ctx.write_all(data).unwrap();
let mut out = [0u8; SHA256_OUTPUT_SIZE];
unsafe { sha256_finalize(&mut ctx, out.as_mut_ptr()) };
out
}
fn digest_inner(data: &[u8], wipe: bool) -> ([u8; 32], [u32; 8]) {
let mut ctx = Sha256::new();
ctx.write_all(data).unwrap();
let mut out = [0u8; 32];
unsafe {
super::finalize_inner(&mut ctx, out.as_mut_ptr(), wipe);
}
let mut state = [0u32; 8];
state.copy_from_slice(ctx.s());
(out, state)
}
const DIGEST_EMPTY: [u8; 32] =
hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
const DIGEST_ABC: [u8; 32] =
hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
#[traced_test]
fn empty_and_abc_match_reference() {
assert_eq!(digest_safe(b""), DIGEST_EMPTY, "`\"\"` digest mismatch");
assert_eq!(digest_safe(b"abc"), DIGEST_ABC, "`\"abc\"` digest mismatch");
}
#[traced_test]
fn all_variants_produce_identical_output_up_to_256_bytes() {
let mut buf = [0u8; 256];
for len in 0..=buf.len() {
buf[len % 256] = len as u8; let msg = &buf[..len];
let safe = digest_safe(msg);
assert_eq!(safe, digest_ffi(msg), "safe ≠ ffi for len={len}");
let (inner, _) = digest_inner(msg, false);
assert_eq!(safe, inner, "safe ≠ inner for len={len}");
}
}
#[traced_test]
fn boundary_lengths_roundtrip() {
const SPECIAL: &[usize] = &[0, 1, 55, 56, 57, 63, 64, 65, 127, 128];
for &len in SPECIAL {
let msg: Vec<u8> = (0..len as u8).collect();
let digest = digest_safe(&msg);
let mut ctx = Sha256::new();
ctx.write_all(&digest).unwrap();
let mut second = [0u8; 32];
ctx.finalize(&mut second);
assert_ne!(digest, second, "digest self‑collision at len={len}");
}
}
#[traced_test]
fn wipe_state_sets_internal_words_to_zero() {
let data = b"wipe-test";
let (_, state_after) = digest_inner(data, true);
assert!(
state_after.iter().all(|&w| w == 0),
"state not zeroed after wipe‑finalize"
);
}
#[traced_test]
fn context_can_be_reused_after_finalize() {
let mut ctx = Sha256::new();
ctx.write_all(b"first").unwrap();
let mut out1 = [0u8; 32];
ctx.finalize(&mut out1);
ctx.reset();
ctx.write_all(b"second").unwrap();
let mut out2 = [0u8; 32];
ctx.finalize(&mut out2);
assert_ne!(out1, out2, "reset did not clear previous state");
assert_eq!(out2, digest_safe(b"second"), "wrong digest after reset()");
}
#[traced_test]
#[ignore = "Run explicitly: cargo test --release -- --ignored"]
fn random_messages_consistent_across_variants() {
let mut rng = StdRng::seed_from_u64(0x5EED_F005_u64);
for _ in 0..10_000 {
let len = (rng.next_u32() % 1024) as usize;
let mut msg = vec![0u8; len];
rng.fill_bytes(&mut msg);
let ref_digest = digest_safe(&msg);
assert_eq!(ref_digest, digest_ffi(&msg), "safe ≠ ffi random");
let (d_inner, _) = digest_inner(&msg, false);
assert_eq!(ref_digest, d_inner, "safe ≠ inner random");
}
}
}