use crate::Hasher;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bytes::BufMut;
use commonware_codec::{Buf, Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write};
use commonware_formatting::Hex;
use commonware_math::algebra::Random;
use commonware_parallel::Strategy;
use commonware_utils::{Array, Span, sequence::FixedBytes};
use core::{
cmp::Ordering,
fmt::{Debug, Display},
ops::Deref,
};
use rand_core::CryptoRng;
use sha2::Digest as _;
use zeroize::Zeroize;
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "x86_64")]
mod avx512;
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
mod padding;
pub type CoreSha512 = sha2::Sha512;
const DIGEST_LENGTH: usize = 64;
#[cfg(target_arch = "x86_64")]
cpufeatures::new!(has_avx512f, "avx512f");
#[cfg(target_arch = "aarch64")]
cpufeatures::new!(has_sha3, "sha3");
fn lanes() -> usize {
#[cfg(target_arch = "x86_64")]
if has_avx512f::get() {
return avx512::LANES;
}
#[cfg(target_arch = "aarch64")]
if has_sha3::get() {
return aarch64::LANES;
}
1
}
#[derive(Debug, Default)]
pub struct Sha512 {
hasher: CoreSha512,
}
impl Hasher for Sha512 {
type Digest = Digest;
fn hash(parts: &[&[u8]]) -> Self::Digest {
let mut hasher = CoreSha512::new();
for part in parts {
hasher.update(part);
}
Digest(hasher.finalize().into())
}
fn hash_pair(
left: &[&[u8]],
right: &[&[u8]],
_strategy: &impl Strategy,
) -> (Self::Digest, Self::Digest) {
(Self::hash(left), Self::hash(right))
}
fn hash_many<M: AsRef<[u8]> + Sync>(
messages: &[M],
strategy: &impl Strategy,
) -> Vec<Self::Digest> {
crate::hash_batches(messages, lanes(), strategy, hash_batch)
}
fn update(&mut self, message: &[u8]) -> &mut Self {
self.hasher.update(message);
self
}
fn finalize(mut self) -> (Self, Self::Digest) {
let finalized = self.hasher.finalize_reset();
(self, Digest(finalized.into()))
}
}
fn hash_batch<M: AsRef<[u8]>>(messages: &[M]) -> Vec<Digest> {
#[cfg(target_arch = "x86_64")]
if has_avx512f::get() {
let mut digests = Vec::with_capacity(messages.len());
for chunk in messages.chunks(avx512::LANES) {
let mut lanes: [&[u8]; avx512::LANES] = [&[]; avx512::LANES];
for (lane, message) in lanes.iter_mut().zip(chunk) {
*lane = message.as_ref();
}
let chunk_digests = unsafe { avx512::hash(&lanes[..chunk.len()]) };
digests.extend_from_slice(&chunk_digests[..chunk.len()]);
}
return digests;
}
#[cfg(target_arch = "aarch64")]
if has_sha3::get() {
let mut digests = Vec::with_capacity(messages.len());
let (pairs, remainder) = messages.as_chunks::<{ aarch64::LANES }>();
for pair in pairs {
let lanes = [pair[0].as_ref(), pair[1].as_ref()];
digests.extend_from_slice(&unsafe { aarch64::hash(&lanes) });
}
for message in remainder {
digests.push(Sha512::hash(&[message.as_ref()]));
}
return digests;
}
messages
.iter()
.map(|message| Sha512::hash(&[message.as_ref()]))
.collect()
}
#[derive(Clone, Copy, Eq, PartialEq, Hash, FixedArray)]
#[fixed_array(infallible)]
#[repr(transparent)]
pub struct Digest(pub [u8; DIGEST_LENGTH]);
impl Ord for Digest {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
FixedBytes::new(self.0).cmp(&FixedBytes::new(other.0))
}
}
impl PartialOrd for Digest {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Digest {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let len = u.int_in_range(0..=256)?;
let data = u.bytes(len)?;
Ok(Sha512::hash(&[data]))
}
}
impl Write for Digest {
fn write(&self, buf: &mut impl BufMut) {
self.0.write(buf);
}
}
impl Read for Digest {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
Ok(Self(array))
}
}
impl FixedSize for Digest {
const SIZE: usize = DIGEST_LENGTH;
}
impl Span for Digest {}
impl Array for Digest {}
impl AsRef<[u8]> for Digest {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Deref for Digest {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl Debug for Digest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", Hex(&self.0))
}
}
impl Display for Digest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", Hex(&self.0))
}
}
impl crate::Digest for Digest {
const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
}
impl Random for Digest {
fn random(mut rng: impl CryptoRng) -> Self {
let mut array = [0u8; DIGEST_LENGTH];
rng.fill_bytes(&mut array);
Self(array)
}
}
impl Zeroize for Digest {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
#[cfg(test)]
mod tests {
use super::*;
use commonware_codec::{Copying, DecodeExt, Encode};
use commonware_formatting::hex;
use commonware_parallel::{Sequential, mocks::explore};
const EMPTY_DIGEST: [u8; DIGEST_LENGTH] = hex!(
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce"
"47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
);
const ABC_DIGEST: [u8; DIGEST_LENGTH] = hex!(
"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
);
fn expected(message: &[u8]) -> Digest {
Digest(CoreSha512::digest(message).into())
}
fn message(len: usize, seed: usize) -> Vec<u8> {
(0..len)
.map(|i| (i.wrapping_mul(31) ^ seed.wrapping_mul(0x9e37)) as u8)
.collect()
}
fn check_hash_many<M: AsRef<[u8]> + Sync>(messages: &[M], strategy: &impl Strategy) {
let digests: Vec<Digest> = messages
.iter()
.map(|message| expected(message.as_ref()))
.collect();
assert_eq!(Sha512::hash_many(messages, strategy), digests);
}
#[test]
fn test_known_answers_and_reset() {
assert_eq!(Sha512::hash(&[]).as_ref(), EMPTY_DIGEST);
assert_eq!(Sha512::hash(&[b"abc"]).as_ref(), ABC_DIGEST);
assert_eq!(Sha512::hash(&[b"a", b"", b"bc"]).as_ref(), ABC_DIGEST);
assert_eq!(
Sha512::hash_pair(&[], &[b"a", b"bc"], &Sequential),
(Digest(EMPTY_DIGEST), Digest(ABC_DIGEST))
);
assert_eq!(
Sha512::hash_many(&[b"".as_slice(), b"abc"], &Sequential),
[Digest(EMPTY_DIGEST), Digest(ABC_DIGEST)]
);
let mut hasher = Sha512::default();
for message in [b"abc".as_slice(), b"", b"abc", b""] {
for byte in message {
hasher.update(core::slice::from_ref(byte));
}
let (reset, digest) = hasher.finalize();
let expected = if message.is_empty() {
EMPTY_DIGEST
} else {
ABC_DIGEST
};
assert_eq!(digest.as_ref(), expected);
hasher = reset;
}
}
#[test]
fn test_hash_many_equal_lengths() {
explore(|strategy| {
for len in 0..=300 {
let messages: Vec<Vec<u8>> = (0..20).map(|lane| message(len, lane)).collect();
for count in 0..=messages.len() {
check_hash_many(&messages[..count], strategy);
}
}
});
}
#[test]
fn test_hash_many_mixed_lengths() {
explore(|strategy| {
for len in (0..=300).chain([1000, 4113]) {
for count in 1..=20 {
let messages: Vec<Vec<u8>> = (0..count)
.map(|lane| {
let other = (len * 7 + lane * 37) % 301;
message(if lane == len % count { len } else { other }, lane)
})
.collect();
check_hash_many(&messages, strategy);
}
}
});
}
#[test]
fn test_hash_many_overlapping_messages() {
let backing = message(400, 0);
let messages: Vec<&[u8]> = (0..20)
.map(|lane| &backing[lane..lane * 13 + 100])
.collect();
explore(|strategy| {
check_hash_many(&messages, strategy);
check_hash_many(&[&backing[..]; 9], strategy);
});
}
#[cfg(target_arch = "x86_64")]
#[test]
fn test_avx512_detection() {
assert_eq!(
has_avx512f::get(),
std::arch::is_x86_feature_detected!("avx512f")
);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn test_sha3_detection() {
assert_eq!(
has_sha3::get(),
std::arch::is_aarch64_feature_detected!("sha3")
);
}
#[test]
fn test_codec_and_zeroize() {
let mut digest = Sha512::hash(&[b"abc"]);
let encoded = digest.encode();
assert_eq!(Digest::SIZE, DIGEST_LENGTH);
assert_eq!(encoded.as_ref(), ABC_DIGEST);
assert_eq!(Digest::decode(encoded).unwrap(), digest);
assert!(Digest::decode(Copying(&ABC_DIGEST[..DIGEST_LENGTH - 1])).is_err());
digest.zeroize();
assert_eq!(digest, <Digest as crate::Digest>::EMPTY);
}
#[test]
fn test_digest_ord() {
let a = Digest([0; DIGEST_LENGTH]);
let mut b = a;
b.0[DIGEST_LENGTH - 1] = 1;
let mut c = a;
c.0[0] = 0x7f;
c.0[1..].fill(0xff);
let mut d = a;
d.0[0] = 0x80;
for (a, b, expected) in [
(a, a, Ordering::Equal),
(a, b, Ordering::Less),
(b, a, Ordering::Greater),
(c, d, Ordering::Less),
(d, c, Ordering::Greater),
] {
assert_eq!(a.cmp(&b), expected);
assert_eq!(a.partial_cmp(&b), Some(expected));
}
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use commonware_codec::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<Digest>,
}
}
}