Skip to main content

cachekit_core/
checksum.rs

1//! Standalone xxHash3-64 integrity primitive.
2//!
3//! Non-cryptographic corruption detection, decoupled from compression. The same
4//! function backs `StorageEnvelope`'s embedded checksum, so the wire value is
5//! identical whether you compute it directly or via `ByteStorage::store`.
6
7use xxhash_rust::xxh3::xxh3_64;
8
9/// Compute the xxHash3-64 checksum of `data`, big-endian (xxhash canonical
10/// byte order — the value embedded in every `StorageEnvelope`).
11///
12/// Non-cryptographic: detects corruption, not tampering. For tamper-resistance
13/// use AES-256-GCM (the auth tag), not this checksum. Intentionally unbounded —
14/// a single-pass, allocation-free O(n) hash over caller-materialized bytes; the
15/// `MAX_UNCOMPRESSED_SIZE` cap is `StorageEnvelope`'s decompression-bomb concern.
16pub fn checksum(data: &[u8]) -> [u8; 8] {
17    xxh3_64(data).to_be_bytes()
18}
19
20/// Verify `data` against an expected xxHash3-64 checksum.
21///
22/// Plain (non-constant-time) equality, consistent with the non-cryptographic
23/// threat model — do NOT change to constant-time (would imply a security
24/// property this primitive does not have).
25pub fn verify_checksum(data: &[u8], expected: &[u8; 8]) -> bool {
26    &checksum(data) == expected
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    #[test]
34    fn checksum_is_deterministic() {
35        let data = b"cachekit checksum determinism vector";
36        assert_eq!(checksum(data), checksum(data));
37    }
38
39    #[test]
40    fn checksum_handles_empty_input() {
41        // Pin the canonical xxHash3-64 of the empty input (big-endian); proves
42        // "handles empty" beyond just "doesn't panic", and locks the value.
43        assert_eq!(
44            checksum(b""),
45            [0x2D, 0x06, 0x80, 0x05, 0x38, 0xD3, 0x94, 0xC2]
46        );
47    }
48
49    #[test]
50    fn verify_checksum_accepts_matching() {
51        let data = b"payload bytes";
52        assert!(verify_checksum(data, &checksum(data)));
53    }
54
55    #[test]
56    fn verify_checksum_rejects_single_bit_flip() {
57        let data = b"payload bytes";
58        let mut corrupted = checksum(data);
59        corrupted[0] ^= 0x01;
60        assert!(!verify_checksum(data, &corrupted));
61    }
62
63    #[test]
64    fn verify_checksum_rejects_wrong_data() {
65        let expected = checksum(b"original");
66        assert!(!verify_checksum(b"tampered", &expected));
67    }
68
69    #[test]
70    fn checksum_known_answer_locks_endianness() {
71        // Captured from checksum(b"cachekit-kat"); pins algorithm + big-endian order.
72        assert_eq!(
73            checksum(b"cachekit-kat"),
74            [209u8, 35, 204, 155, 190, 157, 164, 177]
75        );
76    }
77}