openzeppelin_crypto/
keccak.rs

1//! An interface to the default hashing algorithm used in this library's [merkle
2//! proofs][crate].
3use tiny_keccak::{Hasher as TinyHasher, Keccak};
4
5use crate::hash::{BuildHasher, Hash, Hasher};
6
7/// The default [`Hasher`] builder used in this library's [merkle
8/// proofs][crate].
9///
10/// It instantiates a [`Keccak256`] hasher.
11pub struct KeccakBuilder;
12
13impl BuildHasher for KeccakBuilder {
14    type Hasher = Keccak256;
15
16    #[inline]
17    fn build_hasher(&self) -> Self::Hasher {
18        Keccak256(Keccak::v256())
19    }
20}
21
22/// The default [`Hasher`] used in this library's [merkle proofs][crate].
23///
24/// The underlying implementation is guaranteed to match that of the
25/// `keccak256` algorithm, commonly used in Ethereum.
26pub struct Keccak256(Keccak);
27
28impl Hasher for Keccak256 {
29    type Output = [u8; 32];
30
31    fn update(&mut self, input: impl AsRef<[u8]>) {
32        self.0.update(input.as_ref());
33    }
34
35    fn finalize(self) -> Self::Output {
36        let mut buffer = [0u8; 32];
37        self.0.finalize(&mut buffer);
38        buffer
39    }
40}
41
42impl Hash for [u8; 32] {
43    #[inline]
44    fn hash<H: Hasher>(&self, state: &mut H) {
45        state.update(self);
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use proptest::prelude::*;
52
53    use super::*;
54    use crate::test_helpers::non_empty_u8_vec_strategy;
55
56    #[test]
57    fn single_bit_change_affects_output() {
58        proptest!(|(data in non_empty_u8_vec_strategy())| {
59            let mut modified = data.clone();
60            modified[0] ^= 1;
61
62            let mut hasher1 = KeccakBuilder.build_hasher();
63            let mut hasher2 = KeccakBuilder.build_hasher();
64            hasher1.update(&data);
65            hasher2.update(&modified);
66
67            prop_assert_ne!(hasher1.finalize(), hasher2.finalize());
68        });
69    }
70
71    #[test]
72    fn sequential_updates_match_concatenated() {
73        proptest!(|(data1: Vec<u8>, data2: Vec<u8>)| {
74            let builder = KeccakBuilder;
75
76            let mut hasher1 = builder.build_hasher();
77            hasher1.update(&data1);
78            hasher1.update(&data2);
79            let result1 = hasher1.finalize();
80
81            let mut hasher2 = builder.build_hasher();
82            let mut concatenated = data1.clone();
83            concatenated.extend_from_slice(&data2);
84            hasher2.update(concatenated);
85            let result2 = hasher2.finalize();
86
87            prop_assert_eq!(result1, result2);
88        });
89    }
90
91    #[test]
92    fn split_updates_match_full_update() {
93        proptest!(|(data in non_empty_u8_vec_strategy(), split_point: usize)| {
94            let builder = KeccakBuilder;
95            let split_at = split_point % data.len();
96
97            let mut hasher1 = builder.build_hasher();
98            hasher1.update(&data[..split_at]);
99            hasher1.update(&data[split_at..]);
100            let result1 = hasher1.finalize();
101
102            let mut hasher2 = builder.build_hasher();
103            hasher2.update(&data);
104            let result2 = hasher2.finalize();
105
106            prop_assert_eq!(result1, result2);
107        });
108    }
109
110    #[test]
111    fn multiple_hasher_instances_are_consistent() {
112        proptest!(|(data1: Vec<u8>, data2: Vec<u8>)| {
113            let builder = KeccakBuilder;
114
115            let mut hasher1 = builder.build_hasher();
116            hasher1.update(&data1);
117            hasher1.update(&data2);
118            let result1 = hasher1.finalize();
119
120            let mut hasher2 = builder.build_hasher();
121            hasher2.update(&data1);
122            hasher2.update(&data2);
123            let result2 = hasher2.finalize();
124
125            prop_assert_eq!(result1, result2);
126        });
127    }
128
129    #[test]
130    fn output_is_always_32_bytes() {
131        proptest!(|(data: Vec<u8>)| {
132            let builder = KeccakBuilder;
133            let mut hasher = builder.build_hasher();
134            hasher.update(&data);
135            let result = hasher.finalize();
136            assert_eq!(result.len(), 32);
137        });
138    }
139
140    #[test]
141    fn update_order_dependence() {
142        proptest!(|(data1 in non_empty_u8_vec_strategy(),
143                    data2 in non_empty_u8_vec_strategy())| {
144            prop_assume!(data1 != data2);
145
146            let mut hasher1 = KeccakBuilder.build_hasher();
147            hasher1.update(&data1);
148            hasher1.update(&data2);
149
150            let mut hasher2 = KeccakBuilder.build_hasher();
151            hasher2.update(&data2);
152            hasher2.update(&data1);
153
154            prop_assert_ne!(hasher1.finalize(), hasher2.finalize());
155        });
156    }
157
158    #[test]
159    fn empty_input_order_independence() {
160        proptest!(|(data in non_empty_u8_vec_strategy())| {
161            let empty = vec![];
162
163            let mut hasher1 = KeccakBuilder.build_hasher();
164            hasher1.update(&data);
165            hasher1.update(&empty);
166
167            let mut hasher2 = KeccakBuilder.build_hasher();
168            hasher2.update(&empty);
169            hasher2.update(&data);
170
171            prop_assert_eq!(hasher1.finalize(), hasher2.finalize());
172        });
173    }
174
175    #[test]
176    fn trailing_zero_affects_output() {
177        proptest!(|(data: Vec<u8>)| {
178            let mut hasher1 = KeccakBuilder.build_hasher();
179            hasher1.update(&data);
180
181            let mut padded = data.clone();
182            padded.push(0);
183
184            let mut hasher2 = KeccakBuilder.build_hasher();
185            hasher2.update(&padded);
186
187            prop_assert_ne!(hasher1.finalize(), hasher2.finalize());
188        });
189    }
190
191    #[test]
192    fn leading_zeros_affect_output() {
193        proptest!(|(data in non_empty_u8_vec_strategy())| {
194            let mut hasher1 = KeccakBuilder.build_hasher();
195            hasher1.update(&data);
196            let hash1 = hasher1.finalize();
197
198            let mut padded = vec![0u8; 32];
199            padded.extend(data.iter());
200
201            let mut hasher2 = KeccakBuilder.build_hasher();
202            hasher2.update(&padded);
203            let hash2 = hasher2.finalize();
204
205            prop_assert_ne!(hash1, hash2);
206        });
207    }
208
209    #[test]
210    fn no_trivial_collisions_same_length() {
211        proptest!(|(data in non_empty_u8_vec_strategy())| {
212            let mut hasher1 = KeccakBuilder.build_hasher();
213            hasher1.update(&data);
214
215            let mut modified = data.clone();
216            modified[data.len() - 1] = modified[data.len() - 1].wrapping_add(1);
217
218            let mut hasher2 = KeccakBuilder.build_hasher();
219            hasher2.update(&modified);
220
221            prop_assert_ne!(hasher1.finalize(), hasher2.finalize());
222        });
223    }
224
225    #[test]
226    fn length_extension_attack_resistance() {
227        proptest!(|(data1 in non_empty_u8_vec_strategy(), data2 in non_empty_u8_vec_strategy())| {
228            let mut hasher1 = KeccakBuilder.build_hasher();
229            hasher1.update(&data1);
230            let hash1 = hasher1.finalize();
231
232            let mut hasher2 = KeccakBuilder.build_hasher();
233            hasher2.update(&data1);
234            hasher2.update(&data2);
235            let hash2 = hasher2.finalize();
236
237            let mut hasher3 = KeccakBuilder.build_hasher();
238            hasher3.update(hash1);
239            hasher3.update(&data2);
240            let hash3 = hasher3.finalize();
241
242            prop_assert_ne!(hash2, hash3);
243        });
244    }
245
246    #[test]
247    fn empty_input() {
248        let builder = KeccakBuilder;
249        let mut hasher = builder.build_hasher();
250        hasher.update([]);
251        let result = hasher.finalize();
252        let expected: [u8; 32] = [
253            0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d,
254            0xb2, 0xdc, 0xc7, 0x03, 0xc0, 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82,
255            0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70,
256        ];
257        assert_eq!(result, expected);
258    }
259
260    #[test]
261    fn known_hash() {
262        let builder = KeccakBuilder;
263        let mut hasher = builder.build_hasher();
264        hasher.update(b"hello");
265        let result = hasher.finalize();
266        let expected: [u8; 32] = [
267            0x1c, 0x8a, 0xff, 0x95, 0x06, 0x85, 0xc2, 0xed, 0x4b, 0xc3, 0x17,
268            0x4f, 0x34, 0x72, 0x28, 0x7b, 0x56, 0xd9, 0x51, 0x7b, 0x9c, 0x94,
269            0x81, 0x27, 0x31, 0x9a, 0x09, 0xa7, 0xa3, 0x6d, 0xea, 0xc8,
270        ];
271        assert_eq!(result, expected);
272    }
273}