Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4 from tcharding/05-13-tagged
Browse files Browse the repository at this point in the history
Add tagged SHA-256 hash module
  • Loading branch information
tcharding authored May 14, 2024
2 parents 7b38abe + fba6888 commit 5054272
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 14 deletions.
6 changes: 3 additions & 3 deletions src/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! Hash-based Message Authentication Code (HMAC).
//!

use core::{borrow, fmt, ops, str};
use core::{fmt, ops, str};

use hex::DisplayHex;

Expand Down Expand Up @@ -151,8 +151,8 @@ impl<const N: usize> ops::Index<ops::RangeFull> for Hmac<N> {
fn index(&self, index: ops::RangeFull) -> &[u8] { &self.0[index] }
}

impl<const N: usize> borrow::Borrow<[u8]> for Hmac<N> {
fn borrow(&self) -> &[u8] { &self.0 }
impl<const N: usize> AsRef<[u8]> for Hmac<N> {
fn as_ref(&self) -> &[u8] { &self.0 }
}

#[cfg(feature = "serde")]
Expand Down
12 changes: 11 additions & 1 deletion src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use bitcoin_io::impl_write;

use crate::{ripemd160, sha1, sha256, sha512, siphash24, HashEngine, HmacEngine};
use crate::{ripemd160, sha1, sha256, sha256t, sha512, siphash24, HashEngine, HmacEngine};

impl_write!(
sha1::HashEngine,
Expand Down Expand Up @@ -78,6 +78,16 @@ impl<E: HashEngine> std::io::Write for HmacEngine<E> {
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
}

impl_write!(
sha256t::HashEngine<T>,
|us: &mut sha256t::HashEngine<T>, buf| {
us.input(buf);
Ok(buf.len())
},
|_us| { Ok(()) },
T: crate::sha256t::Tag
);

#[cfg(test)]
mod tests {
use bitcoin_io::Write;
Expand Down
18 changes: 8 additions & 10 deletions src/internal_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ pub(crate) use arr_newtype_fmt_impl;
/// `internal_new` is required so that types with more than one field are constructible.
/// `internal_engine` is required to initialize the engine for given hash type.
macro_rules! hash_trait_impls {
($hash:ident, $bits:expr) => {
impl $crate::_export::_core::str::FromStr for $hash {
($bits:expr $(, $gen:ident: $gent:ident)*) => {
impl<$($gen: $gent),*> $crate::_export::_core::str::FromStr for Hash<$($gen),*> {
type Err = $crate::hex::HexToArrayError;
fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
use $crate::hex::FromHex;
Expand All @@ -66,17 +66,15 @@ macro_rules! hash_trait_impls {
}
}

$crate::internal_macros::arr_newtype_fmt_impl!($hash, $bits / 8);
serde_impl!($hash, $bits / 8);
as_ref_impl!($hash);
$crate::internal_macros::arr_newtype_fmt_impl!(Hash, $bits / 8 $(, $gen: $gent)*);
serde_impl!(Hash, $bits / 8 $(, $gen: $gent)*);
as_ref_impl!(Hash $(, $gen: $gent)*);

impl $crate::_export::_core::convert::AsRef<[u8; $bits / 8]> for $hash {
impl<$($gen: $gent),*> $crate::_export::_core::convert::AsRef<[u8; $bits / 8]> for Hash<$($gen),*> {
fn as_ref(&self) -> &[u8; $bits / 8] { &self.0 }
}

impl<I: $crate::_export::_core::slice::SliceIndex<[u8]>>
$crate::_export::_core::ops::Index<I> for $hash
{
impl<I: $crate::_export::_core::slice::SliceIndex<[u8]> $(, $gen: $gent)*> $crate::_export::_core::ops::Index<I> for Hash<$($gen),*> {
type Output = I::Output;

#[inline]
Expand Down Expand Up @@ -192,7 +190,7 @@ macro_rules! hash_type {
}
}

crate::internal_macros::hash_trait_impls!(Hash, $bits);
crate::internal_macros::hash_trait_impls!($bits);
};
}
pub(crate) use hash_type;
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//! - RIPEMD-160
//! - SipHash-2-4
//! - HMAC-x (where x is any of the hash functions above).
//! - Domain separation using tagged SHA-256.
//!
//! ## Commonly used operations
//!
Expand Down Expand Up @@ -122,6 +123,7 @@ mod impls;
pub mod ripemd160;
pub mod sha1;
pub mod sha256;
pub mod sha256t;
pub mod sha384;
pub mod sha512;
pub mod sha512_256;
Expand Down
194 changes: 194 additions & 0 deletions src/sha256t.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: CC0-1.0

//! SHA256t implementation (tagged SHA256).

use core::cmp;
use core::marker::PhantomData;

use crate::{sha256, HashEngine as _};

/// Engine to compute tagged SHA-256 hash function.
#[derive(Clone)]
pub struct HashEngine<T>(sha256::HashEngine, PhantomData<T>);

impl<T: Tag> Default for HashEngine<T> {
fn default() -> Self { <T as Tag>::engine() }
}

impl<T: Tag> crate::HashEngine for HashEngine<T> {
type Digest = [u8; 32];
type Midstate = sha256::Midstate;
const BLOCK_SIZE: usize = sha256::BLOCK_SIZE;

#[inline]
fn new() -> Self { <T as Tag>::engine() }

#[inline]
fn input(&mut self, data: &[u8]) { self.0.input(data) }

#[inline]
fn n_bytes_hashed(&self) -> usize { self.0.n_bytes_hashed() }

#[inline]
fn finalize(self) -> Self::Digest { self.0.finalize() }

#[inline]
fn midstate(&self) -> Self::Midstate { self.0.midstate() }

#[inline]
fn from_midstate(midstate: sha256::Midstate, length: usize) -> HashEngine<T> {
let inner = sha256::HashEngine::from_midstate(midstate, length);
Self(inner, PhantomData)
}
}

/// Trait representing a tag that can be used as a context for SHA256t hashes.
pub trait Tag: Clone {
/// Returns a hash engine that is pre-tagged and is ready to be used for the data.
fn engine() -> HashEngine<Self>;
}

/// Output of the SHA256t hash function.
#[repr(transparent)]
pub struct Hash<T: Tag>([u8; 32], PhantomData<T>);

impl<T: Tag> Hash<T> {
/// Length of the hash, in bytes.
pub const LEN: usize = 32;

/// Creates a default hash engine, adds `bytes` to it, then finalizes the engine.
///
/// # Returns
///
/// The digest created by hashing `bytes` with engine's hashing algorithm.
#[allow(clippy::self_named_constructors)] // `hash` is a verb but `Hash` is a noun.
pub fn hash(bytes: &[u8]) -> Self {
let mut engine = Self::engine();
engine.input(bytes);
Self(engine.finalize(), PhantomData)
}

/// Returns a hash engine that is ready to be used for data.
pub fn engine() -> HashEngine<T> { <T as Tag>::engine() }

/// Creates a `Hash` from an `engine`.
///
/// This is equivalent to calling `Hash::from_byte_array(engine.finalize())`.
pub fn from_engine(engine: HashEngine<T>) -> Self {
let digest = engine.finalize();
Self(digest, PhantomData)
}

/// Zero cost conversion between a fixed length byte array shared reference and
/// a shared reference to this Hash type.
pub fn from_bytes_ref(bytes: &[u8; 32]) -> &Self {
// Safety: Sound because Self is #[repr(transparent)] containing [u8; Self::LEN]
unsafe { &*(bytes as *const _ as *const Self) }
}

/// Zero cost conversion between a fixed length byte array exclusive reference and
/// an exclusive reference to this Hash type.
pub fn from_bytes_mut(bytes: &mut [u8; 32]) -> &mut Self {
// Safety: Sound because Self is #[repr(transparent)] containing [u8; 32]
unsafe { &mut *(bytes as *mut _ as *mut Self) }
}

/// Copies a byte slice into a hash object.
pub fn from_slice(sl: &[u8]) -> Result<Self, crate::FromSliceError> {
if sl.len() != 32 {
Err(crate::FromSliceError { expected: 32, got: sl.len() })
} else {
let mut ret = [0; 32];
ret.copy_from_slice(sl);
Ok(Self::from_byte_array(ret))
}
}

/// Constructs a hash from the underlying byte array.
pub fn from_byte_array(bytes: [u8; 32]) -> Self { Self(bytes, PhantomData) }

/// Returns the underlying byte array.
pub fn to_byte_array(self) -> [u8; 32] { self.0 }

/// Returns a reference to the underlying byte array.
pub fn as_byte_array(&self) -> &[u8; 32] { &self.0 }

/// Returns an all zero hash.
///
/// An all zeros hash is a made up construct because there is not a known input that can
/// create it, however it is used in various places in Bitcoin e.g., the Bitcoin genesis
/// block's previous blockhash and the coinbase transaction's outpoint txid.
pub fn all_zeros() -> Self { Self([0x00; 32], PhantomData) }
}

#[cfg(feature = "schemars")]
impl<T: Tag> schemars::JsonSchema for Hash<T> {
fn schema_name() -> String { "Hash".to_owned() }

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
let mut schema: schemars::schema::SchemaObject = <String>::json_schema(gen).into();
schema.string = Some(Box::new(schemars::schema::StringValidation {
max_length: Some(32 * 2),
min_length: Some(32 * 2),
pattern: Some("[0-9a-fA-F]+".to_owned()),
}));
schema.into()
}
}

impl<T: Tag> Copy for Hash<T> {}
impl<T: Tag> Clone for Hash<T> {
fn clone(&self) -> Self { *self }
}
impl<T: Tag> PartialEq for Hash<T> {
fn eq(&self, other: &Hash<T>) -> bool { self.0 == other.0 }
}
impl<T: Tag> Eq for Hash<T> {}
impl<T: Tag> Default for Hash<T> {
fn default() -> Self { Hash([0; 32], PhantomData) }
}
impl<T: Tag> PartialOrd for Hash<T> {
fn partial_cmp(&self, other: &Hash<T>) -> Option<cmp::Ordering> {
Some(cmp::Ord::cmp(self, other))
}
}
impl<T: Tag> Ord for Hash<T> {
fn cmp(&self, other: &Hash<T>) -> cmp::Ordering { cmp::Ord::cmp(&self.0, &other.0) }
}
impl<T: Tag> core::hash::Hash for Hash<T> {
fn hash<H: core::hash::Hasher>(&self, h: &mut H) { self.0.hash(h) }
}

crate::internal_macros::hash_trait_impls!(256, T: Tag);

#[cfg(test)]
mod tests {
use super::*;
use crate::sha256;

const TEST_MIDSTATE: [u8; 32] = [
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,
];

// The digest created by sha256 hashing `&[0]` starting with `TEST_MIDSTATE`.
#[cfg(feature = "alloc")]
const HASH_ZERO: &str = "ed1382037800c9dd938dd8854f1a8863bcdeb6705069b4b56a66ec22519d5829";

#[derive(Clone)]
pub struct TestTag;

impl Tag for TestTag {
fn engine() -> HashEngine<Self> {
let midstate = sha256::Midstate::from_byte_array(TEST_MIDSTATE);
let inner = sha256::HashEngine::from_midstate(midstate, 64);
HashEngine(inner, PhantomData)
}
}

#[test]
#[cfg(feature = "alloc")]
fn manually_created_sha256t_hash_type() {
assert_eq!(Hash::<TestTag>::hash(&[0]).to_string(), HASH_ZERO);
}
}

0 comments on commit 5054272

Please sign in to comment.