aimux-core 0.5.0

Core abstractions for aimux: LanguageModel trait, message types, and user-facing API
Documentation
//! Keyed hashing for cache-probe fingerprints (RFC-0015 §3).
//!
//! Zero-dependency keyed hashes (splitmix64-style folding). The 128-bit
//! fingerprint is two 64-bit hashes under two derived keys. Non-cryptographic
//! by design: fingerprints are *keys* in the client-side LCP audit, never
//! security tokens. The two derived-key outputs are correlated (one key is
//! derived from the other), so the claimed strength is "128-bit width,
//! collision-resistant against random inputs" — NOT a strict 2^-128
//! guarantee and NOT an HMAC.

#[inline(always)]
#[must_use]
pub fn mix(mut x: u64) -> u64 {
    x ^= x >> 30;
    x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
    x ^= x >> 27;
    x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
    x ^= x >> 31;
    x
}

/// Keyed 64-bit hash: 8-byte word folding + rotation + finalization (length
/// prefix included).
// Rust 1.98 clippy suggests `as_chunks::<8>()`, which stabilized in 1.88;
// the workspace MSRV is 1.85. Drop this allow when the MSRV moves past 1.88.
#[allow(unknown_lints, clippy::chunks_exact_to_as_chunks)]
#[must_use]
pub fn hash64(key: u64, data: &[u8]) -> u64 {
    let mut h = key ^ 0x9e37_79b9_7f4a_7c15;
    let mut chunks = data.chunks_exact(8);
    for c in &mut chunks {
        let w = u64::from_le_bytes(c.try_into().unwrap());
        h ^= w.wrapping_mul(0x9e37_79b9_7f4a_7c15);
        h = mix(h.rotate_left(27).wrapping_add(key));
    }
    let rem = chunks.remainder();
    let mut w = data.len() as u64;
    for (i, b) in rem.iter().enumerate() {
        w ^= (*b as u64) << (8 * i);
    }
    h ^= w.wrapping_mul(0xd6e8_feb8_6659_fd93);
    mix(h ^ key.rotate_left(17))
}

/// 128-bit keyed hash = two 64-bit hashes under two derived keys.
#[must_use]
pub fn hash128(key: u64, data: &[u8]) -> u128 {
    let k1 = mix(key ^ 0x243f_6a88_85a3_08d3);
    let k2 = mix(k1 ^ 0x1319_8a2e_0370_7344);
    ((hash64(k1, data) as u128) << 64) | (hash64(k2, data) as u128)
}

/// Low 64 bits of a 128-bit hash (reverse-index key; collisions are rejected
/// by full 128-bit chain verification).
#[inline(always)]
#[must_use]
pub fn low64(h: u128) -> u64 {
    (h & 0xffff_ffff_ffff_ffff) as u64
}

/// Hex representation of a 128-bit hash (stable across JSON boundaries —
/// `u128` cannot be serialized by serde_json).
#[must_use]
pub fn hex128(h: u128) -> String {
    format!("{h:032x}")
}