mod sha2_impl;
pub use self::DynamicContext as Context;
#[cfg(target_arch = "x86_64")]
use sha2_impl::Sha2CrateImpl;
#[cfg(feature = "zero_hash_cache")]
use std::sync::LazyLock;
pub const HASH_LEN: usize = 32;
pub fn hash(input: &[u8]) -> Vec<u8> {
DynamicImpl::best().hash(input)
}
pub fn hash_fixed(input: &[u8]) -> [u8; HASH_LEN] {
DynamicImpl::best().hash_fixed(input)
}
pub fn hash32_concat(h1: &[u8], h2: &[u8]) -> [u8; 32] {
let mut ctxt = DynamicContext::new();
ctxt.update(h1);
ctxt.update(h2);
ctxt.finalize()
}
pub trait Sha256Context {
fn new() -> Self;
fn update(&mut self, bytes: &[u8]);
fn finalize(self) -> [u8; HASH_LEN];
}
pub trait Sha256 {
type Context: Sha256Context;
fn hash(&self, input: &[u8]) -> Vec<u8>;
fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN];
}
#[cfg(feature = "ring")]
pub struct RingImpl;
#[cfg(feature = "ring")]
impl Sha256Context for ring::digest::Context {
fn new() -> Self {
Self::new(&ring::digest::SHA256)
}
fn update(&mut self, bytes: &[u8]) {
self.update(bytes)
}
fn finalize(self) -> [u8; HASH_LEN] {
let mut output = [0; HASH_LEN];
output.copy_from_slice(self.finish().as_ref());
output
}
}
#[cfg(feature = "ring")]
impl Sha256 for RingImpl {
type Context = ring::digest::Context;
fn hash(&self, input: &[u8]) -> Vec<u8> {
ring::digest::digest(&ring::digest::SHA256, input)
.as_ref()
.into()
}
fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN] {
let mut ctxt = Self::Context::new(&ring::digest::SHA256);
ctxt.update(input);
ctxt.finalize()
}
}
pub enum DynamicImpl {
#[cfg(target_arch = "x86_64")]
Sha2,
#[cfg(feature = "ring")]
Ring,
}
#[cfg(target_arch = "x86_64")]
cpufeatures::new!(x86_sha_extensions, "sha", "sse2", "ssse3", "sse4.1");
#[inline(always)]
pub fn have_sha_extensions() -> bool {
#[cfg(target_arch = "x86_64")]
return x86_sha_extensions::get();
#[cfg(not(target_arch = "x86_64"))]
return false;
}
impl DynamicImpl {
#[inline(always)]
pub fn best() -> Self {
#[cfg(all(not(feature = "ring"), not(target_arch = "x86_64")))]
{
compile_error!("Ring must be enabled on non-x86_64 architectures");
}
#[cfg(all(not(feature = "ring"), target_arch = "x86_64"))]
{
Self::Sha2
}
#[cfg(all(feature = "ring", target_arch = "x86_64"))]
if have_sha_extensions() {
Self::Sha2
} else {
Self::Ring
}
#[cfg(all(feature = "ring", not(target_arch = "x86_64")))]
{
Self::Ring
}
}
}
impl Sha256 for DynamicImpl {
type Context = DynamicContext;
#[inline(always)]
fn hash(&self, input: &[u8]) -> Vec<u8> {
match self {
#[cfg(target_arch = "x86_64")]
Self::Sha2 => Sha2CrateImpl.hash(input),
#[cfg(feature = "ring")]
Self::Ring => RingImpl.hash(input),
}
}
#[inline(always)]
fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN] {
match self {
#[cfg(target_arch = "x86_64")]
Self::Sha2 => Sha2CrateImpl.hash_fixed(input),
#[cfg(feature = "ring")]
Self::Ring => RingImpl.hash_fixed(input),
}
}
}
pub enum DynamicContext {
#[cfg(target_arch = "x86_64")]
Sha2(sha2::Sha256),
#[cfg(feature = "ring")]
Ring(ring::digest::Context),
}
impl Sha256Context for DynamicContext {
fn new() -> Self {
match DynamicImpl::best() {
#[cfg(target_arch = "x86_64")]
DynamicImpl::Sha2 => Self::Sha2(Sha256Context::new()),
#[cfg(feature = "ring")]
DynamicImpl::Ring => Self::Ring(Sha256Context::new()),
}
}
fn update(&mut self, bytes: &[u8]) {
match self {
#[cfg(target_arch = "x86_64")]
Self::Sha2(ctxt) => Sha256Context::update(ctxt, bytes),
#[cfg(feature = "ring")]
Self::Ring(ctxt) => Sha256Context::update(ctxt, bytes),
}
}
fn finalize(self) -> [u8; HASH_LEN] {
match self {
#[cfg(target_arch = "x86_64")]
Self::Sha2(ctxt) => Sha256Context::finalize(ctxt),
#[cfg(feature = "ring")]
Self::Ring(ctxt) => Sha256Context::finalize(ctxt),
}
}
}
#[cfg(feature = "zero_hash_cache")]
pub const ZERO_HASHES_MAX_INDEX: usize = 48;
#[cfg(feature = "zero_hash_cache")]
pub static ZERO_HASHES: LazyLock<Vec<[u8; HASH_LEN]>> = LazyLock::new(|| {
let mut hashes = vec![[0; HASH_LEN]; ZERO_HASHES_MAX_INDEX + 1];
for i in 0..ZERO_HASHES_MAX_INDEX {
hashes[i + 1] = hash32_concat(&hashes[i], &hashes[i]);
}
hashes
});
#[cfg(test)]
mod tests {
use super::*;
use rustc_hex::FromHex;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_hashing() {
let input: Vec<u8> = b"hello world".as_ref().into();
let output = hash(input.as_ref());
let expected_hex = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
let expected: Vec<u8> = expected_hex.from_hex().unwrap();
assert_eq!(expected, output);
}
#[cfg(feature = "zero_hash_cache")]
mod zero_hash {
use super::*;
#[test]
fn zero_hash_zero() {
assert_eq!(ZERO_HASHES[0], [0; 32]);
}
}
}