#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub mod batch;
pub mod stream;
pub mod bootstrap;
pub mod constants;
mod encoding;
pub mod field;
mod params;
pub mod permutation;
mod sponge;
pub mod sparse;
pub mod tree;
pub use params::{
CAPACITY, CHUNK_SIZE, COLLISION_BITS, OUTPUT_BYTES, OUTPUT_ELEMENTS, RATE, RATE_BYTES,
ROUNDS_F, ROUNDS_P, SBOX_DEGREE, WIDTH,
};
pub use sponge::{Hash, Hasher, OutputReader};
pub fn hash(input: &[u8]) -> Hash {
let mut hasher = Hasher::new();
hasher.update(input);
hasher.finalize()
}
pub fn keyed_hash(key: &[u8; OUTPUT_BYTES], input: &[u8]) -> Hash {
let mut hasher = Hasher::new_keyed(key);
hasher.update(input);
hasher.finalize()
}
pub fn derive_key(context: &str, key_material: &[u8]) -> [u8; OUTPUT_BYTES] {
let ctx_hasher = Hasher::new_derive_key_context(context);
let ctx_hash = ctx_hasher.finalize();
let mut material_hasher = Hasher::new_derive_key_material(&ctx_hash);
material_hasher.update(key_material);
let result = material_hasher.finalize();
*result.as_bytes()
}
#[cfg(test)]
mod tests {
extern crate std;
use std::{vec, vec::Vec};
use super::*;
#[test]
fn hash_basic() {
let h = hash(b"hello");
assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
}
#[test]
fn hash_deterministic() {
let h1 = hash(b"test");
let h2 = hash(b"test");
assert_eq!(h1, h2);
}
#[test]
fn hash_different_inputs() {
assert_ne!(hash(b""), hash(b"a"));
assert_ne!(hash(b"a"), hash(b"b"));
assert_ne!(hash(b"ab"), hash(b"ba"));
}
#[test]
fn hash_matches_streaming() {
let data = b"streaming consistency test with enough data to cross boundaries!!";
let direct = hash(data);
let streamed = {
let mut h = Hasher::new();
h.update(&data[..10]);
h.update(&data[10..]);
h.finalize()
};
assert_eq!(direct, streamed);
}
#[test]
fn keyed_hash_differs_from_plain() {
let data = b"test";
assert_ne!(hash(data), keyed_hash(&[0u8; OUTPUT_BYTES], data));
}
#[test]
fn keyed_hash_different_keys() {
let data = b"test";
let h1 = keyed_hash(&[0u8; OUTPUT_BYTES], data);
let h2 = keyed_hash(&[1u8; OUTPUT_BYTES], data);
assert_ne!(h1, h2);
}
#[test]
fn derive_key_basic() {
let key = derive_key("my context", b"material");
assert_ne!(key, [0u8; OUTPUT_BYTES]);
}
#[test]
fn derive_key_differs_from_hash() {
let data = b"material";
let h = hash(data);
let k = derive_key("context", data);
assert_ne!(h.as_bytes(), &k);
}
#[test]
fn derive_key_different_contexts() {
let k1 = derive_key("context A", b"material");
let k2 = derive_key("context B", b"material");
assert_ne!(k1, k2);
}
#[test]
fn derive_key_different_materials() {
let k1 = derive_key("context", b"material A");
let k2 = derive_key("context", b"material B");
assert_ne!(k1, k2);
}
#[test]
fn xof_extends_hash() {
let mut xof = Hasher::new().update(b"xof test").finalize_xof();
let mut out = [0u8; OUTPUT_BYTES * 2];
xof.fill(&mut out);
let h = hash(b"xof test");
assert_eq!(&out[..OUTPUT_BYTES], h.as_bytes());
}
#[test]
fn large_input() {
let data = vec![0x42u8; 10_000];
let h = hash(&data);
assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
let mut hasher = Hasher::new();
for chunk in data.chunks(137) {
hasher.update(chunk);
}
assert_eq!(h, hasher.finalize());
}
#[test]
fn hash_empty() {
let h = hash(b"");
assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
}
#[test]
fn hash_single_byte_avalanche() {
let hashes: Vec<_> = (0..=255u8).map(|b| hash(&[b])).collect();
for i in 0..256 {
for j in (i + 1)..256 {
assert_ne!(hashes[i], hashes[j], "collision at bytes {i} and {j}");
}
}
}
#[test]
fn keyed_hash_empty_input() {
let h = keyed_hash(&[0u8; OUTPUT_BYTES], b"");
assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
}
#[test]
fn derive_key_long_context() {
let long_ctx = "a]".repeat(100);
let k = derive_key(&long_ctx, b"material");
assert_ne!(k, [0u8; OUTPUT_BYTES]);
}
#[test]
fn derive_key_long_material() {
let material = vec![0x42u8; 1000];
let k = derive_key("ctx", &material);
assert_ne!(k, [0u8; OUTPUT_BYTES]);
}
}
#[cfg(test)]
mod proptests {
extern crate std;
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn hash_is_deterministic(data in proptest::collection::vec(any::<u8>(), 0..500)) {
prop_assert_eq!(hash(&data), hash(&data));
}
#[test]
fn streaming_matches_oneshot(data in proptest::collection::vec(any::<u8>(), 0..500)) {
let oneshot = hash(&data);
let mut hasher = Hasher::new();
let mut pos = 0;
let mut chunk_size = 1;
while pos < data.len() {
let end = (pos + chunk_size).min(data.len());
hasher.update(&data[pos..end]);
pos = end;
chunk_size = (chunk_size * 3 + 1) % 71; if chunk_size == 0 { chunk_size = 1; }
}
prop_assert_eq!(oneshot, hasher.finalize());
}
#[test]
fn xof_prefix_matches_finalize(data in proptest::collection::vec(any::<u8>(), 0..200)) {
let hash_result = hash(&data);
let mut xof = {
let mut h = Hasher::new();
h.update(&data);
h.finalize_xof()
};
let mut xof_bytes = [0u8; OUTPUT_BYTES];
xof.fill(&mut xof_bytes);
prop_assert_eq!(hash_result.as_bytes(), &xof_bytes);
}
#[test]
fn keyed_hash_differs_from_plain(
data in proptest::collection::vec(any::<u8>(), 1..200),
key in proptest::collection::vec(any::<u8>(), OUTPUT_BYTES..=OUTPUT_BYTES),
) {
let key_arr: [u8; OUTPUT_BYTES] = key.try_into().unwrap();
let plain = hash(&data);
let keyed = keyed_hash(&key_arr, &data);
prop_assert_ne!(plain, keyed);
}
#[test]
fn clone_consistency(
prefix in proptest::collection::vec(any::<u8>(), 0..100),
suffix in proptest::collection::vec(any::<u8>(), 0..100),
) {
let mut h1 = Hasher::new();
h1.update(&prefix);
let mut h2 = h1.clone();
h1.update(&suffix);
h2.update(&suffix);
prop_assert_eq!(h1.finalize(), h2.finalize());
}
}
}