Skip to content

Commit

Permalink
refactor: remove ring dependency and associated functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Atamanov committed Oct 23, 2024
1 parent 64c47ad commit 0324ac6
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 134 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ categories = ["cryptography::cryptocurrencies"]
rust-version = "1.80.0"

[dependencies]
ring = "0.17"
sha2 = "0.10"
#ring = "0.17"

[target.'cfg(target_arch = "x86_64")'.dependencies]
cpufeatures = "0.2"
sha2 = "0.10"


[dev-dependencies]
rustc-hex = "2"
Expand Down
170 changes: 38 additions & 132 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,31 @@
//! the beacon chain. The hash function changed during the specification process, so defining it
//! once in this crate made it easy to replace.
//!
//! Now this crate serves primarily as a wrapper over two SHA256 crates: `sha2` and `ring` – which
//! it switches between at runtime based on the availability of SHA intrinsics.
//! Now this crate serves primarily as a wrapper over the `sha2` crate.
mod sha2_impl;

pub use self::DynamicContext as Context;

#[cfg(target_arch = "x86_64")]
use sha2_impl::Sha2CrateImpl;
use sha2::{Digest, Sha256};

#[cfg(feature = "zero_hash_cache")]
use std::sync::LazyLock;

/// Length of a SHA256 hash in bytes.
pub const HASH_LEN: usize = 32;

/// Returns the digest of `input` using the best available implementation.
/// Returns the digest of `input` using the `sha2` implementation.
pub fn hash(input: &[u8]) -> Vec<u8> {
DynamicImpl::best().hash(input)
Sha2Impl.hash(input)
}

/// Hash function returning a fixed-size array (to save on allocations).
///
/// Uses the best available implementation based on CPU features.
/// Uses the `sha2` implementation.
pub fn hash_fixed(input: &[u8]) -> [u8; HASH_LEN] {
DynamicImpl::best().hash_fixed(input)
Sha2Impl.hash_fixed(input)
}

/// Compute the hash of two slices concatenated.
pub fn hash32_concat(h1: &[u8], h2: &[u8]) -> [u8; 32] {
let mut ctxt = DynamicContext::new();
pub fn hash32_concat(h1: &[u8], h2: &[u8]) -> [u8; HASH_LEN] {
let mut ctxt = Sha2Context::new();
ctxt.update(h1);
ctxt.update(h2);
ctxt.finalize()
Expand All @@ -49,142 +43,58 @@ pub trait Sha256Context {
fn finalize(self) -> [u8; HASH_LEN];
}

/// Top-level trait implemented by both `sha2` and `ring` implementations.
pub trait Sha256 {
type Context: Sha256Context;

fn hash(&self, input: &[u8]) -> Vec<u8>;

fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN];
/// Implementation of SHA256 using the `sha2` crate.
pub struct Sha2Context {
hasher: Sha256,
}

/// Implementation of SHA256 using the `ring` crate (fastest on CPUs without SHA extensions).
pub struct RingImpl;

impl Sha256Context for ring::digest::Context {
impl Sha256Context for Sha2Context {
fn new() -> Self {
Self::new(&ring::digest::SHA256)
Self {
hasher: Sha256::new(),
}
}

fn update(&mut self, bytes: &[u8]) {
self.update(bytes)
self.hasher.update(bytes);
}

fn finalize(self) -> [u8; HASH_LEN] {
let mut output = [0; HASH_LEN];
output.copy_from_slice(self.finish().as_ref());
let result = self.hasher.finalize();
let mut output = [0u8; HASH_LEN];
output.copy_from_slice(&result);
output
}
}

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()
}
}

/// Default dynamic implementation that switches between available implementations.
pub enum DynamicImpl {
#[cfg(target_arch = "x86_64")]
Sha2,
Ring,
}

// Runtime latch for detecting the availability of SHA extensions on x86_64.
//
// Inspired by the runtime switch within the `sha2` crate itself.
#[cfg(target_arch = "x86_64")]
cpufeatures::new!(x86_sha_extensions, "sha", "sse2", "ssse3", "sse4.1");
/// Top-level trait implemented by the `sha2` implementation.
pub trait Sha256Trait {
type Context: Sha256Context;

#[inline(always)]
pub fn have_sha_extensions() -> bool {
#[cfg(target_arch = "x86_64")]
return x86_sha_extensions::get();
fn hash(&self, input: &[u8]) -> Vec<u8>;

#[cfg(not(target_arch = "x86_64"))]
return false;
fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN];
}

impl DynamicImpl {
/// Choose the best available implementation based on the currently executing CPU.
#[inline(always)]
pub fn best() -> Self {
#[cfg(target_arch = "x86_64")]
if have_sha_extensions() {
Self::Sha2
} else {
Self::Ring
}

#[cfg(not(target_arch = "x86_64"))]
Self::Ring
}
}
/// Implementation of the `Sha256Trait` using the `sha2` crate.
pub struct Sha2Impl;

impl Sha256 for DynamicImpl {
type Context = DynamicContext;
impl Sha256Trait for Sha2Impl {
type Context = Sha2Context;

#[inline(always)]
fn hash(&self, input: &[u8]) -> Vec<u8> {
match self {
#[cfg(target_arch = "x86_64")]
Self::Sha2 => Sha2CrateImpl.hash(input),
Self::Ring => RingImpl.hash(input),
}
let mut hasher = Sha256::new();
hasher.update(input);
hasher.finalize().to_vec()
}

#[inline(always)]
fn hash_fixed(&self, input: &[u8]) -> [u8; HASH_LEN] {
match self {
#[cfg(target_arch = "x86_64")]
Self::Sha2 => Sha2CrateImpl.hash_fixed(input),
Self::Ring => RingImpl.hash_fixed(input),
}
}
}

/// Context encapsulating all implemenation contexts.
///
/// This enum ends up being 8 bytes larger than the largest inner context.
pub enum DynamicContext {
#[cfg(target_arch = "x86_64")]
Sha2(sha2::Sha256),
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()),
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),
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),
Self::Ring(ctxt) => Sha256Context::finalize(ctxt),
}
let mut hasher = Sha256::new();
hasher.update(input);
let result = hasher.finalize();
let mut output = [0u8; HASH_LEN];
output.copy_from_slice(&result);
output
}
}

Expand All @@ -209,11 +119,7 @@ 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)]
#[test]
fn test_hashing() {
let input: Vec<u8> = b"hello world".as_ref().into();

Expand Down

0 comments on commit 0324ac6

Please sign in to comment.