#![forbid(unsafe_code)]
use core::{
fmt::{self, Debug},
ops::{Deref, DerefMut},
};
use generic_array::{ArrayLength, GenericArray, IntoArrayLength};
use sha3_utils::{encode_string, right_encode_bytes};
use subtle::{Choice, ConstantTimeEq};
use typenum::{
generic_const_mappings::Const,
type_operators::{IsGreaterOrEqual, IsLess},
Unsigned, U32, U65536,
};
pub trait Hash: Clone {
type DigestSize: ArrayLength + IsGreaterOrEqual<U32> + IsLess<U65536> + 'static;
const DIGEST_SIZE: usize = Self::DigestSize::USIZE;
fn new() -> Self;
fn update(&mut self, data: &[u8]);
fn digest(self) -> Digest<Self::DigestSize>;
fn hash(data: &[u8]) -> Digest<Self::DigestSize>
where
Self: Sized,
{
let mut h = Self::new();
h.update(data);
h.digest()
}
}
#[derive(Clone, Default)]
#[repr(transparent)]
pub struct Digest<N: ArrayLength>(GenericArray<u8, N>);
impl<N: ArrayLength> Digest<N> {
#[inline]
pub const fn new(digest: GenericArray<u8, N>) -> Self {
Self(digest)
}
#[inline]
pub const fn from_array<const U: usize>(digest: [u8; U]) -> Self
where
Const<U>: IntoArrayLength<ArrayLength = N>,
{
Self::new(GenericArray::from_array(digest))
}
#[inline]
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> usize {
N::USIZE
}
#[inline]
pub const fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
#[inline]
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.0
}
#[inline]
pub fn into_array(self) -> GenericArray<u8, N> {
self.0
}
}
impl<N: ArrayLength> Copy for Digest<N> where N::ArrayType<u8>: Copy {}
impl<N: ArrayLength> Deref for Digest<N> {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<N: ArrayLength> DerefMut for Digest<N> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<N: ArrayLength> Debug for Digest<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Digest").field(&self.0).finish()
}
}
#[cfg(any(test, feature = "test_util"))]
impl<N: ArrayLength> Eq for Digest<N> {}
#[cfg(any(test, feature = "test_util"))]
impl<N: ArrayLength> PartialEq for Digest<N> {
fn eq(&self, other: &Self) -> bool {
bool::from(ConstantTimeEq::ct_eq(self, other))
}
}
impl<N: ArrayLength> ConstantTimeEq for Digest<N> {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.as_bytes().ct_eq(other.as_bytes())
}
}
pub fn tuple_hash<H, I>(s: I) -> Digest<H::DigestSize>
where
H: Hash,
I: IntoIterator<Item: AsRef<[u8]>>,
{
let mut h = H::new();
s.into_iter().for_each(|v| {
for part in &encode_string(v.as_ref()) {
h.update(part);
}
});
h.update(right_encode_bytes(H::DIGEST_SIZE).as_bytes());
h.digest()
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! test_tuple_hash {
($name:ident, $hash:ty) => {
#[test]
fn $name() {
assert_eq!(
tuple_hash::<$hash, _>(["abc"].iter()),
tuple_hash::<$hash, _>(["abc"])
);
assert_eq!(tuple_hash::<$hash, _>([""]), tuple_hash::<$hash, _>([""]));
assert_ne!(
tuple_hash::<$hash, _>(["a", "b", "c"]),
tuple_hash::<$hash, _>(["abc"])
);
assert_ne!(
tuple_hash::<$hash, _>(["a", ""]),
tuple_hash::<$hash, _>(["a"])
);
}
};
}
macro_rules! tuple_hash_tests {
() => {
use super::*;
test_tuple_hash!(test_sha256, Sha256);
test_tuple_hash!(test_sha384, Sha384);
test_tuple_hash!(test_sha512, Sha512);
};
}
#[cfg(feature = "bearssl")]
mod bearssl {
use crate::bearssl::{Sha256, Sha384, Sha512};
tuple_hash_tests!();
}
mod rust {
use crate::rust::{Sha256, Sha384, Sha512};
tuple_hash_tests!();
}
}