#![allow(clippy::indexing_slicing)]
use self::kernels::CompressBlocksFn;
use crate::{
hashes::{
crypto::dispatch_util::{SizeClassDispatch, len_hint_from_u128},
util::{Aligned64, rotr64},
},
traits::Digest,
};
#[doc(hidden)]
pub(crate) mod dispatch;
#[doc(hidden)]
pub(crate) mod dispatch_tables;
#[cfg(test)]
mod kernel_test;
pub(crate) mod kernels;
#[cfg(target_arch = "aarch64")]
pub(crate) mod aarch64;
#[cfg(target_arch = "riscv64")]
pub(crate) mod riscv64;
#[cfg(target_arch = "s390x")]
pub(crate) mod s390x;
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm;
#[cfg(target_arch = "x86_64")]
pub(crate) mod x86_64;
#[cfg(target_arch = "x86_64")]
pub(crate) mod x86_64_avx2;
#[cfg(target_arch = "x86_64")]
pub(crate) mod x86_64_avx512vl;
const BLOCK_LEN: usize = 128;
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,
];
static K: Aligned64<[u64; 80]> = Aligned64([
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,
]);
#[inline(always)]
fn ch(x: u64, y: u64, z: u64) -> u64 {
(x & y) ^ (!x & z)
}
#[inline(always)]
fn maj(x: u64, y: u64, z: u64) -> u64 {
(x & y) ^ (x & z) ^ (y & z)
}
#[inline(always)]
pub(super) fn big_sigma0(x: u64) -> u64 {
rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39)
}
#[inline(always)]
pub(super) fn big_sigma1(x: u64) -> u64 {
rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41)
}
#[inline(always)]
fn small_sigma0(x: u64) -> u64 {
rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7)
}
#[inline(always)]
fn small_sigma1(x: u64) -> u64 {
rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6)
}
#[derive(Clone)]
pub struct Sha512 {
state: [u64; 8],
block: [u8; BLOCK_LEN],
block_len: usize,
bytes_hashed: u128,
compress_blocks: CompressBlocksFn,
dispatch: Option<SizeClassDispatch<CompressBlocksFn>>,
}
#[derive(Clone, Copy)]
#[cfg(feature = "hmac")]
pub(crate) struct Sha512Prefix {
state: [u64; 8],
bytes_hashed: u128,
compress_blocks: CompressBlocksFn,
dispatch: Option<SizeClassDispatch<CompressBlocksFn>>,
}
#[cfg(feature = "hmac")]
impl Sha512Prefix {
pub(crate) fn zeroize(&mut self) {
for word in self.state.iter_mut() {
unsafe { core::ptr::write_volatile(word, 0) };
}
unsafe { core::ptr::write_volatile(&mut self.bytes_hashed, 0) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
impl core::fmt::Debug for Sha512 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Sha512").finish_non_exhaustive()
}
}
impl Default for Sha512 {
#[inline]
fn default() -> Self {
Self {
state: H0,
block: [0u8; BLOCK_LEN],
block_len: 0,
bytes_hashed: 0,
compress_blocks: Sha512::compress_blocks_portable,
dispatch: None,
}
}
}
impl Sha512 {
#[inline]
#[must_use]
pub fn digest(data: &[u8]) -> [u8; 64] {
dispatch::digest(data)
}
#[inline]
pub(crate) fn compress_blocks_portable(state: &mut [u64; 8], blocks: &[u8]) {
debug_assert_eq!(blocks.len() % BLOCK_LEN, 0);
let mut chunks = blocks.chunks_exact(BLOCK_LEN);
for chunk in &mut chunks {
let block = unsafe { &*(chunk.as_ptr() as *const [u8; BLOCK_LEN]) };
Self::compress_block(state, block);
}
debug_assert!(chunks.remainder().is_empty());
}
#[inline]
fn select_compress(&mut self, incoming_len: usize) -> CompressBlocksFn {
let dispatch = match self.dispatch {
Some(d) => d,
None => {
let d = dispatch::compress_dispatch();
self.dispatch = Some(d);
d
}
};
let total = self
.bytes_hashed
.strict_add(self.block_len as u128)
.strict_add(incoming_len as u128);
let compress = dispatch.select(len_hint_from_u128(total));
self.compress_blocks = compress;
compress
}
#[inline]
fn update_with(&mut self, mut data: &[u8], compress_blocks: CompressBlocksFn) {
if data.is_empty() {
return;
}
if self.block_len != 0 {
let take = core::cmp::min(BLOCK_LEN.strict_sub(self.block_len), data.len());
self.block[self.block_len..self.block_len.strict_add(take)].copy_from_slice(&data[..take]);
self.block_len = self.block_len.strict_add(take);
data = &data[take..];
if self.block_len == BLOCK_LEN {
compress_blocks(&mut self.state, &self.block);
self.bytes_hashed = self.bytes_hashed.strict_add(BLOCK_LEN as u128);
self.block_len = 0;
}
}
let full_len = data.len().strict_sub(data.len() % BLOCK_LEN);
if full_len != 0 {
let (blocks, rest) = data.split_at(full_len);
compress_blocks(&mut self.state, blocks);
self.bytes_hashed = self.bytes_hashed.strict_add(blocks.len() as u128);
data = rest;
}
if !data.is_empty() {
self.block[..data.len()].copy_from_slice(data);
self.block_len = data.len();
}
}
#[inline(always)]
fn compress_block(state: &mut [u64; 8], block: &[u8; BLOCK_LEN]) {
compress_block_with(state, block, big_sigma0, big_sigma1, small_sigma0, small_sigma1);
}
#[inline(always)]
fn finalize_inner_with(&self, compress_blocks: CompressBlocksFn) -> [u8; 64] {
let mut state = self.state;
let mut block = self.block;
let mut block_len = self.block_len;
let total_len = self.bytes_hashed.strict_add(block_len as u128);
let bit_len = total_len << 3;
block[block_len] = 0x80;
block_len = block_len.strict_add(1);
if block_len > 112 {
block[block_len..].fill(0);
compress_blocks(&mut state, &block);
block = [0u8; BLOCK_LEN];
block_len = 0;
}
block[block_len..112].fill(0);
block[112..128].copy_from_slice(&bit_len.to_be_bytes());
compress_blocks(&mut state, &block);
let mut out = [0u8; 64];
out[0..8].copy_from_slice(&state[0].to_be_bytes());
out[8..16].copy_from_slice(&state[1].to_be_bytes());
out[16..24].copy_from_slice(&state[2].to_be_bytes());
out[24..32].copy_from_slice(&state[3].to_be_bytes());
out[32..40].copy_from_slice(&state[4].to_be_bytes());
out[40..48].copy_from_slice(&state[5].to_be_bytes());
out[48..56].copy_from_slice(&state[6].to_be_bytes());
out[56..64].copy_from_slice(&state[7].to_be_bytes());
out
}
#[inline]
#[must_use]
#[cfg(feature = "hmac")]
pub(crate) fn aligned_prefix(&self) -> Sha512Prefix {
debug_assert_eq!(self.block_len, 0);
Sha512Prefix {
state: self.state,
bytes_hashed: self.bytes_hashed,
compress_blocks: self.compress_blocks,
dispatch: self.dispatch,
}
}
#[inline]
#[must_use]
#[cfg(feature = "hmac")]
pub(crate) fn from_aligned_prefix(prefix: Sha512Prefix) -> Self {
Self {
state: prefix.state,
block: [0u8; BLOCK_LEN],
block_len: 0,
bytes_hashed: prefix.bytes_hashed,
compress_blocks: prefix.compress_blocks,
dispatch: prefix.dispatch,
}
}
#[inline]
#[cfg(feature = "hmac")]
pub(crate) fn reset_to_aligned_prefix(&mut self, prefix: Sha512Prefix) {
self.state = prefix.state;
self.block_len = 0;
self.bytes_hashed = prefix.bytes_hashed;
self.compress_blocks = prefix.compress_blocks;
self.dispatch = prefix.dispatch;
}
#[cfg(all(feature = "hmac", test))]
#[inline]
pub(crate) fn new_with_compress_for_test(compress_blocks: CompressBlocksFn) -> Self {
Self {
state: H0,
block: [0u8; BLOCK_LEN],
block_len: 0,
bytes_hashed: 0,
compress_blocks,
dispatch: Some(SizeClassDispatch {
boundaries: [usize::MAX; 3],
xs: compress_blocks,
s: compress_blocks,
m: compress_blocks,
l: compress_blocks,
}),
}
}
}
#[inline(always)]
fn rk(i: usize) -> u64 {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
unsafe { core::ptr::read(K.0.as_ptr().add(i)) }
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
let base = core::hint::black_box(K.0.as_ptr());
unsafe { core::ptr::read(base.add(i)) }
}
}
#[inline(always)]
pub(crate) fn compress_block_with(
state: &mut [u64; 8],
block: &[u8; BLOCK_LEN],
big_s0: fn(u64) -> u64,
big_s1: fn(u64) -> u64,
small_s0: fn(u64) -> u64,
small_s1: fn(u64) -> u64,
) {
let mut w = [0u64; 16];
let (chunks, _) = block.as_chunks::<8>();
for (i, c) in chunks.iter().enumerate() {
w[i] = u64::from_be_bytes(*c);
}
let [
mut w0,
mut w1,
mut w2,
mut w3,
mut w4,
mut w5,
mut w6,
mut w7,
mut w8,
mut w9,
mut w10,
mut w11,
mut w12,
mut w13,
mut w14,
mut w15,
] = w;
let mut a = state[0];
let mut b = state[1];
let mut c = state[2];
let mut d = state[3];
let mut e = state[4];
let mut f = state[5];
let mut g = state[6];
let mut h = state[7];
macro_rules! round {
($k:expr, $wi:expr) => {{
let t1 = h
.wrapping_add(big_s1(e))
.wrapping_add(ch(e, f, g))
.wrapping_add($k)
.wrapping_add($wi);
let t2 = big_s0(a).wrapping_add(maj(a, b, c));
h = g;
g = f;
f = e;
e = d.wrapping_add(t1);
d = c;
c = b;
b = a;
a = t1.wrapping_add(t2);
}};
}
macro_rules! sched {
($w2:expr, $w7:expr, $w15:expr, $w16:expr) => {{
small_s1($w2)
.wrapping_add($w7)
.wrapping_add(small_s0($w15))
.wrapping_add($w16)
}};
}
round!(rk(0), w0);
round!(rk(1), w1);
round!(rk(2), w2);
round!(rk(3), w3);
round!(rk(4), w4);
round!(rk(5), w5);
round!(rk(6), w6);
round!(rk(7), w7);
round!(rk(8), w8);
round!(rk(9), w9);
round!(rk(10), w10);
round!(rk(11), w11);
round!(rk(12), w12);
round!(rk(13), w13);
round!(rk(14), w14);
round!(rk(15), w15);
w0 = sched!(w14, w9, w1, w0);
round!(rk(16), w0);
w1 = sched!(w15, w10, w2, w1);
round!(rk(17), w1);
w2 = sched!(w0, w11, w3, w2);
round!(rk(18), w2);
w3 = sched!(w1, w12, w4, w3);
round!(rk(19), w3);
w4 = sched!(w2, w13, w5, w4);
round!(rk(20), w4);
w5 = sched!(w3, w14, w6, w5);
round!(rk(21), w5);
w6 = sched!(w4, w15, w7, w6);
round!(rk(22), w6);
w7 = sched!(w5, w0, w8, w7);
round!(rk(23), w7);
w8 = sched!(w6, w1, w9, w8);
round!(rk(24), w8);
w9 = sched!(w7, w2, w10, w9);
round!(rk(25), w9);
w10 = sched!(w8, w3, w11, w10);
round!(rk(26), w10);
w11 = sched!(w9, w4, w12, w11);
round!(rk(27), w11);
w12 = sched!(w10, w5, w13, w12);
round!(rk(28), w12);
w13 = sched!(w11, w6, w14, w13);
round!(rk(29), w13);
w14 = sched!(w12, w7, w15, w14);
round!(rk(30), w14);
w15 = sched!(w13, w8, w0, w15);
round!(rk(31), w15);
w0 = sched!(w14, w9, w1, w0);
round!(rk(32), w0);
w1 = sched!(w15, w10, w2, w1);
round!(rk(33), w1);
w2 = sched!(w0, w11, w3, w2);
round!(rk(34), w2);
w3 = sched!(w1, w12, w4, w3);
round!(rk(35), w3);
w4 = sched!(w2, w13, w5, w4);
round!(rk(36), w4);
w5 = sched!(w3, w14, w6, w5);
round!(rk(37), w5);
w6 = sched!(w4, w15, w7, w6);
round!(rk(38), w6);
w7 = sched!(w5, w0, w8, w7);
round!(rk(39), w7);
w8 = sched!(w6, w1, w9, w8);
round!(rk(40), w8);
w9 = sched!(w7, w2, w10, w9);
round!(rk(41), w9);
w10 = sched!(w8, w3, w11, w10);
round!(rk(42), w10);
w11 = sched!(w9, w4, w12, w11);
round!(rk(43), w11);
w12 = sched!(w10, w5, w13, w12);
round!(rk(44), w12);
w13 = sched!(w11, w6, w14, w13);
round!(rk(45), w13);
w14 = sched!(w12, w7, w15, w14);
round!(rk(46), w14);
w15 = sched!(w13, w8, w0, w15);
round!(rk(47), w15);
w0 = sched!(w14, w9, w1, w0);
round!(rk(48), w0);
w1 = sched!(w15, w10, w2, w1);
round!(rk(49), w1);
w2 = sched!(w0, w11, w3, w2);
round!(rk(50), w2);
w3 = sched!(w1, w12, w4, w3);
round!(rk(51), w3);
w4 = sched!(w2, w13, w5, w4);
round!(rk(52), w4);
w5 = sched!(w3, w14, w6, w5);
round!(rk(53), w5);
w6 = sched!(w4, w15, w7, w6);
round!(rk(54), w6);
w7 = sched!(w5, w0, w8, w7);
round!(rk(55), w7);
w8 = sched!(w6, w1, w9, w8);
round!(rk(56), w8);
w9 = sched!(w7, w2, w10, w9);
round!(rk(57), w9);
w10 = sched!(w8, w3, w11, w10);
round!(rk(58), w10);
w11 = sched!(w9, w4, w12, w11);
round!(rk(59), w11);
w12 = sched!(w10, w5, w13, w12);
round!(rk(60), w12);
w13 = sched!(w11, w6, w14, w13);
round!(rk(61), w13);
w14 = sched!(w12, w7, w15, w14);
round!(rk(62), w14);
w15 = sched!(w13, w8, w0, w15);
round!(rk(63), w15);
w0 = sched!(w14, w9, w1, w0);
round!(rk(64), w0);
w1 = sched!(w15, w10, w2, w1);
round!(rk(65), w1);
w2 = sched!(w0, w11, w3, w2);
round!(rk(66), w2);
w3 = sched!(w1, w12, w4, w3);
round!(rk(67), w3);
w4 = sched!(w2, w13, w5, w4);
round!(rk(68), w4);
w5 = sched!(w3, w14, w6, w5);
round!(rk(69), w5);
w6 = sched!(w4, w15, w7, w6);
round!(rk(70), w6);
w7 = sched!(w5, w0, w8, w7);
round!(rk(71), w7);
w8 = sched!(w6, w1, w9, w8);
round!(rk(72), w8);
w9 = sched!(w7, w2, w10, w9);
round!(rk(73), w9);
w10 = sched!(w8, w3, w11, w10);
round!(rk(74), w10);
w11 = sched!(w9, w4, w12, w11);
round!(rk(75), w11);
w12 = sched!(w10, w5, w13, w12);
round!(rk(76), w12);
w13 = sched!(w11, w6, w14, w13);
round!(rk(77), w13);
w14 = sched!(w12, w7, w15, w14);
round!(rk(78), w14);
w15 = sched!(w13, w8, w0, w15);
round!(rk(79), w15);
state[0] = state[0].wrapping_add(a);
state[1] = state[1].wrapping_add(b);
state[2] = state[2].wrapping_add(c);
state[3] = state[3].wrapping_add(d);
state[4] = state[4].wrapping_add(e);
state[5] = state[5].wrapping_add(f);
state[6] = state[6].wrapping_add(g);
state[7] = state[7].wrapping_add(h);
}
impl Drop for Sha512 {
fn drop(&mut self) {
for word in self.state.iter_mut() {
unsafe { core::ptr::write_volatile(word, 0) };
}
crate::traits::ct::zeroize(&mut self.block);
unsafe { core::ptr::write_volatile(&mut self.bytes_hashed, 0) };
unsafe { core::ptr::write_volatile(&mut self.block_len, 0) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
impl Digest for Sha512 {
const OUTPUT_SIZE: usize = 64;
type Output = [u8; 64];
#[inline]
fn new() -> Self {
Self::default()
}
#[inline]
fn update(&mut self, data: &[u8]) {
if data.is_empty() {
return;
}
let compress = self.select_compress(data.len());
self.update_with(data, compress);
}
#[inline]
fn finalize(&self) -> Self::Output {
self.finalize_inner_with(self.compress_blocks)
}
#[inline]
fn reset(&mut self) {
*self = Self::default();
}
#[inline]
fn digest(data: &[u8]) -> Self::Output {
dispatch::digest(data)
}
}
#[cfg(test)]
mod tests {
use super::Sha512;
fn hex64(bytes: &[u8; 64]) -> alloc::string::String {
use alloc::string::String;
use core::fmt::Write;
let mut s = String::new();
for &b in bytes {
write!(&mut s, "{:02x}", b).unwrap();
}
s
}
#[test]
fn known_vectors() {
assert_eq!(
hex64(&Sha512::digest(b"")),
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
);
assert_eq!(
hex64(&Sha512::digest(b"abc")),
"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
);
}
}