From fd4586eaae22cafb5a04cdc55919a4ec4aa6151a Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 27 Jan 2025 10:45:01 +1100 Subject: [PATCH] Invert dependency between io and hashes Currently in order to release `hashes v1.0` we need to 1.0 `io` as well. For multiple reasons, many out of our control, the `io` crate may not stabalise any time soon. Instead we can invert the dependency between the two crates. This is an ingenious idea, props to Kixunil for coming up with it. Notes - `io` does not currently re-export the `hashes` crate. - This work highlights that we cannot call `hash_reader` on a siphash. - The `Hmac::hash_reader` uses the default key which may not be obvious. Signed-off-by: Tobin C. Harding --- Cargo-minimal.lock | 2 +- Cargo-recent.lock | 2 +- bitcoin/Cargo.toml | 4 +- hashes/Cargo.toml | 5 +- hashes/contrib/sanitizer.sh | 2 +- hashes/contrib/test_vars.sh | 4 +- hashes/embedded/Cargo.toml | 7 +- hashes/src/hmac/mod.rs | 3 +- hashes/src/internal_macros.rs | 29 +-- hashes/src/lib.rs | 34 --- hashes/src/sha256t/mod.rs | 21 +- hashes/tests/io.rs | 129 ------------ hashes/tests/regression.rs | 2 + io/Cargo.toml | 6 +- io/contrib/test_vars.sh | 4 +- io/src/hash.rs | 378 ++++++++++++++++++++++++++++++++++ io/src/lib.rs | 8 + primitives/Cargo.toml | 2 +- 18 files changed, 417 insertions(+), 225 deletions(-) delete mode 100644 hashes/tests/io.rs create mode 100644 io/src/hash.rs diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index ecd87a177..f90f321d5 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -98,6 +98,7 @@ name = "bitcoin-io" version = "0.2.0" dependencies = [ "bitcoin-internals", + "bitcoin_hashes 0.16.0", ] [[package]] @@ -140,7 +141,6 @@ dependencies = [ name = "bitcoin_hashes" version = "0.16.0" dependencies = [ - "bitcoin-io", "hex-conservative 0.3.0", "serde", "serde_json", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 16d558d3d..8b590d8ac 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -97,6 +97,7 @@ name = "bitcoin-io" version = "0.2.0" dependencies = [ "bitcoin-internals", + "bitcoin_hashes 0.16.0", ] [[package]] @@ -139,7 +140,6 @@ dependencies = [ name = "bitcoin_hashes" version = "0.16.0" dependencies = [ - "bitcoin-io", "hex-conservative 0.3.0", "serde", "serde_json", diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index cf0575029..9713bf200 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -27,10 +27,10 @@ arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"] [dependencies] base58 = { package = "base58ck", version = "0.2.0", default-features = false, features = ["alloc"] } bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] } -hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["alloc", "bitcoin-io", "hex"] } +hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["alloc", "hex"] } hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] } internals = { package = "bitcoin-internals", version = "0.4.0", features = ["alloc"] } -io = { package = "bitcoin-io", version = "0.2.0", default-features = false, features = ["alloc"] } +io = { package = "bitcoin-io", version = "0.2.0", default-features = false, features = ["alloc", "hashes"] } primitives = { package = "bitcoin-primitives", version = "0.101.0", default-features = false, features = ["alloc"] } secp256k1 = { version = "0.29.0", default-features = false, features = ["hashes", "alloc"] } units = { package = "bitcoin-units", version = "0.2.0", default-features = false, features = ["alloc"] } diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml index c34f2f04e..35760c87e 100644 --- a/hashes/Cargo.toml +++ b/hashes/Cargo.toml @@ -15,8 +15,8 @@ exclude = ["tests", "contrib"] [features] default = ["std"] -std = ["alloc", "bitcoin-io?/std", "hex/std"] -alloc = ["bitcoin-io?/alloc", "hex/alloc"] +std = ["alloc", "hex/std"] +alloc = ["hex/alloc"] serde = ["dep:serde", "hex"] # Smaller (but slower) implementation of sha256, sha512 and ripemd160 small-hash = [] @@ -24,7 +24,6 @@ small-hash = [] [dependencies] hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true } -bitcoin-io = { version = "0.2.0", default-features = false, optional = true } serde = { version = "1.0", default-features = false, optional = true } [dev-dependencies] diff --git a/hashes/contrib/sanitizer.sh b/hashes/contrib/sanitizer.sh index e3d1e8152..495e404c1 100755 --- a/hashes/contrib/sanitizer.sh +++ b/hashes/contrib/sanitizer.sh @@ -5,7 +5,7 @@ set -euox pipefail # Run the sanitizer with these features. -FEATURES="std bitcoin-io serde" +FEATURES="std serde" cargo clean CC='clang -fsanitize=address -fno-omit-frame-pointer' \ diff --git a/hashes/contrib/test_vars.sh b/hashes/contrib/test_vars.sh index 35360740a..efcd3dcfc 100644 --- a/hashes/contrib/test_vars.sh +++ b/hashes/contrib/test_vars.sh @@ -5,10 +5,10 @@ # shellcheck disable=SC2034 # Test all these features with "std" enabled. -FEATURES_WITH_STD="bitcoin-io serde small-hash" +FEATURES_WITH_STD="serde small-hash" # Test all these features without "std" enabled. -FEATURES_WITHOUT_STD="alloc bitcoin-io serde small-hash" +FEATURES_WITHOUT_STD="alloc serde small-hash" # Run these examples. EXAMPLES="" diff --git a/hashes/embedded/Cargo.toml b/hashes/embedded/Cargo.toml index be73fe563..dcf5a7ecb 100644 --- a/hashes/embedded/Cargo.toml +++ b/hashes/embedded/Cargo.toml @@ -19,8 +19,8 @@ cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" panic-halt = "0.2.0" alloc-cortex-m = { version = "0.4.1", optional = true } -bitcoin_hashes = { path="../", default-features = false, features = ["bitcoin-io"] } -bitcoin-io = { path = "../../io", default_features = false } +bitcoin_hashes = { path="../", default-features = false, features = [] } +bitcoin-io = { path = "../../io", default_features = false, features = ["hashes"] } [[bin]] name = "embedded" @@ -32,6 +32,9 @@ codegen-units = 1 # better optimizations debug = true # symbols are nice and they don't increase the size on Flash lto = true # better optimizations +[patch.crates-io.bitcoin_hashes] +path = "../../hashes" + [patch.crates-io.bitcoin-internals] path = "../../internals" diff --git a/hashes/src/hmac/mod.rs b/hashes/src/hmac/mod.rs index 5cddfdc53..eace7f951 100644 --- a/hashes/src/hmac/mod.rs +++ b/hashes/src/hmac/mod.rs @@ -155,7 +155,8 @@ impl<'de, T: GeneralHash + Deserialize<'de>> Deserialize<'de> for Hmac { } } -crate::internal_macros::impl_io_write!( +#[cfg(feature = "std")] +crate::internal_macros::impl_write!( HmacEngine, |us: &mut HmacEngine, buf| { us.input(buf); diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs index 5ae79f119..67b9f6170 100644 --- a/hashes/src/internal_macros.rs +++ b/hashes/src/internal_macros.rs @@ -91,12 +91,6 @@ macro_rules! general_hash_type { { ::hash_byte_chunks(byte_slices) } - - /// Hashes the entire contents of the `reader`. - #[cfg(feature = "bitcoin-io")] - pub fn hash_reader(reader: &mut R) -> Result { - ::hash_reader(reader) - } } }; } @@ -156,7 +150,7 @@ macro_rules! hash_type_no_default { $crate::internal_macros::hash_trait_impls!($bits, $reverse); - $crate::internal_macros::impl_io_write!( + $crate::internal_macros::impl_write!( HashEngine, |us: &mut HashEngine, buf| { crate::HashEngine::input(us, buf); @@ -168,29 +162,16 @@ macro_rules! hash_type_no_default { } pub(crate) use hash_type_no_default; -// We do not use the `bitcoin_io::impl_write` macro because we don't have an unconditional -// dependency on `bitcoin-io` and we want to implement `std:io::Write` even when we don't depend on -// `bitcoin-io`. -macro_rules! impl_io_write { +macro_rules! impl_write { ($ty: ty, $write_fn: expr, $flush_fn: expr $(, $bounded_ty: ident : $bounds: path),*) => { - #[cfg(feature = "bitcoin-io")] - impl<$($bounded_ty: $bounds),*> bitcoin_io::Write for $ty { - #[inline] - fn write(&mut self, buf: &[u8]) -> bitcoin_io::Result { - $write_fn(self, buf) - } - #[inline] - fn flush(&mut self) -> bitcoin_io::Result<()> { - $flush_fn(self) - } - } - + // `bitcoin_io::Write` is implemented in `bitcoin_io`. #[cfg(feature = "std")] impl<$($bounded_ty: $bounds),*> std::io::Write for $ty { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { $write_fn(self, buf) } + #[inline] fn flush(&mut self) -> std::io::Result<()> { $flush_fn(self) @@ -198,7 +179,7 @@ macro_rules! impl_io_write { } } } -pub(crate) use impl_io_write; +pub(crate) use impl_write; macro_rules! engine_input_impl( () => ( diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs index 9f0067aaf..0324da01a 100644 --- a/hashes/src/lib.rs +++ b/hashes/src/lib.rs @@ -75,9 +75,6 @@ extern crate core; #[cfg(feature = "std")] extern crate std; -#[cfg(feature = "bitcoin-io")] -extern crate bitcoin_io as io; - /// A generic serialization/deserialization framework. #[cfg(feature = "serde")] pub extern crate serde; @@ -239,28 +236,6 @@ pub trait GeneralHash: Hash { } Self::from_engine(engine) } - - /// Hashes the entire contents of the `reader`. - #[cfg(feature = "bitcoin-io")] - fn hash_reader(reader: &mut R) -> Result - where - Self::Engine: Default, - { - let mut engine = Self::engine(); - loop { - let bytes = reader.fill_buf()?; - - let read = bytes.len(); - // Empty slice means EOF. - if read == 0 { - break; - } - - engine.input(bytes); - reader.consume(read); - } - Ok(Self::from_engine(engine)) - } } /// Trait which applies to hashes of all types. @@ -367,13 +342,4 @@ mod tests { let rinsed = hex.parse::().expect("failed to parse hex"); assert_eq!(rinsed, orig) } - - #[test] - #[cfg(feature = "bitcoin-io")] - fn hash_reader() { - use crate::sha256; - - let mut reader: &[u8] = b"hello"; - assert_eq!(sha256::Hash::hash_reader(&mut reader).unwrap(), sha256::Hash::hash(b"hello"),) - } } diff --git a/hashes/src/sha256t/mod.rs b/hashes/src/sha256t/mod.rs index 5d28b9d93..e2461eebf 100644 --- a/hashes/src/sha256t/mod.rs +++ b/hashes/src/sha256t/mod.rs @@ -87,25 +87,6 @@ where Self::from_engine(engine) } - /// Hashes the entire contents of the `reader`. - #[cfg(feature = "bitcoin-io")] - pub fn hash_reader(reader: &mut R) -> Result { - let mut engine = Self::engine(); - loop { - let bytes = reader.fill_buf()?; - - let read = bytes.len(); - // Empty slice means EOF. - if read == 0 { - break; - } - - engine.input(bytes); - reader.consume(read); - } - Ok(Self::from_engine(engine)) - } - /// Returns the underlying byte array. pub const fn to_byte_array(self) -> [u8; 32] { self.0 } @@ -152,7 +133,7 @@ impl crate::HashEngine for HashEngine { fn n_bytes_hashed(&self) -> u64 { self.0.n_bytes_hashed() } } -crate::internal_macros::impl_io_write!( +crate::internal_macros::impl_write!( HashEngine, |us: &mut HashEngine, buf| { us.input(buf); diff --git a/hashes/tests/io.rs b/hashes/tests/io.rs deleted file mode 100644 index 9151bba30..000000000 --- a/hashes/tests/io.rs +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Test the `bitcoin-io` implementations. - -#![cfg(feature = "bitcoin-io")] -#![cfg(feature = "hex")] - -use bitcoin_hashes::{ - hash160, hmac, ripemd160, sha1, sha256, sha256d, sha384, sha512, sha512_256, siphash24, - GeneralHash as _, -}; -use bitcoin_io::Write; - -macro_rules! write_test { - ($mod:ident, $exp_empty:expr, $exp_256:expr, $exp_64k:expr,) => { - #[test] - fn $mod() { - let mut engine = $mod::Hash::engine(); - engine.write_all(&[]).unwrap(); - assert_eq!(format!("{}", $mod::Hash::from_engine(engine)), $exp_empty); - - let mut engine = $mod::Hash::engine(); - engine.write_all(&[1; 256]).unwrap(); - assert_eq!(format!("{}", $mod::Hash::from_engine(engine)), $exp_256); - - let mut engine = $mod::Hash::engine(); - engine.write_all(&[99; 64000]).unwrap(); - assert_eq!(format!("{}", $mod::Hash::from_engine(engine)), $exp_64k); - } - }; -} - -write_test!( - sha1, - "da39a3ee5e6b4b0d3255bfef95601890afd80709", - "ac458b067c6b021c7e9358229b636e9d1e4cb154", - "e4b66838f9f7b6f91e5be32a02ae78094df402e7", -); - -write_test!( - sha256, - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "2661920f2409dd6c8adeb0c44972959f232b6429afa913845d0fd95e7e768234", - "5c5e904f5d4fd587c7a906bf846e08a927286f388c54c39213a4884695271bbc", -); - -write_test!( - sha256d, - "56944c5d3f98413ef45cf54545538103cc9f298e0575820ad3591376e2e0f65d", - "374000d830c75d10d9417e493a7652920f30efbd300e3fb092f24c28c20baf64", - "0050d4148ad7a0437ca0643fad5bf4614cd95d9ba21fde52370b37dcc3f03307", -); - -write_test!( - sha384, - "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", - "82135637ef6d6dd31a20e2bc9998681a3eecaf8f8c76d45e545214de38439d9a533848ec75f53e4b1a8805709c5124d0", - "fb7511d9a98c5686f9c2f55e242397815c9229d8759451e1710b8da6861e08d52f0357176f4b74f8cad9e23ab65411c7", -); - -write_test!( - sha512, - "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\ - 47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", - "57ecf739d3a7ca647639adae80a05f4f361304bfcbfa1ceba93296b096e74287\ - 45fc10c142cecdd3bb587a3dba598c072f6f78b31cc0a06a3da0105ee51f75d6", - "dd28f78c53f3bc9bd0c2dca9642a1ad402a70412f985c1f6e54fadb98ce9c458\ - 4761df8d04ed04bb734ba48dd2106bb9ea54524f1394cdd18e6da3166e71c3ee", -); - -write_test!( - sha512_256, - "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a", - "8d4bb96e7956cf5f08bf5c45f7982630c46b0b022f25cbaf722ae97c06a6e7a2", - "3367646f3e264653f7dd664ac2cb6d3b96329e86ffb7a29a1082e2a4ddc9ee7a", -); - -write_test!( - ripemd160, - "9c1185a5c5e9fc54612808977ee8f548b2258d31", - "e571a1ca5b780aa52bafdb9ec852544ffca418ba", - "ddd2ecce739e823629c7d46ab18918e9c4a51c75", -); - -write_test!( - hash160, - "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", - "671356a1a874695ad3bc20cae440f4360835bd5a", - "a9608c952c8dbcc20c53803d2ca5ad31d64d9313", -); - -#[test] -fn hmac() { - let mut engine = hmac::HmacEngine::::new(&[0xde, 0xad, 0xbe, 0xef]); - engine.write_all(&[]).unwrap(); - assert_eq!( - format!("{}", hmac::Hmac::from_engine(engine)), - "bf5515149cf797955c4d3194cca42472883281951697c8375d9d9b107f384225" - ); - - let mut engine = hmac::HmacEngine::::new(&[0xde, 0xad, 0xbe, 0xef]); - engine.write_all(&[1; 256]).unwrap(); - assert_eq!( - format!("{}", hmac::Hmac::from_engine(engine)), - "59c9aca10c81c73cb4c196d94db741b6bf2050e0153d5a45f2526bff34675ac5" - ); - - let mut engine = hmac::HmacEngine::::new(&[0xde, 0xad, 0xbe, 0xef]); - engine.write_all(&[99; 64000]).unwrap(); - assert_eq!( - format!("{}", hmac::Hmac::from_engine(engine)), - "30df499717415a395379a1eaabe50038036e4abb5afc94aa55c952f4aa57be08" - ); -} - -#[test] -fn siphash24() { - let mut engine = siphash24::HashEngine::with_keys(0, 0); - engine.write_all(&[]).unwrap(); - assert_eq!(format!("{}", siphash24::Hash::from_engine(engine)), "d70077739d4b921e"); - - let mut engine = siphash24::HashEngine::with_keys(0, 0); - engine.write_all(&[1; 256]).unwrap(); - assert_eq!(format!("{}", siphash24::Hash::from_engine(engine)), "3a3ccefde9b5b1e3"); - - let mut engine = siphash24::HashEngine::with_keys(0, 0); - engine.write_all(&[99; 64000]).unwrap(); - assert_eq!(format!("{}", siphash24::Hash::from_engine(engine)), "ce456e4e4ecbc5bf"); -} diff --git a/hashes/tests/regression.rs b/hashes/tests/regression.rs index a4ffddc2f..319d6ecf3 100644 --- a/hashes/tests/regression.rs +++ b/hashes/tests/regression.rs @@ -1,6 +1,8 @@ //! Regression tests for each hash type. //! //! Note that if `bitcoin-io` is enabled then we get more regression-like testing from `./io.rs`. +//! +//! Test input data and expected hashes is the same as in `io/src/hash.rs`. #![cfg(feature = "hex")] diff --git a/io/Cargo.toml b/io/Cargo.toml index 224d37804..d245121da 100644 --- a/io/Cargo.toml +++ b/io/Cargo.toml @@ -15,12 +15,14 @@ exclude = ["tests", "contrib"] [features] default = ["std"] -std = ["alloc"] -alloc = [] +std = ["alloc", "hashes?/std"] +alloc = ["hashes?/alloc"] [dependencies] internals = { package = "bitcoin-internals", version = "0.4.0" } +hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, optional = true } + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/io/contrib/test_vars.sh b/io/contrib/test_vars.sh index 92dc09406..279c2507c 100644 --- a/io/contrib/test_vars.sh +++ b/io/contrib/test_vars.sh @@ -5,10 +5,10 @@ # shellcheck disable=SC2034 # Test all these features with "std" enabled. -FEATURES_WITH_STD="" +FEATURES_WITH_STD="hashes" # Test all these features without "std" enabled. -FEATURES_WITHOUT_STD="alloc" +FEATURES_WITHOUT_STD="alloc hashes" # Run these examples. EXAMPLES="" diff --git a/io/src/hash.rs b/io/src/hash.rs new file mode 100644 index 000000000..a6a364e94 --- /dev/null +++ b/io/src/hash.rs @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! I/O hashing support. +//! +//! Support for various hashing related things e.g. +//! +//! - Hashing to a writer. +//! - Implement I/O traits for hash engines. + +use hashes::hmac::HmacEngine; +use hashes::{ + hash160, ripemd160, sha1, sha256, sha256d, sha256t, sha384, sha512, sha512_256, siphash24, + GeneralHash, HashEngine as _, +}; + +macro_rules! impl_write { + ($ty: ty, $write_fn: expr, $flush_fn: expr $(, $bounded_ty: ident : $bounds: path),*) => { + // `std::io::Write` is implemented in `bitcoin_hashes` because of the orphan rule. + impl<$($bounded_ty: $bounds),*> crate::Write for $ty { + #[inline] + fn write(&mut self, buf: &[u8]) -> crate::Result { + $write_fn(self, buf)} + + #[inline] + fn flush(&mut self) -> crate::Result<()> { + $flush_fn(self) + } + } + } +} +pub(crate) use impl_write; + +impl_write!( + hash160::HashEngine, + |us: &mut hash160::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + ripemd160::HashEngine, + |us: &mut ripemd160::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + sha1::HashEngine, + |us: &mut sha1::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + sha256::HashEngine, + |us: &mut sha256::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + sha256d::HashEngine, + |us: &mut sha256d::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + sha256t::HashEngine, + |us: &mut sha256t::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) }, + T: sha256t::Tag +); + +impl_write!( + sha384::HashEngine, + |us: &mut sha384::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + sha512::HashEngine, + |us: &mut sha512::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + sha512_256::HashEngine, + |us: &mut sha512_256::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + siphash24::HashEngine, + |us: &mut siphash24::HashEngine, buf| { + hashes::HashEngine::input(us, buf); + Ok(buf.len()) + }, + |_us| { Ok(()) } +); + +impl_write!( + HmacEngine, + |us: &mut HmacEngine, buf| { + us.input(buf); + Ok(buf.len()) + }, + |_us| { Ok(()) }, + T: hashes::GeneralHash +); + +/// Hashes data from a reader. +/// +/// Adds functionality to a [`hashes::GeneralHash`] type to support hashing data read from a +/// buffered reader. +pub trait GeneralHashExt: GeneralHash + sealed::Sealed { + /// Hashes the entire contents of the `reader`. + fn hash_reader(reader: &mut R) -> Result + where + Self::Engine: Default, + { + let mut engine = Self::engine(); // This calls `Self::Engine::default()`. + loop { + let bytes = reader.fill_buf()?; + + let read = bytes.len(); + // Empty slice means EOF. + if read == 0 { + break; + } + + engine.input(bytes); + reader.consume(read); + } + Ok(Self::from_engine(engine)) + } +} + +impl GeneralHashExt for T {} + +mod sealed { + /// Used to seal the `GeneralHashExt` trait. + pub trait Sealed {} + + impl Sealed for T {} +} + +#[cfg(test)] +#[cfg(feature = "alloc")] +mod tests { + use alloc::format; + + use hashes::{hmac, Hmac}; + + use super::*; + use crate::{Cursor, Write as _}; + + macro_rules! write_test { + ($mod:ident, $exp_empty:expr, $exp_256:expr, $exp_64k:expr,) => { + #[test] + fn $mod() { + let mut engine = $mod::Hash::engine(); + engine.write_all(&[]).unwrap(); + assert_eq!(format!("{}", $mod::Hash::from_engine(engine)), $exp_empty); + + let mut engine = $mod::Hash::engine(); + engine.write_all(&[1; 256]).unwrap(); + assert_eq!(format!("{}", $mod::Hash::from_engine(engine)), $exp_256); + + let mut engine = $mod::Hash::engine(); + engine.write_all(&[99; 64000]).unwrap(); + assert_eq!(format!("{}", $mod::Hash::from_engine(engine)), $exp_64k); + } + }; + } + + write_test!( + sha1, + "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "ac458b067c6b021c7e9358229b636e9d1e4cb154", + "e4b66838f9f7b6f91e5be32a02ae78094df402e7", + ); + + write_test!( + sha256, + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "2661920f2409dd6c8adeb0c44972959f232b6429afa913845d0fd95e7e768234", + "5c5e904f5d4fd587c7a906bf846e08a927286f388c54c39213a4884695271bbc", + ); + + write_test!( + sha256d, + "56944c5d3f98413ef45cf54545538103cc9f298e0575820ad3591376e2e0f65d", + "374000d830c75d10d9417e493a7652920f30efbd300e3fb092f24c28c20baf64", + "0050d4148ad7a0437ca0643fad5bf4614cd95d9ba21fde52370b37dcc3f03307", + ); + + write_test!( + sha384, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + "82135637ef6d6dd31a20e2bc9998681a3eecaf8f8c76d45e545214de38439d9a533848ec75f53e4b1a8805709c5124d0", + "fb7511d9a98c5686f9c2f55e242397815c9229d8759451e1710b8da6861e08d52f0357176f4b74f8cad9e23ab65411c7", + ); + + write_test!( + sha512, + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\ + 47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", + "57ecf739d3a7ca647639adae80a05f4f361304bfcbfa1ceba93296b096e74287\ + 45fc10c142cecdd3bb587a3dba598c072f6f78b31cc0a06a3da0105ee51f75d6", + "dd28f78c53f3bc9bd0c2dca9642a1ad402a70412f985c1f6e54fadb98ce9c458\ + 4761df8d04ed04bb734ba48dd2106bb9ea54524f1394cdd18e6da3166e71c3ee", + ); + + write_test!( + sha512_256, + "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a", + "8d4bb96e7956cf5f08bf5c45f7982630c46b0b022f25cbaf722ae97c06a6e7a2", + "3367646f3e264653f7dd664ac2cb6d3b96329e86ffb7a29a1082e2a4ddc9ee7a", + ); + + write_test!( + ripemd160, + "9c1185a5c5e9fc54612808977ee8f548b2258d31", + "e571a1ca5b780aa52bafdb9ec852544ffca418ba", + "ddd2ecce739e823629c7d46ab18918e9c4a51c75", + ); + + write_test!( + hash160, + "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", + "671356a1a874695ad3bc20cae440f4360835bd5a", + "a9608c952c8dbcc20c53803d2ca5ad31d64d9313", + ); + + #[test] + fn hmac() { + let mut engine = hmac::HmacEngine::::new(&[0xde, 0xad, 0xbe, 0xef]); + engine.write_all(&[]).unwrap(); + assert_eq!( + format!("{}", hmac::Hmac::from_engine(engine)), + "bf5515149cf797955c4d3194cca42472883281951697c8375d9d9b107f384225" + ); + + let mut engine = hmac::HmacEngine::::new(&[0xde, 0xad, 0xbe, 0xef]); + engine.write_all(&[1; 256]).unwrap(); + assert_eq!( + format!("{}", hmac::Hmac::from_engine(engine)), + "59c9aca10c81c73cb4c196d94db741b6bf2050e0153d5a45f2526bff34675ac5" + ); + + let mut engine = hmac::HmacEngine::::new(&[0xde, 0xad, 0xbe, 0xef]); + engine.write_all(&[99; 64000]).unwrap(); + assert_eq!( + format!("{}", hmac::Hmac::from_engine(engine)), + "30df499717415a395379a1eaabe50038036e4abb5afc94aa55c952f4aa57be08" + ); + } + + #[test] + fn siphash24() { + let mut engine = siphash24::HashEngine::with_keys(0, 0); + engine.write_all(&[]).unwrap(); + assert_eq!(format!("{}", siphash24::Hash::from_engine(engine)), "d70077739d4b921e"); + + let mut engine = siphash24::HashEngine::with_keys(0, 0); + engine.write_all(&[1; 256]).unwrap(); + assert_eq!(format!("{}", siphash24::Hash::from_engine(engine)), "3a3ccefde9b5b1e3"); + + let mut engine = siphash24::HashEngine::with_keys(0, 0); + engine.write_all(&[99; 64000]).unwrap(); + assert_eq!(format!("{}", siphash24::Hash::from_engine(engine)), "ce456e4e4ecbc5bf"); + } + + // Data and expected hashes taken from `bitcoin_hashes/tests/regression.rs`. + const DATA: &str = "arbitrary data to hash as a regression test"; + const HMAC_KEY: &[u8] = b"some key"; + + macro_rules! impl_hash_reader_test { + ($($test_name:ident, $module:ident, $want:literal);* $(;)?) => { + $( + #[test] + fn $test_name() { + let hash = $module::Hash::hash(DATA.as_bytes()); + let got = format!("{}", hash); + assert_eq!(got, $want); + + let mut reader = Cursor::new(DATA); + let hash_from_reader = $module::Hash::hash_reader(&mut reader).unwrap(); + assert_eq!(hash_from_reader, hash) + } + )* + } + } + + impl_hash_reader_test! { + hash_from_reader_hash160, hash160, "a17909f6d5373b0085c4180ba207126e5040f74d"; + hash_from_reader_ripemd160, ripemd160, "e6801701c77a1cd85662335258c7869631b4a9a8"; + hash_from_reader_sha1, sha1, "e1e81eeabadafa3d5d41cc3f405385426b0f47fd"; + hash_from_reader_sha256, sha256, "d291c6c5a07fa1d9315cdae090ebe14169fbe0a219cd55a48d0d2104eab6ec51"; + hash_from_reader_sha256d, sha256d, "93a743b022290bde3233a619b21aaebe06c5cf5cc959464c41be35711e37731b"; + hash_from_reader_sha384, sha384, "f545bd83d297978d47a7f26b858a54188499dfb4d7d570a6a2362c765031d57a29d7e002df5e34d184e70b65a4f47153"; + hash_from_reader_sha512, sha512, "057d0a37e9e0ac9a93acde0752748da059a27bcf946c7af00692ac1a95db8d21f965f40af22efc4710f100f8d3e43f79f77b1f48e1e400a95b7344b7bc0dfd10"; + hash_from_reader_sha512_256, sha512_256, "e204244c429b5bca037a2a8a6e7ed8a42b808ceaff182560840bb8c5c8e9a2ec"; + } + + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] + pub struct RegHashTag; // Name comes from regression tests in `bitcoin_hashes`. + + impl sha256t::Tag for RegHashTag { + const MIDSTATE: sha256::Midstate = sha256::Midstate::new([0xab; 32], 64); + } + + type RegHash = sha256t::Hash; + + #[test] + fn regression_sha256t() { + let hash = RegHash::hash(DATA.as_bytes()); + let got = format!("{}", hash); + let want = "17db326d7c13867376ccca1f8a211377be3cbeaeb372f167822284866ddf14ca"; + assert_eq!(got, want); + } + + #[test] + fn regression_hmac_sha256_with_key() { + let mut engine = HmacEngine::::new(HMAC_KEY); + engine.input(DATA.as_bytes()); + let hash = Hmac::from_engine(engine); + + let got = format!("{}", hash); + let want = "d159cecaf4adf90b6a641bab767e4817d3a51c414acea3682686c35ec0b37b52"; + assert_eq!(got, want); + } + + #[test] + fn regression_hmac_sha512_with_key() { + let mut engine = HmacEngine::::new(HMAC_KEY); + engine.input(DATA.as_bytes()); + let hash = Hmac::from_engine(engine); + + let got = format!("{}", hash); + let want = "8511773748f89ba22c07fb3a2981a12c1823695119de41f4a62aead6b848bd34939acf16475c35ed7956114fead3e794cc162ecd35e447a4dabc3227d55f757b"; + assert_eq!(got, want); + } + + #[test] + fn regression_siphash24_with_key() { + let mut engine = siphash24::HashEngine::with_keys(0, 0); + engine.input(DATA.as_bytes()); + let hash = siphash24::Hash::from_engine(engine); + + let got = format!("{}", hash); + let want = "e823ed82311d601a"; + assert_eq!(got, want); + } +} diff --git a/io/src/lib.rs b/io/src/lib.rs index 0e1d33126..3c020d0b4 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -25,11 +25,17 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "hashes")] +pub extern crate hashes; + #[cfg(feature = "std")] mod bridge; mod error; mod macros; +#[cfg(feature = "hashes")] +mod hash; + #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::vec::Vec; use core::cmp; @@ -39,6 +45,8 @@ pub use bridge::{FromStd, ToStd}; #[rustfmt::skip] // Keep public re-exports separate. pub use self::error::{Error, ErrorKind}; +#[cfg(feature = "hashes")] +pub use self::hash::GeneralHashExt; /// Result type returned by functions in this crate. pub type Result = core::result::Result; diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 48074486c..241be623a 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -22,7 +22,7 @@ serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc"] arbitrary = ["dep:arbitrary", "units/arbitrary"] [dependencies] -hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["bitcoin-io", "hex"] } +hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["hex"] } hex = { package = "hex-conservative", version = "0.3.0", default-features = false } internals = { package = "bitcoin-internals", version = "0.4.0" } units = { package = "bitcoin-units", version = "0.2.0", default-features = false }