Merge rust-bitcoin/rust-bitcoin#1413: Add sha512_256 to bitcoin_hashes

411174c391 Add fuzz target for sha512_256 (Calvin Kim)
31fc1f8638 Add support for sha512/256 (Calvin Kim)
15b5af1117 Export sha512::HashEngine fields/function within the crate (Calvin Kim)

Pull request description:

  Adds a new file named `sha512_256.rs` that implements the `sha512/256` hash. This was needed as a part of https://github.com/rust-bitcoin/rust-bitcoin/discussions/1318 to drop the `sha2` dependency.

  All the actual hashing code is exactly the same as `sha512.rs`, minus the initial constants and the use of `hash_type!` macro. Some unit tests were added from wikipedia (for the "" input) and the rest were from the Go standard library's tests for sha512_256.

  Benchmarks on my Ryzen 3600 machine show that it is faster than sha256.

  ```
  test sha256::benches::sha256_10                   ... bench:          37 ns/iter (+/- 0) = 270 MB/s
  test sha256::benches::sha256_1k                   ... bench:       3,338 ns/iter (+/- 24) = 306 MB/s
  test sha256::benches::sha256_64k                  ... bench:     213,605 ns/iter (+/- 1,806) = 306 MB/s
  test sha512_256::benches::sha512_256_10           ... bench:          27 ns/iter (+/- 1) = 370 MB/s
  test sha512_256::benches::sha512_256_1k           ... bench:       2,196 ns/iter (+/- 12) = 466 MB/s
  test sha512_256::benches::sha512_256_64k          ... bench:     140,552 ns/iter (+/- 777) = 466 MB/s
  ```

  One caveat is that I could not get hongfuzz to build locally so I couldn't test the fuzz on my machine. I ended up only testing through the CI for the fuzz tests.

  I thought adding a completely separate file was the easiest and the most straightforward way of implementing it. I'm very much open to changing the implementation if you guys don't think this is the right direction.

ACKs for top commit:
  sanket1729:
    ACK 411174c391. Reviwed range diff from 43feb9ea7b282d9119708a27fa7a1c7412d1386a that I had ACked
  apoelstra:
    ACK 411174c391

Tree-SHA512: 98298a7c177cbb616bfbc02cec5c5860f10204df8275cc9f1e4ea07333b901095e574fbc3fe0a03375e0d321a1579e2c2023a5c14addd863e10cc927f155710c
This commit is contained in:
Andrew Poelstra 2022-12-31 19:17:54 +00:00
commit 0f98e179c3
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
5 changed files with 279 additions and 7 deletions

View File

@ -40,6 +40,10 @@ path = "fuzz_targets/sha256.rs"
name = "sha512" name = "sha512"
path = "fuzz_targets/sha512.rs" path = "fuzz_targets/sha512.rs"
[[bin]]
name = "sha512_256"
path = "fuzz_targets/sha512_256.rs"
[[bin]] [[bin]]
name = "cbor" name = "cbor"
path = "fuzz_targets/cbor.rs" path = "fuzz_targets/cbor.rs"

View File

@ -0,0 +1,57 @@
extern crate bitcoin_hashes;
extern crate crypto;
use bitcoin_hashes::Hash;
use bitcoin_hashes::sha512_256;
use crypto::digest::Digest;
use crypto::sha2::Sha512Trunc256;
fn do_test(data: &[u8]) {
let our_hash = sha512_256::Hash::hash(data);
let mut rc_hash = [0u8; 32];
let mut rc_engine = Sha512Trunc256::new();
rc_engine.input(data);
rc_engine.result(&mut rc_hash);
assert_eq!(&our_hash[..], &rc_hash[..]);
}
#[cfg(feature = "honggfuzz")]
#[macro_use]
extern crate honggfuzz;
#[cfg(feature = "honggfuzz")]
fn main() {
loop {
fuzz!(|d| { do_test(d) });
}
}
#[cfg(test)]
mod tests {
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
let mut b = 0;
for (idx, c) in hex.as_bytes().iter().enumerate() {
b <<= 4;
match *c {
b'A'...b'F' => b |= c - b'A' + 10,
b'a'...b'f' => b |= c - b'a' + 10,
b'0'...b'9' => b |= c - b'0',
_ => panic!("Bad hex"),
}
if (idx & 1) == 1 {
out.push(b);
b = 0;
}
}
}
#[test]
fn duplicate_crash() {
let mut a = Vec::new();
extend_vec_from_hex("00000", &mut a);
super::do_test(&a);
}
}

View File

@ -133,6 +133,7 @@ pub mod sha256d;
pub mod sha256t; pub mod sha256t;
pub mod siphash24; pub mod siphash24;
pub mod sha512; pub mod sha512;
pub mod sha512_256;
pub mod cmp; pub mod cmp;
use core::{borrow, fmt, hash, ops}; use core::{borrow, fmt, hash, ops};

View File

