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