1use crate::Hasher;
31#[cfg(not(feature = "std"))]
32use alloc::vec::Vec;
33use bytes::BufMut;
34use commonware_codec::{Buf, Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write};
35use commonware_formatting::Hex;
36use commonware_math::algebra::Random;
37use commonware_parallel::Strategy;
38use commonware_utils::{Array, Span, sequence::FixedBytes};
39use core::{
40 cmp::Ordering,
41 fmt::{Debug, Display},
42 ops::Deref,
43};
44use rand_core::CryptoRng;
45use sha2::Digest as _;
46use zeroize::Zeroize;
47
48#[cfg(target_arch = "aarch64")]
49mod aarch64;
50#[cfg(target_arch = "x86_64")]
51mod avx512;
52#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
53mod padding;
54
55pub type CoreSha512 = sha2::Sha512;
57
58const DIGEST_LENGTH: usize = 64;
60
61#[cfg(target_arch = "x86_64")]
62cpufeatures::new!(has_avx512f, "avx512f");
63
64#[cfg(target_arch = "aarch64")]
66cpufeatures::new!(has_sha3, "sha3");
67
68fn lanes() -> usize {
70 #[cfg(target_arch = "x86_64")]
71 if has_avx512f::get() {
72 return avx512::LANES;
73 }
74 #[cfg(target_arch = "aarch64")]
75 if has_sha3::get() {
76 return aarch64::LANES;
77 }
78 1
79}
80
81#[derive(Debug, Default)]
83pub struct Sha512 {
84 hasher: CoreSha512,
85}
86
87impl Hasher for Sha512 {
88 type Digest = Digest;
89
90 fn hash(parts: &[&[u8]]) -> Self::Digest {
91 let mut hasher = CoreSha512::new();
92 for part in parts {
93 hasher.update(part);
94 }
95 Digest(hasher.finalize().into())
96 }
97
98 fn hash_pair(
99 left: &[&[u8]],
100 right: &[&[u8]],
101 _strategy: &impl Strategy,
102 ) -> (Self::Digest, Self::Digest) {
103 (Self::hash(left), Self::hash(right))
104 }
105
106 fn hash_many<M: AsRef<[u8]> + Sync>(
107 messages: &[M],
108 strategy: &impl Strategy,
109 ) -> Vec<Self::Digest> {
110 crate::hash_batches(messages, lanes(), strategy, hash_batch)
112 }
113
114 fn update(&mut self, message: &[u8]) -> &mut Self {
115 self.hasher.update(message);
116 self
117 }
118
119 fn finalize(mut self) -> (Self, Self::Digest) {
120 let finalized = self.hasher.finalize_reset();
121 (self, Digest(finalized.into()))
122 }
123}
124
125fn hash_batch<M: AsRef<[u8]>>(messages: &[M]) -> Vec<Digest> {
128 #[cfg(target_arch = "x86_64")]
130 if has_avx512f::get() {
131 let mut digests = Vec::with_capacity(messages.len());
132 for chunk in messages.chunks(avx512::LANES) {
133 let mut lanes: [&[u8]; avx512::LANES] = [&[]; avx512::LANES];
134 for (lane, message) in lanes.iter_mut().zip(chunk) {
135 *lane = message.as_ref();
136 }
137
138 let chunk_digests = unsafe { avx512::hash(&lanes[..chunk.len()]) };
142 digests.extend_from_slice(&chunk_digests[..chunk.len()]);
143 }
144 return digests;
145 }
146
147 #[cfg(target_arch = "aarch64")]
149 if has_sha3::get() {
150 let mut digests = Vec::with_capacity(messages.len());
151 let (pairs, remainder) = messages.as_chunks::<{ aarch64::LANES }>();
152 for pair in pairs {
153 let lanes = [pair[0].as_ref(), pair[1].as_ref()];
154
155 digests.extend_from_slice(&unsafe { aarch64::hash(&lanes) });
157 }
158
159 for message in remainder {
161 digests.push(Sha512::hash(&[message.as_ref()]));
162 }
163 return digests;
164 }
165
166 messages
168 .iter()
169 .map(|message| Sha512::hash(&[message.as_ref()]))
170 .collect()
171}
172
173#[derive(Clone, Copy, Eq, PartialEq, Hash, FixedArray)]
175#[fixed_array(infallible)]
176#[repr(transparent)]
177pub struct Digest(pub [u8; DIGEST_LENGTH]);
178
179impl Ord for Digest {
180 #[inline]
181 fn cmp(&self, other: &Self) -> Ordering {
182 FixedBytes::new(self.0).cmp(&FixedBytes::new(other.0))
183 }
184}
185
186impl PartialOrd for Digest {
187 #[inline]
188 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
189 Some(self.cmp(other))
190 }
191}
192
193#[cfg(feature = "arbitrary")]
194impl<'a> arbitrary::Arbitrary<'a> for Digest {
195 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
196 let len = u.int_in_range(0..=256)?;
197 let data = u.bytes(len)?;
198 Ok(Sha512::hash(&[data]))
199 }
200}
201
202impl Write for Digest {
203 fn write(&self, buf: &mut impl BufMut) {
204 self.0.write(buf);
205 }
206}
207
208impl Read for Digest {
209 type Cfg = ();
210
211 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
212 let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
213 Ok(Self(array))
214 }
215}
216
217impl FixedSize for Digest {
218 const SIZE: usize = DIGEST_LENGTH;
219}
220
221impl Span for Digest {}
222
223impl Array for Digest {}
224
225impl AsRef<[u8]> for Digest {
226 fn as_ref(&self) -> &[u8] {
227 &self.0
228 }
229}
230
231impl Deref for Digest {
232 type Target = [u8];
233 fn deref(&self) -> &[u8] {
234 &self.0
235 }
236}
237
238impl Debug for Digest {
239 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
240 write!(f, "{}", Hex(&self.0))
241 }
242}
243
244impl Display for Digest {
245 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
246 write!(f, "{}", Hex(&self.0))
247 }
248}
249
250impl crate::Digest for Digest {
251 const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
252}
253
254impl Random for Digest {
255 fn random(mut rng: impl CryptoRng) -> Self {
256 let mut array = [0u8; DIGEST_LENGTH];
257 rng.fill_bytes(&mut array);
258 Self(array)
259 }
260}
261
262impl Zeroize for Digest {
263 fn zeroize(&mut self) {
264 self.0.zeroize();
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use commonware_codec::{Copying, DecodeExt, Encode};
272 use commonware_formatting::hex;
273 use commonware_parallel::{Sequential, mocks::explore};
274
275 const EMPTY_DIGEST: [u8; DIGEST_LENGTH] = hex!(
276 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce"
277 "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
278 );
279 const ABC_DIGEST: [u8; DIGEST_LENGTH] = hex!(
280 "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
281 "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
282 );
283
284 fn expected(message: &[u8]) -> Digest {
286 Digest(CoreSha512::digest(message).into())
287 }
288
289 fn message(len: usize, seed: usize) -> Vec<u8> {
291 (0..len)
292 .map(|i| (i.wrapping_mul(31) ^ seed.wrapping_mul(0x9e37)) as u8)
293 .collect()
294 }
295
296 fn check_hash_many<M: AsRef<[u8]> + Sync>(messages: &[M], strategy: &impl Strategy) {
298 let digests: Vec<Digest> = messages
299 .iter()
300 .map(|message| expected(message.as_ref()))
301 .collect();
302 assert_eq!(Sha512::hash_many(messages, strategy), digests);
303 }
304
305 #[test]
306 fn test_known_answers_and_reset() {
307 assert_eq!(Sha512::hash(&[]).as_ref(), EMPTY_DIGEST);
310 assert_eq!(Sha512::hash(&[b"abc"]).as_ref(), ABC_DIGEST);
311 assert_eq!(Sha512::hash(&[b"a", b"", b"bc"]).as_ref(), ABC_DIGEST);
312 assert_eq!(
313 Sha512::hash_pair(&[], &[b"a", b"bc"], &Sequential),
314 (Digest(EMPTY_DIGEST), Digest(ABC_DIGEST))
315 );
316 assert_eq!(
317 Sha512::hash_many(&[b"".as_slice(), b"abc"], &Sequential),
318 [Digest(EMPTY_DIGEST), Digest(ABC_DIGEST)]
319 );
320
321 let mut hasher = Sha512::default();
324 for message in [b"abc".as_slice(), b"", b"abc", b""] {
325 for byte in message {
326 hasher.update(core::slice::from_ref(byte));
327 }
328 let (reset, digest) = hasher.finalize();
329 let expected = if message.is_empty() {
330 EMPTY_DIGEST
331 } else {
332 ABC_DIGEST
333 };
334 assert_eq!(digest.as_ref(), expected);
335 hasher = reset;
336 }
337 }
338
339 #[test]
342 fn test_hash_many_equal_lengths() {
343 explore(|strategy| {
344 for len in 0..=300 {
345 let messages: Vec<Vec<u8>> = (0..20).map(|lane| message(len, lane)).collect();
346 for count in 0..=messages.len() {
347 check_hash_many(&messages[..count], strategy);
348 }
349 }
350 });
351 }
352
353 #[test]
357 fn test_hash_many_mixed_lengths() {
358 explore(|strategy| {
359 for len in (0..=300).chain([1000, 4113]) {
360 for count in 1..=20 {
361 let messages: Vec<Vec<u8>> = (0..count)
362 .map(|lane| {
363 let other = (len * 7 + lane * 37) % 301;
366 message(if lane == len % count { len } else { other }, lane)
367 })
368 .collect();
369 check_hash_many(&messages, strategy);
370 }
371 }
372 });
373 }
374
375 #[test]
377 fn test_hash_many_overlapping_messages() {
378 let backing = message(400, 0);
381 let messages: Vec<&[u8]> = (0..20)
382 .map(|lane| &backing[lane..lane * 13 + 100])
383 .collect();
384 explore(|strategy| {
385 check_hash_many(&messages, strategy);
386
387 check_hash_many(&[&backing[..]; 9], strategy);
390 });
391 }
392
393 #[cfg(target_arch = "x86_64")]
396 #[test]
397 fn test_avx512_detection() {
398 assert_eq!(
399 has_avx512f::get(),
400 std::arch::is_x86_feature_detected!("avx512f")
401 );
402 }
403
404 #[cfg(target_arch = "aarch64")]
407 #[test]
408 fn test_sha3_detection() {
409 assert_eq!(
410 has_sha3::get(),
411 std::arch::is_aarch64_feature_detected!("sha3")
412 );
413 }
414
415 #[test]
416 fn test_codec_and_zeroize() {
417 let mut digest = Sha512::hash(&[b"abc"]);
419 let encoded = digest.encode();
420 assert_eq!(Digest::SIZE, DIGEST_LENGTH);
421 assert_eq!(encoded.as_ref(), ABC_DIGEST);
422 assert_eq!(Digest::decode(encoded).unwrap(), digest);
423 assert!(Digest::decode(Copying(&ABC_DIGEST[..DIGEST_LENGTH - 1])).is_err());
424
425 digest.zeroize();
427 assert_eq!(digest, <Digest as crate::Digest>::EMPTY);
428 }
429
430 #[test]
431 fn test_digest_ord() {
432 let a = Digest([0; DIGEST_LENGTH]);
433 let mut b = a;
434 b.0[DIGEST_LENGTH - 1] = 1;
435
436 let mut c = a;
438 c.0[0] = 0x7f;
439 c.0[1..].fill(0xff);
440 let mut d = a;
441 d.0[0] = 0x80;
442 for (a, b, expected) in [
443 (a, a, Ordering::Equal),
444 (a, b, Ordering::Less),
445 (b, a, Ordering::Greater),
446 (c, d, Ordering::Less),
447 (d, c, Ordering::Greater),
448 ] {
449 assert_eq!(a.cmp(&b), expected);
450 assert_eq!(a.partial_cmp(&b), Some(expected));
451 }
452 }
453
454 #[cfg(feature = "arbitrary")]
455 mod conformance {
456 use super::*;
457 use commonware_codec::conformance::CodecConformance;
458
459 commonware_conformance::conformance_tests! {
460 CodecConformance<Digest>,
461 }
462 }
463}