@ -29,14 +29,14 @@ use crate::{Error, HashEngine as _, hex};
crate::internal_macros::hash_trait_impls!(512, false); crate::internal_macros::hash_trait_impls!(512, false);
const BLOCK_SIZE: usize = 128; pub(crate) const BLOCK_SIZE: usize = 128;
/// Engine to compute SHA512 hash function. /// Engine to compute SHA512 hash function.
#[derive(Clone)] #[derive(Clone)]
pub struct HashEngine { pub struct HashEngine {
h: [u64; 8], pub(crate) h: [u64; 8],
length: usize, pub(crate) length: usize,
buffer: [u8; BLOCK_SIZE], pub(crate) buffer: [u8; BLOCK_SIZE],
} }
impl Default for HashEngine { impl Default for HashEngine {
@ -141,7 +141,7 @@ impl hash::Hash for Hash {
} }
#[cfg(not(fuzzing))] #[cfg(not(fuzzing))]
fn from_engine(mut e: HashEngine) -> Hash { 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 // pad buffer with a single 1-bit then all 0s, until there are exactly 16 bytes remaining
let data_len = e.length as u64; let data_len = e.length as u64;
@ -162,7 +162,7 @@ fn from_engine(mut e: HashEngine) -> Hash {
} }
#[cfg(fuzzing)] #[cfg(fuzzing)]
fn from_engine(e: HashEngine) -> Hash { pub(crate) fn from_engine(e: HashEngine) -> Hash {
let mut hash = e.midstate(); let mut hash = e.midstate();
hash[0] ^= 0xff; // Make this distinct from SHA-256 hash[0] ^= 0xff; // Make this distinct from SHA-256
Hash(hash) Hash(hash)
@ -192,7 +192,7 @@ macro_rules! round(
impl HashEngine { impl HashEngine {
// Algorithm copied from libsecp256k1 // Algorithm copied from libsecp256k1
fn process_block(&mut self) { pub(crate) fn process_block(&mut self) {
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE); debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
let mut w = [0u64; 16]; let mut w = [0u64; 16];

210
hashes/src/sha512_256.rs Normal file
View File

@ -0,0 +1,210 @@
// Bitcoin Hashes Library
// Written in 2022 by
// The rust-bitcoin developers.
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//
// This module is largely copied from the rust-crypto ripemd.rs file;
// while rust-crypto is licensed under Apache, that file specifically
// was written entirely by Andrew Poelstra, who is re-licensing its
// contents here as CC0.
//! SHA512_256 implementation.
//!
//! SHA512/256 is a hash function that uses the sha512 alogrithm but it truncates
//! the output to 256 bits. It has different initial constants than sha512 so it
//! produces an entirely different hash compared to sha512. More information at
//! https://eprint.iacr.org/2010/548.pdf.
use core::str;
use core::ops::Index;
use core::slice::SliceIndex;
use crate::{hex, sha512, sha512::BLOCK_SIZE, Error};
/// Engine to compute SHA512/256 hash function.
///
/// SHA512/256 is a hash function that uses the sha512 alogrithm but it truncates
/// the output to 256 bits. It has different initial constants than sha512 so it
/// produces an entirely different hash compared to sha512. More information at
/// https://eprint.iacr.org/2010/548.pdf.
#[derive(Clone)]
pub struct HashEngine(sha512::HashEngine);
impl Default for HashEngine {
fn default() -> Self {
HashEngine(sha512::HashEngine {
h: [
0x22312194fc2bf72c, 0x9f555fa3c84c64c2, 0x2393b86b6f53b151, 0x963877195940eabd,
0x96283ee2a88effe3, 0xbe5e1e2553863992, 0x2b0199fc2c85b8aa, 0x0eb72ddc81c52ca2,
],
length: 0,
buffer: [0; BLOCK_SIZE],
})
}
}
impl crate::HashEngine for HashEngine {
type MidState = [u8; 64];
fn midstate(&self) -> [u8; 64] {
self.0.midstate()
}
const BLOCK_SIZE: usize = sha512::BLOCK_SIZE;
fn n_bytes_hashed(&self) -> usize {
self.0.length
}
fn input(&mut self, inp: &[u8]) {
self.0.input(inp);
}
}
crate::internal_macros::hash_type! {
256,
false,
"Output of the SHA512/256 hash function.\n\nSHA512/256 is a hash function that uses the sha512 alogrithm but it truncates the output to 256 bits. It has different initial constants than sha512 so it produces an entirely different hash compared to sha512. More information at https://eprint.iacr.org/2010/548.pdf. ",
"crate::util::json_hex_string::len_32"
}
fn from_engine(e: HashEngine) -> Hash {
let mut ret = [0; 32];
ret.copy_from_slice(&sha512::from_engine(e.0)[..32]);
Hash(ret)
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(any(feature = "std", feature = "alloc"))]
fn test() {
use crate::{sha512_256, Hash, HashEngine};
use crate::hex::{FromHex, ToHex};
#[derive(Clone)]
struct Test {
input: &'static str,
output: Vec<u8>,
output_str: &'static str,
}
let tests = vec![
// Examples from go sha512/256 tests.
Test {
input: "",
output: vec![
0xc6, 0x72, 0xb8, 0xd1, 0xef, 0x56, 0xed, 0x28,
0xab, 0x87, 0xc3, 0x62, 0x2c, 0x51, 0x14, 0x06,
0x9b, 0xdd, 0x3a, 0xd7, 0xb8, 0xf9, 0x73, 0x74,
0x98, 0xd0, 0xc0, 0x1e, 0xce, 0xf0, 0x96, 0x7a,
],
output_str: "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a"
},
Test {
input: "abcdef",
output: vec![
0xe4, 0xfd, 0xcb, 0x11, 0xd1, 0xac, 0x14, 0xe6,
0x98, 0x74, 0x3a, 0xcd, 0x88, 0x05, 0x17, 0x4c,
0xea, 0x5d, 0xdc, 0x0d, 0x31, 0x2e, 0x3e, 0x47,
0xf6, 0x37, 0x20, 0x32, 0x57, 0x1b, 0xad, 0x84,
],
output_str: "e4fdcb11d1ac14e698743acd8805174cea5ddc0d312e3e47f6372032571bad84",
},
Test {
input: "Discard medicine more than two years old.",
output: vec![
0x69, 0x0c, 0x8a, 0xd3, 0x91, 0x6c, 0xef, 0xd3,
0xad, 0x29, 0x22, 0x6d, 0x98, 0x75, 0x96, 0x5e,
0x3e, 0xe9, 0xec, 0x0d, 0x44, 0x82, 0xea, 0xcc,
0x24, 0x8f, 0x2f, 0xf4, 0xaa, 0x0d, 0x8e, 0x5b,
],
output_str: "690c8ad3916cefd3ad29226d9875965e3ee9ec0d4482eacc248f2ff4aa0d8e5b",
},
Test {
input: "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977",
output: vec![
0xb5, 0xba, 0xf7, 0x47, 0xc3, 0x07, 0xf9, 0x88,
0x49, 0xec, 0x88, 0x1c, 0xf0, 0xd4, 0x86, 0x05,
0xae, 0x4e, 0xdd, 0x38, 0x63, 0x72, 0xae, 0xa9,
0xb2, 0x6e, 0x71, 0xdb, 0x51, 0x7e, 0x65, 0x0b,
],
output_str: "b5baf747c307f98849ec881cf0d48605ae4edd386372aea9b26e71db517e650b",
},
Test {
input: "The major problem is with sendmail. -Mark Horton",
output: vec![
0x53, 0xed, 0x5f, 0x9b, 0x5c, 0x0b, 0x67, 0x4a,
0xc0, 0xf3, 0x42, 0x5d, 0x9f, 0x9a, 0x5d, 0x46,
0x26, 0x55, 0xb0, 0x7c, 0xc9, 0x0f, 0x5d, 0x0f,
0x69, 0x2e, 0xec, 0x09, 0x38, 0x84, 0xa6, 0x07,
],
output_str: "53ed5f9b5c0b674ac0f3425d9f9a5d462655b07cc90f5d0f692eec093884a607",
},
];
for test in tests {
// Hash through high-level API, check hex encoding/decoding
let hash = sha512_256::Hash::hash(test.input.as_bytes());
assert_eq!(hash, sha512_256::Hash::from_hex(test.output_str).expect("parse hex"));
assert_eq!(&hash[..], &test.output[..]);
assert_eq!(&hash.to_hex(), &test.output_str);
// Hash through engine, checking that we can input byte by byte
let mut engine = sha512_256::Hash::engine();
for ch in test.input.as_bytes() {
engine.0.input(&[*ch]);
}
let manual_hash = sha512_256::Hash::from_engine(engine);
assert_eq!(hash, manual_hash);
assert_eq!(hash.into_inner()[..].as_ref(), test.output.as_slice());
}
}
}
#[cfg(bench)]
mod benches {
use test::Bencher;
use crate::{Hash, HashEngine, sha512_256};
#[bench]
pub fn sha512_256_10(bh: &mut Bencher) {
let mut engine = sha512_256::Hash::engine();
let bytes = [1u8; 10];
bh.iter( || {
engine.input(&bytes);
});
bh.bytes = bytes.len() as u64;
}
#[bench]
pub fn sha512_256_1k(bh: &mut Bencher) {
let mut engine = sha512_256::Hash::engine();
let bytes = [1u8; 1024];
bh.iter( || {
engine.input(&bytes);
});
bh.bytes = bytes.len() as u64;
}
#[bench]
pub fn sha512_256_64k(bh: &mut Bencher) {
let mut engine = sha512_256::Hash::engine();
let bytes = [1u8; 65536];
bh.iter( || {
engine.input(&bytes);
});
bh.bytes = bytes.len() as u64;
}
}