commonware_cryptography/keccak256/
mod.rs1use 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
38pub type CoreKeccak256 = sha3::Keccak256;
40
41const DIGEST_LENGTH: usize = 32;
42
43#[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#[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 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}