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 <me@tobin.cc>
This commit is contained in:
Tobin C. Harding 2025-01-27 10:45:01 +11:00
parent 9833ca32ce
commit fd4586eaae
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
18 changed files with 417 additions and 225 deletions

View File

@ -98,6 +98,7 @@ name = "bitcoin-io"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"bitcoin-internals", "bitcoin-internals",
"bitcoin_hashes 0.16.0",
] ]
[[package]] [[package]]
@ -140,7 +141,6 @@ dependencies = [
name = "bitcoin_hashes" name = "bitcoin_hashes"
version = "0.16.0" version = "0.16.0"
dependencies = [ dependencies = [
"bitcoin-io",
"hex-conservative 0.3.0", "hex-conservative 0.3.0",
"serde", "serde",
"serde_json", "serde_json",

View File

@ -97,6 +97,7 @@ name = "bitcoin-io"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"bitcoin-internals", "bitcoin-internals",
"bitcoin_hashes 0.16.0",
] ]
[[package]] [[package]]
@ -139,7 +140,6 @@ dependencies = [
name = "bitcoin_hashes" name = "bitcoin_hashes"
version = "0.16.0" version = "0.16.0"
dependencies = [ dependencies = [
"bitcoin-io",
"hex-conservative 0.3.0", "hex-conservative 0.3.0",
"serde", "serde",
"serde_json", "serde_json",

View File

@ -27,10 +27,10 @@ arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"]
[dependencies] [dependencies]
base58 = { package = "base58ck", version = "0.2.0", default-features = false, features = ["alloc"] } base58 = { package = "base58ck", version = "0.2.0", default-features = false, features = ["alloc"] }
bech32 = { version = "0.11.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"] } hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] }
internals = { package = "bitcoin-internals", version = "0.4.0", 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"] } primitives = { package = "bitcoin-primitives", version = "0.101.0", default-features = false, features = ["alloc"] }
secp256k1 = { version = "0.29.0", default-features = false, features = ["hashes", "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"] } units = { package = "bitcoin-units", version = "0.2.0", default-features = false, features = ["alloc"] }

View File

@ -15,8 +15,8 @@ exclude = ["tests", "contrib"]
[features] [features]
default = ["std"] default = ["std"]
std = ["alloc", "bitcoin-io?/std", "hex/std"] std = ["alloc", "hex/std"]
alloc = ["bitcoin-io?/alloc", "hex/alloc"] alloc = ["hex/alloc"]
serde = ["dep:serde", "hex"] serde = ["dep:serde", "hex"]
# Smaller (but slower) implementation of sha256, sha512 and ripemd160 # Smaller (but slower) implementation of sha256, sha512 and ripemd160
small-hash = [] small-hash = []
@ -24,7 +24,6 @@ small-hash = []
[dependencies] [dependencies]
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true } 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 } serde = { version = "1.0", default-features = false, optional = true }
[dev-dependencies] [dev-dependencies]

View File

@ -5,7 +5,7 @@
set -euox pipefail set -euox pipefail
# Run the sanitizer with these features. # Run the sanitizer with these features.
FEATURES="std bitcoin-io serde" FEATURES="std serde"
cargo clean cargo clean
CC='clang -fsanitize=address -fno-omit-frame-pointer' \ CC='clang -fsanitize=address -fno-omit-frame-pointer' \

View File

@ -5,10 +5,10 @@
# shellcheck disable=SC2034 # shellcheck disable=SC2034
# Test all these features with "std" enabled. # 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. # 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. # Run these examples.
EXAMPLES="" EXAMPLES=""

View File

@ -19,8 +19,8 @@ cortex-m-rt = "0.6.10"
cortex-m-semihosting = "0.3.3" cortex-m-semihosting = "0.3.3"
panic-halt = "0.2.0" panic-halt = "0.2.0"
alloc-cortex-m = { version = "0.4.1", optional = true } alloc-cortex-m = { version = "0.4.1", optional = true }
bitcoin_hashes = { path="../", default-features = false, features = ["bitcoin-io"] } bitcoin_hashes = { path="../", default-features = false, features = [] }
bitcoin-io = { path = "../../io", default_features = false } bitcoin-io = { path = "../../io", default_features = false, features = ["hashes"] }
[[bin]] [[bin]]
name = "embedded" 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 debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations lto = true # better optimizations
[patch.crates-io.bitcoin_hashes]
path = "../../hashes"
[patch.crates-io.bitcoin-internals] [patch.crates-io.bitcoin-internals]
path = "../../internals" path = "../../internals"

View File

