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}