Skip to main content

arcana/hash/
sha512.rs

1//! SHA-512 hash function (FIPS 180-4).
2//!
3//! 512-bit output, 1024-bit (128-byte) blocks, operates on 64-bit
4//! words. Same Merkle-Damgård / round-mixer structure as
5//! [`super::sha256`], with 80 rounds and 64-bit additions
6//! throughout.
7//!
8//! # Side-channel posture
9//!
10//! Unkeyed → not SCA-sensitive. Same CDPA caveat as SHA-256
11//! when consumed by Ed25519 nonce derivation (`r =
12//! H(prefix ‖ M) mod ℓ`), HMAC-SHA-512, or RFC 6979 HMAC-DRBG-512:
13//! the carry chain inside each 64-bit add is exploitable per
14//! `belenky2023_cdpa_hmac_sha2`. Roadmap item `T2-D` ships a
15//! masked variant when the `sca-protected` feature is enabled
16//! (see `arcana/doc/sca/countermeasures/hmac.rst`).
17
18use crate::Hasher;
19
20pub(crate) const H0_512: [u64; 8] = [
21    0x6a09e667f3bcc908,
22    0xbb67ae8584caa73b,
23    0x3c6ef372fe94f82b,
24    0xa54ff53a5f1d36f1,
25    0x510e527fade682d1,
26    0x9b05688c2b3e6c1f,
27    0x1f83d9abfb41bd6b,
28    0x5be0cd19137e2179,
29];
30
31const K: [u64; 80] = [
32    0x428a2f98d728ae22,
33    0x7137449123ef65cd,
34    0xb5c0fbcfec4d3b2f,
35    0xe9b5dba58189dbbc,
36    0x3956c25bf348b538,
37    0x59f111f1b605d019,
38    0x923f82a4af194f9b,
39    0xab1c5ed5da6d8118,
40    0xd807aa98a3030242,
41    0x12835b0145706fbe,
42    0x243185be4ee4b28c,
43    0x550c7dc3d5ffb4e2,
44    0x72be5d74f27b896f,
45    0x80deb1fe3b1696b1,
46    0x9bdc06a725c71235,
47    0xc19bf174cf692694,
48    0xe49b69c19ef14ad2,
49    0xefbe4786384f25e3,
50    0x0fc19dc68b8cd5b5,
51    0x240ca1cc77ac9c65,
52    0x2de92c6f592b0275,
53    0x4a7484aa6ea6e483,
54    0x5cb0a9dcbd41fbd4,
55    0x76f988da831153b5,
56    0x983e5152ee66dfab,
57    0xa831c66d2db43210,
58    0xb00327c898fb213f,
59    0xbf597fc7beef0ee4,
60    0xc6e00bf33da88fc2,
61    0xd5a79147930aa725,
62    0x06ca6351e003826f,
63    0x142929670a0e6e70,
64    0x27b70a8546d22ffc,
65    0x2e1b21385c26c926,
66    0x4d2c6dfc5ac42aed,
67    0x53380d139d95b3df,
68    0x650a73548baf63de,
69    0x766a0abb3c77b2a8,
70    0x81c2c92e47edaee6,
71    0x92722c851482353b,
72    0xa2bfe8a14cf10364,
73    0xa81a664bbc423001,
74    0xc24b8b70d0f89791,
75    0xc76c51a30654be30,
76    0xd192e819d6ef5218,
77    0xd69906245565a910,
78    0xf40e35855771202a,
79    0x106aa07032bbd1b8,
80    0x19a4c116b8d2d0c8,
81    0x1e376c085141ab53,
82    0x2748774cdf8eeb99,
83    0x34b0bcb5e19b48a8,
84    0x391c0cb3c5c95a63,
85    0x4ed8aa4ae3418acb,
86    0x5b9cca4f7763e373,
87    0x682e6ff3d6b2b8a3,
88    0x748f82ee5defb2fc,
89    0x78a5636f43172f60,
90    0x84c87814a1f0ab72,
91    0x8cc702081a6439ec,
92    0x90befffa23631e28,
93    0xa4506cebde82bde9,
94    0xbef9a3f7b2c67915,
95    0xc67178f2e372532b,
96    0xca273eceea26619c,
97    0xd186b8c721c0c207,
98    0xeada7dd6cde0eb1e,
99    0xf57d4f7fee6ed178,
100    0x06f067aa72176fba,
101    0x0a637dc5a2c898a6,
102    0x113f9804bef90dae,
103    0x1b710b35131c471b,
104    0x28db77f523047d84,
105    0x32caab7b40c72493,
106    0x3c9ebe0a15c9bebc,
107    0x431d67c49c100d4c,
108    0x4cc5d4becb3e42b6,
109    0x597f299cfc657e2a,
110    0x5fcb6fab3ad6faec,
111    0x6c44198c4a475817,
112];
113
114pub(crate) fn compress(state: &mut [u64; 8], block: &[u8]) {
115    let mut w = [0u64; 80];
116    for i in 0..16 {
117        w[i] = u64::from_be_bytes([
118            block[8 * i],
119            block[8 * i + 1],
120            block[8 * i + 2],
121            block[8 * i + 3],
122            block[8 * i + 4],
123            block[8 * i + 5],
124            block[8 * i + 6],
125            block[8 * i + 7],
126        ]);
127    }
128    for i in 16..80 {
129        let s0 = w[i - 15].rotate_right(1) ^ w[i - 15].rotate_right(8) ^ (w[i - 15] >> 7);
130        let s1 = w[i - 2].rotate_right(19) ^ w[i - 2].rotate_right(61) ^ (w[i - 2] >> 6);
131        w[i] = w[i - 16].wrapping_add(s0).wrapping_add(w[i - 7]).wrapping_add(s1);
132    }
133
134    let mut a = state[0];
135    let mut b = state[1];
136    let mut c = state[2];
137    let mut d = state[3];
138    let mut e = state[4];
139    let mut f = state[5];
140    let mut g = state[6];
141    let mut h = state[7];
142
143    for i in 0..80 {
144        let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
145        let ch = (e & f) ^ ((!e) & g);
146        let temp1 = h
147            .wrapping_add(s1)
148            .wrapping_add(ch)
149            .wrapping_add(K[i])
150            .wrapping_add(w[i]);
151        let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
152        let maj = (a & b) ^ (a & c) ^ (b & c);
153        let temp2 = s0.wrapping_add(maj);
154
155        h = g;
156        g = f;
157        f = e;
158        e = d.wrapping_add(temp1);
159        d = c;
160        c = b;
161        b = a;
162        a = temp1.wrapping_add(temp2);
163    }
164
165    state[0] = state[0].wrapping_add(a);
166    state[1] = state[1].wrapping_add(b);
167    state[2] = state[2].wrapping_add(c);
168    state[3] = state[3].wrapping_add(d);
169    state[4] = state[4].wrapping_add(e);
170    state[5] = state[5].wrapping_add(f);
171    state[6] = state[6].wrapping_add(g);
172    state[7] = state[7].wrapping_add(h);
173}
174
175/// SHA-512 hasher (FIPS 180-4). 512-bit output, 128-byte blocks.
176///
177/// 64-bit oriented sibling of SHA-256, with twice the security level
178/// and roughly twice the throughput on 64-bit hardware. Used as the
179/// internal hash of Ed25519 (RFC 8032), as the canonical P-521 ECDSA
180/// hash (FIPS 186-5), and in TLS 1.3 / IPsec for the high-security
181/// tier.
182#[derive(Clone)]
183pub struct Sha512 {
184    pub(crate) state: [u64; 8],
185    buf: [u8; 128],
186    buf_len: usize,
187    total_len: u128,
188}
189
190impl Sha512 {
191    pub(crate) fn new_with_iv(iv: [u64; 8]) -> Self {
192        Self {
193            state: iv,
194            buf: [0u8; 128],
195            buf_len: 0,
196            total_len: 0,
197        }
198    }
199}
200
201impl Hasher for Sha512 {
202    const OUTPUT_LEN: usize = 64;
203    const BLOCK_LEN: usize = 128;
204
205    fn new() -> Self {
206        Self::new_with_iv(H0_512)
207    }
208
209    fn update(&mut self, data: &[u8]) {
210        let mut pos = 0;
211        self.total_len += data.len() as u128;
212
213        if self.buf_len > 0 {
214            let need = 128 - self.buf_len;
215            let take = need.min(data.len());
216            self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&data[..take]);
217            self.buf_len += take;
218            pos = take;
219            if self.buf_len == 128 {
220                let block = self.buf;
221                compress(&mut self.state, &block);
222                self.buf_len = 0;
223            }
224        }
225
226        while pos + 128 <= data.len() {
227            compress(&mut self.state, &data[pos..pos + 128]);
228            pos += 128;
229        }
230
231        if pos < data.len() {
232            let remaining = data.len() - pos;
233            self.buf[..remaining].copy_from_slice(&data[pos..]);
234            self.buf_len = remaining;
235        }
236    }
237
238    fn finalize(self) -> Vec<u8> {
239        let mut out = vec![0u8; 64];
240        self.finalize_into(&mut out);
241        out
242    }
243
244    fn finalize_into(mut self, out: &mut [u8]) {
245        let bit_len = self.total_len * 8;
246        let mut pad = [0u8; 136]; // max padding needed
247        pad[0] = 0x80;
248        let pad_len = if self.buf_len < 112 {
249            112 - self.buf_len
250        } else {
251            240 - self.buf_len
252        };
253        self.update(&pad[..pad_len]);
254        // SHA-512 uses 128-bit length field (big-endian)
255        self.update(&(bit_len as u128).to_be_bytes());
256
257        for (i, word) in self.state.iter().enumerate() {
258            let bytes = word.to_be_bytes();
259            let start = i * 8;
260            if start + 8 <= out.len() {
261                out[start..start + 8].copy_from_slice(&bytes);
262            } else if start < out.len() {
263                let end = out.len() - start;
264                out[start..].copy_from_slice(&bytes[..end]);
265            }
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use crate::Hasher;
274
275    #[test]
276    fn test_sha512_empty() {
277        let digest = Sha512::hash(b"");
278        let expected: [u8; 64] = [
279            0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20,
280            0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c,
281            0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41,
282            0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e,
283        ];
284        assert_eq!(&digest[..], &expected[..]);
285    }
286
287    #[test]
288    fn test_sha512_abc() {
289        let digest = Sha512::hash(b"abc");
290        let expected: [u8; 64] = [
291            0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, 0x12, 0xe6,
292            0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a, 0x21, 0x92, 0x99, 0x2a,
293            0x27, 0x4f, 0xc1, 0xa8, 0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd, 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c,
294            0xe8, 0x0e, 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f,
295        ];
296        assert_eq!(&digest[..], &expected[..]);
297    }
298}