Skip to main content

commonware_cryptography/keccak256/
mod.rs

1//! Keccak-256 implementation of the [Hasher] trait.
2//!
3//! Uses the `sha3` crate with Keccak padding, as used by Ethereum.
4//! Keccak-256 and SHA3-256 produce different digests.
5//!
6//! # Example
7//! ```rust
8//! use commonware_cryptography::{Hasher, Keccak256};
9//!
10//! // Hash data in a single shot
11//! let digest = Keccak256::hash(&[b"hello,", b"world!"]);
12//! println!("digest: {:?}", digest);
13//!
14//! // Or stream data incrementally
15//! let mut hasher = Keccak256::default();
16//! hasher.update(b"hello,");
17//! hasher.update(b"world!");
18//! let (_hasher, digest) = hasher.finalize();
19//! println!("digest: {:?}", digest);
20//! ```
21
22use crate::Hasher;
23use bytes::BufMut;
24use commonware_codec::{Buf, Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write};
25use commonware_formatting::Hex;
26use commonware_math::algebra::Random;
27use commonware_parallel::Strategy;
28use commonware_utils::{Array, Span, sequence::FixedBytes};
29use core::{
30    cmp::Ordering,
31    fmt::{Debug, Display},
32    ops::Deref,
33};
34use rand_core::CryptoRng;
35use sha3::Digest as _;
36use zeroize::Zeroize;
37
38/// The underlying Keccak-256 implementation.
39pub type CoreKeccak256 = sha3::Keccak256;
40
41const DIGEST_LENGTH: usize = 32;
42
43/// Keccak-256 hasher.
44#[derive(Debug, Default)]
45pub struct Keccak256 {
46    hasher: CoreKeccak256,
47}
48
49impl Hasher for Keccak256 {
50    type Digest = Digest;
51
52    fn hash(parts: &[&[u8]]) -> Self::Digest {
53        let mut hasher = Self::default();
54        for part in parts {
55            hasher.update(part);
56        }
57        hasher.finalize().1
58    }
59
60    fn hash_pair(
61        left: &[&[u8]],
62        right: &[&[u8]],
63        _strategy: &impl Strategy,
64    ) -> (Self::Digest, Self::Digest) {
65        (Self::hash(left), Self::hash(right))
66    }
67
68    fn update(&mut self, message: &[u8]) -> &mut Self {
69        self.hasher.update(message);
70        self
71    }
72
73    fn finalize(mut self) -> (Self, Self::Digest) {
74        let finalized = self.hasher.finalize_reset();
75        let array: [u8; DIGEST_LENGTH] = finalized.into();
76        (self, Self::Digest::from(array))
77    }
78}
79
80/// Digest of a Keccak-256 hashing operation.
81#[derive(Clone, Copy, Eq, PartialEq, Hash, FixedArray)]
82#[fixed_array(infallible)]
83#[repr(transparent)]
84pub struct Digest(pub [u8; DIGEST_LENGTH]);
85
86impl Ord for Digest {
87    #[inline]
88    fn cmp(&self, other: &Self) -> Ordering {
89        FixedBytes::new(self.0).cmp(&FixedBytes::new(other.0))
90    }
91}
92
93impl PartialOrd for Digest {
94    #[inline]
95    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
96        Some(self.cmp(other))
97    }
98}
99
100#[cfg(feature = "arbitrary")]
101impl<'a> arbitrary::Arbitrary<'a> for Digest {
102    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
103        let len = u.int_in_range(0..=256)?;
104        let data = u.bytes(len)?;
105        Ok(Keccak256::hash(&[data]))
106    }
107}
108
109impl Write for Digest {
110    fn write(&self, buf: &mut impl BufMut) {
111        self.0.write(buf);
112    }
113}
114
115impl Read for Digest {
116    type Cfg = ();
117
118    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
119        let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
120        Ok(Self(array))
121    }
122}
123
124impl FixedSize for Digest {
125    const SIZE: usize = DIGEST_LENGTH;
126}
127
128impl Span for Digest {}
129
130impl Array for Digest {}
131
132impl AsRef<[u8]> for Digest {
133    fn as_ref(&self) -> &[u8] {
134        &self.0
135    }
136}
137
138impl Deref for Digest {
139    type Target = [u8];
140    fn deref(&self) -> &[u8] {
141        &self.0
142    }
143}
144
145impl Debug for Digest {
146    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
147        write!(f, "{}", Hex(&self.0))
148    }
149}
150
151impl Display for Digest {
152    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
153        write!(f, "{}", Hex(&self.0))
154    }
155}
156
157impl crate::Digest for Digest {
158    const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
159}
160
161impl Random for Digest {
162    fn random(mut rng: impl CryptoRng) -> Self {
163        let mut array = [0u8; DIGEST_LENGTH];
164        rng.fill_bytes(&mut array);
165        Self(array)
166    }
167}
168
169impl Zeroize for Digest {
170    fn zeroize(&mut self) {
171        self.0.zeroize();
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use commonware_codec::{Copying, DecodeExt, Encode};
179    use commonware_formatting::hex;
180    use commonware_parallel::Sequential;
181
182    const EMPTY_DIGEST: [u8; DIGEST_LENGTH] =
183        hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
184    const ABC_DIGEST: [u8; DIGEST_LENGTH] =
185        hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45");
186
187    #[test]
188    fn test_known_answers_and_reset() {
189        assert_eq!(Keccak256::hash(&[]).as_ref(), EMPTY_DIGEST);
190        assert_eq!(Keccak256::hash(&[b"abc"]).as_ref(), ABC_DIGEST);
191        assert_eq!(Keccak256::hash(&[b"a", b"", b"bc"]).as_ref(), ABC_DIGEST);
192        assert_eq!(
193            Keccak256::hash_pair(&[], &[b"a", b"bc"], &Sequential),
194            (Digest(EMPTY_DIGEST), Digest(ABC_DIGEST))
195        );
196
197        let mut hasher = Keccak256::default();
198        for message in [b"abc".as_slice(), b"", b"abc", b""] {
199            for byte in message {
200                hasher.update(core::slice::from_ref(byte));
201            }
202            let (reset, digest) = hasher.finalize();
203            let expected = if message.is_empty() {
204                EMPTY_DIGEST
205            } else {
206                ABC_DIGEST
207            };
208            assert_eq!(digest.as_ref(), expected);
209            hasher = reset;
210        }
211    }
212
213    #[test]
214    fn test_rate_boundaries() {
215        for len in [0, 1, 135, 136, 137, 271, 272, 273, 1024] {
216            let data: Vec<u8> = (0..len).map(|i| i as u8).collect();
217            let expected: [u8; DIGEST_LENGTH] = CoreKeccak256::digest(&data).into();
218            for split in 0..=len {
219                let parts = [&data[..split], &[][..], &data[split..]];
220                assert_eq!(Keccak256::hash(&parts).as_ref(), expected);
221                crate::fuzz::Plan::<Keccak256>::new(
222                    parts.iter().map(|p| p.to_vec()).collect(),
223                    vec![b"abc".to_vec()],
224                )
225                .run();
226            }
227        }
228    }
229
230    #[test]
231    fn test_codec_and_zeroize() {
232        let mut digest = Keccak256::hash(&[b"abc"]);
233        let encoded = digest.encode();
234        assert_eq!(Digest::SIZE, DIGEST_LENGTH);
235        assert_eq!(encoded.as_ref(), ABC_DIGEST);
236        assert_eq!(Digest::decode(encoded).unwrap(), digest);
237        assert!(Digest::decode(Copying(&ABC_DIGEST[..31])).is_err());
238        digest.zeroize();
239        assert_eq!(digest, <Digest as crate::Digest>::EMPTY);
240    }
241
242    #[test]
243    fn test_digest_ord() {
244        let a = Digest([0; DIGEST_LENGTH]);
245        let mut b = a;
246        b.0[DIGEST_LENGTH - 1] = 1;
247
248        // The first byte decides even when the remaining bytes order the other way.
249        let mut c = a;
250        c.0[0] = 0x7f;
251        c.0[1..].fill(0xff);
252        let mut d = a;
253        d.0[0] = 0x80;
254        for (a, b, expected) in [
255            (a, a, Ordering::Equal),
256            (a, b, Ordering::Less),
257            (b, a, Ordering::Greater),
258            (c, d, Ordering::Less),
259            (d, c, Ordering::Greater),
260        ] {
261            assert_eq!(a.cmp(&b), expected);
262            assert_eq!(a.partial_cmp(&b), Some(expected));
263        }
264    }
265
266    #[cfg(feature = "arbitrary")]
267    mod conformance {
268        use super::*;
269        use commonware_codec::conformance::CodecConformance;
270
271        commonware_conformance::conformance_tests! {
272            CodecConformance<Digest>,
273        }
274    }
275}