use crate::Digest;
pub(crate) const H0: [u64; 8] = [
0x6a09_e667_f3bc_c908,
0xbb67_ae85_84ca_a73b,
0x3c6e_f372_fe94_f82b,
0xa54f_f53a_5f1d_36f1,
0x510e_527f_ade6_82d1,
0x9b05_688c_2b3e_6c1f,
0x1f83_d9ab_fb41_bd6b,
0x5be0_cd19_137e_2179,
];
const K: [u64; 80] = [
0x428a_2f98_d728_ae22,
0x7137_4491_23ef_65cd,
0xb5c0_fbcf_ec4d_3b2f,
0xe9b5_dba5_8189_dbbc,
0x3956_c25b_f348_b538,
0x59f1_11f1_b605_d019,
0x923f_82a4_af19_4f9b,
0xab1c_5ed5_da6d_8118,
0xd807_aa98_a303_0242,
0x1283_5b01_4570_6fbe,
0x2431_85be_4ee4_b28c,
0x550c_7dc3_d5ff_b4e2,
0x72be_5d74_f27b_896f,
0x80de_b1fe_3b16_96b1,
0x9bdc_06a7_25c7_1235,
0xc19b_f174_cf69_2694,
0xe49b_69c1_9ef1_4ad2,
0xefbe_4786_384f_25e3,
0x0fc1_9dc6_8b8c_d5b5,
0x240c_a1cc_77ac_9c65,
0x2de9_2c6f_592b_0275,
0x4a74_84aa_6ea6_e483,
0x5cb0_a9dc_bd41_fbd4,
0x76f9_88da_8311_53b5,
0x983e_5152_ee66_dfab,
0xa831_c66d_2db4_3210,
0xb003_27c8_98fb_213f,
0xbf59_7fc7_beef_0ee4,
0xc6e0_0bf3_3da8_8fc2,
0xd5a7_9147_930a_a725,
0x06ca_6351_e003_826f,
0x1429_2967_0a0e_6e70,
0x27b7_0a85_46d2_2ffc,
0x2e1b_2138_5c26_c926,
0x4d2c_6dfc_5ac4_2aed,
0x5338_0d13_9d95_b3df,
0x650a_7354_8baf_63de,
0x766a_0abb_3c77_b2a8,
0x81c2_c92e_47ed_aee6,
0x9272_2c85_1482_353b,
0xa2bf_e8a1_4cf1_0364,
0xa81a_664b_bc42_3001,
0xc24b_8b70_d0f8_9791,
0xc76c_51a3_0654_be30,
0xd192_e819_d6ef_5218,
0xd699_0624_5565_a910,
0xf40e_3585_5771_202a,
0x106a_a070_32bb_d1b8,
0x19a4_c116_b8d2_d0c8,
0x1e37_6c08_5141_ab53,
0x2748_774c_df8e_eb99,
0x34b0_bcb5_e19b_48a8,
0x391c_0cb3_c5c9_5a63,
0x4ed8_aa4a_e341_8acb,
0x5b9c_ca4f_7763_e373,
0x682e_6ff3_d6b2_b8a3,
0x748f_82ee_5def_b2fc,
0x78a5_636f_4317_2f60,
0x84c8_7814_a1f0_ab72,
0x8cc7_0208_1a64_39ec,
0x90be_fffa_2363_1e28,
0xa450_6ceb_de82_bde9,
0xbef9_a3f7_b2c6_7915,
0xc671_78f2_e372_532b,
0xca27_3ece_ea26_619c,
0xd186_b8c7_21c0_c207,
0xeada_7dd6_cde0_eb1e,
0xf57d_4f7f_ee6e_d178,
0x06f0_67aa_7217_6fba,
0x0a63_7dc5_a2c8_98a6,
0x113f_9804_bef9_0dae,
0x1b71_0b35_131c_471b,
0x28db_77f5_2304_7d84,
0x32ca_ab7b_40c7_2493,
0x3c9e_be0a_15c9_bebc,
0x431d_67c4_9c10_0d4c,
0x4cc5_d4be_cb3e_42b6,
0x597f_299c_fc65_7e2a,
0x5fcb_6fab_3ad6_faec,
0x6c44_198c_4a47_5817,
];
#[must_use]
pub fn sha512(data: &[u8]) -> Digest<64> {
let mut h = H0;
let mut chunks = data.chunks_exact(128);
for block in chunks.by_ref() {
compress(&mut h, block.try_into().expect("chunks_exact yields 128"));
}
let rem = chunks.remainder();
let bit_len = (data.len() as u128) << 3;
let mut tail = [0u8; 256];
tail[..rem.len()].copy_from_slice(rem);
tail[rem.len()] = 0x80;
let pad_len = if rem.len() + 17 <= 128 { 128 } else { 256 };
tail[pad_len - 16..pad_len].copy_from_slice(&bit_len.to_be_bytes());
for block in tail[..pad_len].chunks_exact(128) {
compress(&mut h, block.try_into().expect("chunks_exact yields 128"));
}
let mut out = [0u8; 64];
for (word, chunk) in h.iter().zip(out.chunks_exact_mut(8)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
Digest::from_bytes(out)
}
pub(crate) fn absorb<'a>(h: &mut [u64; 8], data: &'a [u8]) -> &'a [u8] {
let mut chunks = data.chunks_exact(128);
for block in chunks.by_ref() {
compress(h, block.try_into().expect("chunks_exact yields 128"));
}
chunks.remainder()
}
pub(crate) fn finish512(h: &[u64; 8], rem: &[u8], total_len: u64) -> [u64; 8] {
let mut h = *h;
let bit_len = u128::from(total_len) << 3;
let mut tail = [0u8; 256];
tail[..rem.len()].copy_from_slice(rem);
tail[rem.len()] = 0x80;
let pad_len = if rem.len() + 17 <= 128 { 128 } else { 256 };
tail[pad_len - 16..pad_len].copy_from_slice(&bit_len.to_be_bytes());
for block in tail[..pad_len].chunks_exact(128) {
compress(&mut h, block.try_into().expect("chunks_exact yields 128"));
}
h
}
fn compress(h: &mut [u64; 8], block: &[u8; 128]) {
let mut w = [0u64; 80];
for (word, chunk) in w.iter_mut().zip(block.chunks_exact(8)) {
*word = u64::from_be_bytes(chunk.try_into().expect("chunks_exact yields 8"));
}
for i in 16..80 {
let s0 = w[i - 15].rotate_right(1) ^ w[i - 15].rotate_right(8) ^ (w[i - 15] >> 7);
let s1 = w[i - 2].rotate_right(19) ^ w[i - 2].rotate_right(61) ^ (w[i - 2] >> 6);
w[i] = w[i - 16]
.wrapping_add(s0)
.wrapping_add(w[i - 7])
.wrapping_add(s1);
}
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = *h;
for (i, &word) in w.iter().enumerate() {
let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
let ch = (e & f) ^ (!e & g);
let t1 = hh
.wrapping_add(s1)
.wrapping_add(ch)
.wrapping_add(K[i])
.wrapping_add(word);
let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
let maj = (a & b) ^ (a & c) ^ (b & c);
let t2 = s0.wrapping_add(maj);
hh = g;
g = f;
f = e;
e = d.wrapping_add(t1);
d = c;
c = b;
b = a;
a = t1.wrapping_add(t2);
}
for (slot, value) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
*slot = slot.wrapping_add(value);
}
}
#[cfg(test)]
mod tests {
use super::{absorb, finish512, sha512};
#[test]
fn fips_180_4_vectors() {
assert_eq!(
sha512(b"").to_string(),
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\
47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
.chars()
.filter(|c| *c != ' ')
.collect::<String>()
);
assert_eq!(
sha512(b"abc").to_string(),
"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
.chars()
.filter(|c| *c != ' ')
.collect::<String>()
);
assert_eq!(
sha512(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq").to_string(),
"204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c335\
96fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445"
.chars()
.filter(|c| *c != ' ')
.collect::<String>()
);
assert_eq!(
sha512(&[b'a'; 1_000_000]).to_string(),
"e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973eb\
de0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"
.chars()
.filter(|c| *c != ' ')
.collect::<String>()
);
}
#[test]
fn padding_boundaries() {
assert_eq!(
sha512(&[b'x'; 111]).to_string(),
"9a2a120825c2319867758ec277924f6faa254968bf752046dacdd948d8ad299b\
10359fd04bfd7d3810b5fa1b16a294236138baff981cbb85248478053ac4d3dd"
.chars()
.filter(|c| *c != ' ')
.collect::<String>()
);
assert_eq!(
sha512(&[b'x'; 112]).to_string(),
"a3722b515ef40c910f2419f6e0da8ca51d410114ce6272faae64045f9e9f630e\
7fa8dd5a3243c9860b899d148c3da4bc0f9e07454542604d030bb55531fe0d5b"
.chars()
.filter(|c| *c != ' ')
.collect::<String>()
);
}
#[test]
fn absorb_finish_matches_one_shot() {
for len in [0usize, 1, 111, 112, 127, 128, 129, 200] {
let data = vec![b'p'; len];
let mut h = super::H0;
let rem = absorb(&mut h, &data);
let h = finish512(&h, rem, len as u64);
let mut out = [0u8; 64];
for (word, chunk) in h.iter().zip(out.chunks_exact_mut(8)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
assert_eq!(out, *sha512(&data).as_bytes(), "len {len}");
}
}
}