#![no_std]
#![forbid(unsafe_code)]
const DIGEST_SIZE: usize = 160 / 8; const BLOCK_SIZE: usize = 256 / 8;
pub fn hash(data: impl AsRef<[u8]>) -> [u8; DIGEST_SIZE] {
let mut state = [
0x5d6daffc4411a967,
0xe22d4dea68577f34,
0xca50864d814cbc2e,
0x894e29b9611eb173,
];
let mut data = data.as_ref();
let message_bit_length = data.len() as u64 * 8;
while data.len() >= BLOCK_SIZE {
xor_data_into_state(&mut state, data);
mix_state(&mut state);
data = &data[BLOCK_SIZE..];
}
if !data.is_empty() {
let mut buffer = [0u8; BLOCK_SIZE];
(&mut buffer[..data.len()]).copy_from_slice(data);
xor_data_into_state(&mut state, &buffer);
mix_state(&mut state);
}
state[0] ^= message_bit_length;
mix_state(&mut state);
mix_state(&mut state);
let mut digest = [0u8; DIGEST_SIZE];
digest[0..8].copy_from_slice(&state[0].to_le_bytes());
digest[8..16].copy_from_slice(&state[1].to_le_bytes());
digest[16..20].copy_from_slice(&state[2].to_le_bytes()[0..4]);
return digest;
}
#[derive(Debug, Copy, Clone)]
pub struct TentHash {
state: [u64; 4], buf: [u8; BLOCK_SIZE], buf_length: usize, message_length: u64, }
impl TentHash {
pub fn new() -> TentHash {
TentHash {
state: [
0x5d6daffc4411a967,
0xe22d4dea68577f34,
0xca50864d814cbc2e,
0x894e29b9611eb173,
],
buf: [0; BLOCK_SIZE],
buf_length: 0,
message_length: 0,
}
}
pub fn update(&mut self, data: impl AsRef<[u8]>) {
let mut data = data.as_ref();
self.message_length += data.len() as u64;
while !data.is_empty() {
if self.buf_length == 0 && data.len() >= BLOCK_SIZE {
xor_data_into_state(&mut self.state, data);
mix_state(&mut self.state);
data = &data[BLOCK_SIZE..];
} else if self.buf_length == BLOCK_SIZE {
xor_data_into_state(&mut self.state, &self.buf);
mix_state(&mut self.state);
self.buf_length = 0;
} else {
let n = (BLOCK_SIZE - self.buf_length).min(data.len());
(&mut self.buf[self.buf_length..(self.buf_length + n)]).copy_from_slice(&data[..n]);
data = &data[n..];
self.buf_length += n;
}
}
}
pub fn finalize(mut self) -> [u8; DIGEST_SIZE] {
if self.buf_length > 0 {
(&mut self.buf[self.buf_length..]).fill(0); xor_data_into_state(&mut self.state, &self.buf);
mix_state(&mut self.state);
}
self.state[0] ^= self.message_length * 8;
mix_state(&mut self.state);
mix_state(&mut self.state);
let mut digest = [0u8; DIGEST_SIZE];
digest[0..8].copy_from_slice(&self.state[0].to_le_bytes());
digest[8..16].copy_from_slice(&self.state[1].to_le_bytes());
digest[16..20].copy_from_slice(&self.state[2].to_le_bytes()[0..4]);
return digest;
}
}
#[inline(always)]
fn xor_data_into_state(state: &mut [u64; 4], data: &[u8]) {
assert!(data.len() >= BLOCK_SIZE);
state[0] ^= u64::from_le_bytes((&data[0..8]).try_into().unwrap());
state[1] ^= u64::from_le_bytes((&data[8..16]).try_into().unwrap());
state[2] ^= u64::from_le_bytes((&data[16..24]).try_into().unwrap());
state[3] ^= u64::from_le_bytes((&data[24..32]).try_into().unwrap());
}
#[inline(always)]
fn mix_state(state: &mut [u64; 4]) {
const ROTATIONS: &[[u32; 2]] = &[
[16, 28],
[14, 57],
[11, 22],
[35, 34],
[57, 16],
[59, 40],
[44, 13],
];
for rot_pair in ROTATIONS.iter() {
state[0] = state[0].wrapping_add(state[2]);
state[2] = state[2].rotate_left(rot_pair[0]) ^ state[0];
state[1] = state[1].wrapping_add(state[3]);
state[3] = state[3].rotate_left(rot_pair[1]) ^ state[1];
state.swap(0, 1);
}
}
pub trait DigestExt {
fn to_16_bytes(self) -> [u8; 16];
fn to_u128(self) -> u128;
}
impl DigestExt for [u8; 20] {
#[inline(always)]
fn to_16_bytes(self) -> [u8; 16] {
(&self[0..16]).try_into().unwrap()
}
#[inline(always)]
fn to_u128(self) -> u128 {
u128::from_le_bytes(self.to_16_bytes())
}
}