@ -155,7 +155,8 @@ impl<'de, T: GeneralHash + Deserialize<'de>> Deserialize<'de> for Hmac<T> {
} }
} }
crate::internal_macros::impl_io_write!( #[cfg(feature = "std")]
crate::internal_macros::impl_write!(
HmacEngine<T>, HmacEngine<T>,
|us: &mut HmacEngine<T>, buf| { |us: &mut HmacEngine<T>, buf| {
us.input(buf); us.input(buf);

View File

@ -91,12 +91,6 @@ macro_rules! general_hash_type {
{ {
<Self as $crate::GeneralHash>::hash_byte_chunks(byte_slices) <Self as $crate::GeneralHash>::hash_byte_chunks(byte_slices)
} }
/// Hashes the entire contents of the `reader`.
#[cfg(feature = "bitcoin-io")]
pub fn hash_reader<R: io::BufRead>(reader: &mut R) -> Result<Self, io::Error> {
<Self as $crate::GeneralHash>::hash_reader(reader)
}
} }
}; };
} }
@ -156,7 +150,7 @@ macro_rules! hash_type_no_default {
$crate::internal_macros::hash_trait_impls!($bits, $reverse); $crate::internal_macros::hash_trait_impls!($bits, $reverse);
$crate::internal_macros::impl_io_write!( $crate::internal_macros::impl_write!(
HashEngine, HashEngine,
|us: &mut HashEngine, buf| { |us: &mut HashEngine, buf| {
crate::HashEngine::input(us, buf); crate::HashEngine::input(us, buf);
@ -168,29 +162,16 @@ macro_rules! hash_type_no_default {
} }
pub(crate) use 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 macro_rules! impl_write {
// 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 {
($ty: ty, $write_fn: expr, $flush_fn: expr $(, $bounded_ty: ident : $bounds: path),*) => { ($ty: ty, $write_fn: expr, $flush_fn: expr $(, $bounded_ty: ident : $bounds: path),*) => {
#[cfg(feature = "bitcoin-io")] // `bitcoin_io::Write` is implemented in `bitcoin_io`.
impl<$($bounded_ty: $bounds),*> bitcoin_io::Write for $ty {
#[inline]
fn write(&mut self, buf: &[u8]) -> bitcoin_io::Result<usize> {
$write_fn(self, buf)
}
#[inline]
fn flush(&mut self) -> bitcoin_io::Result<()> {
$flush_fn(self)
}
}
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<$($bounded_ty: $bounds),*> std::io::Write for $ty { impl<$($bounded_ty: $bounds),*> std::io::Write for $ty {
#[inline] #[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
$write_fn(self, buf) $write_fn(self, buf)
} }
#[inline] #[inline]
fn flush(&mut self) -> std::io::Result<()> { fn flush(&mut self) -> std::io::Result<()> {
$flush_fn(self) $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( macro_rules! engine_input_impl(
() => ( () => (

View File

@ -75,9 +75,6 @@ extern crate core;
#[cfg(feature = "std")] #[cfg(feature = "std")]
extern crate std; extern crate std;
#[cfg(feature = "bitcoin-io")]
extern crate bitcoin_io as io;
/// A generic serialization/deserialization framework. /// A generic serialization/deserialization framework.
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
pub extern crate serde; pub extern crate serde;
@ -239,28 +236,6 @@ pub trait GeneralHash: Hash {
} }
Self::from_engine(engine) Self::from_engine(engine)
} }
/// Hashes the entire contents of the `reader`.
#[cfg(feature = "bitcoin-io")]
fn hash_reader<R: io::BufRead>(reader: &mut R) -> Result<Self, io::Error>
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. /// Trait which applies to hashes of all types.
@ -367,13 +342,4 @@ mod tests {
let rinsed = hex.parse::<TestNewtype>().expect("failed to parse hex"); let rinsed = hex.parse::<TestNewtype>().expect("failed to parse hex");
assert_eq!(rinsed, orig) 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"),)
}
} }

View File

@ -87,25 +87,6 @@ where
Self::from_engine(engine) Self::from_engine(engine)
} }
/// Hashes the entire contents of the `reader`.
#[cfg(feature = "bitcoin-io")]
pub fn hash_reader<R: io::BufRead>(reader: &mut R) -> Result<Self, io::Error> {
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. /// Returns the underlying byte array.
pub const fn to_byte_array(self) -> [u8; 32] { self.0 } pub const fn to_byte_array(self) -> [u8; 32] { self.0 }
@ -152,7 +133,7 @@ impl<T: Tag> crate::HashEngine for HashEngine<T> {
fn n_bytes_hashed(&self) -> u64 { self.0.n_bytes_hashed() } fn n_bytes_hashed(&self) -> u64 { self.0.n_bytes_hashed() }
} }
crate::internal_macros::impl_io_write!( crate::internal_macros::impl_write!(
HashEngine<T>, HashEngine<T>,
|us: &mut HashEngine<T>, buf| { |us: &mut HashEngine<T>, buf| {
us.input(buf); us.input(buf);

View File

@ -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::<sha256::Hash>::new(&[0xde, 0xad, 0xbe, 0xef]);
engine.write_all(&[]).unwrap();
assert_eq!(
format!("{}", hmac::Hmac::from_engine(engine)),
"bf5515149cf797955c4d3194cca42472883281951697c8375d9d9b107f384225"
);
let mut engine = hmac::HmacEngine::<sha256::Hash>::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::<sha256::Hash>::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");
}

View File

@ -1,6 +1,8 @@
//! Regression tests for each hash type. //! Regression tests for each hash type.
//! //!
//! Note that if `bitcoin-io` is enabled then we get more regression-like testing from `./io.rs`. //! 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")] #![cfg(feature = "hex")]

View File

@ -15,12 +15,14 @@ exclude = ["tests", "contrib"]
[features] [features]
default = ["std"] default = ["std"]
std = ["alloc"] std = ["alloc", "hashes?/std"]
alloc = [] alloc = ["hashes?/alloc"]
[dependencies] [dependencies]
internals = { package = "bitcoin-internals", version = "0.4.0" } 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] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]

View File

@ -5,10 +5,10 @@
# shellcheck disable=SC2034 # shellcheck disable=SC2034
# Test all these features with "std" enabled. # Test all these features with "std" enabled.
FEATURES_WITH_STD="" FEATURES_WITH_STD="hashes"
# Test all these features without "std" enabled. # Test all these features without "std" enabled.
FEATURES_WITHOUT_STD="alloc" FEATURES_WITHOUT_STD="alloc hashes"
# Run these examples. # Run these examples.
EXAMPLES="" EXAMPLES=""

378
io/src/hash.rs Normal file
View File

@ -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<usize> {
$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<T>,
|us: &mut sha256t::HashEngine<T>, 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<T>,
|us: &mut HmacEngine<T>, 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<R: crate::BufRead>(reader: &mut R) -> Result<Self, crate::Error>
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<T: GeneralHash> GeneralHashExt for T {}
mod sealed {
/// Used to seal the `GeneralHashExt` trait.
pub trait Sealed {}
impl<T: hashes::GeneralHash> 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::<sha256::Hash>::new(&[0xde, 0xad, 0xbe, 0xef]);
engine.write_all(&[]).unwrap();
assert_eq!(
format!("{}", hmac::Hmac::from_engine(engine)),
"bf5515149cf797955c4d3194cca42472883281951697c8375d9d9b107f384225"
);
let mut engine = hmac::HmacEngine::<sha256::Hash>::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::<sha256::Hash>::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<RegHashTag>;
#[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::<sha256::Hash>::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::<sha512::Hash>::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);
}
}

View File

@ -25,11 +25,17 @@
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
extern crate alloc; extern crate alloc;
#[cfg(feature = "hashes")]
pub extern crate hashes;
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod bridge; mod bridge;
mod error; mod error;
mod macros; mod macros;
#[cfg(feature = "hashes")]
mod hash;
#[cfg(all(not(feature = "std"), feature = "alloc"))] #[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec::Vec; use alloc::vec::Vec;
use core::cmp; use core::cmp;
@ -39,6 +45,8 @@ pub use bridge::{FromStd, ToStd};
#[rustfmt::skip] // Keep public re-exports separate. #[rustfmt::skip] // Keep public re-exports separate.
pub use self::error::{Error, ErrorKind}; pub use self::error::{Error, ErrorKind};
#[cfg(feature = "hashes")]
pub use self::hash::GeneralHashExt;
/// Result type returned by functions in this crate. /// Result type returned by functions in this crate.
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;

View File

@ -22,7 +22,7 @@ serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc"]
arbitrary = ["dep:arbitrary", "units/arbitrary"] arbitrary = ["dep:arbitrary", "units/arbitrary"]
[dependencies] [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 } hex = { package = "hex-conservative", version = "0.3.0", default-features = false }
internals = { package = "bitcoin-internals", version = "0.4.0" } internals = { package = "bitcoin-internals", version = "0.4.0" }
units = { package = "bitcoin-units", version = "0.2.0", default-features = false } units = { package = "bitcoin-units", version = "0.2.0", default-features = false }