krypteia-tessera 0.2.0

Shared pure-Rust hash, XOF, MAC and KDF primitives for the krypteia workspace: SHA-1/2/3, Keccak, SHAKE/cSHAKE, RIPEMD-160, BLAKE2/BLAKE3, HMAC, SP 800-185 (KMAC/TupleHash/ParallelHash), the KDF family (HKDF, PBKDF2, SP 800-108, scrypt, Argon2), MGF1 and CRC-16/32. no_std, zero runtime dependencies.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Cédric Mesnil <cslashm@pm.me>

//! CRC-16 / CRC-32 cyclic redundancy checks (error-detection codes).
//!
//! # ⚠ Not cryptographic
//!
//! A CRC is a **linear error-detection code**, not a hash or a MAC. It is
//! trivially forgeable (an attacker can adjust the message to hit any target
//! CRC) and offers **no** integrity guarantee against tampering. Use it only
//! for detecting *accidental* corruption of **public** data (protocol framing,
//! file/format checksums). For integrity or authentication use a keyed MAC
//! ([`crate::hmac`](mod@crate::hmac), [`crate::sp800_185`] KMAC) or a
//! cryptographic hash.
//!
//! To make misuse a compile error, the CRC types here deliberately do **not**
//! implement the [`Digest`](crate::Digest) / [`Xof`](crate::Xof) traits, so a
//! CRC cannot be fed to `hmac`, `mgf1`, cSHAKE-keyed, or any generic
//! `Digest`-bounded construction.
//!
//! # Not constant-time
//!
//! The table-driven implementation indexes a lookup table with data bytes, so
//! it is **variable-time** by data. This is fine for the public data a CRC is
//! meant for; never compute a CRC over secret material.
//!
//! # Model
//!
//! Parameters follow the Rocksoft^tm model (Williams, "A Painless Guide to CRC
//! Error Detection Algorithms", 1993) as tabulated in the RevEng CRC catalogue:
//! `(poly, init, refin, refout, xorout)`. Each named algorithm additionally
//! records `check` — the CRC of the ASCII string `b"123456789"` — which the
//! test suite asserts, so a mistyped parameter is caught immediately.
//!
//! ```
//! use tessera::crc::{Crc32, CRC32_ISO_HDLC};
//! assert_eq!(Crc32::checksum(&CRC32_ISO_HDLC, b"123456789"), 0xCBF4_3926);
//! ```

