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