Skip to main content

commonware_cryptography/crc32/
mod.rs

1//! CRC32C implementation of the `Hasher` trait.
2//!
3//! This implementation uses the `crc-fast` crate to generate CRC32C (iSCSI/Castagnoli)
4//! checksums as specified in RFC 3720. CRC32C uses polynomial 0x1EDC6F41.
5//!
6//! # Warning
7//!
8//! CRC32 is not a cryptographic hash function. It is designed for error
9//! detection, not security. Use SHA-256 or Blake3 for cryptographic purposes.
10//!
11//! # Example
12//!
13//! ```rust
14//! use commonware_cryptography::{Hasher, Crc32};
15//!
16//! // One-shot checksum (returns u32 directly)
17//! let checksum: u32 = Crc32::checksum(b"hello world");
18//!
19//! // Using the Hasher trait
20//! let mut hasher = Crc32::new();
21//! hasher.update(b"hello ");
22//! hasher.update(b"world");
23//! let digest = hasher.finalize();
24//!
25//! // Convert digest to u32
26//! assert_eq!(digest.as_u32(), checksum);
27//! ```
28
29use crate::Hasher;
30use bytes::{Buf, BufMut};
31use commonware_codec::{Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write};
32use commonware_formatting::Hex;
33use commonware_math::algebra::Random;
34use commonware_utils::{Array, Span};
35use core::{
36    fmt::{Debug, Display},
37    ops::Deref,
38};
39use rand_core::CryptoRng;
40
41/// Size of a CRC32 checksum in bytes.
42const SIZE: usize = 4;
43
44/// The CRC32 algorithm used (CRC32C/iSCSI/Castagnoli).
45const ALGORITHM: crc_fast::CrcAlgorithm = crc_fast::CrcAlgorithm::Crc32Iscsi;
46
47/// CRC32C hasher.
48///
49/// Uses the iSCSI polynomial (0x1EDC6F41) as specified in RFC 3720.
50#[derive(Debug)]
51pub struct Crc32 {
52    inner: crc_fast::Digest,
53}
54
55impl Default for Crc32 {
56    fn default() -> Self {
57        Self {
58            inner: crc_fast::Digest::new(ALGORITHM),
59        }
60    }
61}
62
63impl Clone for Crc32 {
64    fn clone(&self) -> Self {
65        // We manually implement `Clone` to avoid cloning the hasher state.
66        Self::default()
67    }
68}
69
70impl Crc32 {
71    /// Compute a CRC32 checksum of the given data (one-shot).
72    ///
73    /// Returns the checksum as a `u32` directly.
74    #[inline]
75    pub fn checksum(data: &[u8]) -> u32 {
76        crc_fast::checksum(ALGORITHM, data) as u32
77    }
78}
79
80impl Hasher for Crc32 {
81    type Digest = Digest;
82
83    fn update(&mut self, message: &[u8]) -> &mut Self {
84        self.inner.update(message);
85        self
86    }
87
88    fn finalize(&mut self) -> Self::Digest {
89        Self::Digest::from(self.inner.finalize_reset() as u32)
90    }
91
92    fn reset(&mut self) -> &mut Self {
93        self.inner = crc_fast::Digest::new(ALGORITHM);
94        self
95    }
96}
97
98/// Digest of a CRC32 hashing operation (4 bytes).
99#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, FixedArray)]
100#[fixed_array(infallible)]
101#[repr(transparent)]
102pub struct Digest(pub [u8; SIZE]);
103
104#[cfg(feature = "arbitrary")]
105impl<'a> arbitrary::Arbitrary<'a> for Digest {
106    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
107        // Generate random bytes and compute their CRC32 checksum
108        let len = u.int_in_range(0..=256)?;
109        let data = u.bytes(len)?;
110        Ok(Crc32::hash(data))
111    }
112}
113
114impl Digest {
115    /// Get the digest as a `u32` value.
116    #[inline]
117    pub const fn as_u32(&self) -> u32 {
118        u32::from_be_bytes(self.0)
119    }
120}
121
122impl Write for Digest {
123    fn write(&self, buf: &mut impl BufMut) {
124        self.0.write(buf);
125    }
126}
127
128impl Read for Digest {
129    type Cfg = ();
130
131    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
132        let array = <[u8; SIZE]>::read(buf)?;
133        Ok(Self(array))
134    }
135}
136
137impl FixedSize for Digest {
138    const SIZE: usize = SIZE;
139}
140
141impl Span for Digest {}
142
143impl Array for Digest {}
144
145impl From<u32> for Digest {
146    fn from(value: u32) -> Self {
147        Self(value.to_be_bytes())
148    }
149}
150
151impl AsRef<[u8]> for Digest {
152    fn as_ref(&self) -> &[u8] {
153        &self.0
154    }
155}
156
157impl Deref for Digest {
158    type Target = [u8];
159    fn deref(&self) -> &[u8] {
160        &self.0
161    }
162}
163
164impl Debug for Digest {
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        write!(f, "{}", Hex(&self.0))
167    }
168}
169
170impl Display for Digest {
171    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
172        write!(f, "{}", Hex(&self.0))
173    }
174}
175
176impl crate::Digest for Digest {
177    const EMPTY: Self = Self([0u8; SIZE]);
178}
179
180impl Random for Digest {
181    fn random(mut rng: impl CryptoRng) -> Self {
182        let mut array = [0u8; SIZE];
183        rng.fill_bytes(&mut array);
184        Self(array)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::Hasher;
192    use commonware_codec::{DecodeExt, Encode};
193    use crc::{Crc, CRC_32_ISCSI};
194
195    /// Reference CRC32C implementation from the [`crc`](https://crates.io/crates/crc) crate.
196    const CRC32C_REF: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
197
198    /// Verify checksum against both the reference `crc` crate and our implementation.
199    fn verify(data: &[u8], expected: u32) {
200        assert_eq!(CRC32C_REF.checksum(data), expected);
201        assert_eq!(Crc32::checksum(data), expected);
202    }
203
204    /// Generate deterministic test data: sequential bytes wrapping at 256.
205    fn sequential_data(len: usize) -> Vec<u8> {
206        (0..len).map(|i| (i & 0xFF) as u8).collect()
207    }
208
209    /// Test vectors from RFC 3720 Appendix B.4 "CRC Examples".
210    /// https://datatracker.ietf.org/doc/html/rfc3720#appendix-B.4
211    #[test]
212    fn rfc3720_test_vectors() {
213        // 32 bytes of zeros -> CRC = aa 36 91 8a
214        verify(&[0x00; 32], 0x8A9136AA);
215
216        // 32 bytes of 0xFF -> CRC = 43 ab a8 62
217        verify(&[0xFF; 32], 0x62A8AB43);
218
219        // 32 bytes ascending (0x00..0x1F) -> CRC = 4e 79 dd 46
220        let ascending: Vec<u8> = (0x00..0x20).collect();
221        verify(&ascending, 0x46DD794E);
222
223        // 32 bytes descending (0x1F..0x00) -> CRC = 5c db 3f 11
224        let descending: Vec<u8> = (0x00..0x20).rev().collect();
225        verify(&descending, 0x113FDB5C);
226
227        // iSCSI SCSI Read (10) Command PDU -> CRC = 56 3a 96 d9
228        let iscsi_read_pdu: [u8; 48] = [
229            0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
230            0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x14,
231            0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
232            0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
233        ];
234        verify(&iscsi_read_pdu, 0xD9963A56);
235    }
236
237    /// Additional test vectors from external sources.
238    /// https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat.crc-32c
239    /// https://github.com/ICRAR/crc32c/blob/master/test/test_crc32c.py
240    /// https://github.com/google/leveldb/blob/main/util/crc32c_test.cc
241    #[test]
242    fn external_test_vectors() {
243        // CRC catalogue test vector
244        verify(b"", 0x00000000);
245        verify(b"123456789", 0xE3069283);
246
247        // ICRAR test vectors
248        verify(b"23456789", 0xBFE92A83);
249        verify(b"The quick brown fox jumps over the lazy dog", 0x22620404);
250
251        // LevelDB test vector: sequential 0x01-0xF0 (240 bytes)
252        let sequential_240: Vec<u8> = (0x01..=0xF0).collect();
253        verify(&sequential_240, 0x24C5D375);
254    }
255
256    /// SIMD boundary tests.
257    ///
258    /// SIMD implementations (PCLMULQDQ, ARM CRC) have different code paths
259    /// based on input size. These tests verify correctness at critical boundaries.
260    #[test]
261    fn simd_boundaries() {
262        // Critical sizes where SIMD implementations change code paths:
263        // - 16: single 128-bit register
264        // - 32: two 128-bit registers / one 256-bit register
265        // - 64: fold-by-4 block size
266        // - 128: large data threshold
267        // - 256, 512, 1024: power-of-2 boundaries
268        // - 4096: page boundary (common in storage)
269        const BOUNDARY_SIZES: &[usize] = &[
270            0, 1, 2, 3, 4, 7, 8, 9, // Small sizes
271            15, 16, 17, // 128-bit boundary
272            31, 32, 33, // 256-bit boundary
273            63, 64, 65, // Fold-by-4 boundary
274            127, 128, 129, // Large threshold
275            255, 256, 257, // 256-byte boundary
276            511, 512, 513, // 512-byte boundary
277            1023, 1024, 1025, // 1KB boundary
278            4095, 4096, 4097, // Page boundary
279        ];
280
281        // Pre-computed expected values for sequential data pattern.
282        // Generated with the [`crc`](https://crates.io/crates/crc) crate.
283        const EXPECTED: &[(usize, u32)] = &[
284            (0, 0x00000000),
285            (1, 0x527D5351),
286            (2, 0x030AF4D1),
287            (3, 0x92FD4BFA),
288            (4, 0xD9331AA3),
289            (7, 0xA359ED4C),
290            (8, 0x8A2CBC3B),
291            (9, 0x7144C5A8),
292            (15, 0x68EF03F6),
293            (16, 0xD9C908EB),
294            (17, 0x38435E17),
295            (31, 0xE95CABCB),
296            (32, 0x46DD794E), // Matches RFC 3720
297            (33, 0x9F85A26D),
298            (63, 0x7A873004),
299            (64, 0xFB6D36EB),
300            (65, 0x694420FA),
301            (127, 0x6C31BD0C),
302            (128, 0x30D9C515),
303            (129, 0xF514629F),
304            (255, 0x8953C482),
305            (256, 0x9C44184B),
306            (257, 0x8A13A1CE),
307            (511, 0x35348950),
308            (512, 0xAE10EE5A),
309            (513, 0x6814B154),
310            (1023, 0x0C8F24D0),
311            (1024, 0x2CDF6E8F),
312            (1025, 0x8EB48B63),
313            (4095, 0xBCB5BD82),
314            (4096, 0x9C71FE32),
315            (4097, 0x83391BE9),
316        ];
317
318        assert_eq!(
319            BOUNDARY_SIZES,
320            EXPECTED.iter().map(|(size, _)| *size).collect::<Vec<_>>()
321        );
322
323        for &(size, expected) in EXPECTED {
324            let data = sequential_data(size);
325            verify(&data, expected);
326        }
327    }
328
329    /// Verify incremental hashing produces the same result regardless of chunk size.
330    #[test]
331    fn chunk_size_independence() {
332        let data = sequential_data(1024);
333        let expected = CRC32C_REF.checksum(&data);
334
335        // Test chunk sizes from 1 to 64 bytes
336        for chunk_size in 1..=64 {
337            let mut hasher = Crc32::new();
338            for chunk in data.chunks(chunk_size) {
339                hasher.update(chunk);
340            }
341            assert_eq!(hasher.finalize().as_u32(), expected);
342        }
343    }
344
345    /// Test with unaligned data by processing at different offsets within a buffer.
346    #[test]
347    fn alignment_independence() {
348        // Create a larger buffer and test CRC of a fixed-size window at different offsets
349        let base_data: Vec<u8> = (0..256).map(|i| i as u8).collect();
350        let test_len = 64;
351
352        // Get reference CRC for the first 64 bytes
353        let reference = CRC32C_REF.checksum(&base_data[..test_len]);
354
355        // Verify the same 64-byte pattern produces the same CRC regardless of where
356        // it appears in the source buffer (tests alignment handling)
357        for offset in 0..16 {
358            let data = &base_data[offset..offset + test_len];
359            let expected = CRC32C_REF.checksum(data);
360            assert_eq!(Crc32::checksum(data), expected);
361        }
362
363        // Also verify that the first 64 bytes always produce the reference CRC
364        verify(&base_data[..test_len], reference);
365    }
366
367    #[test]
368    fn test_crc32_hasher_trait() {
369        let msg = b"hello world";
370
371        // Generate initial hash using Hasher trait
372        let mut hasher = Crc32::new();
373        hasher.update(msg);
374        let digest = hasher.finalize();
375        assert!(Digest::decode(digest.as_ref()).is_ok());
376
377        // Verify against reference
378        let expected = CRC32C_REF.checksum(msg);
379        assert_eq!(digest.as_u32(), expected);
380
381        // Reuse hasher (should auto-reset after finalize)
382        hasher.update(msg);
383        let digest2 = hasher.finalize();
384        assert_eq!(digest, digest2);
385
386        // Test Hasher::hash convenience method
387        let hash = Crc32::hash(msg);
388        assert_eq!(hash.as_u32(), expected);
389    }
390
391    #[test]
392    fn test_crc32_len() {
393        assert_eq!(Digest::SIZE, SIZE);
394        assert_eq!(SIZE, 4);
395    }
396
397    #[test]
398    fn test_codec() {
399        let msg = b"hello world";
400        let mut hasher = Crc32::new();
401        hasher.update(msg);
402        let digest = hasher.finalize();
403
404        let encoded = digest.encode();
405        assert_eq!(encoded.len(), SIZE);
406        assert_eq!(encoded, digest.as_ref());
407
408        let decoded = Digest::decode(encoded).unwrap();
409        assert_eq!(digest, decoded);
410    }
411
412    #[test]
413    fn test_digest_from_u32() {
414        let value: u32 = 0xDEADBEEF;
415        let digest = Digest::from(value);
416        assert_eq!(digest.as_u32(), value);
417        assert_eq!(digest.0, [0xDE, 0xAD, 0xBE, 0xEF]);
418    }
419
420    #[test]
421    fn test_checksum_returns_u32() {
422        // Verify the one-shot checksum returns u32 directly
423        let checksum: u32 = Crc32::checksum(b"test");
424        let expected = CRC32C_REF.checksum(b"test");
425        assert_eq!(checksum, expected);
426    }
427
428    #[cfg(feature = "arbitrary")]
429    mod conformance {
430        use super::*;
431        use commonware_codec::conformance::CodecConformance;
432
433        commonware_conformance::conformance_tests! {
434            CodecConformance<Digest>,
435        }
436    }
437}