use core::fmt;
use crate::encoding::{bytes_to_rate_block, hash_to_bytes};
use crate::field::Goldilocks;
use crate::params::{self, OUTPUT_BYTES, OUTPUT_BYTES_PER_ELEMENT, OUTPUT_ELEMENTS, RATE, RATE_BYTES, WIDTH};
const DOMAIN_HASH: u64 = 0x00;
const DOMAIN_KEYED: u64 = 0x01;
const DOMAIN_DERIVE_KEY_CONTEXT: u64 = 0x02;
const DOMAIN_DERIVE_KEY_MATERIAL: u64 = 0x03;
const CAPACITY_START: usize = RATE;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Hash([u8; OUTPUT_BYTES]);
impl Hash {
pub const fn from_bytes(bytes: [u8; OUTPUT_BYTES]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; OUTPUT_BYTES] {
&self.0
}
#[cfg(feature = "std")]
#[allow(unknown_lints, rs_no_string)]
pub fn to_hex(&self) -> alloc::string::String {
let mut s = alloc::string::String::with_capacity(OUTPUT_BYTES * 2);
for byte in &self.0 {
use core::fmt::Write;
write!(s, "{byte:02x}").unwrap();
}
s
}
}
impl From<[u8; OUTPUT_BYTES]> for Hash {
fn from(bytes: [u8; OUTPUT_BYTES]) -> Self {
Self(bytes)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Hash {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeTuple;
let mut seq = serializer.serialize_tuple(OUTPUT_BYTES)?;
for byte in &self.0 {
seq.serialize_element(byte)?;
}
seq.end()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Hash {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct HashVisitor;
impl<'de> serde::de::Visitor<'de> for HashVisitor {
type Value = Hash;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a byte array of length {OUTPUT_BYTES}")
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(
self,
mut seq: A,
) -> Result<Hash, A::Error> {
let mut bytes = [0u8; OUTPUT_BYTES];
for (i, byte) in bytes.iter_mut().enumerate() {
*byte = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(i, &self))?;
}
Ok(Hash(bytes))
}
}
deserializer.deserialize_tuple(OUTPUT_BYTES, HashVisitor)
}
}
impl AsRef<[u8]> for Hash {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash({self})")
}
}
#[derive(Clone)]
pub struct Hasher {
state: [Goldilocks; WIDTH],
buf: [u8; RATE_BYTES],
buf_len: usize,
absorbed: u64,
}
impl Hasher {
pub fn new() -> Self {
let mut state = [Goldilocks::new(0); WIDTH];
state[CAPACITY_START + 3] = Goldilocks::new(DOMAIN_HASH);
Self {
state,
buf: [0u8; RATE_BYTES],
buf_len: 0,
absorbed: 0,
}
}
pub fn new_keyed(key: &[u8; OUTPUT_BYTES]) -> Self {
let mut state = [Goldilocks::new(0); WIDTH];
state[CAPACITY_START + 3] = Goldilocks::new(DOMAIN_KEYED);
let mut hasher = Self {
state,
buf: [0u8; RATE_BYTES],
buf_len: 0,
absorbed: 0,
};
hasher.update(key.as_slice());
hasher
}
pub fn new_derive_key_context(context: &str) -> Self {
let mut state = [Goldilocks::new(0); WIDTH];
state[CAPACITY_START + 3] = Goldilocks::new(DOMAIN_DERIVE_KEY_CONTEXT);
let mut hasher = Self {
state,
buf: [0u8; RATE_BYTES],
buf_len: 0,
absorbed: 0,
};
hasher.update(context.as_bytes());
hasher
}
pub fn new_derive_key_material(context_hash: &Hash) -> Self {
let mut state = [Goldilocks::new(0); WIDTH];
state[CAPACITY_START + 3] = Goldilocks::new(DOMAIN_DERIVE_KEY_MATERIAL);
for (i, chunk) in context_hash.0.chunks(OUTPUT_BYTES_PER_ELEMENT).enumerate() {
let val = u64::from_le_bytes(chunk.try_into().unwrap());
state[i] = Goldilocks::new(val);
}
params::permute(&mut state);
Self {
state,
buf: [0u8; RATE_BYTES],
buf_len: 0,
absorbed: 0,
}
}
pub fn update(&mut self, mut data: &[u8]) -> &mut Self {
self.absorbed += data.len() as u64;
while !data.is_empty() {
let space = RATE_BYTES - self.buf_len;
let n = space.min(data.len());
self.buf[self.buf_len..self.buf_len + n].copy_from_slice(&data[..n]);
self.buf_len += n;
data = &data[n..];
if self.buf_len == RATE_BYTES {
let mut rate_block = [Goldilocks::new(0); RATE];
bytes_to_rate_block(&self.buf, &mut rate_block);
self.absorb_block(&rate_block);
self.buf_len = 0;
}
}
self
}
fn absorb_block(&mut self, block: &[Goldilocks; RATE]) {
for (i, block_elem) in block.iter().enumerate() {
self.state[i] = self.state[i] + *block_elem;
}
params::permute(&mut self.state);
}
pub fn finalize_state(&self) -> [Goldilocks; WIDTH] {
let mut state = self.state;
let mut padded = [0u8; RATE_BYTES];
padded[..self.buf_len].copy_from_slice(&self.buf[..self.buf_len]);
padded[self.buf_len] = 0x01;
let mut rate_block = [Goldilocks::new(0); RATE];
bytes_to_rate_block(&padded, &mut rate_block);
for i in 0..RATE {
state[i] = state[i] + rate_block[i];
}
state[CAPACITY_START + 2] = Goldilocks::new(self.absorbed);
params::permute(&mut state);
state
}
pub fn update_traced<V: crate::trace::RoundVisitor>(
&mut self,
mut data: &[u8],
visitor: &mut V,
) -> &mut Self {
self.absorbed += data.len() as u64;
while !data.is_empty() {
let space = RATE_BYTES - self.buf_len;
let n = space.min(data.len());
self.buf[self.buf_len..self.buf_len + n].copy_from_slice(&data[..n]);
self.buf_len += n;
data = &data[n..];
if self.buf_len == RATE_BYTES {
let mut rate_block = [Goldilocks::new(0); RATE];
bytes_to_rate_block(&self.buf, &mut rate_block);
for (i, elem) in rate_block.iter().enumerate() {
self.state[i] = self.state[i] + *elem;
}
crate::permutation::permute_traced(&mut self.state, visitor);
self.buf_len = 0;
}
}
self
}
pub fn finalize_traced<V: crate::trace::RoundVisitor>(&self, visitor: &mut V) -> Hash {
let mut state = self.state;
let mut padded = [0u8; RATE_BYTES];
padded[..self.buf_len].copy_from_slice(&self.buf[..self.buf_len]);
padded[self.buf_len] = 0x01;
let mut rate_block = [Goldilocks::new(0); RATE];
bytes_to_rate_block(&padded, &mut rate_block);
for i in 0..RATE {
state[i] = state[i] + rate_block[i];
}
state[CAPACITY_START + 2] = Goldilocks::new(self.absorbed);
crate::permutation::permute_traced(&mut state, visitor);
let output: [Goldilocks; OUTPUT_ELEMENTS] = state[..OUTPUT_ELEMENTS]
.try_into()
.unwrap();
Hash(hash_to_bytes(&output))
}
pub fn finalize(&self) -> Hash {
let state = self.finalize_state();
let output: [Goldilocks; OUTPUT_ELEMENTS] = state[..OUTPUT_ELEMENTS]
.try_into()
.unwrap();
Hash(hash_to_bytes(&output))
}
pub fn finalize_xof(&self) -> OutputReader {
let state = self.finalize_state();
OutputReader {
state,
buffer: [0u8; OUTPUT_BYTES],
buffer_pos: OUTPUT_BYTES, }
}
}
impl Default for Hasher {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Hasher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Hasher")
.field("absorbed", &self.absorbed)
.field("buffered", &self.buf_len)
.finish()
}
}
pub struct OutputReader {
state: [Goldilocks; WIDTH],
buffer: [u8; OUTPUT_BYTES],
buffer_pos: usize,
}
impl OutputReader {
pub fn fill(&mut self, output: &mut [u8]) {
let mut written = 0;
while written < output.len() {
if self.buffer_pos >= OUTPUT_BYTES {
self.squeeze();
}
let available = OUTPUT_BYTES - self.buffer_pos;
let needed = output.len() - written;
let n = available.min(needed);
output[written..written + n]
.copy_from_slice(&self.buffer[self.buffer_pos..self.buffer_pos + n]);
self.buffer_pos += n;
written += n;
}
}
fn squeeze(&mut self) {
let output_elems: [Goldilocks; OUTPUT_ELEMENTS] = self.state[..OUTPUT_ELEMENTS]
.try_into()
.unwrap();
self.buffer = hash_to_bytes(&output_elems);
self.buffer_pos = 0;
params::permute(&mut self.state);
}
}
#[cfg(feature = "std")]
impl std::io::Read for OutputReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.fill(buf);
Ok(buf.len())
}
}
impl fmt::Debug for OutputReader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OutputReader").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::{format, vec, vec::Vec};
use super::*;
#[test]
fn hash_display_is_hex() {
let h = Hash([0xAB; OUTPUT_BYTES]);
let s = format!("{h}");
assert_eq!(s.len(), OUTPUT_BYTES * 2);
assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn empty_hash_is_not_zero() {
let h = Hasher::new().finalize();
assert_ne!(h.0, [0u8; OUTPUT_BYTES]);
}
#[test]
fn different_inputs_different_hashes() {
let h1 = Hasher::new().update(b"a").finalize();
let h2 = Hasher::new().update(b"b").finalize();
assert_ne!(h1, h2);
}
#[test]
fn streaming_consistency() {
let data = b"hello world, this is a test of streaming consistency!";
let one_shot = {
let mut h = Hasher::new();
h.update(data);
h.finalize()
};
let streamed = {
let mut h = Hasher::new();
h.update(&data[..5]);
h.update(&data[5..20]);
h.update(&data[20..]);
h.finalize()
};
assert_eq!(one_shot, streamed);
}
#[test]
fn streaming_across_rate_boundary() {
let data = vec![0x42u8; 100];
let one_shot = {
let mut h = Hasher::new();
h.update(&data);
h.finalize()
};
let byte_at_a_time = {
let mut h = Hasher::new();
for b in &data {
h.update(core::slice::from_ref(b));
}
h.finalize()
};
assert_eq!(one_shot, byte_at_a_time);
}
#[test]
fn domain_separation_hash_vs_keyed() {
let data = b"test data";
let plain = Hasher::new().update(data).finalize();
let keyed = Hasher::new_keyed(&[0u8; OUTPUT_BYTES]).update(data).finalize();
assert_ne!(plain, keyed);
}
#[test]
fn domain_separation_hash_vs_derive_key() {
let data = b"test material";
let plain = Hasher::new().update(data).finalize();
let ctx_hasher = Hasher::new_derive_key_context("test context");
let ctx_hash = ctx_hasher.finalize();
let derived = Hasher::new_derive_key_material(&ctx_hash)
.update(data)
.finalize();
assert_ne!(plain, derived);
}
#[test]
fn xof_first_32_match_finalize() {
let data = b"xof test";
let hash = Hasher::new().update(data).finalize();
let mut xof = Hasher::new().update(data).finalize_xof();
let mut xof_bytes = [0u8; OUTPUT_BYTES];
xof.fill(&mut xof_bytes);
assert_eq!(hash.as_bytes(), &xof_bytes);
}
#[test]
fn xof_produces_more_than_32_bytes() {
let mut xof = Hasher::new().update(b"xof").finalize_xof();
let mut out = [0u8; 128];
xof.fill(&mut out);
assert_ne!(out, [0u8; 128]);
assert_ne!(out[..OUTPUT_BYTES], out[OUTPUT_BYTES..OUTPUT_BYTES * 2]);
}
#[test]
fn xof_read_trait() {
use std::io::Read;
let mut xof = Hasher::new().update(b"read trait").finalize_xof();
let mut buf = [0u8; 64];
let n = xof.read(&mut buf).unwrap();
assert_eq!(n, 64);
}
#[test]
fn keyed_hash_different_keys() {
let data = b"same data";
let h1 = Hasher::new_keyed(&[0u8; OUTPUT_BYTES]).update(data).finalize();
let h2 = Hasher::new_keyed(&[1u8; OUTPUT_BYTES]).update(data).finalize();
assert_ne!(h1, h2);
}
#[test]
fn exact_rate_block_input() {
let data = vec![0x42u8; RATE_BYTES];
let h = Hasher::new().update(&data).finalize();
assert_ne!(h.0, [0u8; OUTPUT_BYTES]);
let h2 = {
let mut hasher = Hasher::new();
hasher.update(&data[..28]);
hasher.update(&data[28..]);
hasher.finalize()
};
assert_eq!(h, h2);
}
#[test]
fn exact_two_rate_blocks_input() {
let data = vec![0x42u8; RATE_BYTES * 2];
let h = Hasher::new().update(&data).finalize();
let h_streamed = {
let mut hasher = Hasher::new();
for chunk in data.chunks(17) { hasher.update(chunk);
}
hasher.finalize()
};
assert_eq!(h, h_streamed);
}
#[test]
fn one_less_than_rate_block() {
let data = vec![0x42u8; RATE_BYTES - 1];
let h = Hasher::new().update(&data).finalize();
assert_ne!(h.0, [0u8; OUTPUT_BYTES]);
}
#[test]
fn one_more_than_rate_block() {
let data = vec![0x42u8; RATE_BYTES + 1];
let h = Hasher::new().update(&data).finalize();
assert_ne!(h.0, [0u8; OUTPUT_BYTES]);
}
#[test]
fn hasher_clone_produces_same_hash() {
let mut h1 = Hasher::new();
h1.update(b"some data");
let h2 = h1.clone();
h1.update(b" more");
let mut h3 = h1.clone();
h3.update(b"");
assert_eq!(h1.finalize(), h3.finalize());
assert_ne!(h1.finalize(), h2.finalize());
}
#[test]
fn hasher_clone_mid_block() {
let mut h = Hasher::new();
h.update(&[0xAB; 30]); let cloned = h.clone();
h.update(&[0xCD; 30]);
let mut cloned2 = cloned.clone();
cloned2.update(&[0xCD; 30]);
assert_eq!(h.finalize(), cloned2.finalize());
}
#[test]
fn xof_incremental_reads_match_bulk() {
let mut xof1 = Hasher::new().update(b"xof incremental").finalize_xof();
let mut xof2 = Hasher::new().update(b"xof incremental").finalize_xof();
let mut bulk = [0u8; 200];
xof1.fill(&mut bulk);
let mut incremental = Vec::new();
for size in [1, 3, 7, 13, 64, 50, 62] {
let mut buf = vec![0u8; size];
xof2.fill(&mut buf);
incremental.extend_from_slice(&buf);
}
assert_eq!(&bulk[..], &incremental[..]);
}
#[test]
fn xof_deterministic() {
let mut xof1 = Hasher::new().update(b"deterministic").finalize_xof();
let mut xof2 = Hasher::new().update(b"deterministic").finalize_xof();
let mut out1 = [0u8; 256];
let mut out2 = [0u8; 256];
xof1.fill(&mut out1);
xof2.fill(&mut out2);
assert_eq!(out1, out2);
}
#[test]
fn xof_different_inputs_different_streams() {
let mut xof1 = Hasher::new().update(b"input A").finalize_xof();
let mut xof2 = Hasher::new().update(b"input B").finalize_xof();
let mut out1 = [0u8; 128];
let mut out2 = [0u8; 128];
xof1.fill(&mut out1);
xof2.fill(&mut out2);
assert_ne!(out1, out2);
}
#[test]
fn xof_zero_length_fill() {
let mut xof = Hasher::new().update(b"zero").finalize_xof();
let mut empty = [];
xof.fill(&mut empty);
let mut out = [0u8; 64];
xof.fill(&mut out);
assert_ne!(out, [0u8; 64]);
}
#[test]
fn hash_from_bytes_roundtrip() {
let bytes = [0xAB; OUTPUT_BYTES];
let h = Hash::from_bytes(bytes);
assert_eq!(h.as_bytes(), &bytes);
}
#[test]
fn hash_to_hex_length() {
let h = Hash::from_bytes([0x00; OUTPUT_BYTES]);
let hex = format!("{h}");
assert_eq!(hex.len(), OUTPUT_BYTES * 2);
assert_eq!(hex, "0".repeat(OUTPUT_BYTES * 2));
}
#[test]
fn hash_debug_format() {
let h = Hash::from_bytes([0x00; OUTPUT_BYTES]);
let debug = format!("{h:?}");
assert!(debug.starts_with("Hash("));
assert!(debug.ends_with(')'));
}
#[test]
fn hash_as_ref() {
let h = Hash::from_bytes([0x42; OUTPUT_BYTES]);
let slice: &[u8] = h.as_ref();
assert_eq!(slice.len(), OUTPUT_BYTES);
assert!(slice.iter().all(|&b| b == 0x42));
}
#[test]
fn hash_eq_and_hash_trait() {
use std::collections::HashSet;
let h1 = Hash::from_bytes([1; OUTPUT_BYTES]);
let h2 = Hash::from_bytes([1; OUTPUT_BYTES]);
let h3 = Hash::from_bytes([2; OUTPUT_BYTES]);
assert_eq!(h1, h2);
assert_ne!(h1, h3);
let mut set = HashSet::new();
set.insert(h1);
set.insert(h2);
set.insert(h3);
assert_eq!(set.len(), 2);
}
#[test]
fn hasher_default_matches_new() {
let h1 = Hasher::new().update(b"test").finalize();
let h2 = Hasher::default().update(b"test").finalize();
assert_eq!(h1, h2);
}
#[test]
fn all_four_domains_produce_different_outputs() {
let data = b"domain test data";
let plain = Hasher::new().update(data).finalize();
let keyed = Hasher::new_keyed(&[0u8; OUTPUT_BYTES])
.update(data)
.finalize();
let ctx = Hasher::new_derive_key_context("ctx");
let ctx_hash = ctx.finalize();
let derived = Hasher::new_derive_key_material(&ctx_hash)
.update(data)
.finalize();
let context_only = Hasher::new_derive_key_context(
core::str::from_utf8(data).unwrap()
).finalize();
let hashes = [plain, keyed, derived, context_only];
for i in 0..hashes.len() {
for j in (i + 1)..hashes.len() {
assert_ne!(hashes[i], hashes[j], "domains {i} and {j} collide");
}
}
}
#[test]
fn keyed_hash_empty_data() {
let h = Hasher::new_keyed(&[0u8; OUTPUT_BYTES]).finalize();
assert_ne!(h.0, [0u8; OUTPUT_BYTES]);
}
#[test]
fn derive_key_empty_material() {
let key = crate::derive_key("context", b"");
assert_ne!(key, [0u8; OUTPUT_BYTES]);
}
#[test]
fn derive_key_empty_context() {
let key = crate::derive_key("", b"material");
assert_ne!(key, [0u8; OUTPUT_BYTES]);
}
#[test]
fn hasher_debug_shows_absorbed() {
let mut h = Hasher::new();
h.update(b"hello");
let debug = format!("{h:?}");
assert!(debug.contains("absorbed"));
assert!(debug.contains("5")); }
#[test]
fn pinned_empty_hash() {
let h1 = Hasher::new().finalize();
let h2 = Hasher::new().finalize();
assert_eq!(h1, h2);
let hex = format!("{h1}");
assert_eq!(hex.len(), 64); }
#[test]
fn pinned_hemera_hash() {
let h1 = crate::hash(b"hemera");
let h2 = crate::hash(b"hemera");
assert_eq!(h1, h2);
}
#[cfg(feature = "serde")]
#[test]
fn serde_roundtrip() {
let h = crate::hash(b"serde test");
let json = serde_json::to_string(&h).unwrap();
let recovered: Hash = serde_json::from_str(&json).unwrap();
assert_eq!(h, recovered);
}
#[cfg(feature = "serde")]
#[test]
fn serde_roundtrip_zero_hash() {
let h = Hash::from_bytes([0u8; OUTPUT_BYTES]);
let json = serde_json::to_string(&h).unwrap();
let recovered: Hash = serde_json::from_str(&json).unwrap();
assert_eq!(h, recovered);
}
}