Skip to main content

vole_document/
integrity.rs

1//! Integrity primitives.
2//!
3//! Two digests with two distinct roles, per the architecture:
4//!
5//! * **CRC32C (Castagnoli)** guards physical framing and header bytes. It is a
6//!   corruption detector, not a security boundary.
7//! * **SHA-256** is the durable archival identity of the whole reconstructed
8//!   source. Byte comparison remains the court authority during development;
9//!   the digest is the fast, portable receipt.
10
11use sha2::{Digest, Sha256};
12
13/// A streaming SHA-256 hasher that hides the dependency's concrete type.
14pub struct Sha256Hasher {
15    inner: Sha256,
16}
17
18impl Sha256Hasher {
19    /// Start a new hash.
20    pub fn new() -> Self {
21        Sha256Hasher {
22            inner: Sha256::new(),
23        }
24    }
25
26    /// Absorb bytes.
27    pub fn update(&mut self, data: &[u8]) {
28        self.inner.update(data);
29    }
30
31    /// Finish and return the 32-byte digest.
32    pub fn finalize(self) -> [u8; 32] {
33        let out = self.inner.finalize();
34        let mut buf = [0u8; 32];
35        buf.copy_from_slice(&out);
36        buf
37    }
38}
39
40impl Default for Sha256Hasher {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46/// SHA-256 of a byte slice.
47pub fn sha256(data: &[u8]) -> [u8; 32] {
48    let mut h = Sha256Hasher::new();
49    h.update(data);
50    h.finalize()
51}
52
53/// Lower-case hex encoding (no external dependency).
54pub fn to_hex(bytes: &[u8]) -> String {
55    const HEX: &[u8; 16] = b"0123456789abcdef";
56    let mut s = String::with_capacity(bytes.len() * 2);
57    for &b in bytes {
58        s.push(HEX[(b >> 4) as usize] as char);
59        s.push(HEX[(b & 0x0F) as usize] as char);
60    }
61    s
62}
63
64/// CRC-32C (Castagnoli), reflected, init/xorout `0xFFFFFFFF`.
65///
66/// Used only for framing/header corruption detection.
67pub fn crc32c(data: &[u8]) -> u32 {
68    let mut crc = 0xFFFF_FFFFu32;
69    for &b in data {
70        let idx = ((crc ^ b as u32) & 0xFF) as usize;
71        crc = (crc >> 8) ^ CRC32C_TABLE[idx];
72    }
73    !crc
74}
75
76const fn build_crc32c_table() -> [u32; 256] {
77    let mut table = [0u32; 256];
78    let mut i = 0usize;
79    while i < 256 {
80        let mut crc = i as u32;
81        let mut bit = 0;
82        while bit < 8 {
83            if crc & 1 != 0 {
84                crc = (crc >> 1) ^ 0x82F6_3B78;
85            } else {
86                crc >>= 1;
87            }
88            bit += 1;
89        }
90        table[i] = crc;
91        i += 1;
92    }
93    table
94}
95
96static CRC32C_TABLE: [u32; 256] = build_crc32c_table();
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn crc32c_standard_vector() {
104        // The canonical CRC-32C check value for "123456789".
105        assert_eq!(crc32c(b"123456789"), 0xE306_9283);
106        assert_eq!(crc32c(b""), 0x0000_0000);
107    }
108
109    #[test]
110    fn sha256_standard_vectors() {
111        assert_eq!(
112            to_hex(&sha256(b"abc")),
113            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
114        );
115        assert_eq!(
116            to_hex(&sha256(b"")),
117            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
118        );
119    }
120
121    #[test]
122    fn streaming_matches_oneshot() {
123        let data = b"the quick brown fox jumps over the lazy dog";
124        let mut h = Sha256Hasher::new();
125        h.update(&data[..10]);
126        h.update(&data[10..]);
127        assert_eq!(h.finalize(), sha256(data));
128    }
129
130    #[test]
131    fn hex_roundtrip_shape() {
132        assert_eq!(to_hex(&[0x00, 0x0f, 0xff]), "000fff");
133    }
134}