use core::fmt::{self, Debug};
use alloc::sync::Arc;
use universal_hash::{
KeyInit, UhfClosure, UniversalHash,
common::{BlockSizeUser, KeySizeUser},
consts::U32,
};
use crate::backend::{Absorb, State16, State24, State32, State40};
use crate::entropy::{self, ENTROPY_BLOCK_LANES, EntropyTooShort, prepare_external};
use crate::key::{self, MasterKey};
use crate::variant::{HalftimeVariant, hh16, hh24, hh32, hh40};
pub const KEY_SIZE: usize = 32;
macro_rules! define_halftime {
(
$(#[$meta:meta])*
$name:ident,
$state:ty,
$block:ty,
$variant:ty,
$tag_bytes:expr
) => {
$(#[$meta])*
pub struct $name {
state: $state,
}
impl KeySizeUser for $name {
type KeySize = U32;
}
impl BlockSizeUser for $name {
type BlockSize = $block;
}
impl KeyInit for $name {
fn new(key: &universal_hash::Key<Self>) -> Self {
let mut material = [0u8; KEY_SIZE];
material.copy_from_slice(key.as_slice());
Self {
state: <$state>::new(&material),
}
}
}
impl UniversalHash for $name {
fn update_with_backend(
&mut self,
f: impl UhfClosure<BlockSize = Self::BlockSize>,
) {
self.state.update_with_backend(f);
}
#[inline]
fn update_padded(&mut self, data: &[u8]) {
self.state.absorb(data);
}
fn finalize(self) -> universal_hash::Block<Self> {
self.state.finalize()
}
}
impl $name {
#[must_use]
pub fn digest(
key: &universal_hash::Key<Self>,
data: &[u8],
) -> universal_hash::Block<Self> {
let mut h = Self::new(key);
h.update_padded(data);
h.finalize()
}
#[must_use]
pub fn from_master_key(key: &impl MasterKey) -> Self {
let material = key::to_material(key);
Self {
state: <$state>::new(&material),
}
}
#[must_use]
pub fn digest_master_key(
key: &impl MasterKey,
data: &[u8],
) -> universal_hash::Block<Self> {
let mut h = Self::from_master_key(key);
h.update_padded(data);
h.finalize()
}
pub fn with_entropy(entropy: &[u64]) -> Result<Self, EntropyTooShort> {
Self::with_entropy_for(entropy, entropy::DEFAULT_ENTROPY_INPUT)
}
pub fn with_entropy_for(
entropy: &[u64],
max_input_bytes: usize,
) -> Result<Self, EntropyTooShort> {
let prepared = prepare_external(
Arc::from(entropy),
max_input_bytes,
ENTROPY_BLOCK_LANES,
<$variant as HalftimeVariant>::DIM,
<$variant as HalftimeVariant>::ENC,
<$variant as HalftimeVariant>::OUT,
)?;
Ok(Self {
state: <$state>::from_entropy(prepared),
})
}
pub fn digest_with_entropy(
entropy: &[u64],
data: &[u8],
) -> Result<universal_hash::Block<Self>, EntropyTooShort> {
let mut h = Self::with_entropy_for(entropy, data.len())?;
h.update_padded(data);
Ok(h.finalize())
}
#[must_use]
pub fn entropy_words_needed(input_bytes: usize) -> usize {
entropy::entropy_words_needed(
input_bytes,
ENTROPY_BLOCK_LANES,
<$variant as HalftimeVariant>::DIM,
<$variant as HalftimeVariant>::ENC,
<$variant as HalftimeVariant>::OUT,
)
}
pub const TAG_BYTES: usize = $tag_bytes;
}
impl Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($name)).finish_non_exhaustive()
}
}
};
}
define_halftime! {
HalftimeHash16,
State16,
universal_hash::consts::U16,
crate::variant::Hh16,
hh16::TAG_BYTES
}
define_halftime! {
HalftimeHash24,
State24,
universal_hash::consts::U24,
crate::variant::Hh24,
hh24::TAG_BYTES
}
define_halftime! {
HalftimeHash32,
State32,
U32,
crate::variant::Hh32,
hh32::TAG_BYTES
}
define_halftime! {
HalftimeHash40,
State40,
universal_hash::consts::U40,
crate::variant::Hh40,
hh40::TAG_BYTES
}
pub type Tag = Tag24;
pub type Tag16 = universal_hash::Block<HalftimeHash16>;
pub type Tag24 = universal_hash::Block<HalftimeHash24>;
pub type Tag32 = universal_hash::Block<HalftimeHash32>;
pub type Tag40 = universal_hash::Block<HalftimeHash40>;
#[cfg(test)]
mod tests {
use super::*;
use crate::key::{Key32, Key64, Key128};
macro_rules! variant_tests {
($name:ident, $det:ident, $diff:ident, $len:ident) => {
#[test]
fn $det() {
let key = Key32::from([0xA5u8; 32]);
let a = $name::digest_master_key(&key, b"hello halftime");
let b = $name::digest_master_key(&key, b"hello halftime");
assert_eq!(a, b);
}
#[test]
fn $diff() {
let key = Key32::from([0xA5u8; 32]);
let a = $name::digest_master_key(&key, b"alpha");
let b = $name::digest_master_key(&key, b"beta");
assert_ne!(a, b);
}
#[test]
fn $len() {
let key = Key32::from([0xA5u8; 32]);
let short = $name::digest_master_key(&key, b"x");
let long = $name::digest_master_key(&key, &[b'x'; 1024]);
assert_ne!(short, long);
}
};
}
variant_tests!(HalftimeHash16, hh16_det, hh16_diff, hh16_len);
variant_tests!(HalftimeHash24, hh24_det, hh24_diff, hh24_len);
variant_tests!(HalftimeHash32, hh32_det, hh32_diff, hh32_len);
variant_tests!(HalftimeHash40, hh40_det, hh40_diff, hh40_len);
#[test]
fn variants_differ() {
let key = Key32::from([0xA5u8; 32]);
let t16 = HalftimeHash16::digest_master_key(&key, b"same");
let t24 = HalftimeHash24::digest_master_key(&key, b"same");
assert_ne!(t16.as_slice(), &t24.as_slice()[..16]);
}
#[test]
fn key64_and_key128_work_with_any_variant() {
let k64 = Key64::from([0x11u8; 64]);
let k128 = Key128::from([0x22u8; 128]);
let _ = HalftimeHash16::digest_master_key(&k64, b"a");
let _ = HalftimeHash40::digest_master_key(&k128, b"b");
}
#[test]
fn key_init_matches_master_key32() {
let bytes = [0x42u8; 32];
let ukey = universal_hash::Key::<HalftimeHash24>::from(bytes);
let mkey = Key32::from(bytes);
let a = HalftimeHash24::digest(&ukey, b"msg");
let b = HalftimeHash24::digest_master_key(&mkey, b"msg");
assert_eq!(a, b);
}
#[test]
fn external_entropy_matches_key_derived() {
let key = Key32::from([0xA5u8; 32]);
let key_tag = HalftimeHash24::digest_master_key(&key, b"entropy test");
let need = HalftimeHash24::entropy_words_needed(b"entropy test".len());
let mut words = alloc::vec::Vec::with_capacity(need);
{
let mut e = entropy::Entropy::from_key_material(&key.0);
e.ensure_for_input(b"entropy test".len(), ENTROPY_BLOCK_LANES, 7, 9, 3)
.unwrap();
words.extend_from_slice(e.as_slice());
}
let ext_tag = HalftimeHash24::digest_with_entropy(&words, b"entropy test").unwrap();
assert_eq!(key_tag, ext_tag);
}
}