diff --git a/bitcoin/src/blockdata/script/instruction.rs b/bitcoin/src/blockdata/script/instruction.rs index 410f6d521..6ec3d5613 100644 --- a/bitcoin/src/blockdata/script/instruction.rs +++ b/bitcoin/src/blockdata/script/instruction.rs @@ -47,7 +47,8 @@ impl Instruction<'_> { _ => None, } } - Instruction::PushBytes(bytes) => super::read_scriptint_non_minimal(bytes.as_bytes()).ok(), + Instruction::PushBytes(bytes) => + super::read_scriptint_non_minimal(bytes.as_bytes()).ok(), } } diff --git a/hashes/src/hash160.rs b/hashes/src/hash160/mod.rs similarity index 100% rename from hashes/src/hash160.rs rename to hashes/src/hash160/mod.rs diff --git a/hashes/src/hkdf.rs b/hashes/src/hkdf/mod.rs similarity index 100% rename from hashes/src/hkdf.rs rename to hashes/src/hkdf/mod.rs diff --git a/hashes/src/hmac.rs b/hashes/src/hmac/mod.rs similarity index 100% rename from hashes/src/hmac.rs rename to hashes/src/hmac/mod.rs diff --git a/hashes/src/ripemd160/benches.rs b/hashes/src/ripemd160/benches.rs new file mode 100644 index 000000000..cb023a933 --- /dev/null +++ b/hashes/src/ripemd160/benches.rs @@ -0,0 +1,33 @@ +use test::Bencher; + +use crate::{ripemd160, Hash, HashEngine}; + +#[bench] +pub fn ripemd160_10(bh: &mut Bencher) { + let mut engine = ripemd160::Hash::engine(); + let bytes = [1u8; 10]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn ripemd160_1k(bh: &mut Bencher) { + let mut engine = ripemd160::Hash::engine(); + let bytes = [1u8; 1024]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn ripemd160_64k(bh: &mut Bencher) { + let mut engine = ripemd160::Hash::engine(); + let bytes = [1u8; 65536]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} diff --git a/hashes/src/ripemd160.rs b/hashes/src/ripemd160/crypto.rs similarity index 72% rename from hashes/src/ripemd160.rs rename to hashes/src/ripemd160/crypto.rs index fa0893d7a..4c6226892 100644 --- a/hashes/src/ripemd160.rs +++ b/hashes/src/ripemd160/crypto.rs @@ -1,92 +1,6 @@ // SPDX-License-Identifier: CC0-1.0 -//! RIPEMD160 implementation. - -use core::cmp; - -use crate::{incomplete_block_len, HashEngine as _}; - -crate::internal_macros::general_hash_type! { - 160, - false, - "Output of the RIPEMD160 hash function." -} - -#[cfg(not(hashes_fuzz))] -fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 8]; - e.input(&[0x80]); - if crate::incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - - e.input(&(8 * n_bytes_hashed).to_le_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); - - Hash(e.midstate()) -} - -#[cfg(hashes_fuzz)] -fn from_engine(e: HashEngine) -> Hash { - let mut res = e.midstate(); - res[0] ^= (e.bytes_hashed & 0xff) as u8; - Hash(res) -} - -const BLOCK_SIZE: usize = 64; - -/// Engine to compute RIPEMD160 hash function. -#[derive(Clone)] -pub struct HashEngine { - buffer: [u8; BLOCK_SIZE], - h: [u32; 5], - bytes_hashed: u64, -} - -impl HashEngine { - /// Constructs a new SHA256 hash engine. - pub const fn new() -> Self { - Self { - h: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0], - bytes_hashed: 0, - buffer: [0; BLOCK_SIZE], - } - } - - #[cfg(not(hashes_fuzz))] - fn midstate(&self) -> [u8; 20] { - let mut ret = [0; 20]; - for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) { - ret_bytes.copy_from_slice(&(*val).to_le_bytes()); - } - ret - } - - #[cfg(hashes_fuzz)] - fn midstate(&self) -> [u8; 20] { - let mut ret = [0; 20]; - ret.copy_from_slice(&self.buffer[..20]); - ret - } -} - -impl Default for HashEngine { - fn default() -> Self { Self::new() } -} - -impl crate::HashEngine for HashEngine { - const BLOCK_SIZE: usize = 64; - - fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } - - crate::internal_macros::engine_input_impl!(); -} +use super::{HashEngine, BLOCK_SIZE}; #[cfg(feature = "small-hash")] #[macro_use] @@ -214,7 +128,7 @@ macro_rules! process_block( ); impl HashEngine { - fn process_block(&mut self) { + pub(super) fn process_block(&mut self) { debug_assert_eq!(self.buffer.len(), BLOCK_SIZE); let mut w = [0u32; 16]; @@ -405,149 +319,3 @@ impl HashEngine { ); } } - -#[cfg(test)] -mod tests { - #[test] - #[cfg(feature = "alloc")] - fn test() { - use alloc::string::ToString; - - use crate::{ripemd160, HashEngine}; - - #[derive(Clone)] - struct Test { - input: &'static str, - output: [u8; 20], - output_str: &'static str, - } - - #[rustfmt::skip] - let tests = [ - // Test messages from FIPS 180-1 - Test { - input: "abc", - output: [ - 0x8e, 0xb2, 0x08, 0xf7, - 0xe0, 0x5d, 0x98, 0x7a, - 0x9b, 0x04, 0x4a, 0x8e, - 0x98, 0xc6, 0xb0, 0x87, - 0xf1, 0x5a, 0x0b, 0xfc, - ], - output_str: "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc" - }, - Test { - input: - "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", - output: [ - 0x12, 0xa0, 0x53, 0x38, - 0x4a, 0x9c, 0x0c, 0x88, - 0xe4, 0x05, 0xa0, 0x6c, - 0x27, 0xdc, 0xf4, 0x9a, - 0xda, 0x62, 0xeb, 0x2b, - ], - output_str: "12a053384a9c0c88e405a06c27dcf49ada62eb2b" - }, - // Examples from wikipedia - Test { - input: "The quick brown fox jumps over the lazy dog", - output: [ - 0x37, 0xf3, 0x32, 0xf6, - 0x8d, 0xb7, 0x7b, 0xd9, - 0xd7, 0xed, 0xd4, 0x96, - 0x95, 0x71, 0xad, 0x67, - 0x1c, 0xf9, 0xdd, 0x3b, - ], - output_str: "37f332f68db77bd9d7edd4969571ad671cf9dd3b", - }, - Test { - input: "The quick brown fox jumps over the lazy cog", - output: [ - 0x13, 0x20, 0x72, 0xdf, - 0x69, 0x09, 0x33, 0x83, - 0x5e, 0xb8, 0xb6, 0xad, - 0x0b, 0x77, 0xe7, 0xb6, - 0xf1, 0x4a, 0xca, 0xd7, - ], - output_str: "132072df690933835eb8b6ad0b77e7b6f14acad7", - }, - ]; - - for mut test in tests { - // Hash through high-level API, check hex encoding/decoding - let hash = ripemd160::Hash::hash(test.input.as_bytes()); - assert_eq!(hash, test.output_str.parse::().expect("parse hex")); - assert_eq!(hash.as_byte_array(), &test.output); - assert_eq!(hash.to_string(), test.output_str); - assert_eq!(ripemd160::Hash::from_bytes_ref(&test.output), &hash); - assert_eq!(ripemd160::Hash::from_bytes_mut(&mut test.output), &hash); - - // Hash through engine, checking that we can input byte by byte - let mut engine = ripemd160::Hash::engine(); - for ch in test.input.as_bytes() { - engine.input(&[*ch]); - } - let manual_hash = ripemd160::Hash::from_engine(engine); - assert_eq!(hash, manual_hash); - assert_eq!(hash.to_byte_array(), test.output); - } - } - - #[test] - #[cfg(feature = "serde")] - fn ripemd_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - use crate::ripemd160; - - #[rustfmt::skip] - static HASH_BYTES: [u8; 20] = [ - 0x13, 0x20, 0x72, 0xdf, - 0x69, 0x09, 0x33, 0x83, - 0x5e, 0xb8, 0xb6, 0xad, - 0x0b, 0x77, 0xe7, 0xb6, - 0xf1, 0x4a, 0xca, 0xd7, - ]; - - let hash = ripemd160::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); - assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); - assert_tokens(&hash.readable(), &[Token::Str("132072df690933835eb8b6ad0b77e7b6f14acad7")]); - } -} - -#[cfg(bench)] -mod benches { - use test::Bencher; - - use crate::{ripemd160, Hash, HashEngine}; - - #[bench] - pub fn ripemd160_10(bh: &mut Bencher) { - let mut engine = ripemd160::Hash::engine(); - let bytes = [1u8; 10]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn ripemd160_1k(bh: &mut Bencher) { - let mut engine = ripemd160::Hash::engine(); - let bytes = [1u8; 1024]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn ripemd160_64k(bh: &mut Bencher) { - let mut engine = ripemd160::Hash::engine(); - let bytes = [1u8; 65536]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } -} diff --git a/hashes/src/ripemd160/mod.rs b/hashes/src/ripemd160/mod.rs new file mode 100644 index 000000000..759900ce2 --- /dev/null +++ b/hashes/src/ripemd160/mod.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! RIPEMD160 implementation. + +#[cfg(bench)] +mod benches; +mod crypto; +#[cfg(bench)] +mod tests; + +use core::cmp; + +use crate::{incomplete_block_len, HashEngine as _}; + +crate::internal_macros::general_hash_type! { + 160, + false, + "Output of the RIPEMD160 hash function." +} + +#[cfg(not(hashes_fuzz))] +fn from_engine(mut e: HashEngine) -> Hash { + // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 8]; + e.input(&[0x80]); + if crate::incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); + + e.input(&(8 * n_bytes_hashed).to_le_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); + + Hash(e.midstate()) +} + +#[cfg(hashes_fuzz)] +fn from_engine(e: HashEngine) -> Hash { + let mut res = e.midstate(); + res[0] ^= (e.bytes_hashed & 0xff) as u8; + Hash(res) +} + +const BLOCK_SIZE: usize = 64; + +/// Engine to compute RIPEMD160 hash function. +#[derive(Clone)] +pub struct HashEngine { + buffer: [u8; BLOCK_SIZE], + h: [u32; 5], + bytes_hashed: u64, +} + +impl HashEngine { + /// Constructs a new SHA256 hash engine. + pub const fn new() -> Self { + Self { + h: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0], + bytes_hashed: 0, + buffer: [0; BLOCK_SIZE], + } + } + + #[cfg(not(hashes_fuzz))] + fn midstate(&self) -> [u8; 20] { + let mut ret = [0; 20]; + for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) { + ret_bytes.copy_from_slice(&(*val).to_le_bytes()); + } + ret + } + + #[cfg(hashes_fuzz)] + fn midstate(&self) -> [u8; 20] { + let mut ret = [0; 20]; + ret.copy_from_slice(&self.buffer[..20]); + ret + } +} + +impl Default for HashEngine { + fn default() -> Self { Self::new() } +} + +impl crate::HashEngine for HashEngine { + const BLOCK_SIZE: usize = 64; + + fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } + + crate::internal_macros::engine_input_impl!(); +} diff --git a/hashes/src/ripemd160/tests.rs b/hashes/src/ripemd160/tests.rs new file mode 100644 index 000000000..d6340591c --- /dev/null +++ b/hashes/src/ripemd160/tests.rs @@ -0,0 +1,105 @@ +#[test] +#[cfg(feature = "alloc")] +fn test() { + use alloc::string::ToString; + + use crate::{ripemd160, HashEngine}; + + #[derive(Clone)] + struct Test { + input: &'static str, + output: [u8; 20], + output_str: &'static str, + } + + #[rustfmt::skip] + let tests = [ + // Test messages from FIPS 180-1 + Test { + input: "abc", + output: [ + 0x8e, 0xb2, 0x08, 0xf7, + 0xe0, 0x5d, 0x98, 0x7a, + 0x9b, 0x04, 0x4a, 0x8e, + 0x98, 0xc6, 0xb0, 0x87, + 0xf1, 0x5a, 0x0b, 0xfc, + ], + output_str: "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc" + }, + Test { + input: + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + output: [ + 0x12, 0xa0, 0x53, 0x38, + 0x4a, 0x9c, 0x0c, 0x88, + 0xe4, 0x05, 0xa0, 0x6c, + 0x27, 0xdc, 0xf4, 0x9a, + 0xda, 0x62, 0xeb, 0x2b, + ], + output_str: "12a053384a9c0c88e405a06c27dcf49ada62eb2b" + }, + // Examples from wikipedia + Test { + input: "The quick brown fox jumps over the lazy dog", + output: [ + 0x37, 0xf3, 0x32, 0xf6, + 0x8d, 0xb7, 0x7b, 0xd9, + 0xd7, 0xed, 0xd4, 0x96, + 0x95, 0x71, 0xad, 0x67, + 0x1c, 0xf9, 0xdd, 0x3b, + ], + output_str: "37f332f68db77bd9d7edd4969571ad671cf9dd3b", + }, + Test { + input: "The quick brown fox jumps over the lazy cog", + output: [ + 0x13, 0x20, 0x72, 0xdf, + 0x69, 0x09, 0x33, 0x83, + 0x5e, 0xb8, 0xb6, 0xad, + 0x0b, 0x77, 0xe7, 0xb6, + 0xf1, 0x4a, 0xca, 0xd7, + ], + output_str: "132072df690933835eb8b6ad0b77e7b6f14acad7", + }, + ]; + + for mut test in tests { + // Hash through high-level API, check hex encoding/decoding + let hash = ripemd160::Hash::hash(test.input.as_bytes()); + assert_eq!(hash, test.output_str.parse::().expect("parse hex")); + assert_eq!(hash.as_byte_array(), &test.output); + assert_eq!(hash.to_string(), test.output_str); + assert_eq!(ripemd160::Hash::from_bytes_ref(&test.output), &hash); + assert_eq!(ripemd160::Hash::from_bytes_mut(&mut test.output), &hash); + + // Hash through engine, checking that we can input byte by byte + let mut engine = ripemd160::Hash::engine(); + for ch in test.input.as_bytes() { + engine.input(&[*ch]); + } + let manual_hash = ripemd160::Hash::from_engine(engine); + assert_eq!(hash, manual_hash); + assert_eq!(hash.to_byte_array(), test.output); + } +} + +#[test] +#[cfg(feature = "serde")] +fn ripemd_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + use crate::ripemd160; + + #[rustfmt::skip] + static HASH_BYTES: [u8; 20] = [ + 0x13, 0x20, 0x72, 0xdf, + 0x69, 0x09, 0x33, 0x83, + 0x5e, 0xb8, 0xb6, 0xad, + 0x0b, 0x77, 0xe7, 0xb6, + 0xf1, 0x4a, 0xca, 0xd7, + ]; + + let hash = ripemd160::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); + assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); + assert_tokens(&hash.readable(), &[Token::Str("132072df690933835eb8b6ad0b77e7b6f14acad7")]); +} diff --git a/hashes/src/sha1.rs b/hashes/src/sha1.rs deleted file mode 100644 index 152a8f348..000000000 --- a/hashes/src/sha1.rs +++ /dev/null @@ -1,257 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! SHA1 implementation. - -use core::cmp; - -use crate::{incomplete_block_len, HashEngine as _}; - -crate::internal_macros::general_hash_type! { - 160, - false, - "Output of the SHA1 hash function." -} - -fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 8]; - e.input(&[0x80]); - if incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - - e.input(&(8 * n_bytes_hashed).to_be_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); - - Hash(e.midstate()) -} - -const BLOCK_SIZE: usize = 64; - -/// Engine to compute SHA1 hash function. -#[derive(Clone)] -pub struct HashEngine { - buffer: [u8; BLOCK_SIZE], - h: [u32; 5], - bytes_hashed: u64, -} - -impl HashEngine { - /// Constructs a new SHA1 hash engine. - pub const fn new() -> Self { - Self { - h: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0], - bytes_hashed: 0, - buffer: [0; BLOCK_SIZE], - } - } - - #[cfg(not(hashes_fuzz))] - pub(crate) fn midstate(&self) -> [u8; 20] { - let mut ret = [0; 20]; - for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) { - ret_bytes.copy_from_slice(&val.to_be_bytes()) - } - ret - } - - #[cfg(hashes_fuzz)] - pub(crate) fn midstate(&self) -> [u8; 20] { - let mut ret = [0; 20]; - ret.copy_from_slice(&self.buffer[..20]); - ret - } -} - -impl Default for HashEngine { - fn default() -> Self { Self::new() } -} - -impl crate::HashEngine for HashEngine { - const BLOCK_SIZE: usize = 64; - - fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } - - crate::internal_macros::engine_input_impl!(); -} - -impl HashEngine { - // Basic unoptimized algorithm from Wikipedia - fn process_block(&mut self) { - debug_assert_eq!(self.buffer.len(), BLOCK_SIZE); - - let mut w = [0u32; 80]; - for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.chunks_exact(4)) { - *w_val = u32::from_be_bytes(buff_bytes.try_into().expect("4 bytes slice")) - } - for i in 16..80 { - w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1); - } - - let mut a = self.h[0]; - let mut b = self.h[1]; - let mut c = self.h[2]; - let mut d = self.h[3]; - let mut e = self.h[4]; - - for (i, &wi) in w.iter().enumerate() { - let (f, k) = match i { - 0..=19 => ((b & c) | (!b & d), 0x5a827999), - 20..=39 => (b ^ c ^ d, 0x6ed9eba1), - 40..=59 => ((b & c) | (b & d) | (c & d), 0x8f1bbcdc), - 60..=79 => (b ^ c ^ d, 0xca62c1d6), - _ => unreachable!(), - }; - - let new_a = - a.rotate_left(5).wrapping_add(f).wrapping_add(e).wrapping_add(k).wrapping_add(wi); - e = d; - d = c; - c = b.rotate_left(30); - b = a; - a = new_a; - } - - self.h[0] = self.h[0].wrapping_add(a); - self.h[1] = self.h[1].wrapping_add(b); - self.h[2] = self.h[2].wrapping_add(c); - self.h[3] = self.h[3].wrapping_add(d); - self.h[4] = self.h[4].wrapping_add(e); - } -} - -#[cfg(test)] -mod tests { - #[test] - #[cfg(feature = "alloc")] - fn test() { - use alloc::string::ToString; - - use crate::{sha1, HashEngine}; - - #[derive(Clone)] - struct Test { - input: &'static str, - output: [u8; 20], - output_str: &'static str, - } - - #[rustfmt::skip] - let tests = [ - // Examples from wikipedia - Test { - input: "", - output: [ - 0xda, 0x39, 0xa3, 0xee, - 0x5e, 0x6b, 0x4b, 0x0d, - 0x32, 0x55, 0xbf, 0xef, - 0x95, 0x60, 0x18, 0x90, - 0xaf, 0xd8, 0x07, 0x09, - ], - output_str: "da39a3ee5e6b4b0d3255bfef95601890afd80709" - }, - Test { - input: "The quick brown fox jumps over the lazy dog", - output: [ - 0x2f, 0xd4, 0xe1, 0xc6, - 0x7a, 0x2d, 0x28, 0xfc, - 0xed, 0x84, 0x9e, 0xe1, - 0xbb, 0x76, 0xe7, 0x39, - 0x1b, 0x93, 0xeb, 0x12, - ], - output_str: "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", - }, - Test { - input: "The quick brown fox jumps over the lazy cog", - output: [ - 0xde, 0x9f, 0x2c, 0x7f, - 0xd2, 0x5e, 0x1b, 0x3a, - 0xfa, 0xd3, 0xe8, 0x5a, - 0x0b, 0xd1, 0x7d, 0x9b, - 0x10, 0x0d, 0xb4, 0xb3, - ], - output_str: "de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3", - }, - ]; - - for test in tests { - // Hash through high-level API, check hex encoding/decoding - let hash = sha1::Hash::hash(test.input.as_bytes()); - assert_eq!(hash, test.output_str.parse::().expect("parse hex")); - assert_eq!(hash.as_byte_array(), &test.output); - assert_eq!(hash.to_string(), test.output_str); - - // Hash through engine, checking that we can input byte by byte - let mut engine = sha1::Hash::engine(); - for ch in test.input.as_bytes() { - engine.input(&[*ch]); - } - let manual_hash = sha1::Hash::from_engine(engine); - assert_eq!(hash, manual_hash); - assert_eq!(hash.to_byte_array(), test.output); - } - } - - #[test] - #[cfg(feature = "serde")] - fn sha1_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - use crate::sha1; - - #[rustfmt::skip] - static HASH_BYTES: [u8; 20] = [ - 0x13, 0x20, 0x72, 0xdf, - 0x69, 0x09, 0x33, 0x83, - 0x5e, 0xb8, 0xb6, 0xad, - 0x0b, 0x77, 0xe7, 0xb6, - 0xf1, 0x4a, 0xca, 0xd7, - ]; - - let hash = sha1::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); - assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); - assert_tokens(&hash.readable(), &[Token::Str("132072df690933835eb8b6ad0b77e7b6f14acad7")]); - } -} - -#[cfg(bench)] -mod benches { - use test::Bencher; - - use crate::{sha1, Hash, HashEngine}; - - #[bench] - pub fn sha1_10(bh: &mut Bencher) { - let mut engine = sha1::Hash::engine(); - let bytes = [1u8; 10]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn sha1_1k(bh: &mut Bencher) { - let mut engine = sha1::Hash::engine(); - let bytes = [1u8; 1024]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn sha1_64k(bh: &mut Bencher) { - let mut engine = sha1::Hash::engine(); - let bytes = [1u8; 65536]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } -} diff --git a/hashes/src/sha1/benches.rs b/hashes/src/sha1/benches.rs new file mode 100644 index 000000000..fcf2518df --- /dev/null +++ b/hashes/src/sha1/benches.rs @@ -0,0 +1,33 @@ +use test::Bencher; + +use crate::{sha1, Hash, HashEngine}; + +#[bench] +pub fn sha1_10(bh: &mut Bencher) { + let mut engine = sha1::Hash::engine(); + let bytes = [1u8; 10]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn sha1_1k(bh: &mut Bencher) { + let mut engine = sha1::Hash::engine(); + let bytes = [1u8; 1024]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn sha1_64k(bh: &mut Bencher) { + let mut engine = sha1::Hash::engine(); + let bytes = [1u8; 65536]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} diff --git a/hashes/src/sha1/crypto.rs b/hashes/src/sha1/crypto.rs new file mode 100644 index 000000000..6f8a3fd8a --- /dev/null +++ b/hashes/src/sha1/crypto.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: CC0-1.0 + +use super::{HashEngine, BLOCK_SIZE}; + +impl HashEngine { + // Basic unoptimized algorithm from Wikipedia + pub(super) fn process_block(&mut self) { + debug_assert_eq!(self.buffer.len(), BLOCK_SIZE); + + let mut w = [0u32; 80]; + for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.chunks_exact(4)) { + *w_val = u32::from_be_bytes(buff_bytes.try_into().expect("4 bytes slice")) + } + for i in 16..80 { + w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1); + } + + let mut a = self.h[0]; + let mut b = self.h[1]; + let mut c = self.h[2]; + let mut d = self.h[3]; + let mut e = self.h[4]; + + for (i, &wi) in w.iter().enumerate() { + let (f, k) = match i { + 0..=19 => ((b & c) | (!b & d), 0x5a827999), + 20..=39 => (b ^ c ^ d, 0x6ed9eba1), + 40..=59 => ((b & c) | (b & d) | (c & d), 0x8f1bbcdc), + 60..=79 => (b ^ c ^ d, 0xca62c1d6), + _ => unreachable!(), + }; + + let new_a = + a.rotate_left(5).wrapping_add(f).wrapping_add(e).wrapping_add(k).wrapping_add(wi); + e = d; + d = c; + c = b.rotate_left(30); + b = a; + a = new_a; + } + + self.h[0] = self.h[0].wrapping_add(a); + self.h[1] = self.h[1].wrapping_add(b); + self.h[2] = self.h[2].wrapping_add(c); + self.h[3] = self.h[3].wrapping_add(d); + self.h[4] = self.h[4].wrapping_add(e); + } +} diff --git a/hashes/src/sha1/mod.rs b/hashes/src/sha1/mod.rs new file mode 100644 index 000000000..6c198d185 --- /dev/null +++ b/hashes/src/sha1/mod.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! SHA1 implementation. + +#[cfg(bench)] +mod benches; +mod crypto; +#[cfg(bench)] +mod tests; + +use core::cmp; + +use crate::{incomplete_block_len, HashEngine as _}; + +crate::internal_macros::general_hash_type! { + 160, + false, + "Output of the SHA1 hash function." +} + +fn from_engine(mut e: HashEngine) -> Hash { + // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 8]; + e.input(&[0x80]); + if incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); + + e.input(&(8 * n_bytes_hashed).to_be_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); + + Hash(e.midstate()) +} + +const BLOCK_SIZE: usize = 64; + +/// Engine to compute SHA1 hash function. +#[derive(Clone)] +pub struct HashEngine { + buffer: [u8; BLOCK_SIZE], + h: [u32; 5], + bytes_hashed: u64, +} + +impl HashEngine { + /// Constructs a new SHA1 hash engine. + pub const fn new() -> Self { + Self { + h: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0], + bytes_hashed: 0, + buffer: [0; BLOCK_SIZE], + } + } + + #[cfg(not(hashes_fuzz))] + pub(crate) fn midstate(&self) -> [u8; 20] { + let mut ret = [0; 20]; + for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) { + ret_bytes.copy_from_slice(&val.to_be_bytes()) + } + ret + } + + #[cfg(hashes_fuzz)] + pub(crate) fn midstate(&self) -> [u8; 20] { + let mut ret = [0; 20]; + ret.copy_from_slice(&self.buffer[..20]); + ret + } +} + +impl Default for HashEngine { + fn default() -> Self { Self::new() } +} + +impl crate::HashEngine for HashEngine { + const BLOCK_SIZE: usize = 64; + + fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } + + crate::internal_macros::engine_input_impl!(); +} diff --git a/hashes/src/sha1/tests.rs b/hashes/src/sha1/tests.rs new file mode 100644 index 000000000..2bfe37b1c --- /dev/null +++ b/hashes/src/sha1/tests.rs @@ -0,0 +1,90 @@ +#[test] +#[cfg(feature = "alloc")] +fn test() { + use alloc::string::ToString; + + use crate::{sha1, HashEngine}; + + #[derive(Clone)] + struct Test { + input: &'static str, + output: [u8; 20], + output_str: &'static str, + } + + #[rustfmt::skip] + let tests = [ + // Examples from wikipedia + Test { + input: "", + output: [ + 0xda, 0x39, 0xa3, 0xee, + 0x5e, 0x6b, 0x4b, 0x0d, + 0x32, 0x55, 0xbf, 0xef, + 0x95, 0x60, 0x18, 0x90, + 0xaf, 0xd8, 0x07, 0x09, + ], + output_str: "da39a3ee5e6b4b0d3255bfef95601890afd80709" + }, + Test { + input: "The quick brown fox jumps over the lazy dog", + output: [ + 0x2f, 0xd4, 0xe1, 0xc6, + 0x7a, 0x2d, 0x28, 0xfc, + 0xed, 0x84, 0x9e, 0xe1, + 0xbb, 0x76, 0xe7, 0x39, + 0x1b, 0x93, 0xeb, 0x12, + ], + output_str: "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", + }, + Test { + input: "The quick brown fox jumps over the lazy cog", + output: [ + 0xde, 0x9f, 0x2c, 0x7f, + 0xd2, 0x5e, 0x1b, 0x3a, + 0xfa, 0xd3, 0xe8, 0x5a, + 0x0b, 0xd1, 0x7d, 0x9b, + 0x10, 0x0d, 0xb4, 0xb3, + ], + output_str: "de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3", + }, + ]; + + for test in tests { + // Hash through high-level API, check hex encoding/decoding + let hash = sha1::Hash::hash(test.input.as_bytes()); + assert_eq!(hash, test.output_str.parse::().expect("parse hex")); + assert_eq!(hash.as_byte_array(), &test.output); + assert_eq!(hash.to_string(), test.output_str); + + // Hash through engine, checking that we can input byte by byte + let mut engine = sha1::Hash::engine(); + for ch in test.input.as_bytes() { + engine.input(&[*ch]); + } + let manual_hash = sha1::Hash::from_engine(engine); + assert_eq!(hash, manual_hash); + assert_eq!(hash.to_byte_array(), test.output); + } +} + +#[test] +#[cfg(feature = "serde")] +fn sha1_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + use crate::sha1; + + #[rustfmt::skip] + static HASH_BYTES: [u8; 20] = [ + 0x13, 0x20, 0x72, 0xdf, + 0x69, 0x09, 0x33, 0x83, + 0x5e, 0xb8, 0xb6, 0xad, + 0x0b, 0x77, 0xe7, 0xb6, + 0xf1, 0x4a, 0xca, 0xd7, + ]; + + let hash = sha1::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); + assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); + assert_tokens(&hash.readable(), &[Token::Str("132072df690933835eb8b6ad0b77e7b6f14acad7")]); +} diff --git a/hashes/src/sha256/benches.rs b/hashes/src/sha256/benches.rs new file mode 100644 index 000000000..f6e270d90 --- /dev/null +++ b/hashes/src/sha256/benches.rs @@ -0,0 +1,33 @@ +use test::Bencher; + +use crate::{sha256, Hash, HashEngine}; + +#[bench] +pub fn sha256_10(bh: &mut Bencher) { + let mut engine = sha256::Hash::engine(); + let bytes = [1u8; 10]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn sha256_1k(bh: &mut Bencher) { + let mut engine = sha256::Hash::engine(); + let bytes = [1u8; 1024]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn sha256_64k(bh: &mut Bencher) { + let mut engine = sha256::Hash::engine(); + let bytes = [1u8; 65536]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} diff --git a/hashes/src/sha256.rs b/hashes/src/sha256/crypto.rs similarity index 61% rename from hashes/src/sha256.rs rename to hashes/src/sha256/crypto.rs index 8770b332b..210eb6e6e 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256/crypto.rs @@ -1,260 +1,11 @@ // SPDX-License-Identifier: CC0-1.0 -//! SHA256 implementation. - #[cfg(all(feature = "std", target_arch = "x86"))] use core::arch::x86::*; #[cfg(all(feature = "std", target_arch = "x86_64"))] use core::arch::x86_64::*; -use core::{cmp, convert, fmt}; -use crate::{incomplete_block_len, sha256d, HashEngine as _}; -#[cfg(doc)] -use crate::{sha256t, sha256t_tag}; - -crate::internal_macros::general_hash_type! { - 256, - false, - "Output of the SHA256 hash function." -} - -#[cfg(not(hashes_fuzz))] -fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 8]; - e.input(&[0x80]); - if incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - - e.input(&(8 * n_bytes_hashed).to_be_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); - - Hash(e.midstate_unchecked().bytes) -} - -#[cfg(hashes_fuzz)] -fn from_engine(e: HashEngine) -> Hash { - let mut hash = e.midstate_unchecked().bytes; - if hash == [0; 32] { - // Assume sha256 is secure and never generate 0-hashes (which represent invalid - // secp256k1 secret keys, causing downstream application breakage). - hash[0] = 1; - } - Hash(hash) -} - -const BLOCK_SIZE: usize = 64; - -/// Engine to compute SHA256 hash function. -#[derive(Clone)] -pub struct HashEngine { - buffer: [u8; BLOCK_SIZE], - h: [u32; 8], - bytes_hashed: u64, -} - -impl HashEngine { - /// Constructs a new SHA256 hash engine. - pub const fn new() -> Self { - Self { - h: [ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, - 0x5be0cd19, - ], - bytes_hashed: 0, - buffer: [0; BLOCK_SIZE], - } - } - - /// Constructs a new [`HashEngine`] from a [`Midstate`]. - /// - /// Please see docs on [`Midstate`] before using this function. - pub fn from_midstate(midstate: Midstate) -> HashEngine { - let mut ret = [0; 8]; - for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate.as_ref().chunks_exact(4)) { - *ret_val = u32::from_be_bytes(midstate_bytes.try_into().expect("4 byte slice")); - } - - HashEngine { buffer: [0; BLOCK_SIZE], h: ret, bytes_hashed: midstate.bytes_hashed } - } - - /// Returns `true` if the midstate can be extracted from this engine. - /// - /// The midstate can only be extracted if the number of bytes input into - /// the hash engine is a multiple of 64. See caveat on [`Self::midstate`]. - /// - /// Please see docs on [`Midstate`] before using this function. - pub const fn can_extract_midstate(&self) -> bool { self.bytes_hashed % 64 == 0 } - - /// Outputs the midstate of the hash engine. - /// - /// Please see docs on [`Midstate`] before using this function. - pub fn midstate(&self) -> Result { - if !self.can_extract_midstate() { - return Err(MidstateError { invalid_n_bytes_hashed: self.bytes_hashed }); - } - Ok(self.midstate_unchecked()) - } - - // Does not check that `HashEngine::can_extract_midstate`. - #[cfg(not(hashes_fuzz))] - fn midstate_unchecked(&self) -> Midstate { - let mut ret = [0; 32]; - for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) { - ret_bytes.copy_from_slice(&val.to_be_bytes()); - } - Midstate { bytes: ret, bytes_hashed: self.bytes_hashed } - } - - // Does not check that `HashEngine::can_extract_midstate`. - #[cfg(hashes_fuzz)] - fn midstate_unchecked(&self) -> Midstate { - let mut ret = [0; 32]; - ret.copy_from_slice(&self.buffer[..32]); - Midstate { bytes: ret, bytes_hashed: self.bytes_hashed } - } -} - -impl Default for HashEngine { - fn default() -> Self { Self::new() } -} - -impl crate::HashEngine for HashEngine { - const BLOCK_SIZE: usize = 64; - - fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } - - crate::internal_macros::engine_input_impl!(); -} - -impl Hash { - /// Iterate the sha256 algorithm to turn a sha256 hash into a sha256d hash - #[must_use] - pub fn hash_again(&self) -> sha256d::Hash { - crate::Hash::from_byte_array(::hash(&self.0).0) - } - - /// Computes hash from `bytes` in `const` context. - /// - /// Warning: this function is inefficient. It should be only used in `const` context. - #[deprecated(since = "0.15.0", note = "use `Self::hash_unoptimized` instead")] - pub const fn const_hash(bytes: &[u8]) -> Self { Hash::hash_unoptimized(bytes) } - - /// Computes hash from `bytes` in `const` context. - /// - /// Warning: this function is inefficient. It should be only used in `const` context. - pub const fn hash_unoptimized(bytes: &[u8]) -> Self { - Hash(Midstate::compute_midstate_unoptimized(bytes, true).bytes) - } -} - -/// Unfinalized output of the SHA256 hash function. -/// -/// The `Midstate` type is obscure and specialized and should not be used unless you are sure of -/// what you are doing. -/// -/// It represents "partially hashed data" but does not itself have properties of cryptographic -/// hashes. For example, when (ab)used as hashes, midstates are vulnerable to trivial -/// length-extension attacks. They are typically used to optimize the computation of full hashes. -/// For example, when implementing BIP-340 tagged hashes, which always begin by hashing the same -/// fixed 64-byte prefix, it makes sense to hash the prefix once, store the midstate as a constant, -/// and hash any future data starting from the constant rather than from a fresh hash engine. -/// -/// For BIP-340 support we provide the [`sha256t`] module, and the [`sha256t_tag`] macro which will -/// create the midstate for you in const context. -#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Midstate { - /// Raw bytes of the midstate i.e., the already-hashed contents of the hash engine. - bytes: [u8; 32], - /// Number of bytes hashed to achieve this midstate. - // INVARIANT must always be a multiple of 64. - bytes_hashed: u64, -} - -impl Midstate { - /// Construct a new [`Midstate`] from the `state` and the `bytes_hashed` to get to that state. - /// - /// # Panics - /// - /// Panics if `bytes_hashed` is not a multiple of 64. - pub const fn new(state: [u8; 32], bytes_hashed: u64) -> Self { - if bytes_hashed % 64 != 0 { - panic!("bytes hashed is not a multiple of 64"); - } - - Midstate { bytes: state, bytes_hashed } - } - - /// Deconstructs the [`Midstate`], returning the underlying byte array and number of bytes hashed. - pub const fn as_parts(&self) -> (&[u8; 32], u64) { (&self.bytes, self.bytes_hashed) } - - /// Deconstructs the [`Midstate`], returning the underlying byte array and number of bytes hashed. - pub const fn to_parts(self) -> ([u8; 32], u64) { (self.bytes, self.bytes_hashed) } - - /// Constructs a new midstate for tagged hashes. - /// - /// Warning: this function is inefficient. It should be only used in `const` context. - /// - /// Computes non-finalized hash of `sha256(tag) || sha256(tag)` for use in [`sha256t`]. It's - /// provided for use with [`sha256t`]. - #[must_use] - pub const fn hash_tag(tag: &[u8]) -> Self { - let hash = Hash::hash_unoptimized(tag); - let mut buf = [0u8; 64]; - let mut i = 0usize; - while i < buf.len() { - buf[i] = hash.0[i % hash.0.len()]; - i += 1; - } - Self::compute_midstate_unoptimized(&buf, false) - } -} - -impl fmt::Debug for Midstate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - struct Encoder<'a> { - bytes: &'a [u8; 32], - } - impl fmt::Debug for Encoder<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { crate::debug_hex(self.bytes, f) } - } - - f.debug_struct("Midstate") - .field("bytes", &Encoder { bytes: &self.bytes }) - .field("length", &self.bytes_hashed) - .finish() - } -} - -impl convert::AsRef<[u8]> for Midstate { - fn as_ref(&self) -> &[u8] { &self.bytes } -} - -/// `Midstate` invariant violated (not a multiple of 64). -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MidstateError { - /// The invalid number of bytes hashed. - invalid_n_bytes_hashed: u64, -} - -impl fmt::Display for MidstateError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "invalid number of bytes hashed {} (should have been a multiple of 64)", - self.invalid_n_bytes_hashed - ) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for MidstateError {} +use super::{HashEngine, Midstate, BLOCK_SIZE}; #[allow(non_snake_case)] const fn Ch(x: u32, y: u32, z: u32) -> u32 { z ^ (x & (y ^ z)) } @@ -345,7 +96,7 @@ impl Midstate { w } - const fn compute_midstate_unoptimized(bytes: &[u8], finalize: bool) -> Self { + pub(super) const fn compute_midstate_unoptimized(bytes: &[u8], finalize: bool) -> Self { let mut state = [ 0x6a09e667u32, 0xbb67ae85, @@ -495,7 +246,7 @@ impl Midstate { } impl HashEngine { - fn process_block(&mut self) { + pub(super) fn process_block(&mut self) { #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))] { if std::is_x86_feature_detected!("sse4.1") @@ -866,277 +617,3 @@ impl HashEngine { self.h[7] = self.h[7].wrapping_add(h); } } - -#[cfg(test)] -mod tests { - use core::array; - - use super::*; - use crate::{sha256, HashEngine}; - - #[test] - #[cfg(feature = "alloc")] - fn test() { - use alloc::string::ToString; - - #[derive(Clone)] - struct Test { - input: &'static str, - output: [u8; 32], - output_str: &'static str, - } - - #[rustfmt::skip] - let tests = [ - // Examples from wikipedia - Test { - input: "", - output: [ - 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, - 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, - 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, - 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, - ], - output_str: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - }, - Test { - input: "The quick brown fox jumps over the lazy dog", - output: [ - 0xd7, 0xa8, 0xfb, 0xb3, 0x07, 0xd7, 0x80, 0x94, - 0x69, 0xca, 0x9a, 0xbc, 0xb0, 0x08, 0x2e, 0x4f, - 0x8d, 0x56, 0x51, 0xe4, 0x6d, 0x3c, 0xdb, 0x76, - 0x2d, 0x02, 0xd0, 0xbf, 0x37, 0xc9, 0xe5, 0x92, - ], - output_str: "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", - }, - Test { - input: "The quick brown fox jumps over the lazy dog.", - output: [ - 0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, - 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, 0x3d, 0x97, - 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2, - 0xb7, 0x65, 0x44, 0x8c, 0x86, 0x35, 0xfb, 0x6c, - ], - output_str: "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c", - }, - ]; - - for test in tests { - // Hash through high-level API, check hex encoding/decoding - let hash = sha256::Hash::hash(test.input.as_bytes()); - assert_eq!(hash, test.output_str.parse::().expect("parse hex")); - assert_eq!(hash.as_byte_array(), &test.output); - assert_eq!(hash.to_string(), test.output_str); - - // Hash through engine, checking that we can input byte by byte - let mut engine = sha256::Hash::engine(); - for ch in test.input.as_bytes() { - engine.input(&[*ch]); - } - let manual_hash = sha256::Hash::from_engine(engine); - assert_eq!(hash, manual_hash); - assert_eq!(hash.to_byte_array(), test.output); - } - } - - #[test] - #[cfg(feature = "alloc")] - fn fmt_roundtrips() { - use alloc::format; - - let hash = sha256::Hash::hash(b"some arbitrary bytes"); - let hex = format!("{}", hash); - let rinsed = hex.parse::().expect("failed to parse hex"); - assert_eq!(rinsed, hash) - } - - #[test] - #[rustfmt::skip] - pub(crate) fn midstate() { - // Test vector obtained by doing an asset issuance on Elements - let mut engine = sha256::Hash::engine(); - // sha256dhash of outpoint - // 73828cbc65fd68ab78dc86992b76ae50ae2bf8ceedbe8de0483172f0886219f7:0 - engine.input(&[ - 0x9d, 0xd0, 0x1b, 0x56, 0xb1, 0x56, 0x45, 0x14, - 0x3e, 0xad, 0x15, 0x8d, 0xec, 0x19, 0xf8, 0xce, - 0xa9, 0x0b, 0xd0, 0xa9, 0xb2, 0xf8, 0x1d, 0x21, - 0xff, 0xa3, 0xa4, 0xc6, 0x44, 0x81, 0xd4, 0x1c, - ]); - // 32 bytes of zeroes representing "new asset" - engine.input(&[0; 32]); - - // RPC output - static WANT: Midstate = sha256::Midstate::new([ - 0x0b, 0xcf, 0xe0, 0xe5, 0x4e, 0x6c, 0xc7, 0xd3, - 0x4f, 0x4f, 0x7c, 0x1d, 0xf0, 0xb0, 0xf5, 0x03, - 0xf2, 0xf7, 0x12, 0x91, 0x2a, 0x06, 0x05, 0xb4, - 0x14, 0xed, 0x33, 0x7f, 0x7f, 0x03, 0x2e, 0x03, - ], 64); - - assert_eq!( - engine.midstate().expect("total_bytes_hashed is valid"), - WANT, - ); - } - - #[test] - fn engine_with_state() { - let mut engine = sha256::Hash::engine(); - let midstate_engine = sha256::HashEngine::from_midstate(engine.midstate_unchecked()); - // Fresh engine and engine initialized with fresh state should have same state - assert_eq!(engine.h, midstate_engine.h); - - // Midstate changes after writing 64 bytes - engine.input(&[1; 63]); - assert_eq!(engine.h, midstate_engine.h); - engine.input(&[2; 1]); - assert_ne!(engine.h, midstate_engine.h); - - // Initializing an engine with midstate from another engine should result in - // both engines producing the same hashes - let data_vec: &[&[u8]] = &[&[3u8; 1], &[4u8; 63], &[5u8; 65], &[6u8; 66]]; - for data in data_vec { - let mut engine = engine.clone(); - let mut midstate_engine = - sha256::HashEngine::from_midstate(engine.midstate_unchecked()); - assert_eq!(engine.h, midstate_engine.h); - assert_eq!(engine.bytes_hashed, midstate_engine.bytes_hashed); - engine.input(data); - midstate_engine.input(data); - assert_eq!(engine.h, midstate_engine.h); - let hash1 = sha256::Hash::from_engine(engine); - let hash2 = sha256::Hash::from_engine(midstate_engine); - assert_eq!(hash1, hash2); - } - - // Test that a specific midstate results in a specific hash. Midstate was - // obtained by applying sha256 to sha256("MuSig coefficient")||sha256("MuSig - // coefficient"). - #[rustfmt::skip] - static MIDSTATE: [u8; 32] = [ - 0x0f, 0xd0, 0x69, 0x0c, 0xfe, 0xfe, 0xae, 0x97, - 0x99, 0x6e, 0xac, 0x7f, 0x5c, 0x30, 0xd8, 0x64, - 0x8c, 0x4a, 0x05, 0x73, 0xac, 0xa1, 0xa2, 0x2f, - 0x6f, 0x43, 0xb8, 0x01, 0x85, 0xce, 0x27, 0xcd, - ]; - #[rustfmt::skip] - static HASH_EXPECTED: [u8; 32] = [ - 0x18, 0x84, 0xe4, 0x72, 0x40, 0x4e, 0xf4, 0x5a, - 0xb4, 0x9c, 0x4e, 0xa4, 0x9a, 0xe6, 0x23, 0xa8, - 0x88, 0x52, 0x7f, 0x7d, 0x8a, 0x06, 0x94, 0x20, - 0x8f, 0xf1, 0xf7, 0xa9, 0xd5, 0x69, 0x09, 0x59, - ]; - let midstate_engine = - sha256::HashEngine::from_midstate(sha256::Midstate::new(MIDSTATE, 64)); - let hash = sha256::Hash::from_engine(midstate_engine); - assert_eq!(hash, sha256::Hash(HASH_EXPECTED)); - } - - #[test] - fn hash_unoptimized() { - let bytes: [u8; 256] = array::from_fn(|i| i as u8); - - for i in 0..=256 { - let bytes = &bytes[0..i]; - assert_eq!( - Hash::hash(bytes), - Hash::hash_unoptimized(bytes), - "hashes don't match for n_bytes_hashed {}", - i + 1 - ); - } - } - - // The midstate of an empty hash engine tagged with "TapLeaf". - const TAP_LEAF_MIDSTATE: Midstate = Midstate::new( - [ - 156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211, 243, - 147, 108, 71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201, - ], - 64, - ); - - #[test] - fn const_midstate() { assert_eq!(Midstate::hash_tag(b"TapLeaf"), TAP_LEAF_MIDSTATE,) } - - #[test] - #[cfg(feature = "alloc")] - fn regression_midstate_debug_format() { - use alloc::format; - - let want = "Midstate { bytes: 9ce0e4e67c116c3938b3caf2c30f5089d3f3936c47636e607db33eeaddc6f0c9, length: 64 }"; - let got = format!("{:?}", TAP_LEAF_MIDSTATE); - assert_eq!(got, want); - } - - #[test] - #[cfg(feature = "serde")] - fn sha256_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - #[rustfmt::skip] - static HASH_BYTES: [u8; 32] = [ - 0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, - 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, 0x3d, 0x97, - 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2, - 0xb7, 0x65, 0x44, 0x8c, 0x86, 0x35, 0xfb, 0x6c, - ]; - - let hash = sha256::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); - assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); - assert_tokens( - &hash.readable(), - &[Token::Str("ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c")], - ); - } - - #[cfg(target_arch = "wasm32")] - mod wasm_tests { - use super::*; - #[test] - #[wasm_bindgen_test::wasm_bindgen_test] - fn sha256_tests() { - test(); - midstate(); - engine_with_state(); - } - } -} - -#[cfg(bench)] -mod benches { - use test::Bencher; - - use crate::{sha256, Hash, HashEngine}; - - #[bench] - pub fn sha256_10(bh: &mut Bencher) { - let mut engine = sha256::Hash::engine(); - let bytes = [1u8; 10]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn sha256_1k(bh: &mut Bencher) { - let mut engine = sha256::Hash::engine(); - let bytes = [1u8; 1024]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn sha256_64k(bh: &mut Bencher) { - let mut engine = sha256::Hash::engine(); - let bytes = [1u8; 65536]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } -} diff --git a/hashes/src/sha256/mod.rs b/hashes/src/sha256/mod.rs new file mode 100644 index 000000000..70bf9121b --- /dev/null +++ b/hashes/src/sha256/mod.rs @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! SHA256 implementation. + +#[cfg(bench)] +mod benches; +mod crypto; +#[cfg(bench)] +mod tests; + +use core::{cmp, convert, fmt}; + +use crate::{incomplete_block_len, sha256d, HashEngine as _}; +#[cfg(doc)] +use crate::{sha256t, sha256t_tag}; + +crate::internal_macros::general_hash_type! { + 256, + false, + "Output of the SHA256 hash function." +} + +#[cfg(not(hashes_fuzz))] +fn from_engine(mut e: HashEngine) -> Hash { + // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 8]; + e.input(&[0x80]); + if incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); + + e.input(&(8 * n_bytes_hashed).to_be_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); + + Hash(e.midstate_unchecked().bytes) +} + +#[cfg(hashes_fuzz)] +fn from_engine(e: HashEngine) -> Hash { + let mut hash = e.midstate_unchecked().bytes; + if hash == [0; 32] { + // Assume sha256 is secure and never generate 0-hashes (which represent invalid + // secp256k1 secret keys, causing downstream application breakage). + hash[0] = 1; + } + Hash(hash) +} + +const BLOCK_SIZE: usize = 64; + +/// Engine to compute SHA256 hash function. +#[derive(Clone)] +pub struct HashEngine { + buffer: [u8; BLOCK_SIZE], + h: [u32; 8], + bytes_hashed: u64, +} + +impl HashEngine { + /// Constructs a new SHA256 hash engine. + pub const fn new() -> Self { + Self { + h: [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, + 0x5be0cd19, + ], + bytes_hashed: 0, + buffer: [0; BLOCK_SIZE], + } + } + + /// Constructs a new [`HashEngine`] from a [`Midstate`]. + /// + /// Please see docs on [`Midstate`] before using this function. + pub fn from_midstate(midstate: Midstate) -> HashEngine { + let mut ret = [0; 8]; + for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate.as_ref().chunks_exact(4)) { + *ret_val = u32::from_be_bytes(midstate_bytes.try_into().expect("4 byte slice")); + } + + HashEngine { buffer: [0; BLOCK_SIZE], h: ret, bytes_hashed: midstate.bytes_hashed } + } + + /// Returns `true` if the midstate can be extracted from this engine. + /// + /// The midstate can only be extracted if the number of bytes input into + /// the hash engine is a multiple of 64. See caveat on [`Self::midstate`]. + /// + /// Please see docs on [`Midstate`] before using this function. + pub const fn can_extract_midstate(&self) -> bool { self.bytes_hashed % 64 == 0 } + + /// Outputs the midstate of the hash engine. + /// + /// Please see docs on [`Midstate`] before using this function. + pub fn midstate(&self) -> Result { + if !self.can_extract_midstate() { + return Err(MidstateError { invalid_n_bytes_hashed: self.bytes_hashed }); + } + Ok(self.midstate_unchecked()) + } + + // Does not check that `HashEngine::can_extract_midstate`. + #[cfg(not(hashes_fuzz))] + fn midstate_unchecked(&self) -> Midstate { + let mut ret = [0; 32]; + for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) { + ret_bytes.copy_from_slice(&val.to_be_bytes()); + } + Midstate { bytes: ret, bytes_hashed: self.bytes_hashed } + } + + // Does not check that `HashEngine::can_extract_midstate`. + #[cfg(hashes_fuzz)] + fn midstate_unchecked(&self) -> Midstate { + let mut ret = [0; 32]; + ret.copy_from_slice(&self.buffer[..32]); + Midstate { bytes: ret, bytes_hashed: self.bytes_hashed } + } +} + +impl Default for HashEngine { + fn default() -> Self { Self::new() } +} + +impl crate::HashEngine for HashEngine { + const BLOCK_SIZE: usize = 64; + + fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } + + crate::internal_macros::engine_input_impl!(); +} + +impl Hash { + /// Iterate the sha256 algorithm to turn a sha256 hash into a sha256d hash + #[must_use] + pub fn hash_again(&self) -> sha256d::Hash { + crate::Hash::from_byte_array(::hash(&self.0).0) + } + + /// Computes hash from `bytes` in `const` context. + /// + /// Warning: this function is inefficient. It should be only used in `const` context. + #[deprecated(since = "0.15.0", note = "use `Self::hash_unoptimized` instead")] + pub const fn const_hash(bytes: &[u8]) -> Self { Hash::hash_unoptimized(bytes) } + + /// Computes hash from `bytes` in `const` context. + /// + /// Warning: this function is inefficient. It should be only used in `const` context. + pub const fn hash_unoptimized(bytes: &[u8]) -> Self { + Hash(Midstate::compute_midstate_unoptimized(bytes, true).bytes) + } +} + +/// Unfinalized output of the SHA256 hash function. +/// +/// The `Midstate` type is obscure and specialized and should not be used unless you are sure of +/// what you are doing. +/// +/// It represents "partially hashed data" but does not itself have properties of cryptographic +/// hashes. For example, when (ab)used as hashes, midstates are vulnerable to trivial +/// length-extension attacks. They are typically used to optimize the computation of full hashes. +/// For example, when implementing BIP-340 tagged hashes, which always begin by hashing the same +/// fixed 64-byte prefix, it makes sense to hash the prefix once, store the midstate as a constant, +/// and hash any future data starting from the constant rather than from a fresh hash engine. +/// +/// For BIP-340 support we provide the [`sha256t`] module, and the [`sha256t_tag`] macro which will +/// create the midstate for you in const context. +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Midstate { + /// Raw bytes of the midstate i.e., the already-hashed contents of the hash engine. + bytes: [u8; 32], + /// Number of bytes hashed to achieve this midstate. + // INVARIANT must always be a multiple of 64. + bytes_hashed: u64, +} + +impl Midstate { + /// Construct a new [`Midstate`] from the `state` and the `bytes_hashed` to get to that state. + /// + /// # Panics + /// + /// Panics if `bytes_hashed` is not a multiple of 64. + pub const fn new(state: [u8; 32], bytes_hashed: u64) -> Self { + if bytes_hashed % 64 != 0 { + panic!("bytes hashed is not a multiple of 64"); + } + + Midstate { bytes: state, bytes_hashed } + } + + /// Deconstructs the [`Midstate`], returning the underlying byte array and number of bytes hashed. + pub const fn as_parts(&self) -> (&[u8; 32], u64) { (&self.bytes, self.bytes_hashed) } + + /// Deconstructs the [`Midstate`], returning the underlying byte array and number of bytes hashed. + pub const fn to_parts(self) -> ([u8; 32], u64) { (self.bytes, self.bytes_hashed) } + + /// Constructs a new midstate for tagged hashes. + /// + /// Warning: this function is inefficient. It should be only used in `const` context. + /// + /// Computes non-finalized hash of `sha256(tag) || sha256(tag)` for use in [`sha256t`]. It's + /// provided for use with [`sha256t`]. + #[must_use] + pub const fn hash_tag(tag: &[u8]) -> Self { + let hash = Hash::hash_unoptimized(tag); + let mut buf = [0u8; 64]; + let mut i = 0usize; + while i < buf.len() { + buf[i] = hash.0[i % hash.0.len()]; + i += 1; + } + Self::compute_midstate_unoptimized(&buf, false) + } +} + +impl fmt::Debug for Midstate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + struct Encoder<'a> { + bytes: &'a [u8; 32], + } + impl fmt::Debug for Encoder<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { crate::debug_hex(self.bytes, f) } + } + + f.debug_struct("Midstate") + .field("bytes", &Encoder { bytes: &self.bytes }) + .field("length", &self.bytes_hashed) + .finish() + } +} + +impl convert::AsRef<[u8]> for Midstate { + fn as_ref(&self) -> &[u8] { &self.bytes } +} + +/// `Midstate` invariant violated (not a multiple of 64). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MidstateError { + /// The invalid number of bytes hashed. + invalid_n_bytes_hashed: u64, +} + +impl fmt::Display for MidstateError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "invalid number of bytes hashed {} (should have been a multiple of 64)", + self.invalid_n_bytes_hashed + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for MidstateError {} diff --git a/hashes/src/sha256/tests.rs b/hashes/src/sha256/tests.rs new file mode 100644 index 000000000..65f166e17 --- /dev/null +++ b/hashes/src/sha256/tests.rs @@ -0,0 +1,231 @@ +use core::array; + +use super::*; +use crate::{sha256, HashEngine}; + +#[test] +#[cfg(feature = "alloc")] +fn test() { + use alloc::string::ToString; + + #[derive(Clone)] + struct Test { + input: &'static str, + output: [u8; 32], + output_str: &'static str, + } + + #[rustfmt::skip] + let tests = [ + // Examples from wikipedia + Test { + input: "", + output: [ + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, + ], + output_str: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + Test { + input: "The quick brown fox jumps over the lazy dog", + output: [ + 0xd7, 0xa8, 0xfb, 0xb3, 0x07, 0xd7, 0x80, 0x94, + 0x69, 0xca, 0x9a, 0xbc, 0xb0, 0x08, 0x2e, 0x4f, + 0x8d, 0x56, 0x51, 0xe4, 0x6d, 0x3c, 0xdb, 0x76, + 0x2d, 0x02, 0xd0, 0xbf, 0x37, 0xc9, 0xe5, 0x92, + ], + output_str: "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", + }, + Test { + input: "The quick brown fox jumps over the lazy dog.", + output: [ + 0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, + 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, 0x3d, 0x97, + 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2, + 0xb7, 0x65, 0x44, 0x8c, 0x86, 0x35, 0xfb, 0x6c, + ], + output_str: "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c", + }, + ]; + + for test in tests { + // Hash through high-level API, check hex encoding/decoding + let hash = sha256::Hash::hash(test.input.as_bytes()); + assert_eq!(hash, test.output_str.parse::().expect("parse hex")); + assert_eq!(hash.as_byte_array(), &test.output); + assert_eq!(hash.to_string(), test.output_str); + + // Hash through engine, checking that we can input byte by byte + let mut engine = sha256::Hash::engine(); + for ch in test.input.as_bytes() { + engine.input(&[*ch]); + } + let manual_hash = sha256::Hash::from_engine(engine); + assert_eq!(hash, manual_hash); + assert_eq!(hash.to_byte_array(), test.output); + } +} + +#[test] +#[cfg(feature = "alloc")] +fn fmt_roundtrips() { + use alloc::format; + + let hash = sha256::Hash::hash(b"some arbitrary bytes"); + let hex = format!("{}", hash); + let rinsed = hex.parse::().expect("failed to parse hex"); + assert_eq!(rinsed, hash) +} + +#[test] +#[rustfmt::skip] +pub(crate) fn midstate() { + // Test vector obtained by doing an asset issuance on Elements + let mut engine = sha256::Hash::engine(); + // sha256dhash of outpoint + // 73828cbc65fd68ab78dc86992b76ae50ae2bf8ceedbe8de0483172f0886219f7:0 + engine.input(&[ + 0x9d, 0xd0, 0x1b, 0x56, 0xb1, 0x56, 0x45, 0x14, + 0x3e, 0xad, 0x15, 0x8d, 0xec, 0x19, 0xf8, 0xce, + 0xa9, 0x0b, 0xd0, 0xa9, 0xb2, 0xf8, 0x1d, 0x21, + 0xff, 0xa3, 0xa4, 0xc6, 0x44, 0x81, 0xd4, 0x1c, + ]); + // 32 bytes of zeroes representing "new asset" + engine.input(&[0; 32]); + + // RPC output + static WANT: Midstate = sha256::Midstate::new([ + 0x0b, 0xcf, 0xe0, 0xe5, 0x4e, 0x6c, 0xc7, 0xd3, + 0x4f, 0x4f, 0x7c, 0x1d, 0xf0, 0xb0, 0xf5, 0x03, + 0xf2, 0xf7, 0x12, 0x91, 0x2a, 0x06, 0x05, 0xb4, + 0x14, 0xed, 0x33, 0x7f, 0x7f, 0x03, 0x2e, 0x03, + ], 64); + + assert_eq!( + engine.midstate().expect("total_bytes_hashed is valid"), + WANT, + ); +} + +#[test] +fn engine_with_state() { + let mut engine = sha256::Hash::engine(); + let midstate_engine = sha256::HashEngine::from_midstate(engine.midstate_unchecked()); + // Fresh engine and engine initialized with fresh state should have same state + assert_eq!(engine.h, midstate_engine.h); + + // Midstate changes after writing 64 bytes + engine.input(&[1; 63]); + assert_eq!(engine.h, midstate_engine.h); + engine.input(&[2; 1]); + assert_ne!(engine.h, midstate_engine.h); + + // Initializing an engine with midstate from another engine should result in + // both engines producing the same hashes + let data_vec: &[&[u8]] = &[&[3u8; 1], &[4u8; 63], &[5u8; 65], &[6u8; 66]]; + for data in data_vec { + let mut engine = engine.clone(); + let mut midstate_engine = sha256::HashEngine::from_midstate(engine.midstate_unchecked()); + assert_eq!(engine.h, midstate_engine.h); + assert_eq!(engine.bytes_hashed, midstate_engine.bytes_hashed); + engine.input(data); + midstate_engine.input(data); + assert_eq!(engine.h, midstate_engine.h); + let hash1 = sha256::Hash::from_engine(engine); + let hash2 = sha256::Hash::from_engine(midstate_engine); + assert_eq!(hash1, hash2); + } + + // Test that a specific midstate results in a specific hash. Midstate was + // obtained by applying sha256 to sha256("MuSig coefficient")||sha256("MuSig + // coefficient"). + #[rustfmt::skip] + static MIDSTATE: [u8; 32] = [ + 0x0f, 0xd0, 0x69, 0x0c, 0xfe, 0xfe, 0xae, 0x97, + 0x99, 0x6e, 0xac, 0x7f, 0x5c, 0x30, 0xd8, 0x64, + 0x8c, 0x4a, 0x05, 0x73, 0xac, 0xa1, 0xa2, 0x2f, + 0x6f, 0x43, 0xb8, 0x01, 0x85, 0xce, 0x27, 0xcd, + ]; + #[rustfmt::skip] + static HASH_EXPECTED: [u8; 32] = [ + 0x18, 0x84, 0xe4, 0x72, 0x40, 0x4e, 0xf4, 0x5a, + 0xb4, 0x9c, 0x4e, 0xa4, 0x9a, 0xe6, 0x23, 0xa8, + 0x88, 0x52, 0x7f, 0x7d, 0x8a, 0x06, 0x94, 0x20, + 0x8f, 0xf1, 0xf7, 0xa9, 0xd5, 0x69, 0x09, 0x59, + ]; + let midstate_engine = sha256::HashEngine::from_midstate(sha256::Midstate::new(MIDSTATE, 64)); + let hash = sha256::Hash::from_engine(midstate_engine); + assert_eq!(hash, sha256::Hash(HASH_EXPECTED)); +} + +#[test] +fn hash_unoptimized() { + let bytes: [u8; 256] = array::from_fn(|i| i as u8); + + for i in 0..=256 { + let bytes = &bytes[0..i]; + assert_eq!( + Hash::hash(bytes), + Hash::hash_unoptimized(bytes), + "hashes don't match for n_bytes_hashed {}", + i + 1 + ); + } +} + +// The midstate of an empty hash engine tagged with "TapLeaf". +const TAP_LEAF_MIDSTATE: Midstate = Midstate::new( + [ + 156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211, 243, 147, + 108, 71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201, + ], + 64, +); + +#[test] +fn const_midstate() { assert_eq!(Midstate::hash_tag(b"TapLeaf"), TAP_LEAF_MIDSTATE,) } + +#[test] +#[cfg(feature = "alloc")] +fn regression_midstate_debug_format() { + use alloc::format; + + let want = "Midstate { bytes: 9ce0e4e67c116c3938b3caf2c30f5089d3f3936c47636e607db33eeaddc6f0c9, length: 64 }"; + let got = format!("{:?}", TAP_LEAF_MIDSTATE); + assert_eq!(got, want); +} + +#[test] +#[cfg(feature = "serde")] +fn sha256_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + #[rustfmt::skip] + static HASH_BYTES: [u8; 32] = [ + 0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, + 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, 0x3d, 0x97, + 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2, + 0xb7, 0x65, 0x44, 0x8c, 0x86, 0x35, 0xfb, 0x6c, + ]; + + let hash = sha256::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); + assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); + assert_tokens( + &hash.readable(), + &[Token::Str("ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c")], + ); +} + +#[cfg(target_arch = "wasm32")] +mod wasm_tests { + use super::*; + #[test] + #[wasm_bindgen_test::wasm_bindgen_test] + fn sha256_tests() { + test(); + midstate(); + engine_with_state(); + } +} diff --git a/hashes/src/sha256d.rs b/hashes/src/sha256d/mod.rs similarity index 100% rename from hashes/src/sha256d.rs rename to hashes/src/sha256d/mod.rs diff --git a/hashes/src/sha256t.rs b/hashes/src/sha256t/mod.rs similarity index 100% rename from hashes/src/sha256t.rs rename to hashes/src/sha256t/mod.rs diff --git a/hashes/src/sha384.rs b/hashes/src/sha384/mod.rs similarity index 100% rename from hashes/src/sha384.rs rename to hashes/src/sha384/mod.rs diff --git a/hashes/src/sha512/benches.rs b/hashes/src/sha512/benches.rs new file mode 100644 index 000000000..56b71800b --- /dev/null +++ b/hashes/src/sha512/benches.rs @@ -0,0 +1,33 @@ +use test::Bencher; + +use crate::{sha512, Hash, HashEngine}; + +#[bench] +pub fn sha512_10(bh: &mut Bencher) { + let mut engine = sha512::Hash::engine(); + let bytes = [1u8; 10]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn sha512_1k(bh: &mut Bencher) { + let mut engine = sha512::Hash::engine(); + let bytes = [1u8; 1024]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} + +#[bench] +pub fn sha512_64k(bh: &mut Bencher) { + let mut engine = sha512::Hash::engine(); + let bytes = [1u8; 65536]; + bh.iter(|| { + engine.input(&bytes); + }); + bh.bytes = bytes.len() as u64; +} diff --git a/hashes/src/sha512.rs b/hashes/src/sha512/crypto.rs similarity index 53% rename from hashes/src/sha512.rs rename to hashes/src/sha512/crypto.rs index 1ecd563df..cbb1c82ab 100644 --- a/hashes/src/sha512.rs +++ b/hashes/src/sha512/crypto.rs @@ -1,125 +1,6 @@ // SPDX-License-Identifier: CC0-1.0 -//! SHA512 implementation. - -use core::cmp; - -use crate::{incomplete_block_len, HashEngine as _}; - -crate::internal_macros::general_hash_type! { - 512, - false, - "Output of the SHA512 hash function." -} - -#[cfg(not(hashes_fuzz))] -pub(crate) fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 16 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 16]; - e.input(&[0x80]); - if incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - - e.input(&[0; 8]); - e.input(&(8 * n_bytes_hashed).to_be_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); - - Hash(e.midstate()) -} - -#[cfg(hashes_fuzz)] -pub(crate) fn from_engine(e: HashEngine) -> Hash { - let mut hash = e.midstate(); - hash[0] ^= 0xff; // Make this distinct from SHA-256 - Hash(hash) -} - -pub(crate) const BLOCK_SIZE: usize = 128; - -/// Engine to compute SHA512 hash function. -#[derive(Clone)] -pub struct HashEngine { - h: [u64; 8], - bytes_hashed: u64, - buffer: [u8; BLOCK_SIZE], -} - -impl HashEngine { - /// Constructs a new SHA512 hash engine. - #[rustfmt::skip] - pub const fn new() -> Self { - Self { - h: [ - 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, - 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, - ], - bytes_hashed: 0, - buffer: [0; BLOCK_SIZE], - } - } -} - -impl Default for HashEngine { - fn default() -> Self { Self::new() } -} - -impl HashEngine { - #[cfg(not(hashes_fuzz))] - pub(crate) fn midstate(&self) -> [u8; 64] { - let mut ret = [0; 64]; - for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(8)) { - ret_bytes.copy_from_slice(&val.to_be_bytes()); - } - ret - } - - #[cfg(hashes_fuzz)] - pub(crate) fn midstate(&self) -> [u8; 64] { - let mut ret = [0; 64]; - ret.copy_from_slice(&self.buffer[..64]); - ret - } - - /// Constructs a new hash engine suitable for use constructing a `sha512_256::HashEngine`. - #[rustfmt::skip] - pub(crate) const fn sha512_256() -> Self { - HashEngine { - h: [ - 0x22312194fc2bf72c, 0x9f555fa3c84c64c2, 0x2393b86b6f53b151, 0x963877195940eabd, - 0x96283ee2a88effe3, 0xbe5e1e2553863992, 0x2b0199fc2c85b8aa, 0x0eb72ddc81c52ca2, - ], - bytes_hashed: 0, - buffer: [0; BLOCK_SIZE], - } - } - - /// Constructs a new hash engine suitable for constructing a `sha384::HashEngine`. - #[rustfmt::skip] - pub(crate) const fn sha384() -> Self { - HashEngine { - h: [ - 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, - 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4, - ], - bytes_hashed: 0, - buffer: [0; BLOCK_SIZE], - } - } -} - -impl crate::HashEngine for HashEngine { - const BLOCK_SIZE: usize = 128; - - fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } - - crate::internal_macros::engine_input_impl!(); -} +use super::{HashEngine, BLOCK_SIZE}; #[allow(non_snake_case)] fn Ch(x: u64, y: u64, z: u64) -> u64 { z ^ (x & (y ^ z)) } @@ -302,152 +183,3 @@ impl HashEngine { self.h[7] = self.h[7].wrapping_add(h); } } - -#[cfg(test)] -mod tests { - #[test] - #[cfg(feature = "alloc")] - fn test() { - use alloc::string::ToString; - - use crate::{sha512, HashEngine}; - - #[derive(Clone)] - struct Test { - input: &'static str, - output: [u8; 64], - output_str: &'static str, - } - - #[rustfmt::skip] - let tests = [ - // Test vectors computed with `sha512sum` - Test { - input: "", - output: [ - 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, - 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, - 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, - 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, - 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, - 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, - 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, - 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e, - ], - output_str: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" - }, - Test { - input: "The quick brown fox jumps over the lazy dog", - output: [ - 0x07, 0xe5, 0x47, 0xd9, 0x58, 0x6f, 0x6a, 0x73, - 0xf7, 0x3f, 0xba, 0xc0, 0x43, 0x5e, 0xd7, 0x69, - 0x51, 0x21, 0x8f, 0xb7, 0xd0, 0xc8, 0xd7, 0x88, - 0xa3, 0x09, 0xd7, 0x85, 0x43, 0x6b, 0xbb, 0x64, - 0x2e, 0x93, 0xa2, 0x52, 0xa9, 0x54, 0xf2, 0x39, - 0x12, 0x54, 0x7d, 0x1e, 0x8a, 0x3b, 0x5e, 0xd6, - 0xe1, 0xbf, 0xd7, 0x09, 0x78, 0x21, 0x23, 0x3f, - 0xa0, 0x53, 0x8f, 0x3d, 0xb8, 0x54, 0xfe, 0xe6, - ], - output_str: "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6", - }, - Test { - input: "The quick brown fox jumps over the lazy dog.", - output: [ - 0x91, 0xea, 0x12, 0x45, 0xf2, 0x0d, 0x46, 0xae, - 0x9a, 0x03, 0x7a, 0x98, 0x9f, 0x54, 0xf1, 0xf7, - 0x90, 0xf0, 0xa4, 0x76, 0x07, 0xee, 0xb8, 0xa1, - 0x4d, 0x12, 0x89, 0x0c, 0xea, 0x77, 0xa1, 0xbb, - 0xc6, 0xc7, 0xed, 0x9c, 0xf2, 0x05, 0xe6, 0x7b, - 0x7f, 0x2b, 0x8f, 0xd4, 0xc7, 0xdf, 0xd3, 0xa7, - 0xa8, 0x61, 0x7e, 0x45, 0xf3, 0xc4, 0x63, 0xd4, - 0x81, 0xc7, 0xe5, 0x86, 0xc3, 0x9a, 0xc1, 0xed, - ], - output_str: "91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed", - }, - ]; - - for test in tests { - // Hash through high-level API, check hex encoding/decoding - let hash = sha512::Hash::hash(test.input.as_bytes()); - assert_eq!(hash, test.output_str.parse::().expect("parse hex")); - assert_eq!(hash.as_byte_array(), &test.output); - assert_eq!(hash.to_string(), test.output_str); - - // Hash through engine, checking that we can input byte by byte - let mut engine = sha512::Hash::engine(); - for ch in test.input.as_bytes() { - engine.input(&[*ch]); - } - let manual_hash = sha512::Hash::from_engine(engine); - assert_eq!(hash, manual_hash); - assert_eq!(hash.to_byte_array(), test.output); - } - } - - #[test] - #[cfg(feature = "serde")] - fn sha512_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - use crate::sha512; - - #[rustfmt::skip] - static HASH_BYTES: [u8; 64] = [ - 0x8b, 0x41, 0xe1, 0xb7, 0x8a, 0xd1, 0x15, 0x21, - 0x11, 0x3c, 0x52, 0xff, 0x18, 0x2a, 0x1b, 0x8e, - 0x0a, 0x19, 0x57, 0x54, 0xaa, 0x52, 0x7f, 0xcd, - 0x00, 0xa4, 0x11, 0x62, 0x0b, 0x46, 0xf2, 0x0f, - 0xff, 0xfb, 0x80, 0x88, 0xcc, 0xf8, 0x54, 0x97, - 0x12, 0x1a, 0xd4, 0x49, 0x9e, 0x08, 0x45, 0xb8, - 0x76, 0xf6, 0xdd, 0x66, 0x40, 0x08, 0x8a, 0x2f, - 0x0b, 0x2d, 0x8a, 0x60, 0x0b, 0xdf, 0x4c, 0x0c, - ]; - - let hash = sha512::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); - assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); - assert_tokens( - &hash.readable(), - &[Token::Str( - "8b41e1b78ad11521113c52ff182a1b8e0a195754aa527fcd00a411620b46f20f\ - fffb8088ccf85497121ad4499e0845b876f6dd6640088a2f0b2d8a600bdf4c0c", - )], - ); - } -} - -#[cfg(bench)] -mod benches { - use test::Bencher; - - use crate::{sha512, Hash, HashEngine}; - - #[bench] - pub fn sha512_10(bh: &mut Bencher) { - let mut engine = sha512::Hash::engine(); - let bytes = [1u8; 10]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn sha512_1k(bh: &mut Bencher) { - let mut engine = sha512::Hash::engine(); - let bytes = [1u8; 1024]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } - - #[bench] - pub fn sha512_64k(bh: &mut Bencher) { - let mut engine = sha512::Hash::engine(); - let bytes = [1u8; 65536]; - bh.iter(|| { - engine.input(&bytes); - }); - bh.bytes = bytes.len() as u64; - } -} diff --git a/hashes/src/sha512/mod.rs b/hashes/src/sha512/mod.rs new file mode 100644 index 000000000..f72f62e56 --- /dev/null +++ b/hashes/src/sha512/mod.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! SHA512 implementation. + +#[cfg(bench)] +mod benches; +mod crypto; +#[cfg(bench)] +mod tests; + +use core::cmp; + +use crate::{incomplete_block_len, HashEngine as _}; + +crate::internal_macros::general_hash_type! { + 512, + false, + "Output of the SHA512 hash function." +} + +#[cfg(not(hashes_fuzz))] +pub(crate) fn from_engine(mut e: HashEngine) -> Hash { + // pad buffer with a single 1-bit then all 0s, until there are exactly 16 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 16]; + e.input(&[0x80]); + if incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); + + e.input(&[0; 8]); + e.input(&(8 * n_bytes_hashed).to_be_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); + + Hash(e.midstate()) +} + +#[cfg(hashes_fuzz)] +pub(crate) fn from_engine(e: HashEngine) -> Hash { + let mut hash = e.midstate(); + hash[0] ^= 0xff; // Make this distinct from SHA-256 + Hash(hash) +} + +pub(crate) const BLOCK_SIZE: usize = 128; + +/// Engine to compute SHA512 hash function. +#[derive(Clone)] +pub struct HashEngine { + h: [u64; 8], + bytes_hashed: u64, + buffer: [u8; BLOCK_SIZE], +} + +impl HashEngine { + /// Constructs a new SHA512 hash engine. + #[rustfmt::skip] + pub const fn new() -> Self { + Self { + h: [ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, + ], + bytes_hashed: 0, + buffer: [0; BLOCK_SIZE], + } + } +} + +impl Default for HashEngine { + fn default() -> Self { Self::new() } +} + +impl HashEngine { + #[cfg(not(hashes_fuzz))] + pub(crate) fn midstate(&self) -> [u8; 64] { + let mut ret = [0; 64]; + for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(8)) { + ret_bytes.copy_from_slice(&val.to_be_bytes()); + } + ret + } + + #[cfg(hashes_fuzz)] + pub(crate) fn midstate(&self) -> [u8; 64] { + let mut ret = [0; 64]; + ret.copy_from_slice(&self.buffer[..64]); + ret + } + + /// Constructs a new hash engine suitable for use constructing a `sha512_256::HashEngine`. + #[rustfmt::skip] + pub(crate) const fn sha512_256() -> Self { + HashEngine { + h: [ + 0x22312194fc2bf72c, 0x9f555fa3c84c64c2, 0x2393b86b6f53b151, 0x963877195940eabd, + 0x96283ee2a88effe3, 0xbe5e1e2553863992, 0x2b0199fc2c85b8aa, 0x0eb72ddc81c52ca2, + ], + bytes_hashed: 0, + buffer: [0; BLOCK_SIZE], + } + } + + /// Constructs a new hash engine suitable for constructing a `sha384::HashEngine`. + #[rustfmt::skip] + pub(crate) const fn sha384() -> Self { + HashEngine { + h: [ + 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, + 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4, + ], + bytes_hashed: 0, + buffer: [0; BLOCK_SIZE], + } + } +} + +impl crate::HashEngine for HashEngine { + const BLOCK_SIZE: usize = 128; + + fn n_bytes_hashed(&self) -> u64 { self.bytes_hashed } + + crate::internal_macros::engine_input_impl!(); +} diff --git a/hashes/src/sha512/tests.rs b/hashes/src/sha512/tests.rs new file mode 100644 index 000000000..4b46f0176 --- /dev/null +++ b/hashes/src/sha512/tests.rs @@ -0,0 +1,108 @@ +#[test] +#[cfg(feature = "alloc")] +fn test() { + use alloc::string::ToString; + + use crate::{sha512, HashEngine}; + + #[derive(Clone)] + struct Test { + input: &'static str, + output: [u8; 64], + output_str: &'static str, + } + + #[rustfmt::skip] + let tests = [ + // Test vectors computed with `sha512sum` + Test { + input: "", + output: [ + 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, + 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, + 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, + 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, + 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, + 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, + 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, + 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e, + ], + output_str: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" + }, + Test { + input: "The quick brown fox jumps over the lazy dog", + output: [ + 0x07, 0xe5, 0x47, 0xd9, 0x58, 0x6f, 0x6a, 0x73, + 0xf7, 0x3f, 0xba, 0xc0, 0x43, 0x5e, 0xd7, 0x69, + 0x51, 0x21, 0x8f, 0xb7, 0xd0, 0xc8, 0xd7, 0x88, + 0xa3, 0x09, 0xd7, 0x85, 0x43, 0x6b, 0xbb, 0x64, + 0x2e, 0x93, 0xa2, 0x52, 0xa9, 0x54, 0xf2, 0x39, + 0x12, 0x54, 0x7d, 0x1e, 0x8a, 0x3b, 0x5e, 0xd6, + 0xe1, 0xbf, 0xd7, 0x09, 0x78, 0x21, 0x23, 0x3f, + 0xa0, 0x53, 0x8f, 0x3d, 0xb8, 0x54, 0xfe, 0xe6, + ], + output_str: "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6", + }, + Test { + input: "The quick brown fox jumps over the lazy dog.", + output: [ + 0x91, 0xea, 0x12, 0x45, 0xf2, 0x0d, 0x46, 0xae, + 0x9a, 0x03, 0x7a, 0x98, 0x9f, 0x54, 0xf1, 0xf7, + 0x90, 0xf0, 0xa4, 0x76, 0x07, 0xee, 0xb8, 0xa1, + 0x4d, 0x12, 0x89, 0x0c, 0xea, 0x77, 0xa1, 0xbb, + 0xc6, 0xc7, 0xed, 0x9c, 0xf2, 0x05, 0xe6, 0x7b, + 0x7f, 0x2b, 0x8f, 0xd4, 0xc7, 0xdf, 0xd3, 0xa7, + 0xa8, 0x61, 0x7e, 0x45, 0xf3, 0xc4, 0x63, 0xd4, + 0x81, 0xc7, 0xe5, 0x86, 0xc3, 0x9a, 0xc1, 0xed, + ], + output_str: "91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed", + }, + ]; + + for test in tests { + // Hash through high-level API, check hex encoding/decoding + let hash = sha512::Hash::hash(test.input.as_bytes()); + assert_eq!(hash, test.output_str.parse::().expect("parse hex")); + assert_eq!(hash.as_byte_array(), &test.output); + assert_eq!(hash.to_string(), test.output_str); + + // Hash through engine, checking that we can input byte by byte + let mut engine = sha512::Hash::engine(); + for ch in test.input.as_bytes() { + engine.input(&[*ch]); + } + let manual_hash = sha512::Hash::from_engine(engine); + assert_eq!(hash, manual_hash); + assert_eq!(hash.to_byte_array(), test.output); + } +} + +#[test] +#[cfg(feature = "serde")] +fn sha512_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + use crate::sha512; + + #[rustfmt::skip] + static HASH_BYTES: [u8; 64] = [ + 0x8b, 0x41, 0xe1, 0xb7, 0x8a, 0xd1, 0x15, 0x21, + 0x11, 0x3c, 0x52, 0xff, 0x18, 0x2a, 0x1b, 0x8e, + 0x0a, 0x19, 0x57, 0x54, 0xaa, 0x52, 0x7f, 0xcd, + 0x00, 0xa4, 0x11, 0x62, 0x0b, 0x46, 0xf2, 0x0f, + 0xff, 0xfb, 0x80, 0x88, 0xcc, 0xf8, 0x54, 0x97, + 0x12, 0x1a, 0xd4, 0x49, 0x9e, 0x08, 0x45, 0xb8, + 0x76, 0xf6, 0xdd, 0x66, 0x40, 0x08, 0x8a, 0x2f, + 0x0b, 0x2d, 0x8a, 0x60, 0x0b, 0xdf, 0x4c, 0x0c, + ]; + + let hash = sha512::Hash::from_slice(&HASH_BYTES).expect("right number of bytes"); + assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]); + assert_tokens( + &hash.readable(), + &[Token::Str( + "8b41e1b78ad11521113c52ff182a1b8e0a195754aa527fcd00a411620b46f20f\ + fffb8088ccf85497121ad4499e0845b876f6dd6640088a2f0b2d8a600bdf4c0c", + )], + ); +} diff --git a/hashes/src/sha512_256.rs b/hashes/src/sha512_256/mod.rs similarity index 100% rename from hashes/src/sha512_256.rs rename to hashes/src/sha512_256/mod.rs diff --git a/hashes/src/siphash24.rs b/hashes/src/siphash24/mod.rs similarity index 100% rename from hashes/src/siphash24.rs rename to hashes/src/siphash24/mod.rs