1use crate::Hasher;
29#[cfg(not(feature = "std"))]
30use alloc::vec;
31#[cfg(all(not(feature = "std"), target_arch = "x86_64"))]
32use alloc::vec::Vec;
33use bytes::BufMut;
34use commonware_codec::{
35 Buf, DecodeExt, Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write,
36};
37use commonware_formatting::Hex;
38use commonware_math::algebra::Random;
39use commonware_parallel::Strategy;
40use commonware_utils::{Array, Span, sequence::FixedBytes};
41use core::{
42 cmp::Ordering,
43 fmt::{Debug, Display},
44 ops::Deref,
45};
46use rand_core::CryptoRng;
47use sha2::{Digest as _, Sha256 as ISha256, block_api::compress256};
48use zeroize::Zeroize;
49
50#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
51mod simd;
52
53pub type CoreSha256 = ISha256;
55
56const DIGEST_LENGTH: usize = 32;
57
58const BLOCK_LENGTH: usize = 64;
60
61const MAX_FIXED: usize = 2 * BLOCK_LENGTH - 9;
68
69const IV: [u32; 8] = [
71 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
72];
73
74#[inline]
76fn digest_from_state(state: [u32; 8]) -> [u8; DIGEST_LENGTH] {
77 let mut out = [0u8; DIGEST_LENGTH];
78 for (chunk, word) in out.as_chunks_mut::<4>().0.iter_mut().zip(state) {
79 *chunk = word.to_be_bytes();
80 }
81 out
82}
83
84#[inline]
90fn finalize_fixed_fresh(scratch: &mut [u8; 2 * BLOCK_LENGTH], len: usize) -> [u8; DIGEST_LENGTH] {
91 assert!(len <= MAX_FIXED);
92 let bit_len = ((len as u64) * 8).to_be_bytes();
93 scratch[len] = 0x80;
94 let mut state = IV;
95 if len < BLOCK_LENGTH - 8 {
96 scratch[BLOCK_LENGTH - 8..BLOCK_LENGTH].copy_from_slice(&bit_len);
98 let (blocks, _) = scratch[..BLOCK_LENGTH].as_chunks::<BLOCK_LENGTH>();
99 compress256(&mut state, blocks);
100 } else {
101 scratch[2 * BLOCK_LENGTH - 8..].copy_from_slice(&bit_len);
103 let (blocks, _) = scratch.as_chunks::<BLOCK_LENGTH>();
104 compress256(&mut state, blocks);
105 }
106 digest_from_state(state)
107}
108
109#[inline(always)]
113fn hash_specialized(parts: &[&[u8]]) -> Digest {
114 match parts {
115 [p, l, r] if p.len() == 8 && l.len() == 32 && r.len() == 32 => {
116 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
117 scratch[..8].copy_from_slice(p);
118 scratch[8..40].copy_from_slice(l);
119 scratch[40..72].copy_from_slice(r);
120 Digest(finalize_fixed_fresh(&mut scratch, 72))
121 }
122 [a, b] if a.len() == 32 && b.len() == 32 => {
123 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
124 scratch[..32].copy_from_slice(a);
125 scratch[32..64].copy_from_slice(b);
126 Digest(finalize_fixed_fresh(&mut scratch, 64))
127 }
128 [p, d] if p.len() == 8 && d.len() == 32 => {
129 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
130 scratch[..8].copy_from_slice(p);
131 scratch[8..40].copy_from_slice(d);
132 Digest(finalize_fixed_fresh(&mut scratch, 40))
133 }
134 [p, d] if p.len() == 4 && d.len() == 32 => {
135 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
136 scratch[..4].copy_from_slice(p);
137 scratch[4..36].copy_from_slice(d);
138 Digest(finalize_fixed_fresh(&mut scratch, 36))
139 }
140 _ => hash_general(parts),
141 }
142}
143
144#[inline(never)]
148fn hash_general(parts: &[&[u8]]) -> Digest {
149 let mut scratch = [0u8; 2 * BLOCK_LENGTH];
150 let mut len = 0usize;
151 let mut parts = parts.iter();
152 loop {
153 match parts.next() {
154 Some(part) if len + part.len() <= MAX_FIXED => {
155 scratch[len..len + part.len()].copy_from_slice(part);
156 len += part.len();
157 }
158 Some(part) => {
159 let mut hasher = ISha256::new();
160 hasher.update(&scratch[..len]);
161 hasher.update(part);
162 for part in parts {
163 hasher.update(part);
164 }
165 let array: [u8; DIGEST_LENGTH] = hasher.finalize().into();
166 return Digest(array);
167 }
168 None => break,
169 }
170 }
171 Digest(finalize_fixed_fresh(&mut scratch, len))
172}
173
174#[derive(Debug, Default)]
176pub struct Sha256 {
177 hasher: ISha256,
178}
179
180impl Sha256 {
181 pub fn fill(b: u8) -> <Self as Hasher>::Digest {
184 <Self as Hasher>::Digest::decode(vec![b; DIGEST_LENGTH]).unwrap()
185 }
186}
187
188impl Hasher for Sha256 {
189 type Digest = Digest;
190
191 #[inline]
192 fn hash(parts: &[&[u8]]) -> Self::Digest {
193 hash_specialized(parts)
194 }
195
196 #[inline]
197 fn hash_pair(
198 left: &[&[u8]],
199 right: &[&[u8]],
200 _strategy: &impl Strategy,
201 ) -> (Self::Digest, Self::Digest) {
202 #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
203 if let Some(pair) = simd::hash_pair(left, right) {
204 return pair;
205 }
206 (Self::hash(left), Self::hash(right))
207 }
208
209 #[cfg(target_arch = "x86_64")]
210 fn hash_many<M: AsRef<[u8]> + Sync>(
211 messages: &[M],
212 strategy: &impl Strategy,
213 ) -> Vec<Self::Digest> {
214 let lanes = if simd::minimum_x16_batch_len().is_some() && !simd::supports_sha_ni() {
220 simd::X16_LANES
221 } else {
222 1
223 };
224 crate::hash_batches(messages, lanes, strategy, hash_batch)
225 }
226
227 #[inline]
228 fn update(&mut self, message: &[u8]) -> &mut Self {
229 self.hasher.update(message);
230 self
231 }
232
233 #[inline]
234 fn finalize(mut self) -> (Self, Self::Digest) {
235 let finalized = self.hasher.finalize_reset();
236 let array: [u8; DIGEST_LENGTH] = finalized.into();
237 (self, Digest(array))
238 }
239}
240
241#[cfg(target_arch = "x86_64")]
245fn hash_batch<M: AsRef<[u8]>>(messages: &[M]) -> Vec<Digest> {
246 let Some(minimum) = simd::minimum_x16_batch_len() else {
247 return messages
248 .iter()
249 .map(|message| hash_specialized(&[message.as_ref()]))
250 .collect();
251 };
252
253 let mut digests = Vec::with_capacity(messages.len());
256 for run in messages.chunk_by(|left, right| left.as_ref().len() == right.as_ref().len()) {
257 for batch in run.chunks(simd::X16_LANES) {
258 if batch.len() >= minimum {
259 let mut inputs = [batch[0].as_ref(); simd::X16_LANES];
261 for (input, message) in inputs[1..].iter_mut().zip(&batch[1..]) {
262 *input = message.as_ref();
263 }
264 if let Some(batch_digests) = simd::hash_x16(inputs) {
265 digests.extend_from_slice(&batch_digests[..batch.len()]);
266 continue;
267 }
268 }
269 digests.extend(
270 batch
271 .iter()
272 .map(|message| hash_specialized(&[message.as_ref()])),
273 );
274 }
275 }
276 digests
277}
278
279#[derive(Clone, Copy, Eq, PartialEq, Hash, FixedArray)]
281#[fixed_array(infallible)]
282#[repr(transparent)]
283pub struct Digest(pub [u8; DIGEST_LENGTH]);
284
285impl Ord for Digest {
286 #[inline]
287 fn cmp(&self, other: &Self) -> Ordering {
288 FixedBytes::new(self.0).cmp(&FixedBytes::new(other.0))
289 }
290}
291
292impl PartialOrd for Digest {
293 #[inline]
294 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
295 Some(self.cmp(other))
296 }
297}
298
299#[cfg(feature = "arbitrary")]
300impl<'a> arbitrary::Arbitrary<'a> for Digest {
301 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
302 let len = u.int_in_range(0..=256)?;
304 let data = u.bytes(len)?;
305 Ok(Sha256::hash(&[data]))
306 }
307}
308
309impl Write for Digest {
310 fn write(&self, buf: &mut impl BufMut) {
311 self.0.write(buf);
312 }
313}
314
315impl Read for Digest {
316 type Cfg = ();
317
318 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
319 let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
320 Ok(Self(array))
321 }
322}
323
324impl FixedSize for Digest {
325 const SIZE: usize = DIGEST_LENGTH;
326}
327
328impl Span for Digest {}
329
330impl Array for Digest {}
331
332impl AsRef<[u8]> for Digest {
333 fn as_ref(&self) -> &[u8] {
334 &self.0
335 }
336}
337
338impl Deref for Digest {
339 type Target = [u8];
340 fn deref(&self) -> &[u8] {
341 &self.0
342 }
343}
344
345impl Debug for Digest {
346 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
347 write!(f, "{}", Hex(&self.0))
348 }
349}
350
351impl Display for Digest {
352 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
353 write!(f, "{}", Hex(&self.0))
354 }
355}
356
357impl crate::Digest for Digest {
358 const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
359}
360
361impl Random for Digest {
362 fn random(mut rng: impl CryptoRng) -> Self {
363 let mut array = [0u8; DIGEST_LENGTH];
364 rng.fill_bytes(&mut array);
365 Self(array)
366 }
367}
368
369impl Zeroize for Digest {
370 fn zeroize(&mut self) {
371 self.0.zeroize();
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use commonware_codec::{Copying, DecodeExt, Encode};
379 use commonware_parallel::{Sequential, mocks::explore};
380
381 const HELLO_DIGEST: [u8; DIGEST_LENGTH] = commonware_formatting::hex!(
382 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
383 );
384
385 #[test]
386 fn test_digest_ord() {
387 let a = Digest([0; DIGEST_LENGTH]);
388 let mut b = a;
389 b.0[DIGEST_LENGTH - 1] = 1;
390
391 let mut c = a;
393 c.0[0] = 0x7f;
394 c.0[1..].fill(0xff);
395 let mut d = a;
396 d.0[0] = 0x80;
397 for (a, b, expected) in [
398 (a, a, Ordering::Equal),
399 (a, b, Ordering::Less),
400 (b, a, Ordering::Greater),
401 (c, d, Ordering::Less),
402 (d, c, Ordering::Greater),
403 ] {
404 assert_eq!(a.cmp(&b), expected);
405 assert_eq!(a.partial_cmp(&b), Some(expected));
406 }
407 }
408
409 #[test]
413 fn test_sha256() {
414 let msg = b"hello world";
415
416 let mut hasher = Sha256::default();
418 hasher.update(msg);
419 let (_, digest) = hasher.finalize();
420 assert!(Digest::decode(Copying(&digest)).is_ok());
421 assert_eq!(digest.as_ref(), HELLO_DIGEST);
422
423 let hash = Sha256::hash(&[msg]);
425 assert_eq!(hash.as_ref(), HELLO_DIGEST);
426
427 let hash = Sha256::hash(&[b"hello", b" world"]);
429 assert_eq!(hash.as_ref(), HELLO_DIGEST);
430 }
431
432 #[test]
437 fn test_sha256_hash_parts_boundaries() {
438 for total in 0..=300usize {
439 let data: Vec<u8> = (0..total).map(|i| i as u8).collect();
440 let mid = total / 3;
442 let parts: [&[u8]; 3] = [&data[..mid], &data[mid..2 * mid], &data[2 * mid..]];
443
444 let oneshot = Sha256::hash(&parts);
445
446 let mut hasher = Sha256::default();
447 for part in &parts {
448 hasher.update(part);
449 }
450 let (_, streamed) = hasher.finalize();
451
452 assert_eq!(oneshot, streamed, "mismatch for total={total}");
453 }
454 }
455
456 #[test]
458 fn test_sha256_hash_specialized_arms() {
459 let data: Vec<u8> = (0u8..72).collect();
460 let shapes: [&[&[u8]]; 4] = [
461 &[&data[..8], &data[8..40], &data[40..72]],
462 &[&data[..32], &data[32..64]],
463 &[&data[..8], &data[8..40]],
464 &[&data[..4], &data[4..36]],
465 ];
466 for parts in shapes {
467 let oneshot = Sha256::hash(parts);
468
469 let mut hasher = Sha256::default();
470 for part in parts {
471 hasher.update(part);
472 }
473 let (_, streamed) = hasher.finalize();
474
475 assert_eq!(oneshot, streamed, "mismatch for shape {parts:?}");
476 }
477 }
478
479 #[test]
480 fn test_sha256_len() {
481 assert_eq!(Digest::SIZE, DIGEST_LENGTH);
482 }
483
484 #[test]
488 fn test_hash_pair_mmr_node_shape_matches_streaming() {
489 fn node(position: u64, fill: u8) -> Vec<Vec<u8>> {
490 vec![
491 position.to_be_bytes().to_vec(),
492 vec![fill; 32],
493 vec![fill + 1; 32],
494 ]
495 }
496 crate::fuzz::Plan::<Sha256>::new(node(42, 0x11), node(43, 0x33)).run();
497 }
498
499 #[test]
503 fn test_hash_pair_bmt_node_shape_matches_streaming() {
504 fn assert_pair(left: [&[u8]; 2], right: [&[u8]; 2]) {
505 let expected = |parts: &[&[u8]]| {
506 let mut hasher = Sha256::default();
507 for part in parts {
508 hasher.update(part);
509 }
510 hasher.finalize().1
511 };
512 let (left_digest, right_digest) = Sha256::hash_pair(&left, &right, &Sequential);
513 let expected_left = expected(&left);
514 let expected_right = expected(&right);
515 assert_eq!(Sha256::hash(&left), expected_left);
516 assert_eq!(Sha256::hash(&right), expected_right);
517 assert_eq!(left_digest, expected_left);
518 assert_eq!(right_digest, expected_right);
519 }
520
521 let zero = [0u8; 32];
522 let ff = [0xff; 32];
523 let ascending: [u8; 32] = core::array::from_fn(|i| i as u8);
524 let descending: [u8; 32] = core::array::from_fn(|i| 0xff - i as u8);
525 assert_pair([&zero, &zero], [&zero, &zero]);
526 assert_pair([&ff, &ff], [&ff, &ff]);
527 assert_pair([&ascending, &descending], [&ff, &zero]);
528
529 let backing: Vec<u8> = (0..96).map(|i| i as u8).collect();
530 assert_pair(
531 [&backing[1..33], &backing[17..49]],
532 [&backing[2..34], &backing[18..50]],
533 );
534 }
535
536 fn check_hash_many(messages: &[&[u8]], strategy: &impl Strategy) {
538 let expected = messages
539 .iter()
540 .map(|&message| Sha256::hash(&[message]))
541 .collect::<Vec<_>>();
542 assert_eq!(Sha256::hash_many(messages, strategy), expected);
543 }
544
545 #[test]
546 fn test_hash_many_boundaries_match_individual_hashes() {
547 explore(|strategy| {
548 for len in (0..=129).chain([255, 256, 1024, 12_634, 50_534]) {
549 let messages: [Vec<u8>; 33] = core::array::from_fn(|lane| {
550 (0..len)
551 .map(|i| (i as u8).wrapping_add(lane as u8))
552 .collect()
553 });
554 let refs = messages.each_ref().map(Vec::as_slice);
555 for count in [0, 1, 2, 6, 7, 15, 16, 17, 31, 32, 33] {
556 check_hash_many(&refs[..count], strategy);
557 }
558 }
559 });
560
561 let messages: [Vec<u8>; 16] = core::array::from_fn(|lane| vec![lane as u8; 64]);
562 let mut refs = messages.each_ref().map(Vec::as_slice);
563 refs[9] = &messages[9][..63];
564 explore(|strategy| check_hash_many(&refs, strategy));
565 #[cfg(target_arch = "x86_64")]
566 assert!(simd::hash_x16(refs).is_none());
567 }
568
569 #[test]
570 fn test_hash_many_aliased_unaligned_inputs_match_individual_hashes() {
571 let backing: Vec<u8> = (0..160).map(|i| i as u8).collect();
572 let messages: [&[u8]; 16] = core::array::from_fn(|lane| &backing[lane..lane + 129]);
573 explore(|strategy| {
574 for count in 1..=messages.len() {
575 check_hash_many(&messages[..count], strategy);
576 }
577 check_hash_many(&[&backing[1..130]; 16], strategy);
578 });
579 }
580
581 #[test]
582 fn test_codec() {
583 let msg = b"hello world";
584 let mut hasher = Sha256::default();
585 hasher.update(msg);
586 let (_, digest) = hasher.finalize();
587
588 let encoded = digest.encode();
589 assert_eq!(encoded.len(), DIGEST_LENGTH);
590 assert_eq!(encoded, digest.as_ref());
591
592 let decoded = Digest::decode(encoded).unwrap();
593 assert_eq!(digest, decoded);
594 }
595
596 #[cfg(feature = "arbitrary")]
597 mod conformance {
598 use super::*;
599 use commonware_codec::conformance::CodecConformance;
600
601 commonware_conformance::conformance_tests! {
602 CodecConformance<Digest>,
603 }
604 }
605}