Skip to main content

logicaffeine_base/
hash.rs

1//! Cryptographic hash primitives — MD5 (RFC 1321) and SHA-1 (RFC 3174), rolled in pure Rust.
2//!
3//! The REFERENCE ORACLE. MD5 and SHA-1 (the hashes RFC 9562 defines UUID v3/v5 on) are written in the
4//! LOGOS language (`crates/logicaffeine_compile/assets/std/uuid.lg`: `md5Digest`/`sha1Digest`, which the
5//! `md5`/`sha1`/`uuid_v3`/`uuid_v5` stdlib functions call) and compile to native through the Futamura
6//! pipeline — that is the language's implementation. These pure-Rust versions are the independent oracle
7//! the Logos ones are proven byte-exact against (here and cross-tier), NOT on any language path.
8//! (SHA-3/Keccak is the modern hash and lives in `logicaffeine_system`.)
9//!
10//! Both are block functions over 64-byte chunks with the standard Merkle–Damgård padding. They are NOT
11//! for security (MD5 and SHA-1 are both broken against collision attacks). Validated bit-exact against
12//! the `md-5`/`sha1` reference crates (known RFC vectors + differential fuzz) in the tests.
13
14/// MD5 digest of `data` (RFC 1321) — 16 bytes.
15pub fn md5(data: &[u8]) -> [u8; 16] {
16    // Per-round left-rotation amounts (RFC 1321 §3.4).
17    const S: [u32; 64] = [
18        7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9,
19        14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15,
20        21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
21    ];
22    // K[i] = floor(2^32 · |sin(i+1)|) — the additive constants (RFC 1321 §3.4).
23    const K: [u32; 64] = [
24        0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
25        0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
26        0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
27        0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
28        0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
29        0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
30        0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
31        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
32    ];
33
34    // The rotation amounts are baked into the unrolled steps below as literals; the linear `S` table
35    // is unused by this form.
36    let _ = S;
37
38    let mut state: [u32; 4] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476];
39
40    #[inline(always)]
41    fn compress(state: &mut [u32; 4], block: &[u8; 64]) {
42        let mut m = [0u32; 16];
43        for (i, w) in m.iter_mut().enumerate() {
44            *w = u32::from_le_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
45        }
46        let (mut a, mut b, mut c, mut d) = (state[0], state[1], state[2], state[3]);
47        // Fully-unrolled rotating-variable form (RFC 1321): instead of moving a=d;d=c;c=b each step,
48        // the OUTPUT variable cycles a→d→c→b, so the 192 register-shuffle moves vanish and every
49        // g/s/K is a compile-time constant. `op(a,b,c,d,x,s,t): a = b + rotl(a + f(b,c,d) + x + t, s)`.
50        macro_rules! ff { ($a:ident,$b:ident,$c:ident,$d:ident,$g:expr,$s:expr,$t:expr) => {
51            $a = $b.wrapping_add(($a.wrapping_add(($b & $c) | (!$b & $d)).wrapping_add(m[$g]).wrapping_add($t)).rotate_left($s));
52        }}
53        macro_rules! gg { ($a:ident,$b:ident,$c:ident,$d:ident,$g:expr,$s:expr,$t:expr) => {
54            $a = $b.wrapping_add(($a.wrapping_add(($b & $d) | ($c & !$d)).wrapping_add(m[$g]).wrapping_add($t)).rotate_left($s));
55        }}
56        macro_rules! hh { ($a:ident,$b:ident,$c:ident,$d:ident,$g:expr,$s:expr,$t:expr) => {
57            $a = $b.wrapping_add(($a.wrapping_add($b ^ $c ^ $d).wrapping_add(m[$g]).wrapping_add($t)).rotate_left($s));
58        }}
59        macro_rules! ii { ($a:ident,$b:ident,$c:ident,$d:ident,$g:expr,$s:expr,$t:expr) => {
60            $a = $b.wrapping_add(($a.wrapping_add($c ^ ($b | !$d)).wrapping_add(m[$g]).wrapping_add($t)).rotate_left($s));
61        }}
62        // Round 1 — g = i.
63        ff!(a, b, c, d, 0, 7, K[0]); ff!(d, a, b, c, 1, 12, K[1]); ff!(c, d, a, b, 2, 17, K[2]); ff!(b, c, d, a, 3, 22, K[3]);
64        ff!(a, b, c, d, 4, 7, K[4]); ff!(d, a, b, c, 5, 12, K[5]); ff!(c, d, a, b, 6, 17, K[6]); ff!(b, c, d, a, 7, 22, K[7]);
65        ff!(a, b, c, d, 8, 7, K[8]); ff!(d, a, b, c, 9, 12, K[9]); ff!(c, d, a, b, 10, 17, K[10]); ff!(b, c, d, a, 11, 22, K[11]);
66        ff!(a, b, c, d, 12, 7, K[12]); ff!(d, a, b, c, 13, 12, K[13]); ff!(c, d, a, b, 14, 17, K[14]); ff!(b, c, d, a, 15, 22, K[15]);
67        // Round 2 — g = (5i+1) mod 16.
68        gg!(a, b, c, d, 1, 5, K[16]); gg!(d, a, b, c, 6, 9, K[17]); gg!(c, d, a, b, 11, 14, K[18]); gg!(b, c, d, a, 0, 20, K[19]);
69        gg!(a, b, c, d, 5, 5, K[20]); gg!(d, a, b, c, 10, 9, K[21]); gg!(c, d, a, b, 15, 14, K[22]); gg!(b, c, d, a, 4, 20, K[23]);
70        gg!(a, b, c, d, 9, 5, K[24]); gg!(d, a, b, c, 14, 9, K[25]); gg!(c, d, a, b, 3, 14, K[26]); gg!(b, c, d, a, 8, 20, K[27]);
71        gg!(a, b, c, d, 13, 5, K[28]); gg!(d, a, b, c, 2, 9, K[29]); gg!(c, d, a, b, 7, 14, K[30]); gg!(b, c, d, a, 12, 20, K[31]);
72        // Round 3 — g = (3i+5) mod 16.
73        hh!(a, b, c, d, 5, 4, K[32]); hh!(d, a, b, c, 8, 11, K[33]); hh!(c, d, a, b, 11, 16, K[34]); hh!(b, c, d, a, 14, 23, K[35]);
74        hh!(a, b, c, d, 1, 4, K[36]); hh!(d, a, b, c, 4, 11, K[37]); hh!(c, d, a, b, 7, 16, K[38]); hh!(b, c, d, a, 10, 23, K[39]);
75        hh!(a, b, c, d, 13, 4, K[40]); hh!(d, a, b, c, 0, 11, K[41]); hh!(c, d, a, b, 3, 16, K[42]); hh!(b, c, d, a, 6, 23, K[43]);
76        hh!(a, b, c, d, 9, 4, K[44]); hh!(d, a, b, c, 12, 11, K[45]); hh!(c, d, a, b, 15, 16, K[46]); hh!(b, c, d, a, 2, 23, K[47]);
77        // Round 4 — g = 7i mod 16.
78        ii!(a, b, c, d, 0, 6, K[48]); ii!(d, a, b, c, 7, 10, K[49]); ii!(c, d, a, b, 14, 15, K[50]); ii!(b, c, d, a, 5, 21, K[51]);
79        ii!(a, b, c, d, 12, 6, K[52]); ii!(d, a, b, c, 3, 10, K[53]); ii!(c, d, a, b, 10, 15, K[54]); ii!(b, c, d, a, 1, 21, K[55]);
80        ii!(a, b, c, d, 8, 6, K[56]); ii!(d, a, b, c, 15, 10, K[57]); ii!(c, d, a, b, 6, 15, K[58]); ii!(b, c, d, a, 13, 21, K[59]);
81        ii!(a, b, c, d, 4, 6, K[60]); ii!(d, a, b, c, 11, 10, K[61]); ii!(c, d, a, b, 2, 15, K[62]); ii!(b, c, d, a, 9, 21, K[63]);
82        state[0] = state[0].wrapping_add(a);
83        state[1] = state[1].wrapping_add(b);
84        state[2] = state[2].wrapping_add(c);
85        state[3] = state[3].wrapping_add(d);
86    }
87
88    each_padded_block(data, Endian::Little, |block| compress(&mut state, block));
89
90    let mut out = [0u8; 16];
91    out[0..4].copy_from_slice(&state[0].to_le_bytes());
92    out[4..8].copy_from_slice(&state[1].to_le_bytes());
93    out[8..12].copy_from_slice(&state[2].to_le_bytes());
94    out[12..16].copy_from_slice(&state[3].to_le_bytes());
95    out
96}
97
98/// The 64 MD5 additive constants `floor(2^32·|sin(i+1)|)` and per-round rotation amounts (RFC 1321).
99const MD5_K: [u32; 64] = [
100    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
101    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
102    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
103    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
104    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
105    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
106    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
107    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
108];
109const MD5_S: [u32; 64] = [
110    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14,
111    20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6,
112    10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
113];
114
115/// MD5 of FOUR equal-length messages at once — 4-way SSE2 multi-buffer, the high-throughput path for
116/// hashing many same-size records (bulk name-based ids, dedup keys, content addresses). All four ABCD
117/// states advance in one `__m128i` (lane j = message j), so ~4× a scalar MD5 per message. The four
118/// inputs MUST share a length; returns the four 16-byte digests, byte-identical to [`md5`] per lane.
119#[cfg(target_arch = "x86_64")]
120pub fn md5_x4(msgs: [&[u8]; 4]) -> [[u8; 16]; 4] {
121    let len = msgs[0].len();
122    assert!(msgs.iter().all(|m| m.len() == len), "md5_x4 requires equal-length inputs");
123    if std::is_x86_feature_detected!("sse2") {
124        // SAFETY: guarded by the sse2 feature detection just above.
125        unsafe { md5_x4_sse2(msgs, len) }
126    } else {
127        [md5(msgs[0]), md5(msgs[1]), md5(msgs[2]), md5(msgs[3])]
128    }
129}
130
131#[cfg(target_arch = "x86_64")]
132#[target_feature(enable = "sse2")]
133unsafe fn md5_x4_sse2(msgs: [&[u8]; 4], len: usize) -> [[u8; 16]; 4] {
134    // Identical length ⇒ identical Merkle–Damgård padding (0x80, zero fill, 64-bit LE bit length) and
135    // block count for all four lanes. A 256-byte inline buffer per lane keeps the common case (records
136    // up to 247 bytes) entirely on the stack — no per-call heap; longer inputs fall back to a Vec.
137    let total = (len + 8) / 64 * 64 + 64;
138    let nb = total / 64;
139    let bitlen = (len as u64).wrapping_mul(8);
140    const CAP: usize = 256;
141    if total <= CAP {
142        let mut pad = [[0u8; CAP]; 4];
143        for (j, p) in pad.iter_mut().enumerate() {
144            p[..len].copy_from_slice(msgs[j]);
145            p[len] = 0x80;
146            p[total - 8..total].copy_from_slice(&bitlen.to_le_bytes());
147        }
148        md5_x4_run([&pad[0][..], &pad[1][..], &pad[2][..], &pad[3][..]], nb)
149    } else {
150        let mut pad = [vec![0u8; total], vec![0u8; total], vec![0u8; total], vec![0u8; total]];
151        for (j, p) in pad.iter_mut().enumerate() {
152            p[..len].copy_from_slice(msgs[j]);
153            p[len] = 0x80;
154            p[total - 8..].copy_from_slice(&bitlen.to_le_bytes());
155        }
156        md5_x4_run([&pad[0][..], &pad[1][..], &pad[2][..], &pad[3][..]], nb)
157    }
158}
159
160/// The 4-way SSE2 MD5 compress + transpose over the four (already padded) lane buffers — the alloc-free
161/// core shared by both the stack and heap staging paths of [`md5_x4_sse2`].
162#[cfg(target_arch = "x86_64")]
163#[target_feature(enable = "sse2")]
164unsafe fn md5_x4_run(pad: [&[u8]; 4], nb: usize) -> [[u8; 16]; 4] {
165    use core::arch::x86_64::*;
166
167    let rotl = |x: __m128i, s: u32| -> __m128i {
168        _mm_or_si128(
169            _mm_sll_epi32(x, _mm_cvtsi32_si128(s as i32)),
170            _mm_srl_epi32(x, _mm_cvtsi32_si128((32 - s) as i32)),
171        )
172    };
173    let ones = _mm_set1_epi32(-1);
174
175    let mut sa = _mm_set1_epi32(0x67452301u32 as i32);
176    let mut sb = _mm_set1_epi32(0xefcdab89u32 as i32);
177    let mut sc = _mm_set1_epi32(0x98badcfeu32 as i32);
178    let mut sd = _mm_set1_epi32(0x10325476u32 as i32);
179
180    for bi in 0..nb {
181        // Transpose the four lanes' 16 message words: mw[g] = (msg0[g], msg1[g], msg2[g], msg3[g]).
182        let mut mw = [_mm_setzero_si128(); 16];
183        for (g, slot) in mw.iter_mut().enumerate() {
184            let o = bi * 64 + g * 4;
185            let w = |p: &[u8]| u32::from_le_bytes([p[o], p[o + 1], p[o + 2], p[o + 3]]) as i32;
186            *slot = _mm_setr_epi32(w(&pad[0]), w(&pad[1]), w(&pad[2]), w(&pad[3]));
187        }
188        let (mut a, mut b, mut c, mut d) = (sa, sb, sc, sd);
189        for i in 0..64 {
190            let (f, g) = match i / 16 {
191                0 => (_mm_or_si128(_mm_and_si128(b, c), _mm_andnot_si128(b, d)), i),
192                1 => (_mm_or_si128(_mm_and_si128(b, d), _mm_andnot_si128(d, c)), (5 * i + 1) % 16),
193                2 => (_mm_xor_si128(_mm_xor_si128(b, c), d), (3 * i + 5) % 16),
194                _ => (_mm_xor_si128(c, _mm_or_si128(b, _mm_xor_si128(d, ones))), (7 * i) % 16),
195            };
196            let t = _mm_add_epi32(
197                _mm_add_epi32(a, f),
198                _mm_add_epi32(mw[g], _mm_set1_epi32(MD5_K[i] as i32)),
199            );
200            a = d;
201            d = c;
202            c = b;
203            b = _mm_add_epi32(b, rotl(t, MD5_S[i]));
204        }
205        sa = _mm_add_epi32(sa, a);
206        sb = _mm_add_epi32(sb, b);
207        sc = _mm_add_epi32(sc, c);
208        sd = _mm_add_epi32(sd, d);
209    }
210
211    let mut a4 = [0u32; 4];
212    let mut b4 = [0u32; 4];
213    let mut c4 = [0u32; 4];
214    let mut d4 = [0u32; 4];
215    _mm_storeu_si128(a4.as_mut_ptr() as *mut __m128i, sa);
216    _mm_storeu_si128(b4.as_mut_ptr() as *mut __m128i, sb);
217    _mm_storeu_si128(c4.as_mut_ptr() as *mut __m128i, sc);
218    _mm_storeu_si128(d4.as_mut_ptr() as *mut __m128i, sd);
219    let mut out = [[0u8; 16]; 4];
220    for (j, o) in out.iter_mut().enumerate() {
221        o[0..4].copy_from_slice(&a4[j].to_le_bytes());
222        o[4..8].copy_from_slice(&b4[j].to_le_bytes());
223        o[8..12].copy_from_slice(&c4[j].to_le_bytes());
224        o[12..16].copy_from_slice(&d4[j].to_le_bytes());
225    }
226    out
227}
228
229/// MD5 of EIGHT equal-length messages at once — AVX2 8-way multi-buffer, twice the width of [`md5_x4`].
230/// All eight ABCD states advance in one `__m256i` (lane j = message j). Falls back to two `md5_x4`
231/// passes without AVX2. Byte-identical to [`md5`] per lane; the high-throughput path for hashing many
232/// same-size records (bulk name-based ids, dedup/content keys).
233#[cfg(target_arch = "x86_64")]
234pub fn md5_x8(msgs: [&[u8]; 8]) -> [[u8; 16]; 8] {
235    let len = msgs[0].len();
236    assert!(msgs.iter().all(|m| m.len() == len), "md5_x8 requires equal-length inputs");
237    if std::is_x86_feature_detected!("avx2") {
238        // SAFETY: guarded by the avx2 feature detection just above.
239        unsafe { md5_x8_avx2(msgs, len) }
240    } else {
241        let a = md5_x4([msgs[0], msgs[1], msgs[2], msgs[3]]);
242        let b = md5_x4([msgs[4], msgs[5], msgs[6], msgs[7]]);
243        [a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]]
244    }
245}
246
247#[cfg(target_arch = "x86_64")]
248#[target_feature(enable = "avx2")]
249unsafe fn md5_x8_avx2(msgs: [&[u8]; 8], len: usize) -> [[u8; 16]; 8] {
250    let total = (len + 8) / 64 * 64 + 64;
251    let nb = total / 64;
252    let bitlen = (len as u64).wrapping_mul(8);
253    const CAP: usize = 256;
254    if total <= CAP {
255        let mut pad = [[0u8; CAP]; 8];
256        for (j, p) in pad.iter_mut().enumerate() {
257            p[..len].copy_from_slice(msgs[j]);
258            p[len] = 0x80;
259            p[total - 8..total].copy_from_slice(&bitlen.to_le_bytes());
260        }
261        let s: [&[u8]; 8] =
262            [&pad[0], &pad[1], &pad[2], &pad[3], &pad[4], &pad[5], &pad[6], &pad[7]];
263        md5_x8_run(s, nb)
264    } else {
265        let mut pad: Vec<Vec<u8>> = (0..8).map(|_| vec![0u8; total]).collect();
266        for (j, p) in pad.iter_mut().enumerate() {
267            p[..len].copy_from_slice(msgs[j]);
268            p[len] = 0x80;
269            p[total - 8..].copy_from_slice(&bitlen.to_le_bytes());
270        }
271        let s: [&[u8]; 8] = [
272            &pad[0], &pad[1], &pad[2], &pad[3], &pad[4], &pad[5], &pad[6], &pad[7],
273        ];
274        md5_x8_run(s, nb)
275    }
276}
277
278/// Transpose eight `__m256i` rows (8 lanes each) — the textbook AVX2 8×8 32-bit transpose (unpack
279/// `epi32` → unpack `epi64` → `permute2x128`). `out[k]` is column `k` = `(r0[k], r1[k], …, r7[k])`.
280#[cfg(target_arch = "x86_64")]
281#[target_feature(enable = "avx2")]
282unsafe fn transpose8x8(r: [core::arch::x86_64::__m256i; 8]) -> [core::arch::x86_64::__m256i; 8] {
283    use core::arch::x86_64::*;
284    let t0 = _mm256_unpacklo_epi32(r[0], r[1]);
285    let t1 = _mm256_unpackhi_epi32(r[0], r[1]);
286    let t2 = _mm256_unpacklo_epi32(r[2], r[3]);
287    let t3 = _mm256_unpackhi_epi32(r[2], r[3]);
288    let t4 = _mm256_unpacklo_epi32(r[4], r[5]);
289    let t5 = _mm256_unpackhi_epi32(r[4], r[5]);
290    let t6 = _mm256_unpacklo_epi32(r[6], r[7]);
291    let t7 = _mm256_unpackhi_epi32(r[6], r[7]);
292    let s0 = _mm256_unpacklo_epi64(t0, t2);
293    let s1 = _mm256_unpackhi_epi64(t0, t2);
294    let s2 = _mm256_unpacklo_epi64(t1, t3);
295    let s3 = _mm256_unpackhi_epi64(t1, t3);
296    let s4 = _mm256_unpacklo_epi64(t4, t6);
297    let s5 = _mm256_unpackhi_epi64(t4, t6);
298    let s6 = _mm256_unpacklo_epi64(t5, t7);
299    let s7 = _mm256_unpackhi_epi64(t5, t7);
300    [
301        _mm256_permute2x128_si256(s0, s4, 0x20),
302        _mm256_permute2x128_si256(s1, s5, 0x20),
303        _mm256_permute2x128_si256(s2, s6, 0x20),
304        _mm256_permute2x128_si256(s3, s7, 0x20),
305        _mm256_permute2x128_si256(s0, s4, 0x31),
306        _mm256_permute2x128_si256(s1, s5, 0x31),
307        _mm256_permute2x128_si256(s2, s6, 0x31),
308        _mm256_permute2x128_si256(s3, s7, 0x31),
309    ]
310}
311
312/// The 8-way AVX2 MD5 compress + transpose over the eight (already padded) lane buffers.
313#[cfg(target_arch = "x86_64")]
314#[target_feature(enable = "avx2")]
315unsafe fn md5_x8_run(pad: [&[u8]; 8], nb: usize) -> [[u8; 16]; 8] {
316    use core::arch::x86_64::*;
317
318    let rotl = |x: __m256i, s: u32| -> __m256i {
319        _mm256_or_si256(
320            _mm256_sll_epi32(x, _mm_cvtsi32_si128(s as i32)),
321            _mm256_srl_epi32(x, _mm_cvtsi32_si128((32 - s) as i32)),
322        )
323    };
324    let ones = _mm256_set1_epi32(-1);
325
326    let mut sa = _mm256_set1_epi32(0x67452301u32 as i32);
327    let mut sb = _mm256_set1_epi32(0xefcdab89u32 as i32);
328    let mut sc = _mm256_set1_epi32(0x98badcfeu32 as i32);
329    let mut sd = _mm256_set1_epi32(0x10325476u32 as i32);
330
331    for bi in 0..nb {
332        // Register transpose: load each lane's block as two rows (words 0–7 and 8–15) and transpose
333        // the 8×8 so `mw[g]` = word `g` of all eight messages — SIMD shuffles, not 128 scalar loads.
334        let base = bi * 64;
335        let ld = |p: &[u8], off: usize| _mm256_loadu_si256(p.as_ptr().add(base + off) as *const __m256i);
336        let lo = transpose8x8([
337            ld(pad[0], 0), ld(pad[1], 0), ld(pad[2], 0), ld(pad[3], 0),
338            ld(pad[4], 0), ld(pad[5], 0), ld(pad[6], 0), ld(pad[7], 0),
339        ]);
340        let hi = transpose8x8([
341            ld(pad[0], 32), ld(pad[1], 32), ld(pad[2], 32), ld(pad[3], 32),
342            ld(pad[4], 32), ld(pad[5], 32), ld(pad[6], 32), ld(pad[7], 32),
343        ]);
344        let mut mw = [_mm256_setzero_si256(); 16];
345        mw[..8].copy_from_slice(&lo);
346        mw[8..].copy_from_slice(&hi);
347        let (mut a, mut b, mut c, mut d) = (sa, sb, sc, sd);
348        for i in 0..64 {
349            let (f, g) = match i / 16 {
350                0 => (_mm256_or_si256(_mm256_and_si256(b, c), _mm256_andnot_si256(b, d)), i),
351                1 => (_mm256_or_si256(_mm256_and_si256(b, d), _mm256_andnot_si256(d, c)), (5 * i + 1) % 16),
352                2 => (_mm256_xor_si256(_mm256_xor_si256(b, c), d), (3 * i + 5) % 16),
353                _ => (_mm256_xor_si256(c, _mm256_or_si256(b, _mm256_xor_si256(d, ones))), (7 * i) % 16),
354            };
355            let t = _mm256_add_epi32(
356                _mm256_add_epi32(a, f),
357                _mm256_add_epi32(mw[g], _mm256_set1_epi32(MD5_K[i] as i32)),
358            );
359            a = d;
360            d = c;
361            c = b;
362            b = _mm256_add_epi32(b, rotl(t, MD5_S[i]));
363        }
364        sa = _mm256_add_epi32(sa, a);
365        sb = _mm256_add_epi32(sb, b);
366        sc = _mm256_add_epi32(sc, c);
367        sd = _mm256_add_epi32(sd, d);
368    }
369
370    let mut a8 = [0u32; 8];
371    let mut b8 = [0u32; 8];
372    let mut c8 = [0u32; 8];
373    let mut d8 = [0u32; 8];
374    _mm256_storeu_si256(a8.as_mut_ptr() as *mut __m256i, sa);
375    _mm256_storeu_si256(b8.as_mut_ptr() as *mut __m256i, sb);
376    _mm256_storeu_si256(c8.as_mut_ptr() as *mut __m256i, sc);
377    _mm256_storeu_si256(d8.as_mut_ptr() as *mut __m256i, sd);
378    let mut out = [[0u8; 16]; 8];
379    for (j, o) in out.iter_mut().enumerate() {
380        o[0..4].copy_from_slice(&a8[j].to_le_bytes());
381        o[4..8].copy_from_slice(&b8[j].to_le_bytes());
382        o[8..12].copy_from_slice(&c8[j].to_le_bytes());
383        o[12..16].copy_from_slice(&d8[j].to_le_bytes());
384    }
385    out
386}
387
388/// SHA-1 digest of `data` (RFC 3174) — 20 bytes.
389pub fn sha1(data: &[u8]) -> [u8; 20] {
390    let mut h: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
391
392    #[inline(always)]
393    fn compress(h: &mut [u32; 5], block: &[u8; 64]) {
394        // Decode and extend the message schedule to 80 big-endian words.
395        let mut w = [0u32; 80];
396        for (i, word) in w.iter_mut().take(16).enumerate() {
397            *word = u32::from_be_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
398        }
399        for i in 16..80 {
400            w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
401        }
402        let (mut a, mut b, mut c, mut d, mut e) = (h[0], h[1], h[2], h[3], h[4]);
403        for (i, &wi) in w.iter().enumerate() {
404            let (f, k) = match i {
405                0..=19 => ((b & c) | (!b & d), 0x5A827999),
406                20..=39 => (b ^ c ^ d, 0x6ED9EBA1),
407                40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDC),
408                _ => (b ^ c ^ d, 0xCA62C1D6),
409            };
410            let tmp = a
411                .rotate_left(5)
412                .wrapping_add(f)
413                .wrapping_add(e)
414                .wrapping_add(k)
415                .wrapping_add(wi);
416            e = d;
417            d = c;
418            c = b.rotate_left(30);
419            b = a;
420            a = tmp;
421        }
422        h[0] = h[0].wrapping_add(a);
423        h[1] = h[1].wrapping_add(b);
424        h[2] = h[2].wrapping_add(c);
425        h[3] = h[3].wrapping_add(d);
426        h[4] = h[4].wrapping_add(e);
427    }
428
429    // Fast path: SHA-NI hardware instructions (≈ an order of magnitude faster than scalar). Gated on
430    // a one-shot runtime CPU-feature check; falls back to the scalar `compress` everywhere else.
431    #[cfg(target_arch = "x86_64")]
432    {
433        if x86_sha::available() {
434            // SAFETY: guarded by the `sha`/`ssse3`/`sse4.1` feature detection just checked.
435            unsafe { x86_sha::sha1_blocks(data, &mut h) };
436            let mut out = [0u8; 20];
437            for (i, hi) in h.iter().enumerate() {
438                out[4 * i..4 * i + 4].copy_from_slice(&hi.to_be_bytes());
439            }
440            return out;
441        }
442    }
443
444    each_padded_block(data, Endian::Big, |block| compress(&mut h, block));
445
446    let mut out = [0u8; 20];
447    for (i, hi) in h.iter().enumerate() {
448        out[4 * i..4 * i + 4].copy_from_slice(&hi.to_be_bytes());
449    }
450    out
451}
452
453#[derive(Clone, Copy)]
454enum Endian {
455    Little,
456    Big,
457}
458
459/// Drive `compress` over the Merkle–Damgård padded message — ALLOC-FREE. Full 64-byte blocks are
460/// fed directly from `data` (no copy); only the final partial block plus the `0x80`/zero/length
461/// padding is staged in a 128-byte stack buffer (the padding spills to a second block when the tail
462/// is ≥ 56 bytes). The bit length is little-endian for MD5, big-endian for SHA-1. This keeps the hot
463/// path — small UUID inputs — entirely on the stack.
464#[inline(always)]
465fn each_padded_block(data: &[u8], endian: Endian, mut compress: impl FnMut(&[u8; 64])) {
466    let mut chunks = data.chunks_exact(64);
467    for chunk in &mut chunks {
468        let mut block = [0u8; 64];
469        block.copy_from_slice(chunk);
470        compress(&block);
471    }
472    let rem = chunks.remainder();
473    let bit_len = (data.len() as u64).wrapping_mul(8);
474    let len_bytes = match endian {
475        Endian::Little => bit_len.to_le_bytes(),
476        Endian::Big => bit_len.to_be_bytes(),
477    };
478
479    let mut tail = [0u8; 128];
480    tail[..rem.len()].copy_from_slice(rem);
481    tail[rem.len()] = 0x80;
482    if rem.len() < 56 {
483        // Padding + length fit in one final block.
484        tail[56..64].copy_from_slice(&len_bytes);
485        let mut b = [0u8; 64];
486        b.copy_from_slice(&tail[..64]);
487        compress(&b);
488    } else {
489        // Spills to a second block; the length goes at the very end.
490        tail[120..128].copy_from_slice(&len_bytes);
491        let mut b0 = [0u8; 64];
492        b0.copy_from_slice(&tail[..64]);
493        compress(&b0);
494        let mut b1 = [0u8; 64];
495        b1.copy_from_slice(&tail[64..128]);
496        compress(&b1);
497    }
498}
499
500/// Lowercase hex of a digest — handy for tests and debugging.
501pub fn to_hex(bytes: &[u8]) -> String {
502    const HEX: &[u8; 16] = b"0123456789abcdef";
503    let mut s = String::with_capacity(bytes.len() * 2);
504    for &b in bytes {
505        s.push(HEX[(b >> 4) as usize] as char);
506        s.push(HEX[(b & 0xf) as usize] as char);
507    }
508    s
509}
510
511/// SHA-1 over Intel SHA extensions (SHA-NI). Beats the scalar core by ~10× by running the round
512/// function in dedicated silicon (`_mm_sha1rnds4` etc.). Selected at runtime; the scalar core is the
513/// fallback. The instruction sequence is the canonical Intel reference (validated bit-exact against
514/// the `sha1` crate + RFC vectors in the tests).
515#[cfg(target_arch = "x86_64")]
516mod x86_sha {
517    use super::{each_padded_block, Endian};
518
519    /// True when the CPU has the SHA + SSSE3 + SSE4.1 instructions this path needs. `std`'s detection
520    /// caches the result, so this is a cheap load after the first call.
521    #[inline]
522    pub fn available() -> bool {
523        std::is_x86_feature_detected!("sha")
524            && std::is_x86_feature_detected!("ssse3")
525            && std::is_x86_feature_detected!("sse4.1")
526    }
527
528    /// Hash every (padded) block of `data` into `h`. Caller must ensure [`available`].
529    #[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
530    pub unsafe fn sha1_blocks(data: &[u8], h: &mut [u32; 5]) {
531        each_padded_block(data, Endian::Big, |block| compress(h, block));
532    }
533
534    /// One 64-byte block through the SHA-NI round pipeline (Intel's `sha1_process_x86`).
535    #[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
536    unsafe fn compress(state: &mut [u32; 5], block: &[u8; 64]) {
537        use core::arch::x86_64::*;
538
539        // Full 16-byte reversal: puts big-endian W0 in lane 3, aligned with A (lane 3) and the carried
540        // E (lane 3). E is NOT shuffled — `set_epi32(e,0,0,0)` already places it in lane 3.
541        let mask = _mm_set_epi64x(0x0001_0203_0405_0607, 0x0809_0a0b_0c0d_0e0fu64 as i64);
542        let mut abcd = _mm_shuffle_epi32(_mm_loadu_si128(state.as_ptr() as *const __m128i), 0x1B);
543        let mut e0 = _mm_set_epi32(state[4] as i32, 0, 0, 0);
544        let abcd_save = abcd;
545        let e0_save = e0;
546        let p = block.as_ptr();
547        let mut msg0 = _mm_shuffle_epi8(_mm_loadu_si128(p as *const __m128i), mask);
548        let mut msg1 = _mm_shuffle_epi8(_mm_loadu_si128(p.add(16) as *const __m128i), mask);
549        let mut msg2 = _mm_shuffle_epi8(_mm_loadu_si128(p.add(32) as *const __m128i), mask);
550        let mut msg3 = _mm_shuffle_epi8(_mm_loadu_si128(p.add(48) as *const __m128i), mask);
551        let mut e1;
552
553        // Rounds 0–3
554        e0 = _mm_add_epi32(e0, msg0);
555        e1 = abcd;
556        abcd = _mm_sha1rnds4_epu32(abcd, e0, 0);
557        // 4–7
558        e1 = _mm_sha1nexte_epu32(e1, msg1);
559        e0 = abcd;
560        abcd = _mm_sha1rnds4_epu32(abcd, e1, 0);
561        msg0 = _mm_sha1msg1_epu32(msg0, msg1);
562        // 8–11
563        e0 = _mm_sha1nexte_epu32(e0, msg2);
564        e1 = abcd;
565        abcd = _mm_sha1rnds4_epu32(abcd, e0, 0);
566        msg1 = _mm_sha1msg1_epu32(msg1, msg2);
567        msg0 = _mm_xor_si128(msg0, msg2);
568        // 12–15
569        e1 = _mm_sha1nexte_epu32(e1, msg3);
570        e0 = abcd;
571        msg0 = _mm_sha1msg2_epu32(msg0, msg3);
572        abcd = _mm_sha1rnds4_epu32(abcd, e1, 0);
573        msg2 = _mm_sha1msg1_epu32(msg2, msg3);
574        msg1 = _mm_xor_si128(msg1, msg3);
575        // 16–19
576        e0 = _mm_sha1nexte_epu32(e0, msg0);
577        e1 = abcd;
578        msg1 = _mm_sha1msg2_epu32(msg1, msg0);
579        abcd = _mm_sha1rnds4_epu32(abcd, e0, 0);
580        msg3 = _mm_sha1msg1_epu32(msg3, msg0);
581        msg2 = _mm_xor_si128(msg2, msg0);
582        // 20–23
583        e1 = _mm_sha1nexte_epu32(e1, msg1);
584        e0 = abcd;
585        msg2 = _mm_sha1msg2_epu32(msg2, msg1);
586        abcd = _mm_sha1rnds4_epu32(abcd, e1, 1);
587        msg0 = _mm_sha1msg1_epu32(msg0, msg1);
588        msg3 = _mm_xor_si128(msg3, msg1);
589        // 24–27
590        e0 = _mm_sha1nexte_epu32(e0, msg2);
591        e1 = abcd;
592        msg3 = _mm_sha1msg2_epu32(msg3, msg2);
593        abcd = _mm_sha1rnds4_epu32(abcd, e0, 1);
594        msg1 = _mm_sha1msg1_epu32(msg1, msg2);
595        msg0 = _mm_xor_si128(msg0, msg2);
596        // 28–31
597        e1 = _mm_sha1nexte_epu32(e1, msg3);
598        e0 = abcd;
599        msg0 = _mm_sha1msg2_epu32(msg0, msg3);
600        abcd = _mm_sha1rnds4_epu32(abcd, e1, 1);
601        msg2 = _mm_sha1msg1_epu32(msg2, msg3);
602        msg1 = _mm_xor_si128(msg1, msg3);
603        // 32–35
604        e0 = _mm_sha1nexte_epu32(e0, msg0);
605        e1 = abcd;
606        msg1 = _mm_sha1msg2_epu32(msg1, msg0);
607        abcd = _mm_sha1rnds4_epu32(abcd, e0, 1);
608        msg3 = _mm_sha1msg1_epu32(msg3, msg0);
609        msg2 = _mm_xor_si128(msg2, msg0);
610        // 36–39
611        e1 = _mm_sha1nexte_epu32(e1, msg1);
612        e0 = abcd;
613        msg2 = _mm_sha1msg2_epu32(msg2, msg1);
614        abcd = _mm_sha1rnds4_epu32(abcd, e1, 1);
615        msg0 = _mm_sha1msg1_epu32(msg0, msg1);
616        msg3 = _mm_xor_si128(msg3, msg1);
617        // 40–43
618        e0 = _mm_sha1nexte_epu32(e0, msg2);
619        e1 = abcd;
620        msg3 = _mm_sha1msg2_epu32(msg3, msg2);
621        abcd = _mm_sha1rnds4_epu32(abcd, e0, 2);
622        msg1 = _mm_sha1msg1_epu32(msg1, msg2);
623        msg0 = _mm_xor_si128(msg0, msg2);
624        // 44–47
625        e1 = _mm_sha1nexte_epu32(e1, msg3);
626        e0 = abcd;
627        msg0 = _mm_sha1msg2_epu32(msg0, msg3);
628        abcd = _mm_sha1rnds4_epu32(abcd, e1, 2);
629        msg2 = _mm_sha1msg1_epu32(msg2, msg3);
630        msg1 = _mm_xor_si128(msg1, msg3);
631        // 48–51
632        e0 = _mm_sha1nexte_epu32(e0, msg0);
633        e1 = abcd;
634        msg1 = _mm_sha1msg2_epu32(msg1, msg0);
635        abcd = _mm_sha1rnds4_epu32(abcd, e0, 2);
636        msg3 = _mm_sha1msg1_epu32(msg3, msg0);
637        msg2 = _mm_xor_si128(msg2, msg0);
638        // 52–55
639        e1 = _mm_sha1nexte_epu32(e1, msg1);
640        e0 = abcd;
641        msg2 = _mm_sha1msg2_epu32(msg2, msg1);
642        abcd = _mm_sha1rnds4_epu32(abcd, e1, 2);
643        msg0 = _mm_sha1msg1_epu32(msg0, msg1);
644        msg3 = _mm_xor_si128(msg3, msg1);
645        // 56–59
646        e0 = _mm_sha1nexte_epu32(e0, msg2);
647        e1 = abcd;
648        msg3 = _mm_sha1msg2_epu32(msg3, msg2);
649        abcd = _mm_sha1rnds4_epu32(abcd, e0, 2);
650        msg1 = _mm_sha1msg1_epu32(msg1, msg2);
651        msg0 = _mm_xor_si128(msg0, msg2);
652        // 60–63
653        e1 = _mm_sha1nexte_epu32(e1, msg3);
654        e0 = abcd;
655        msg0 = _mm_sha1msg2_epu32(msg0, msg3);
656        abcd = _mm_sha1rnds4_epu32(abcd, e1, 3);
657        msg2 = _mm_sha1msg1_epu32(msg2, msg3);
658        msg1 = _mm_xor_si128(msg1, msg3);
659        // 64–67
660        e0 = _mm_sha1nexte_epu32(e0, msg0);
661        e1 = abcd;
662        msg1 = _mm_sha1msg2_epu32(msg1, msg0);
663        abcd = _mm_sha1rnds4_epu32(abcd, e0, 3);
664        msg3 = _mm_sha1msg1_epu32(msg3, msg0);
665        msg2 = _mm_xor_si128(msg2, msg0);
666        // 68–71
667        e1 = _mm_sha1nexte_epu32(e1, msg1);
668        e0 = abcd;
669        msg2 = _mm_sha1msg2_epu32(msg2, msg1);
670        abcd = _mm_sha1rnds4_epu32(abcd, e1, 3);
671        msg3 = _mm_xor_si128(msg3, msg1);
672        // 72–75
673        e0 = _mm_sha1nexte_epu32(e0, msg2);
674        e1 = abcd;
675        msg3 = _mm_sha1msg2_epu32(msg3, msg2);
676        abcd = _mm_sha1rnds4_epu32(abcd, e0, 3);
677        // 76–79
678        e1 = _mm_sha1nexte_epu32(e1, msg3);
679        e0 = abcd;
680        abcd = _mm_sha1rnds4_epu32(abcd, e1, 3);
681
682        // Fold this block's result back into the running state.
683        e0 = _mm_sha1nexte_epu32(e0, e0_save);
684        abcd = _mm_add_epi32(abcd, abcd_save);
685
686        abcd = _mm_shuffle_epi32(abcd, 0x1B);
687        _mm_storeu_si128(state.as_mut_ptr() as *mut __m128i, abcd);
688        state[4] = _mm_extract_epi32(e0, 3) as u32;
689    }
690}
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695
696    #[test]
697    fn md5_known_rfc_vectors() {
698        assert_eq!(to_hex(&md5(b"")), "d41d8cd98f00b204e9800998ecf8427e");
699        assert_eq!(to_hex(&md5(b"a")), "0cc175b9c0f1b6a831c399e269772661");
700        assert_eq!(to_hex(&md5(b"abc")), "900150983cd24fb0d6963f7d28e17f72");
701        assert_eq!(to_hex(&md5(b"message digest")), "f96b697d7cb7938d525a2f31aaf161d0");
702        assert_eq!(
703            to_hex(&md5(b"abcdefghijklmnopqrstuvwxyz")),
704            "c3fcd3d76192e4007dfb496cca67e13b"
705        );
706        assert_eq!(
707            to_hex(&md5(b"The quick brown fox jumps over the lazy dog")),
708            "9e107d9d372bb6826bd81d3542a419d6"
709        );
710    }
711
712    #[test]
713    fn sha1_known_rfc_vectors() {
714        assert_eq!(to_hex(&sha1(b"")), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
715        assert_eq!(to_hex(&sha1(b"abc")), "a9993e364706816aba3e25717850c26c9cd0d89d");
716        assert_eq!(
717            to_hex(&sha1(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")),
718            "84983e441c3bd26ebaae4aa1f95129e5e54670f1"
719        );
720        assert_eq!(
721            to_hex(&sha1(b"The quick brown fox jumps over the lazy dog")),
722            "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"
723        );
724    }
725
726    #[test]
727    fn md5_block_boundaries_are_exact() {
728        // Lengths straddling the 55/56/64/119/120-byte padding edges — the cases padding bugs hide in.
729        for n in [0usize, 1, 54, 55, 56, 57, 63, 64, 65, 119, 120, 127, 128, 256, 1000] {
730            let data = vec![0xABu8; n];
731            let mut oracle = <md5::Md5 as md5::Digest>::new();
732            md5::Digest::update(&mut oracle, &data);
733            let want: [u8; 16] = md5::Digest::finalize(oracle).into();
734            assert_eq!(md5(&data), want, "md5 mismatch at len {n}");
735        }
736    }
737
738    #[test]
739    #[cfg(target_arch = "x86_64")]
740    fn md5_x4_matches_scalar_over_lengths() {
741        // The 4-way SIMD multi-buffer MD5 must equal the scalar MD5 on every lane, across lengths that
742        // straddle the block and one/two-block padding edges (55/56/57, 63/64/65, 119/120).
743        for len in [0usize, 1, 3, 16, 32, 55, 56, 57, 63, 64, 65, 119, 120, 128, 200] {
744            let mut msgs = [Vec::new(), Vec::new(), Vec::new(), Vec::new()];
745            let mut st = 0x1234_5678u64.wrapping_add(len as u64);
746            for m in msgs.iter_mut() {
747                for _ in 0..len {
748                    st = st.wrapping_mul(6364136223846793005).wrapping_add(1);
749                    m.push((st >> 56) as u8);
750                }
751            }
752            let refs: [&[u8]; 4] = [&msgs[0], &msgs[1], &msgs[2], &msgs[3]];
753            let x4 = md5_x4(refs);
754            for (j, m) in msgs.iter().enumerate() {
755                assert_eq!(x4[j], md5(m), "md5_x4 lane {j} differs at len {len}");
756            }
757        }
758    }
759
760    #[test]
761    #[cfg(target_arch = "x86_64")]
762    fn md5_x8_matches_scalar_over_lengths() {
763        // The 8-way AVX2 multi-buffer MD5 must equal the scalar MD5 on every lane, across the same
764        // block/padding edges the 4-way is checked at.
765        for len in [0usize, 1, 3, 16, 32, 55, 56, 57, 63, 64, 65, 119, 120, 128, 200] {
766            let mut msgs: Vec<Vec<u8>> = (0..8).map(|_| Vec::new()).collect();
767            let mut st = 0x9e37_79b9u64.wrapping_add(len as u64);
768            for m in msgs.iter_mut() {
769                for _ in 0..len {
770                    st = st.wrapping_mul(6364136223846793005).wrapping_add(1);
771                    m.push((st >> 56) as u8);
772                }
773            }
774            let refs: [&[u8]; 8] = [
775                &msgs[0], &msgs[1], &msgs[2], &msgs[3], &msgs[4], &msgs[5], &msgs[6], &msgs[7],
776            ];
777            let x8 = md5_x8(refs);
778            for (j, m) in msgs.iter().enumerate() {
779                assert_eq!(x8[j], md5(m), "md5_x8 lane {j} differs at len {len}");
780            }
781        }
782    }
783
784    #[test]
785    fn sha1_block_boundaries_are_exact() {
786        for n in [0usize, 1, 54, 55, 56, 57, 63, 64, 65, 119, 120, 127, 128, 256, 1000] {
787            let data = vec![0x5Au8; n];
788            let mut oracle = <sha1::Sha1 as sha1::Digest>::new();
789            sha1::Digest::update(&mut oracle, &data);
790            let want: [u8; 20] = sha1::Digest::finalize(oracle).into();
791            assert_eq!(sha1(&data), want, "sha1 mismatch at len {n}");
792        }
793    }
794
795    #[test]
796    fn differential_fuzz_against_the_reference_crates() {
797        // A cheap deterministic LCG drives 5000 random-length, random-content inputs; both digests
798        // must agree with the reference crates bit-for-bit. (No external RNG — reproducible.)
799        let mut state: u64 = 0x1234_5678_9abc_def0;
800        let mut next = || {
801            state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
802            (state >> 33) as u32
803        };
804        for _ in 0..5000 {
805            let len = (next() % 600) as usize;
806            let data: Vec<u8> = (0..len).map(|_| (next() & 0xff) as u8).collect();
807
808            let mut m = <md5::Md5 as md5::Digest>::new();
809            md5::Digest::update(&mut m, &data);
810            let m_want: [u8; 16] = md5::Digest::finalize(m).into();
811            assert_eq!(md5(&data), m_want, "md5 differs at len {len}");
812
813            let mut s = <sha1::Sha1 as sha1::Digest>::new();
814            sha1::Digest::update(&mut s, &data);
815            let s_want: [u8; 20] = sha1::Digest::finalize(s).into();
816            assert_eq!(sha1(&data), s_want, "sha1 differs at len {len}");
817        }
818    }
819}