use core::cmp;
use core::marker::PhantomData;
use crate::{sha256, HashEngine};
#[derive(Clone)]
pub struct Engine<T>(sha256::Engine, PhantomData<T>);
impl<T: Tag> Default for Engine<T> {
fn default() -> Self { <T as Tag>::engine() }
}
impl<T: Tag> HashEngine for Engine<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) -> Engine<T> {
let inner = sha256::Engine::from_midstate(midstate, length);
Self(inner, PhantomData)
}
}
pub trait Tag: Clone {
fn engine() -> Engine<Self>;
}
#[repr(transparent)]
pub struct Hash<T: Tag>([u8; 32], PhantomData<T>);
impl<T: Tag> Hash<T> {
pub const LEN: usize = 32;
#[allow(clippy::self_named_constructors)] pub fn hash(bytes: &[u8]) -> Self {
let mut engine = Self::engine();
engine.input(bytes);
Self(engine.finalize(), PhantomData)
}
pub fn engine() -> Engine<T> { <T as Tag>::engine() }
pub fn from_engine(engine: Engine<T>) -> Self {
let digest = engine.finalize();
Self(digest, PhantomData)
}
pub fn from_bytes_ref(bytes: &[u8; 32]) -> &Self {
unsafe { &*(bytes as *const _ as *const Self) }
}
pub fn from_bytes_mut(bytes: &mut [u8; 32]) -> &mut Self {
unsafe { &mut *(bytes as *mut _ as *mut Self) }
}
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))
}
}
pub fn from_byte_array(bytes: [u8; 32]) -> Self { Self(bytes, PhantomData) }
pub fn to_byte_array(self) -> [u8; 32] { self.0 }
pub fn as_byte_array(&self) -> &[u8; 32] { &self.0 }
pub fn as_bytes(&self) -> &[u8] { &self.0 }
#[cfg(feature = "alloc")]
pub fn to_bytes(&self) -> Vec<u8> { self.0.to_vec() }
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,
];
#[cfg(feature = "alloc")]
const HASH_ZERO: &str = "ed1382037800c9dd938dd8854f1a8863bcdeb6705069b4b56a66ec22519d5829";
#[derive(Clone)]
pub struct TestTag;
impl Tag for TestTag {
fn engine() -> Engine<Self> {
let midstate = sha256::Midstate::from_byte_array(TEST_MIDSTATE);
let inner = sha256::Engine::from_midstate(midstate, 64);
Engine(inner, PhantomData)
}
}
#[test]
#[cfg(feature = "alloc")]
fn manually_created_sha256t_hash_type() {
assert_eq!(Hash::<TestTag>::hash(&[0]).to_string(), HASH_ZERO);
}
}