commonware_cryptography/sha256/
mod.rs1use crate::Hasher;
22#[cfg(not(feature = "std"))]
23use alloc::vec;
24use bytes::{Buf, BufMut};
25use commonware_codec::{
26 DecodeExt, Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write,
27};
28use commonware_formatting::Hex;
29use commonware_math::algebra::Random;
30use commonware_utils::{Array, Span};
31use core::{
32 fmt::{Debug, Display},
33 ops::Deref,
34};
35use rand_core::CryptoRng;
36use sha2::{Digest as _, Sha256 as ISha256, block_api::compress256};
37use zeroize::Zeroize;
38
39#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
40mod simd;
41
42pub type CoreSha256 = ISha256;
44
45const DIGEST_LENGTH: usize = 32;
46
47const BLOCK_LENGTH: usize = 64;
49
50const MAX_FIXED: usize = 2 * BLOCK_LENGTH - 9;
57
58const IV: [u32; 8] = [
60 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
61];
62
63#[inline]
65fn digest_from_state(state: [u32; 8]) -> [u8; DIGEST_LENGTH] {
66 let mut out = [0u8; DIGEST_LENGTH];
67 for (chunk, word) in out.as_chunks_mut::<4>().0.iter_mut().zip(state) {
68 *chunk = word.to_be_bytes();
69 }
70 out
71}
72
73#[inline]
79fn finalize_fixed_fresh(scratch: &mut [u8; 2 * BLOCK_LENGTH], len: usize) -> [u8; DIGEST_LENGTH] {
80 assert!(len <= MAX_FIXED);
81 let bit_len = ((len as u64) * 8).to_be_bytes();
82 scratch[len] = 0x80;
83 let mut state = IV;
84 if len < BLOCK_LENGTH - 8 {
85 scratch[BLOCK_LENGTH - 8..BLOCK_LENGTH].copy_from_slice(&bit_len);
87 let (blocks, _) = scratch[..BLOCK_LENGTH].as_chunks::<BLOCK_LENGTH>();
88 compress256(&mut state, blocks);
89 } else {
90 scratch[2 * BLOCK_LENGTH - 8..].copy_from_slice(&bit_len);
92 let (blocks, _) = scratch.as_chunks::<BLOCK_LENGTH>();
93 compress256(&mut state, blocks);
94 }
95 digest_from_state(state)
96}
97
98#[inline(always)]
102fn hash_specialized(parts: &[&[u8]]) -> Digest {
103 match parts {
104 [p, l, r] if p.len() == 8 && l.len() == 32 && r.len() == 32 => {
105 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
106 scratch[..8].copy_from_slice(p);
107 scratch[8..40].copy_from_slice(l);
108 scratch[40..72].copy_from_slice(r);
109 Digest(finalize_fixed_fresh(&mut scratch, 72))
110 }
111 [a, b] if a.len() == 32 && b.len() == 32 => {
112 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
113 scratch[..32].copy_from_slice(a);
114 scratch[32..64].copy_from_slice(b);
115 Digest(finalize_fixed_fresh(&mut scratch, 64))
116 }
117 [p, d] if p.len() == 8 && d.len() == 32 => {
118 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
119 scratch[..8].copy_from_slice(p);
120 scratch[8..40].copy_from_slice(d);
121 Digest(finalize_fixed_fresh(&mut scratch, 40))
122 }
123 [p, d] if p.len() == 4 && d.len() == 32 => {
124 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
125 scratch[..4].copy_from_slice(p);
126 scratch[4..36].copy_from_slice(d);
127 Digest(finalize_fixed_fresh(&mut scratch, 36))
128 }
129 _ => hash_general(parts),
130 }
131}
132
133#[inline(never)]
137fn hash_general(parts: &[&[u8]]) -> Digest {
138 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
139 let mut len = 0usize;
140 let mut parts = parts.iter();
141 loop {
142 match parts.next() {
143 Some(part) if len + part.len() <= MAX_FIXED => {
144 scratch[len..len + part.len()].copy_from_slice(part);
145 len += part.len();
146 }
147 Some(part) => {
148 let mut hasher = ISha256::new();
149 hasher.update(&scratch[..len]);
150 hasher.update(part);
151 for part in parts {
152 hasher.update(part);
153 }
154 let array: [u8; DIGEST_LENGTH] = hasher.finalize().into();
155 return Digest(array);
156 }
157 None => break,
158 }
159 }
160 Digest(finalize_fixed_fresh(&mut scratch, len))
161}
162
163#[derive(Debug, Default)]
165pub struct Sha256 {
166 hasher: ISha256,
167}
168
169impl Sha256 {
170 pub fn fill(b: u8) -> <Self as Hasher>::Digest {
173 <Self as Hasher>::Digest::decode(vec![b; DIGEST_LENGTH].as_ref()).unwrap()
174 }
175}
176
177impl Hasher for Sha256 {
178 type Digest = Digest;
179
180 #[inline]
181 fn hash(parts: &[&[u8]]) -> Self::Digest {
182 hash_specialized(parts)
183 }
184
185 #[inline]
186 fn hash_pair(left: &[&[u8]], right: &[&[u8]]) -> (Self::Digest, Self::Digest) {
187 #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
188 if let Some(pair) = simd::hash_pair(left, right) {
189 return pair;
190 }
191 (Self::hash(left), Self::hash(right))
192 }
193
194 #[inline]
195 fn update(&mut self, message: &[u8]) -> &mut Self {
196 self.hasher.update(message);
197 self
198 }
199
200 #[inline]
201 fn finalize(mut self) -> (Self, Self::Digest) {
202 let finalized = self.hasher.finalize_reset();
203 let array: [u8; DIGEST_LENGTH] = finalized.into();
204 (self, Digest(array))
205 }
206}
207
208#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, FixedArray)]
210#[fixed_array(infallible)]
211#[repr(transparent)]
212pub struct Digest(pub [u8; DIGEST_LENGTH]);
213
214#[cfg(feature = "arbitrary")]
215impl<'a> arbitrary::Arbitrary<'a> for Digest {
216 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
217 let len = u.int_in_range(0..=256)?;
219 let data = u.bytes(len)?;
220 Ok(Sha256::hash(&[data]))
221 }
222}
223
224impl Write for Digest {
225 fn write(&self, buf: &mut impl BufMut) {
226 self.0.write(buf);
227 }
228}
229
230impl Read for Digest {
231 type Cfg = ();
232
233 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
234 let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
235 Ok(Self(array))
236 }
237}
238
239impl FixedSize for Digest {
240 const SIZE: usize = DIGEST_LENGTH;
241}
242
243impl Span for Digest {}
244
245impl Array for Digest {}
246
247impl AsRef<[u8]> for Digest {
248 fn as_ref(&self) -> &[u8] {
249 &self.0
250 }
251}
252
253impl Deref for Digest {
254 type Target = [u8];
255 fn deref(&self) -> &[u8] {
256 &self.0
257 }
258}
259
260impl Debug for Digest {
261 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
262 write!(f, "{}", Hex(&self.0))
263 }
264}
265
266impl Display for Digest {
267 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
268 write!(f, "{}", Hex(&self.0))
269 }
270}
271
272impl crate::Digest for Digest {
273 const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
274}
275
276impl Random for Digest {
277 fn random(mut rng: impl CryptoRng) -> Self {
278 let mut array = [0u8; DIGEST_LENGTH];
279 rng.fill_bytes(&mut array);
280 Self(array)
281 }
282}
283
284impl Zeroize for Digest {
285 fn zeroize(&mut self) {
286 self.0.zeroize();
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use commonware_codec::{DecodeExt, Encode};
294
295 const HELLO_DIGEST: [u8; DIGEST_LENGTH] = commonware_formatting::hex!(
296 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
297 );
298
299 #[test]
303 fn test_sha256() {
304 let msg = b"hello world";
305
306 let mut hasher = Sha256::default();
308 hasher.update(msg);
309 let (_, digest) = hasher.finalize();
310 assert!(Digest::decode(digest.as_ref()).is_ok());
311 assert_eq!(digest.as_ref(), HELLO_DIGEST);
312
313 let hash = Sha256::hash(&[msg]);
315 assert_eq!(hash.as_ref(), HELLO_DIGEST);
316
317 let hash = Sha256::hash(&[b"hello", b" world"]);
319 assert_eq!(hash.as_ref(), HELLO_DIGEST);
320 }
321
322 #[test]
327 fn test_sha256_hash_parts_boundaries() {
328 for total in 0..=300usize {
329 let data: Vec<u8> = (0..total).map(|i| i as u8).collect();
330 let mid = total / 3;
332 let parts: [&[u8]; 3] = [&data[..mid], &data[mid..2 * mid], &data[2 * mid..]];
333
334 let oneshot = Sha256::hash(&parts);
335
336 let mut hasher = Sha256::default();
337 for part in &parts {
338 hasher.update(part);
339 }
340 let (_, streamed) = hasher.finalize();
341
342 assert_eq!(oneshot, streamed, "mismatch for total={total}");
343 }
344 }
345
346 #[test]
348 fn test_sha256_hash_specialized_arms() {
349 let data: Vec<u8> = (0u8..72).collect();
350 let shapes: [&[&[u8]]; 4] = [
351 &[&data[..8], &data[8..40], &data[40..72]],
352 &[&data[..32], &data[32..64]],
353 &[&data[..8], &data[8..40]],
354 &[&data[..4], &data[4..36]],
355 ];
356 for parts in shapes {
357 let oneshot = Sha256::hash(parts);
358
359 let mut hasher = Sha256::default();
360 for part in parts {
361 hasher.update(part);
362 }
363 let (_, streamed) = hasher.finalize();
364
365 assert_eq!(oneshot, streamed, "mismatch for shape {parts:?}");
366 }
367 }
368
369 #[test]
370 fn test_sha256_len() {
371 assert_eq!(Digest::SIZE, DIGEST_LENGTH);
372 }
373
374 #[test]
378 fn test_hash_pair_mmr_node_shape_matches_streaming() {
379 fn node(position: u64, fill: u8) -> Vec<Vec<u8>> {
380 vec![
381 position.to_be_bytes().to_vec(),
382 vec![fill; 32],
383 vec![fill + 1; 32],
384 ]
385 }
386 crate::fuzz::Plan::<Sha256>::new(node(42, 0x11), node(43, 0x33)).run();
387 }
388
389 #[test]
393 fn test_hash_pair_bmt_node_shape_matches_streaming() {
394 fn node(fill: u8) -> Vec<Vec<u8>> {
395 vec![vec![fill; 32], vec![fill + 1; 32]]
396 }
397 crate::fuzz::Plan::<Sha256>::new(node(0x11), node(0x33)).run();
398 }
399
400 #[test]
401 fn test_codec() {
402 let msg = b"hello world";
403 let mut hasher = Sha256::default();
404 hasher.update(msg);
405 let (_, digest) = hasher.finalize();
406
407 let encoded = digest.encode();
408 assert_eq!(encoded.len(), DIGEST_LENGTH);
409 assert_eq!(encoded, digest.as_ref());
410
411 let decoded = Digest::decode(encoded).unwrap();
412 assert_eq!(digest, decoded);
413 }
414
415 #[cfg(feature = "arbitrary")]
416 mod conformance {
417 use super::*;
418 use commonware_codec::conformance::CodecConformance;
419
420 commonware_conformance::conformance_tests! {
421 CodecConformance<Digest>,
422 }
423 }
424}