/// Generate the `reflect`, table, `Algorithm` and engine for one CRC width.
///
/// `$width` is 16 or 32; `$int` the matching unsigned type. The reflected /
/// normal table recurrences and the `refin`/`refout` handling follow the
/// RevEng reference model.
macro_rules! crc_family {
    ($algo:ident, $engine:ident, $int:ty, $width:expr, $reflect:ident, $table:ident, $doc_algo:literal, $doc_eng:literal) => {
        /// Reflect the low `$width` bits of `v` (bit `i` ↔ bit `$width-1-i`).
        const fn $reflect(mut v: $int) -> $int {
            let mut r: $int = 0;
            let mut i = 0;
            while i < $width {
                r = (r << 1) | (v & 1);
                v >>= 1;
                i += 1;
            }
            r
        }

        /// Build the 256-entry byte-wise CRC table for `poly`, in reflected
        /// (LSB-first) or normal (MSB-first) form, at compile time.
        const fn $table(poly: $int, reflected: bool) -> [$int; 256] {
            let mut table = [0 as $int; 256];
            let mut i = 0usize;
            while i < 256 {
                let mut c: $int;
                if reflected {
                    let rpoly = $reflect(poly);
                    c = i as $int;
                    let mut k = 0;
                    while k < 8 {
                        c = if c & 1 != 0 { (c >> 1) ^ rpoly } else { c >> 1 };
                        k += 1;
                    }
                } else {
                    c = (i as $int) << ($width - 8);
                    let msb: $int = 1 << ($width - 1);
                    let mut k = 0;
                    while k < 8 {
                        c = if c & msb != 0 { (c << 1) ^ poly } else { c << 1 };
                        k += 1;
                    }
                }
                table[i] = c;
                i += 1;
            }
            table
        }

        #[doc = $doc_algo]
        ///
        /// Build a custom variant with [`new`](Self::new); the named `pub const`
        /// definitions in this module cover the usual sets. Not cryptographic —
        /// see the [module docs](self).
        pub struct $algo {
            /// Generator polynomial (Rocksoft normal form, MSB-first).
            pub poly: $int,
            /// Initial register value.
            pub init: $int,
            /// Reflect input bytes (LSB-first) if `true`.
            pub refin: bool,
            /// Reflect the final register if `true`.
            pub refout: bool,
            /// Value XORed into the final register.
            pub xorout: $int,
            /// CRC of `b"123456789"` — the self-check value (RevEng `check`).
            pub check: $int,
            /// Precomputed byte-wise table (const-generated from `poly`/`refin`).
            table: [$int; 256],
        }

        impl $algo {
            /// Define a CRC variant from its Rocksoft parameters. `const`, so the
            /// 256-entry table is generated at compile time.
            ///
            /// - `poly` / `init` / `refin` / `refout` / `xorout`: model params.
            /// - `check`: the expected CRC of `b"123456789"` (asserted in tests).
            pub const fn new(poly: $int, init: $int, refin: bool, refout: bool, xorout: $int, check: $int) -> Self {
                Self {
                    poly,
                    init,
                    refin,
                    refout,
                    xorout,
                    check,
                    table: $table(poly, refin),
                }
            }
        }

        #[doc = $doc_eng]
        ///
        /// `new` → `update`* → `finalize` (or the one-shot [`checksum`](Self::checksum)).
        /// Not cryptographic — see the [module docs](self).
        pub struct $engine<'a> {
            alg: &'a $algo,
            value: $int,
        }

        impl<'a> $engine<'a> {
            /// Start a running CRC under `alg`.
            pub fn new(alg: &'a $algo) -> Self {
                let value = if alg.refin { $reflect(alg.init) } else { alg.init };
                Self { alg, value }
            }

            /// Feed a chunk of input. May be called repeatedly.
            pub fn update(&mut self, data: &[u8]) {
                if self.alg.refin {
                    for &b in data {
                        let idx = ((self.value ^ b as $int) & 0xff) as usize;
                        self.value = (self.value >> 8) ^ self.alg.table[idx];
                    }
                } else {
                    for &b in data {
                        let idx = (((self.value >> ($width - 8)) ^ b as $int) & 0xff) as usize;
                        self.value = (self.value << 8) ^ self.alg.table[idx];
                    }
                }
            }

            /// Consume the state and return the final CRC value.
            pub fn finalize(self) -> $int {
                // The reflected engine keeps the register reflected; reflect
                // again only when refin and refout disagree.
                let v = if self.alg.refin != self.alg.refout {
                    $reflect(self.value)
                } else {
                    self.value
                };
                v ^ self.alg.xorout
            }

            /// One-shot convenience: `new(alg)` then `update(data)` then `finalize`.
            pub fn checksum(alg: &$algo, data: &[u8]) -> $int {
                let mut c = $engine::new(alg);
                c.update(data);
                c.finalize()
            }
        }
    };
}

crc_family!(
    Crc16Algorithm,
    Crc16,
    u16,
    16,
    reflect16,
    crc16_table,
    "A CRC-16 variant (16-bit generator, Rocksoft parameters).",
    "A running CRC-16 checksum over a chosen [`Crc16Algorithm`]."
);
crc_family!(
    Crc32Algorithm,
    Crc32,
    u32,
    32,
    reflect32,
    crc32_table,
    "A CRC-32 variant (32-bit generator, Rocksoft parameters).",
    "A running CRC-32 checksum over a chosen [`Crc32Algorithm`]."
);

// ---------------------------------------------------------------------------
// Usual parameter sets (RevEng CRC catalogue). `check` = CRC of b"123456789".
// ---------------------------------------------------------------------------

/// CRC-32/ISO-HDLC — the ubiquitous "CRC-32" (zlib, gzip, PNG, Ethernet, POSIX).
pub const CRC32_ISO_HDLC: Crc32Algorithm =
    Crc32Algorithm::new(0x04C1_1DB7, 0xFFFF_FFFF, true, true, 0xFFFF_FFFF, 0xCBF4_3926);
/// CRC-32/ISCSI — Castagnoli, "CRC-32C" (iSCSI, SCTP, ext4, Btrfs, hardware CRC).
pub const CRC32_ISCSI: Crc32Algorithm =
    Crc32Algorithm::new(0x1EDC_6F41, 0xFFFF_FFFF, true, true, 0xFFFF_FFFF, 0xE306_9283);
