Skip to main content

dryoc/sha512/
mod.rs

1//! # SHA-512 hash algorithm
2//!
3//! Provides an implementation of the SHA-512 hash algorithm.
4//!
5//! ## Example
6//!
7//! ```
8//! # #[cfg(feature = "alloc")]
9//! # {
10//! use dryoc::sha512::Sha512;
11//!
12//! let mut state = Sha512::new();
13//! state.update(b"bytes");
14//! let hash = state.finalize_to_vec();
15//! # }
16//! ```
17use crate::constants::CRYPTO_HASH_SHA512_BYTES;
18use crate::sha2_impl::sha2_hasher;
19use crate::types::*;
20
21#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
22mod sha512_aarch64;
23
24/// Type alias for a SHA-512 digest.
25pub type Digest = StackByteArray<CRYPTO_HASH_SHA512_BYTES>;
26
27const BLOCK_BYTES: usize = 128;
28
29const IV: [u64; 8] = [
30    0x6a09e667f3bcc908,
31    0xbb67ae8584caa73b,
32    0x3c6ef372fe94f82b,
33    0xa54ff53a5f1d36f1,
34    0x510e527fade682d1,
35    0x9b05688c2b3e6c1f,
36    0x1f83d9abfb41bd6b,
37    0x5be0cd19137e2179,
38];
39
40/// Compresses whole blocks into `state`, using the hardware SHA-512
41/// instructions when the running CPU has them.
42///
43/// Out of line at opt-level `z` (with the kernels it calls at `z`, `s` and
44/// `2`), which adds no copy: they only get `&mut` to the hasher's own state
45/// or to `compute_into_bytes`' wiped local, and `&` to the blocks.
46#[inline]
47fn compress(state: &mut [u64; 8], blocks: &[[u8; BLOCK_BYTES]]) {
48    #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
49    if let Some(sha3) = crate::aarch64::Sha3::new() {
50        sha512_aarch64::compress(sha3, state, blocks);
51        return;
52    }
53    sha2::block_api::compress512(state, blocks);
54}
55
56sha2_hasher! {
57    /// SHA-512 hasher.
58    ///
59    /// Buffers input into 128-byte blocks and drives the hardware SHA-512
60    /// compression (runtime-detected on AArch64) or the `sha2` crate's portable
61    /// compression function. The state and any buffered input are wiped when
62    /// the hasher is dropped.
63    pub struct Sha512;
64    algorithm: "SHA-512",
65    word: u64,
66    word_bytes: 8,
67    length: u128,
68    length_bytes: 16,
69    block_bytes: BLOCK_BYTES,
70    digest_bytes: CRYPTO_HASH_SHA512_BYTES,
71    iv: IV,
72    compress: compress,
73}
74
75impl Sha512 {
76    /// [`Self::absorb_key_block`] of `a` into the fresh hasher `self` and of
77    /// `b` into the fresh hasher `other` (the HMAC inner and outer key
78    /// blocks), compressed together where the hardware path can interleave
79    /// them (see `sha512_aarch64::compress2`). Both chaining states are
80    /// compressed in place.
81    #[inline]
82    pub(crate) fn absorb_key_blocks(
83        &mut self,
84        a: &[u8; BLOCK_BYTES],
85        other: &mut Self,
86        b: &[u8; BLOCK_BYTES],
87    ) {
88        #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
89        if let Some(sha3) = crate::aarch64::Sha3::new() {
90            debug_assert!(self.len == 0 && self.buflen == 0);
91            debug_assert!(other.len == 0 && other.buflen == 0);
92            sha512_aarch64::compress2(sha3, &mut self.state, a, &mut other.state, b);
93            self.len = BLOCK_BYTES as u128;
94            other.len = BLOCK_BYTES as u128;
95            return;
96        }
97        self.absorb_key_block(a);
98        other.absorb_key_block(b);
99    }
100}
101
102#[cfg(all(test, feature = "alloc"))]
103mod tests {
104    use sha2::Digest as _;
105
106    use super::*;
107    use crate::test_prelude::*;
108    use crate::utils::test_util::hex;
109
110    /// FIPS 180-2 test vectors, including the 112-byte message whose padding
111    /// needs a second block.
112    #[test]
113    fn test_sha512_known_answers() {
114        assert_eq!(
115            Sha512::compute_to_vec(b""),
116            hex(concat!(
117                "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce",
118                "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
119            ))
120        );
121        assert_eq!(
122            Sha512::compute_to_vec(b"abc"),
123            hex(concat!(
124                "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a",
125                "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
126            ))
127        );
128        assert_eq!(
129            Sha512::compute_to_vec(
130                b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno\
131                  ijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
132            ),
133            hex(concat!(
134                "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018",
135                "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
136            ))
137        );
138    }
139
140    /// The interleaved two-state compression gives the same states as two
141    /// single compressions, on random block pairs and edge patterns.
142    #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
143    #[test]
144    fn test_compress2_matches_compress() {
145        let Some(sha3) = crate::aarch64::Sha3::new() else {
146            return;
147        };
148        let mut seed = 0x1234_5678_9abc_def0u64;
149        let mut next = || {
150            seed ^= seed << 13;
151            seed ^= seed >> 7;
152            seed ^= seed << 17;
153            seed
154        };
155        for i in 0..500 {
156            let mut a = [0u8; BLOCK_BYTES];
157            let mut b = [0u8; BLOCK_BYTES];
158            match i {
159                0 => {}
160                1 => b = [0xff; BLOCK_BYTES],
161                _ => {
162                    for chunk in a.chunks_mut(8).chain(b.chunks_mut(8)) {
163                        chunk.copy_from_slice(&next().to_le_bytes());
164                    }
165                }
166            }
167            let mut sa = IV;
168            let mut sb = IV;
169            if i % 3 == 0 {
170                // Non-IV starting states too.
171                for w in sa.iter_mut().chain(sb.iter_mut()) {
172                    *w = next();
173                }
174            }
175            let (mut ea, mut eb) = (sa, sb);
176            compress(&mut ea, core::slice::from_ref(&a));
177            compress(&mut eb, core::slice::from_ref(&b));
178            sha512_aarch64::compress2(sha3, &mut sa, &a, &mut sb, &b);
179            assert_eq!(sa, ea, "state a {i}");
180            assert_eq!(sb, eb, "state b {i}");
181        }
182        let (mut x, mut y) = (Sha512::new(), Sha512::new());
183        x.absorb_key_blocks(&[0x36; BLOCK_BYTES], &mut y, &[0x5c; BLOCK_BYTES]);
184        let (mut ex, mut ey) = (Sha512::new(), Sha512::new());
185        ex.absorb_key_block(&[0x36; BLOCK_BYTES]);
186        ey.absorb_key_block(&[0x5c; BLOCK_BYTES]);
187        assert_eq!(x.state, ex.state);
188        assert_eq!(y.state, ey.state);
189    }
190
191    /// Every buffer fill level and padding boundary, absorbed in one call and
192    /// in irregular chunks, matches the `sha2` crate.
193    #[test]
194    fn test_sha512_matches_sha2_for_all_lengths_and_chunkings() {
195        let message: Vec<u8> = (0..1200u32).map(|i| (i * 31 % 251) as u8).collect();
196        for len in (0..400).chain([511, 512, 513, 1023, 1024, 1025, 1199, 1200]) {
197            let expected = sha2::Sha512::digest(&message[..len]).to_vec();
198            assert_eq!(
199                Sha512::compute_to_vec(&message[..len]),
200                expected,
201                "len {len}"
202            );
203
204            let mut hasher = Sha512::new();
205            let mut offset = 0;
206            for chunk in [1usize, 7, 127, 128, 129, 200, 3].iter().cycle() {
207                if offset >= len {
208                    break;
209                }
210                let end = (offset + chunk).min(len);
211                hasher.update(&message[offset..end]);
212                offset = end;
213            }
214            assert_eq!(hasher.finalize_to_vec(), expected, "chunked len {len}");
215        }
216    }
217
218    /// Empty updates at every buffer state and updates that end exactly on
219    /// a block boundary from a partially filled buffer (1 + 127, 127 + 1), a
220    /// whole block from an empty buffer, and finalization from both an empty
221    /// and an almost-full buffer, against the `sha2` crate.
222    #[test]
223    fn test_empty_and_exact_fill_updates_match_sha2() {
224        const B: usize = BLOCK_BYTES;
225        let message: Vec<u8> = (0..3 * B as u32).map(|i| (i * 31 % 251) as u8).collect();
226        for len in [B, B + 1, 2 * B, 2 * B + 1, 3 * B - 1, 3 * B] {
227            let message = &message[..len];
228            let mut cuts: Vec<usize> = [0, 1, B, 2 * B, 3 * B - 1, len]
229                .into_iter()
230                .filter(|&cut| cut <= len)
231                .collect();
232            cuts.dedup();
233            let mut hasher = Sha512::new();
234            hasher.update(b"");
235            for window in cuts.windows(2) {
236                hasher.update(&message[window[0]..window[1]]);
237                hasher.update(b"");
238            }
239            assert_eq!(
240                hasher.finalize_to_vec(),
241                sha2::Sha512::digest(message).to_vec(),
242                "len {len}"
243            );
244        }
245    }
246
247    #[cfg(dryoc_native_tests)]
248    #[test]
249    fn test_sha512_matches_libsodium() {
250        use crate::native_test_util::HashSha512State;
251        use crate::rng::randombytes_buf;
252
253        let mut their_state = HashSha512State::new();
254        let mut our_state = Sha512::new();
255
256        for _ in 0..10 {
257            let r = randombytes_buf(64);
258            their_state.update(&r);
259            our_state.update(&r);
260        }
261
262        let their_digest = their_state.finalize();
263        let our_digest = our_state.finalize_to_vec();
264
265        assert_eq!(their_digest.as_slice(), our_digest);
266    }
267
268    /// The hardware loop agrees with the portable compression for every block
269    /// count around its loop boundaries, including the empty run.
270    #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
271    #[test]
272    fn test_hw_compress_matches_portable() {
273        let Some(sha3) = crate::aarch64::Sha3::new() else {
274            return;
275        };
276        let blocks: Vec<[u8; 128]> = (0..40u32)
277            .map(|b| {
278                core::array::from_fn(|i| (b * 128 + i as u32).wrapping_mul(2654435761) as u8 >> 1)
279            })
280            .collect();
281        for n in 0..=blocks.len() {
282            let mut expected = IV;
283            let mut actual = IV;
284            sha2::block_api::compress512(&mut expected, &blocks[..n]);
285            sha512_aarch64::compress(sha3, &mut actual, &blocks[..n]);
286            assert_eq!(actual, expected, "{n} blocks");
287        }
288    }
289}