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