mod hash64;
pub use hash64::Hash64;
use crate::bases::*;
use crate::rand::UnspecifiedRandError;
#[cfg(target_arch = "wasm32")]
use cryptoxide::{digest::Digest, sha2::Sha256};
#[cfg(not(target_arch = "wasm32"))]
use ring::digest;
use serde::{Deserialize, Serialize};
use std::{
fmt::{Debug, Display, Error, Formatter},
str::FromStr,
};
const BLAKE3_CUTOFF: usize = 1 << 17;
#[derive(
Copy,
Clone,
Deserialize,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
zerocopy::AsBytes,
zerocopy::FromBytes,
)]
#[repr(transparent)]
pub struct Hash(pub [u8; 32]);
impl AsRef<[u8]> for Hash {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl Display for Hash {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.to_hex())
}
}
impl Debug for Hash {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "Hash({})", self)
}
}
impl Default for Hash {
fn default() -> Hash {
Hash([0; 32])
}
}
impl FromStr for Hash {
type Err = BaseConversionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Hash::from_hex(s)
}
}
impl Hash {
pub const SIZE_IN_BYTES: usize = 32;
#[inline]
pub fn random() -> Result<Self, UnspecifiedRandError> {
let random_bytes = crate::rand::gen_32_bytes().map_err(|_| UnspecifiedRandError)?;
Ok(Hash(random_bytes))
}
#[cfg(target_arch = "wasm32")]
#[cfg(not(tarpaulin_include))]
pub fn compute(datas: &[u8]) -> Hash {
let mut hasher = Sha256::new();
hasher.input(datas);
let mut hash_buffer = [0u8; 32];
hasher.result(&mut hash_buffer);
Hash(hash_buffer)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn compute(datas: &[u8]) -> Hash {
let mut hash_buffer = [0u8; 32];
hash_buffer.copy_from_slice(digest::digest(&digest::SHA256, datas).as_ref());
Hash(hash_buffer)
}
#[cfg(target_arch = "wasm32")]
#[cfg(not(tarpaulin_include))]
pub fn compute_multipart(data_parts: &[&[u8]]) -> Hash {
let mut hasher = Sha256::new();
for data in data_parts {
hasher.input(data);
}
let mut hash_buffer = [0u8; 32];
hasher.result(&mut hash_buffer);
Hash(hash_buffer)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn compute_multipart(data_parts: &[&[u8]]) -> Hash {
let mut ctx = digest::Context::new(&digest::SHA256);
for data in data_parts {
ctx.update(data);
}
let mut hash_buffer = [0u8; 32];
hash_buffer.copy_from_slice(ctx.finish().as_ref());
Hash(hash_buffer)
}
pub fn compute_blake3(datas: &[u8]) -> Hash {
if datas.len() > BLAKE3_CUTOFF {
let mut hasher = blake3::Hasher::new();
hasher.update_with_join::<blake3::join::RayonJoin>(datas);
let hash = hasher.finalize();
Hash(hash.into())
} else {
Hash(blake3::hash(datas).into())
}
}
pub fn to_bytes_vector(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn to_hex(&self) -> String {
let strings: Vec<String> = self.0.iter().map(|b| format!("{:02X}", b)).collect();
strings.join("")
}
#[inline]
pub fn from_hex(text: &str) -> Result<Hash, BaseConversionError> {
Ok(Hash(b16::str_hex_to_32bytes(text)?))
}
pub const fn max() -> Hash {
Hash([255; 32])
}
}
#[cfg(test)]
mod tests {
use super::*;
use unwrap::unwrap;
#[test]
fn test_hash_random() {
let hash1 = Hash::random();
let hash2 = Hash::random();
assert_ne!(hash1, hash2);
}
#[test]
fn test_hash_debug() {
assert_eq!(
"Hash(0000000000000000000000000000000000000000000000000000000000000000)".to_owned(),
format!("{:?}", Hash::default()),
);
}
#[test]
fn test_hash_to_bytes() {
assert_eq!(
vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0
],
Hash::default().to_bytes_vector(),
);
}
#[test]
fn test_hash_computation() {
assert_eq!(
unwrap!(Hash::from_hex(
"2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
)),
Hash::compute(b"hello"),
);
assert_eq!(
unwrap!(Hash::from_hex(
"2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
)),
Hash::compute(b"hello"),
);
}
#[test]
fn test_hash_from_hex() {
assert_eq!(
Ok(Hash::default()),
Hash::from_hex("0000000000000000000000000000000000000000000000000000000000000000")
);
assert_eq!(
Err(BaseConversionError::InvalidLength {
expected: 64,
found: 65,
}),
Hash::from_hex("00000000000000000000000000000000000000000000000000000000000000000")
);
assert_eq!(
Err(BaseConversionError::InvalidCharacter {
character: '_',
offset: 0,
}),
Hash::from_hex("_000000000000000000000000000000000000000000000000000000000000000")
);
assert_eq!(
Err(BaseConversionError::InvalidCharacter {
character: '_',
offset: 1,
}),
Hash::from_hex("0_00000000000000000000000000000000000000000000000000000000000000")
);
}
}