/// CRC-32/BZIP2 — big-endian variant of the ISO-HDLC polynomial (bzip2).
pub const CRC32_BZIP2: Crc32Algorithm =
    Crc32Algorithm::new(0x04C1_1DB7, 0xFFFF_FFFF, false, false, 0xFFFF_FFFF, 0xFC89_1918);
/// CRC-32/MPEG-2 — ISO-HDLC polynomial, no final XOR (MPEG-2 systems).
pub const CRC32_MPEG_2: Crc32Algorithm =
    Crc32Algorithm::new(0x04C1_1DB7, 0xFFFF_FFFF, false, false, 0x0000_0000, 0x0376_E6E7);
/// CRC-32/CKSUM — the POSIX `cksum` polynomial (without the trailing length).
pub const CRC32_CKSUM: Crc32Algorithm =
    Crc32Algorithm::new(0x04C1_1DB7, 0x0000_0000, false, false, 0xFFFF_FFFF, 0x765E_7680);

/// CRC-16/ARC — the classic "CRC-16" / "CRC-IBM" (LHA, many legacy protocols).
pub const CRC16_ARC: Crc16Algorithm = Crc16Algorithm::new(0x8005, 0x0000, true, true, 0x0000, 0xBB3D);
/// CRC-16/MODBUS — Modbus RTU serial framing.
pub const CRC16_MODBUS: Crc16Algorithm = Crc16Algorithm::new(0x8005, 0xFFFF, true, true, 0x0000, 0x4B37);
/// CRC-16/USB — USB data/token packet checks (MODBUS with final XOR).
pub const CRC16_USB: Crc16Algorithm = Crc16Algorithm::new(0x8005, 0xFFFF, true, true, 0xFFFF, 0xB4C8);
/// CRC-16/CCITT-FALSE — the "CCITT" often meant in practice (a.k.a. IBM-3740).
pub const CRC16_CCITT_FALSE: Crc16Algorithm = Crc16Algorithm::new(0x1021, 0xFFFF, false, false, 0x0000, 0x29B1);
/// CRC-16/XMODEM — XMODEM / ZMODEM file transfer.
pub const CRC16_XMODEM: Crc16Algorithm = Crc16Algorithm::new(0x1021, 0x0000, false, false, 0x0000, 0x31C3);
/// CRC-16/KERMIT — the true CCITT reflected variant (Kermit protocol).
pub const CRC16_KERMIT: Crc16Algorithm = Crc16Algorithm::new(0x1021, 0x0000, true, true, 0x0000, 0x2189);

#[cfg(test)]
mod tests {
    use super::*;

    /// Every named variant must reproduce its RevEng `check` over "123456789".
    #[test]
    fn named_variants_self_check() {
        macro_rules! check32 {
            ($($alg:ident),+ $(,)?) => {$(
                assert_eq!(Crc32::checksum(&$alg, b"123456789"), $alg.check, stringify!($alg));
            )+};
        }
        macro_rules! check16 {
            ($($alg:ident),+ $(,)?) => {$(
                assert_eq!(Crc16::checksum(&$alg, b"123456789"), $alg.check, stringify!($alg));
            )+};
        }
        check32!(CRC32_ISO_HDLC, CRC32_ISCSI, CRC32_BZIP2, CRC32_MPEG_2, CRC32_CKSUM);
        check16!(
            CRC16_ARC,
            CRC16_MODBUS,
            CRC16_USB,
            CRC16_CCITT_FALSE,
            CRC16_XMODEM,
            CRC16_KERMIT
        );
    }

    /// Chunked `update` must equal the one-shot checksum (streaming invariance).
    #[test]
    fn streaming_matches_one_shot() {
        let msg = b"123456789";
        let mut c = Crc32::new(&CRC32_ISO_HDLC);
        c.update(&msg[..4]);
        c.update(&msg[4..]);
        assert_eq!(c.finalize(), Crc32::checksum(&CRC32_ISO_HDLC, msg));

        let mut c = Crc16::new(&CRC16_CCITT_FALSE);
        for &b in msg {
            c.update(&[b]);
        }
        assert_eq!(c.finalize(), Crc16::checksum(&CRC16_CCITT_FALSE, msg));
    }
}