hashes: Add a new hash_reader function

Add a function `hash_reader` that uses the `BufRead` trait to read
bytes directly into the hash engine.

Add the functionality to:

- as a trait method in the `GeneralHash` trait with default implementation
- as inherent functions to all the hash types

Close: #3050
This commit is contained in:
Tobin C. Harding 2024-07-18 07:33:20 +10:00
parent 5cca2f271d
commit 0a045d87ea
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
3 changed files with 67 additions and 1 deletions

View File

@ -209,6 +209,13 @@ macro_rules! hash_type {
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> {
<Self as crate::GeneralHash>::hash_reader(reader)
}
/// Returns the underlying byte array.
pub const fn to_byte_array(self) -> [u8; $bits / 8] { self.0 }

View File

@ -80,6 +80,9 @@ extern crate alloc;
#[cfg(any(test, feature = "std"))]
extern crate core;
#[cfg(feature = "bitcoin-io")]
extern crate bitcoin_io as io;
#[cfg(feature = "serde")]
/// A generic serialization/deserialization framework.
pub extern crate serde;
@ -232,6 +235,27 @@ pub trait GeneralHash: Hash {
}
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();
if read == 0 { // Empty slice means EOF.
break
}
engine.input(bytes);
reader.consume(read);
}
Ok(Self::from_engine(engine))
}
}
/// Trait which applies to hashes of all types.
@ -298,7 +322,8 @@ impl std::error::Error for FromSliceError {}
#[cfg(test)]
mod tests {
use crate::sha256d;
use super::*;
use crate::{sha256, sha256d};
hash_newtype! {
/// A test newtype
@ -323,4 +348,13 @@ mod tests {
let rinsed = hex.parse::<TestNewtype>().expect("failed to parse hex");
assert_eq!(rinsed, orig)
}
#[test]
fn hash_reader() {
let mut reader: &[u8] = b"hello";
assert_eq!(
sha256::Hash::hash_reader(&mut reader).unwrap(),
sha256::Hash::hash(b"hello"),
)
}
}

View File

@ -96,6 +96,24 @@ where
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();
if read == 0 { // Empty slice means EOF.
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 }
@ -231,6 +249,13 @@ macro_rules! sha256t_hash_newtype {
}
Self::from_engine(engine)
}
/// Hashes the entire contents of the `reader`.
#[cfg(feature = "bitcoin-io")]
#[allow(unused)] // the user of macro may not need this
fn hash_reader<R: io::BufRead>(reader: &mut R) -> Result<Self, io::Error> {
<$hash_name as $crate::GeneralHash>::hash_reader(reader)
}
}
impl $crate::GeneralHash for $hash_name {