Skip to main content

ic_hash/
sha2.rs

1//! FIPS 180-4 SHA-2 family.
2//!
3//! Two cores (32-bit and 64-bit) are shared by six published output variants,
4//! which differ only in their initial hash value and truncation length.
5
6//! Indexed loops over fixed-size limb and word arrays are used throughout; they
7//! mirror the index algebra in the specifications these routines implement, so
8//! `needless_range_loop` is allowed rather than obscuring the correspondence.
9#![allow(clippy::needless_range_loop)]
10
11use ic_core::traits::{Algorithm, Digest, SelfTest};
12use ic_core::{ensure, Result, Zeroize};
13
14// SHA-NI, where the CPU has it. Only under `std`, because the detection does:
15// a `no_std` build has no way to ask, and guessing wrong is an illegal
16// instruction rather than a wrong answer.
17#[cfg(all(target_arch = "x86_64", feature = "std"))]
18mod x86;
19
20/// Whether this CPU has the instructions [`x86::compress`] needs.
21///
22/// Asked once. `is_x86_feature_detected!` is not free, and SHA-256 is called
23/// often enough on small inputs that paying for the query per block would show
24/// up in exactly the workloads this is meant to help.
25#[cfg(all(target_arch = "x86_64", feature = "std"))]
26fn sha_ni() -> bool {
27    use core::sync::atomic::{AtomicU8, Ordering};
28    // 0 not yet asked, 1 yes, 2 no.
29    static CACHED: AtomicU8 = AtomicU8::new(0);
30    match CACHED.load(Ordering::Relaxed) {
31        1 => true,
32        2 => false,
33        _ => {
34            let have = std::is_x86_feature_detected!("sha")
35                && std::is_x86_feature_detected!("sse2")
36                && std::is_x86_feature_detected!("ssse3")
37                && std::is_x86_feature_detected!("sse4.1");
38            CACHED.store(u8::from(!have) + 1, Ordering::Relaxed);
39            have
40        }
41    }
42}
43
44const K256: [u32; 64] = [
45    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
46    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
47    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
48    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
49    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
50    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
51    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
52    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
53];
54
55const K512: [u64; 80] = [
56    0x428a2f98d728ae22,
57    0x7137449123ef65cd,
58    0xb5c0fbcfec4d3b2f,
59    0xe9b5dba58189dbbc,
60    0x3956c25bf348b538,
61    0x59f111f1b605d019,
62    0x923f82a4af194f9b,
63    0xab1c5ed5da6d8118,
64    0xd807aa98a3030242,
65    0x12835b0145706fbe,
66    0x243185be4ee4b28c,
67    0x550c7dc3d5ffb4e2,
68    0x72be5d74f27b896f,
69    0x80deb1fe3b1696b1,
70    0x9bdc06a725c71235,
71    0xc19bf174cf692694,
72    0xe49b69c19ef14ad2,
73    0xefbe4786384f25e3,
74    0x0fc19dc68b8cd5b5,
75    0x240ca1cc77ac9c65,
76    0x2de92c6f592b0275,
77    0x4a7484aa6ea6e483,
78    0x5cb0a9dcbd41fbd4,
79    0x76f988da831153b5,
80    0x983e5152ee66dfab,
81    0xa831c66d2db43210,
82    0xb00327c898fb213f,
83    0xbf597fc7beef0ee4,
84    0xc6e00bf33da88fc2,
85    0xd5a79147930aa725,
86    0x06ca6351e003826f,
87    0x142929670a0e6e70,
88    0x27b70a8546d22ffc,
89    0x2e1b21385c26c926,
90    0x4d2c6dfc5ac42aed,
91    0x53380d139d95b3df,
92    0x650a73548baf63de,
93    0x766a0abb3c77b2a8,
94    0x81c2c92e47edaee6,
95    0x92722c851482353b,
96    0xa2bfe8a14cf10364,
97    0xa81a664bbc423001,
98    0xc24b8b70d0f89791,
99    0xc76c51a30654be30,
100    0xd192e819d6ef5218,
101    0xd69906245565a910,
102    0xf40e35855771202a,
103    0x106aa07032bbd1b8,
104    0x19a4c116b8d2d0c8,
105    0x1e376c085141ab53,
106    0x2748774cdf8eeb99,
107    0x34b0bcb5e19b48a8,
108    0x391c0cb3c5c95a63,
109    0x4ed8aa4ae3418acb,
110    0x5b9cca4f7763e373,
111    0x682e6ff3d6b2b8a3,
112    0x748f82ee5defb2fc,
113    0x78a5636f43172f60,
114    0x84c87814a1f0ab72,
115    0x8cc702081a6439ec,
116    0x90befffa23631e28,
117    0xa4506cebde82bde9,
118    0xbef9a3f7b2c67915,
119    0xc67178f2e372532b,
120    0xca273eceea26619c,
121    0xd186b8c721c0c207,
122    0xeada7dd6cde0eb1e,
123    0xf57d4f7fee6ed178,
124    0x06f067aa72176fba,
125    0x0a637dc5a2c898a6,
126    0x113f9804bef90dae,
127    0x1b710b35131c471b,
128    0x28db77f523047d84,
129    0x32caab7b40c72493,
130    0x3c9ebe0a15c9bebc,
131    0x431d67c49c100d4c,
132    0x4cc5d4becb3e42b6,
133    0x597f299cfc657e2a,
134    0x5fcb6fab3ad6faec,
135    0x6c44198c4a475817,
136];
137
138/// The shared 32-bit SHA-2 compression core (SHA-224 / SHA-256).
139#[derive(Clone)]
140struct Core256 {
141    h: [u32; 8],
142    buf: [u8; 64],
143    buffered: usize,
144    len: u64,
145}
146
147impl Drop for Core256 {
148    /// Wipe the chaining state and the buffered block.
149    ///
150    /// A hash is not a secret, but this state is not only used for hashing:
151    /// `Hmac<D>` holds two of these with the key already absorbed into them,
152    /// so the ipad and opad states are key-derived material. Putting the wipe
153    /// here rather than on `Hmac` means every consumer inherits it through
154    /// ordinary field drop, with no `Zeroize` bound threaded through the
155    /// `Digest` trait and no chance of a new wrapper forgetting.
156    fn drop(&mut self) {
157        self.h.zeroize();
158        self.buf.zeroize();
159        self.buffered = 0;
160        self.len = 0;
161    }
162}
163
164impl Core256 {
165    const fn new(iv: [u32; 8]) -> Self {
166        Self {
167            h: iv,
168            buf: [0u8; 64],
169            buffered: 0,
170            len: 0,
171        }
172    }
173
174    fn compress(&mut self, block: &[u8]) {
175        let mut w = [0u32; 64];
176        for i in 0..16 {
177            w[i] = u32::from_be_bytes([
178                block[i * 4],
179                block[i * 4 + 1],
180                block[i * 4 + 2],
181                block[i * 4 + 3],
182            ]);
183        }
184        for i in 16..64 {
185            let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
186            let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
187            w[i] = w[i - 16]
188                .wrapping_add(s0)
189                .wrapping_add(w[i - 7])
190                .wrapping_add(s1);
191        }
192        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = self.h;
193        for i in 0..64 {
194            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
195            let ch = (e & f) ^ ((!e) & g);
196            let t1 = hh
197                .wrapping_add(s1)
198                .wrapping_add(ch)
199                .wrapping_add(K256[i])
200                .wrapping_add(w[i]);
201            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
202            let maj = (a & b) ^ (a & c) ^ (b & c);
203            let t2 = s0.wrapping_add(maj);
204            hh = g;
205            g = f;
206            f = e;
207            e = d.wrapping_add(t1);
208            d = c;
209            c = b;
210            b = a;
211            a = t1.wrapping_add(t2);
212        }
213        let upd = [a, b, c, d, e, f, g, hh];
214        for i in 0..8 {
215            self.h[i] = self.h[i].wrapping_add(upd[i]);
216        }
217        w.zeroize();
218    }
219
220    /// Compress a whole number of blocks, using the hardware path when there
221    /// is one.
222    ///
223    /// Taking a run rather than a block at a time is the point: the SHA-NI
224    /// backend shuffles the state into and out of its register layout once per
225    /// call, so feeding it one block at a time would pay that on every block.
226    fn compress_blocks(&mut self, data: &[u8]) {
227        debug_assert!(data.len() % 64 == 0);
228        if data.is_empty() {
229            return;
230        }
231        #[cfg(all(target_arch = "x86_64", feature = "std"))]
232        if sha_ni() {
233            // SAFETY: `sha_ni()` is exactly the feature test this requires, and
234            // the length is a multiple of the block size by the assertion above.
235            unsafe { x86::compress(&mut self.h, data) };
236            return;
237        }
238        for block in data.chunks_exact(64) {
239            self.compress(block);
240        }
241    }
242
243    fn update(&mut self, mut data: &[u8]) {
244        self.len = self.len.wrapping_add(data.len() as u64);
245        if self.buffered > 0 {
246            let need = 64 - self.buffered;
247            let take = core::cmp::min(need, data.len());
248            self.buf[self.buffered..self.buffered + take].copy_from_slice(&data[..take]);
249            self.buffered += take;
250            data = &data[take..];
251            if self.buffered < 64 {
252                // The whole input fit in the partial block; nothing to compress.
253                return;
254            }
255            let block = self.buf;
256            self.compress(&block);
257            self.buffered = 0;
258        }
259        let whole = data.len() - data.len() % 64;
260        self.compress_blocks(&data[..whole]);
261        let rest = &data[whole..];
262        self.buf[..rest.len()].copy_from_slice(rest);
263        self.buffered = rest.len();
264    }
265
266    fn finalize(mut self) -> [u32; 8] {
267        let bit_len = self.len.wrapping_mul(8);
268        let mut pad = [0u8; 72];
269        pad[0] = 0x80;
270        // Pad so that (buffered + 1 + zeros) % 64 == 56.
271        let zeros = (55 + 64 - (self.buffered % 64)) % 64;
272        pad[1 + zeros..1 + zeros + 8].copy_from_slice(&bit_len.to_be_bytes());
273        self.update_no_count(&pad[..1 + zeros + 8]);
274        let out = self.h;
275        self.buf.zeroize();
276        self.h.zeroize();
277        out
278    }
279
280    /// Absorb padding without disturbing the message-length counter.
281    fn update_no_count(&mut self, data: &[u8]) {
282        let saved = self.len;
283        self.update(data);
284        self.len = saved;
285    }
286}
287
288/// The shared 64-bit SHA-2 compression core (SHA-384 / SHA-512 / SHA-512-t).
289#[derive(Clone)]
290struct Core512 {
291    h: [u64; 8],
292    buf: [u8; 128],
293    buffered: usize,
294    len: u128,
295}
296
297impl Drop for Core512 {
298    /// Wipe the chaining state and the buffered block. See [`Core256`].
299    fn drop(&mut self) {
300        self.h.zeroize();
301        self.buf.zeroize();
302        self.buffered = 0;
303        self.len = 0;
304    }
305}
306
307/// SHA-512's compression, and what has already been tried on it.
308///
309/// It runs about 1.3 to 1.75 times behind RustCrypto's, which has an AVX2
310/// backend for the message schedule. There is no SHA-512 instruction on x86 the
311/// way there is for SHA-256, so the portable path below is what runs.
312///
313/// Two source-level optimisations were measured and reverted, and are recorded
314/// so they are not tried a third time:
315///
316/// - **A rolling sixteen-word schedule window** instead of the eighty-word
317///   array. The array is 640 bytes cleared per 128-byte block, five bytes wiped
318///   per byte hashed, which looks like the cost. A controlled A/B showed it
319///   *slower*: 640 bytes sits in L1, and the modulo indexing defeats whatever
320///   unrolling the flat array was getting.
321/// - **Unrolling the round loop by eight**, naming the working variables in
322///   rotation so the eight moves per round disappear. No measurable change in
323///   either direction; LLVM already renames and unrolls this shape.
324///
325/// Both failed the same way: the waste was visible in the source and absent
326/// from the object code. What did pay elsewhere in this workspace was work the
327/// compiler cannot do -- breaking a serial dependency chain, selecting a
328/// hardware instruction, changing the algorithm. The remaining gap here is the
329/// vectorised schedule, and even that addresses only the third or so of the
330/// work the schedule represents, since the rounds are inherently serial.
331impl Core512 {
332    const fn new(iv: [u64; 8]) -> Self {
333        Self {
334            h: iv,
335            buf: [0u8; 128],
336            buffered: 0,
337            len: 0,
338        }
339    }
340
341    fn compress(&mut self, block: &[u8]) {
342        let mut w = [0u64; 80];
343        for i in 0..16 {
344            let mut b = [0u8; 8];
345            b.copy_from_slice(&block[i * 8..i * 8 + 8]);
346            w[i] = u64::from_be_bytes(b);
347        }
348        for i in 16..80 {
349            let s0 = w[i - 15].rotate_right(1) ^ w[i - 15].rotate_right(8) ^ (w[i - 15] >> 7);
350            let s1 = w[i - 2].rotate_right(19) ^ w[i - 2].rotate_right(61) ^ (w[i - 2] >> 6);
351            w[i] = w[i - 16]
352                .wrapping_add(s0)
353                .wrapping_add(w[i - 7])
354                .wrapping_add(s1);
355        }
356        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = self.h;
357        for i in 0..80 {
358            let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
359            let ch = (e & f) ^ ((!e) & g);
360            let t1 = hh
361                .wrapping_add(s1)
362                .wrapping_add(ch)
363                .wrapping_add(K512[i])
364                .wrapping_add(w[i]);
365            let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
366            let maj = (a & b) ^ (a & c) ^ (b & c);
367            let t2 = s0.wrapping_add(maj);
368            hh = g;
369            g = f;
370            f = e;
371            e = d.wrapping_add(t1);
372            d = c;
373            c = b;
374            b = a;
375            a = t1.wrapping_add(t2);
376        }
377        let upd = [a, b, c, d, e, f, g, hh];
378        for i in 0..8 {
379            self.h[i] = self.h[i].wrapping_add(upd[i]);
380        }
381        w.zeroize();
382    }
383
384    fn update(&mut self, mut data: &[u8]) {
385        self.len = self.len.wrapping_add(data.len() as u128);
386        if self.buffered > 0 {
387            let need = 128 - self.buffered;
388            let take = core::cmp::min(need, data.len());
389            self.buf[self.buffered..self.buffered + take].copy_from_slice(&data[..take]);
390            self.buffered += take;
391            data = &data[take..];
392            if self.buffered < 128 {
393                // The whole input fit in the partial block; nothing to compress.
394                return;
395            }
396            let block = self.buf;
397            self.compress(&block);
398            self.buffered = 0;
399        }
400        let mut chunks = data.chunks_exact(128);
401        for block in &mut chunks {
402            self.compress(block);
403        }
404        let rest = chunks.remainder();
405        self.buf[..rest.len()].copy_from_slice(rest);
406        self.buffered = rest.len();
407    }
408
409    fn finalize(mut self) -> [u64; 8] {
410        let bit_len = self.len.wrapping_mul(8);
411        let mut pad = [0u8; 145];
412        pad[0] = 0x80;
413        let zeros = (111 + 128 - (self.buffered % 128)) % 128;
414        pad[1 + zeros..1 + zeros + 16].copy_from_slice(&bit_len.to_be_bytes());
415        let saved = self.len;
416        self.update(&pad[..1 + zeros + 16]);
417        self.len = saved;
418        let out = self.h;
419        self.buf.zeroize();
420        self.h.zeroize();
421        out
422    }
423}
424
425macro_rules! sha2_32 {
426    ($name:ident, $id:literal, $disp:literal, $out:literal, $iv:expr, $kat:literal) => {
427        #[doc = concat!("FIPS 180-4 ", $disp, ".")]
428        #[derive(Clone)]
429        pub struct $name(Core256);
430
431        impl Default for $name {
432            fn default() -> Self {
433                Self(Core256::new($iv))
434            }
435        }
436
437        impl Algorithm for $name {
438            const ID: &'static str = $id;
439            const NAME: &'static str = $disp;
440        }
441
442        impl Digest for $name {
443            type Output = [u8; $out];
444            const OUTPUT_LEN: usize = $out;
445            const BLOCK_LEN: usize = 64;
446
447            fn update(&mut self, data: &[u8]) {
448                self.0.update(data);
449            }
450
451            fn finalize(self) -> Self::Output {
452                let h = self.0.finalize();
453                let mut full = [0u8; 32];
454                for i in 0..8 {
455                    full[i * 4..i * 4 + 4].copy_from_slice(&h[i].to_be_bytes());
456                }
457                let mut out = [0u8; $out];
458                out.copy_from_slice(&full[..$out]);
459                out
460            }
461        }
462
463        impl SelfTest for $name {
464            fn self_test() -> Result<()> {
465                let got = <Self as Digest>::digest(b"abc");
466                let mut want = [0u8; $out];
467                ic_core::codec::hex_decode($kat.as_bytes(), &mut want)?;
468                ensure!(
469                    ic_core::ct::verify(&want, got.as_ref()),
470                    SelfTestFailed,
471                    $id
472                );
473                Ok(())
474            }
475        }
476    };
477}
478
479macro_rules! sha2_64 {
480    ($name:ident, $id:literal, $disp:literal, $out:literal, $iv:expr, $kat:literal) => {
481        #[doc = concat!("FIPS 180-4 ", $disp, ".")]
482        #[derive(Clone)]
483        pub struct $name(Core512);
484
485        impl Default for $name {
486            fn default() -> Self {
487                Self(Core512::new($iv))
488            }
489        }
490
491        impl Algorithm for $name {
492            const ID: &'static str = $id;
493            const NAME: &'static str = $disp;
494        }
495
496        impl Digest for $name {
497            type Output = [u8; $out];
498            const OUTPUT_LEN: usize = $out;
499            const BLOCK_LEN: usize = 128;
500
501            fn update(&mut self, data: &[u8]) {
502                self.0.update(data);
503            }
504
505            fn finalize(self) -> Self::Output {
506                let h = self.0.finalize();
507                let mut full = [0u8; 64];
508                for i in 0..8 {
509                    full[i * 8..i * 8 + 8].copy_from_slice(&h[i].to_be_bytes());
510                }
511                let mut out = [0u8; $out];
512                out.copy_from_slice(&full[..$out]);
513                out
514            }
515        }
516
517        impl SelfTest for $name {
518            fn self_test() -> Result<()> {
519                let got = <Self as Digest>::digest(b"abc");
520                let mut want = [0u8; $out];
521                ic_core::codec::hex_decode($kat.as_bytes(), &mut want)?;
522                ensure!(
523                    ic_core::ct::verify(&want, got.as_ref()),
524                    SelfTestFailed,
525                    $id
526                );
527                Ok(())
528            }
529        }
530    };
531}
532
533sha2_32!(
534    Sha224,
535    "sha2-224",
536    "SHA-224",
537    28,
538    [
539        0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7,
540        0xbefa4fa4
541    ],
542    "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"
543);
544
545sha2_32!(
546    Sha256,
547    "sha2-256",
548    "SHA-256",
549    32,
550    [
551        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
552        0x5be0cd19
553    ],
554    "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
555);
556
557sha2_64!(
558    Sha384,
559    "sha2-384",
560    "SHA-384",
561    48,
562    [
563        0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939,
564        0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4
565    ],
566    "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"
567);
568
569sha2_64!(
570    Sha512,
571    "sha2-512",
572    "SHA-512",
573    64,
574    [
575        0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
576        0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179
577    ],
578    "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
579);
580
581sha2_64!(
582    Sha512_224,
583    "sha2-512-224",
584    "SHA-512/224",
585    28,
586    [
587        0x8c3d37c819544da2,
588        0x73e1996689dcd4d6,
589        0x1dfab7ae32ff9c82,
590        0x679dd514582f9fcf,
591        0x0f6d2b697bd44da8,
592        0x77e36f7304c48942,
593        0x3f9d85a86a1d36c8,
594        0x1112e6ad91d692a1
595    ],
596    "4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa"
597);
598
599sha2_64!(
600    Sha512_256,
601    "sha2-512-256",
602    "SHA-512/256",
603    32,
604    [
605        0x22312194fc2bf72c,
606        0x9f555fa3c84c64c2,
607        0x2393b86b6f53b151,
608        0x963877195940eabd,
609        0x96283ee2a88effe3,
610        0xbe5e1e2553863992,
611        0x2b0199fc2c85b8aa,
612        0x0eb72ddc81c52ca2
613    ],
614    "53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23"
615);
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620
621    /// The hardware path must agree with the portable one, block for block.
622    ///
623    /// The published vectors above do not establish this. They pass whichever
624    /// path runs, so on a machine with SHA-NI they check the backend and on one
625    /// without they check the fallback -- and either way they cannot notice
626    /// that the two disagree, which is the failure a second implementation
627    /// introduces. This runs both over the same input and compares the states.
628    ///
629    /// It reports which path it took rather than asserting one, because a CPU
630    /// without the instructions is a legitimate machine to run the suite on.
631    /// What it does assert is that the comparison happened when it could.
632    #[cfg(all(target_arch = "x86_64", feature = "std"))]
633    #[test]
634    fn the_sha_ni_backend_agrees_with_the_portable_one() {
635        if !sha_ni() {
636            println!("no SHA-NI on this CPU; the backend was not exercised");
637            return;
638        }
639
640        // Lengths either side of the block boundary, and long enough to run the
641        // message schedule over several blocks.
642        let mut checked = 0;
643        for blocks in [1usize, 2, 3, 4, 7, 16] {
644            let mut data = vec![0u8; blocks * 64];
645            // Not random, but not uniform either: a counter through a couple of
646            // multiplications, so every byte position varies between cases.
647            for (i, b) in data.iter_mut().enumerate() {
648                *b = ((i as u64).wrapping_mul(0x9e37_79b9).rotate_left(7) & 0xff) as u8;
649            }
650
651            // FIPS 180-4 section 5.3.3, the same value the macro below passes.
652            const IV: [u32; 8] = [
653                0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
654                0x5be0cd19,
655            ];
656            let mut portable = Core256::new(IV);
657            for block in data.chunks_exact(64) {
658                portable.compress(block);
659            }
660
661            let mut hardware = Core256::new(IV);
662            // SAFETY: guarded by the `sha_ni()` check above.
663            unsafe { x86::compress(&mut hardware.h, &data) };
664
665            assert_eq!(
666                portable.h, hardware.h,
667                "SHA-NI and portable disagree after {blocks} blocks"
668            );
669            checked += 1;
670        }
671        assert_eq!(checked, 6, "the comparison did not run");
672    }
673
674    /// Say which path this build will take, so a benchmark or a vector run is
675    /// not silently measuring the fallback.
676    #[cfg(all(target_arch = "x86_64", feature = "std"))]
677    #[test]
678    fn the_active_sha256_path_is_reported() {
679        println!(
680            "sha-256 backend: {}",
681            if sha_ni() { "SHA-NI" } else { "portable" }
682        );
683    }
684
685    #[test]
686    fn nist_abc_vectors() {
687        assert_eq!(
688            ic_core::codec::hex(Sha256::digest(b"abc").as_ref()),
689            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
690        );
691        assert_eq!(
692            ic_core::codec::hex(Sha224::digest(b"abc").as_ref()),
693            "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"
694        );
695        assert_eq!(
696            ic_core::codec::hex(Sha512::digest(b"abc").as_ref()),
697            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
698        );
699        assert_eq!(
700            ic_core::codec::hex(Sha384::digest(b"abc").as_ref()),
701            "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"
702        );
703    }
704
705    #[test]
706    fn empty_input_vectors() {
707        assert_eq!(
708            ic_core::codec::hex(Sha256::digest(b"").as_ref()),
709            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
710        );
711        assert_eq!(
712            ic_core::codec::hex(Sha512::digest(b"").as_ref()),
713            "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
714        );
715    }
716
717    /// The 448-bit boundary case: input length forces an extra padding block.
718    #[test]
719    fn two_block_vector() {
720        let msg = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
721        assert_eq!(
722            ic_core::codec::hex(Sha256::digest(msg).as_ref()),
723            "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
724        );
725    }
726
727    #[test]
728    fn million_a_vector() {
729        let mut h = Sha256::new();
730        let chunk = [b'a'; 1000];
731        for _ in 0..1000 {
732            h.update(&chunk);
733        }
734        assert_eq!(
735            ic_core::codec::hex(h.finalize().as_ref()),
736            "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"
737        );
738    }
739
740    #[test]
741    fn streaming_matches_one_shot() {
742        let data: [u8; 300] = core::array::from_fn(|i| i as u8);
743        for split in [0usize, 1, 63, 64, 65, 127, 128, 200, 300] {
744            let mut h = Sha512::new();
745            h.update(&data[..split]);
746            h.update(&data[split..]);
747            assert_eq!(h.finalize(), Sha512::digest(&data), "split at {split}");
748        }
749    }
750
751    #[test]
752    fn truncated_variants() {
753        assert_eq!(
754            ic_core::codec::hex(Sha512_224::digest(b"abc").as_ref()),
755            "4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa"
756        );
757        assert_eq!(
758            ic_core::codec::hex(Sha512_256::digest(b"abc").as_ref()),
759            "53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23"
760        );
761    }
762
763    #[test]
764    fn all_self_tests_pass() {
765        Sha224::self_test().unwrap();
766        Sha256::self_test().unwrap();
767        Sha384::self_test().unwrap();
768        Sha512::self_test().unwrap();
769        Sha512_224::self_test().unwrap();
770        Sha512_256::self_test().unwrap();
771    }
772}