Something went wrong. Try again.
Typelevel cryptographic algo tracking crates.io/crates/evidence
typelevel cryptography util
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312//! Content-addressed digests with phantom type tracking.//!//! [`Digest<T, P, C>`] is a hash digest that tracks at the type level://! - `T`: the type of value that was hashed//! - `P`: the hash primitive used (e.g., [`Sha256`](sha2::Sha256))//! - `C`: the codec used to serialize the value before hashing//!//! # Example//!//! ```//! # #[cfg(feature = "sha2")]//! # {//! use evidence::digest::{Digest, sha2::Sha256};//! use evidence::codec::Identity;//!//! let data = b"hello world";//! let digest: Digest<[u8; 11], Sha256, Identity> = Digest::hash(data);//!//! assert_eq!(digest.as_bytes().len(), 32);//! # }//! ```
use core::marker::PhantomData;
use hybrid_array::{Array, ArraySize};
use crate::codec::Encode;
#[cfg(feature = "blake3")]pub mod blake3;
#[cfg(feature = "sha2")]pub mod sha2;
#[cfg(feature = "sha3")]pub mod sha3;
/// A digest with phantom types tracking what was digested and how.pub struct Digest<T, P: DigestPrimitive, C> { bytes: Array<u8, P::Size>, _marker: PhantomData<fn() -> (T, C)>,}
impl<T, P: DigestPrimitive, C> Clone for Digest<T, P, C>where Array<u8, P::Size>: Clone,{ fn clone(&self) -> Self { Self { bytes: self.bytes.clone(), _marker: PhantomData, } }}
impl<T, P: DigestPrimitive, C> PartialEq for Digest<T, P, C>where Array<u8, P::Size>: PartialEq,{ fn eq(&self, other: &Self) -> bool { self.bytes == other.bytes }}
impl<T, P: DigestPrimitive, C> Eq for Digest<T, P, C> where Array<u8, P::Size>: Eq {}
impl<T, P: DigestPrimitive, C> core::hash::Hash for Digest<T, P, C>where Array<u8, P::Size>: core::hash::Hash,{ fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.bytes.hash(state); }}
impl<T, P: DigestPrimitive, C> Digest<T, P, C> { /// Compute a digest of the value. /// /// The value is first encoded using codec `C`, then hashed with primitive `P`. /// /// # Panics /// /// Panics if the codec fails to encode the value. #[allow(clippy::expect_used)] // documented panic on encode failure pub fn hash(value: &T) -> Self where C: Encode<T>, { let encoded = C::encode(value).expect("encoding failed"); let bytes = P::hash(&encoded); Self::new(bytes) }
/// Internal constructor for creating a digest from raw bytes. #[must_use] pub(crate) const fn new(bytes: Array<u8, P::Size>) -> Self { Self { bytes, _marker: PhantomData, } }
/// Get the raw bytes of the digest. #[must_use] pub const fn as_array(&self) -> &Array<u8, P::Size> { &self.bytes }
/// Get the digest as a byte slice. #[must_use] pub const fn as_bytes(&self) -> &[u8] { self.bytes.as_slice() }
/// Consume the digest and return the underlying byte array. #[must_use] pub fn into_array(self) -> Array<u8, P::Size> { self.bytes }}
/// Extension trait for constructing digests from raw bytes.////// This trait is _intentionally_ not in the prelude. Importing it is an explicit/// acknowledgment that you are bypassing the type-level guarantees that [`Digest`]/// normally provides.////// # When to use////// - Deserializing a digest from storage or network/// - Interoperating with external systems that provide raw hash bytes/// - Testing////// # Example////// ```/// # #[cfg(feature = "sha2")]/// # {/// use evidence::digest::{Digest, DigestUnchecked, sha2::Sha256};/// use evidence::codec::Identity;/// use hybrid_array::Array;////// // Explicit import required — this is the "escape hatch"/// let bytes: Array<u8, _> = Array::try_from([0u8; 32].as_slice()).unwrap();/// let digest: Digest<String, Sha256, Identity> = Digest::from_unchecked_array(bytes);/// # }/// ```pub trait DigestUnchecked<T, P: DigestPrimitive, C> { /// Create a digest from a raw byte array. /// /// # Safety (logical) /// /// This does not perform any hashing. The caller must ensure the bytes /// represent a valid digest of a `T` value encoded with codec `C`. /// Prefer [`Digest::hash`] when possible. #[must_use] fn from_unchecked_array(bytes: Array<u8, P::Size>) -> Self;}
impl<T, P: DigestPrimitive, C> DigestUnchecked<T, P, C> for Digest<T, P, C> { fn from_unchecked_array(bytes: Array<u8, P::Size>) -> Self { Self::new(bytes) }}
impl<T, P: DigestPrimitive, C> Copy for Digest<T, P, C> where Array<u8, P::Size>: Copy {}
impl<T, P: DigestPrimitive, C> core::fmt::Debug for Digest<T, P, C> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "Digest({:02x?})", self.bytes.as_slice()) }}
/// A hash primitive that can digest bytes into a fixed-size output.pub trait DigestPrimitive { /// The output size of this hash function. type Size: ArraySize;
/// Hash the input bytes and return the digest. fn hash(data: &[u8]) -> Array<u8, Self::Size>;}
#[cfg(feature = "serde")]impl<T, P: DigestPrimitive, C> serde::Serialize for Digest<T, P, C>where Array<u8, P::Size>: serde::Serialize,{ fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { self.bytes.serialize(serializer) }}
#[cfg(feature = "serde")]impl<'de, T, P: DigestPrimitive, C> serde::Deserialize<'de> for Digest<T, P, C>where Array<u8, P::Size>: serde::Deserialize<'de>,{ fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { let bytes = Array::deserialize(deserializer)?; Ok(Self::new(bytes)) }}
#[cfg(feature = "arbitrary")]impl<'a, T, P: DigestPrimitive, C> arbitrary::Arbitrary<'a> for Digest<T, P, C>where Array<u8, P::Size>: arbitrary::Arbitrary<'a>,{ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { let bytes = Array::arbitrary(u)?; Ok(Self::new(bytes)) }}
#[cfg(feature = "bolero")]impl<T: 'static, P: DigestPrimitive + 'static, C: 'static> bolero_generator::TypeGenerator for Digest<T, P, C>where Array<u8, P::Size>: bolero_generator::TypeGenerator,{ fn generate<D: bolero_generator::Driver>(driver: &mut D) -> Option<Self> { let bytes = Array::generate(driver)?; Some(Self::new(bytes)) }}
#[cfg(feature = "proptest")]impl<T: 'static, P: DigestPrimitive + 'static, C: 'static> proptest::arbitrary::Arbitrary for Digest<T, P, C>where Array<u8, P::Size>: core::fmt::Debug,{ type Parameters = (); type Strategy = proptest::strategy::BoxedStrategy<Self>;
#[allow(clippy::expect_used)] // length is guaranteed by proptest generator fn arbitrary_with((): Self::Parameters) -> Self::Strategy { use hybrid_array::typenum::Unsigned; use proptest::prelude::*; proptest::collection::vec(any::<u8>(), P::Size::USIZE) .prop_map(|v| Self::new(Array::try_from(v.as_slice()).expect("correct length"))) .boxed() }}
#[cfg(feature = "rkyv")]/// Zero-copy [`rkyv`] serialization support for [`Digest`].pub mod archive { use super::{Array, Digest, DigestPrimitive}; use alloc::vec::Vec; use rkyv::{Archive, Deserialize, Serialize, rancor::Fallible};
/// Helper struct for rkyv serialization. /// /// The phantom type parameters from [`Digest`] are erased in the archived /// form since they only matter at compile time. #[derive(Debug, Archive, Serialize, Deserialize)] #[rkyv(derive(Debug))] pub struct DigestBytes { bytes: Vec<u8>, }
impl ArchivedDigestBytes { /// Get the archived bytes as a slice. #[must_use] pub fn as_bytes(&self) -> &[u8] { &self.bytes } }
impl<T, P: DigestPrimitive, C> Archive for Digest<T, P, C> { type Archived = ArchivedDigestBytes; type Resolver = <DigestBytes as Archive>::Resolver;
fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) { let helper = DigestBytes { bytes: self.bytes.as_slice().to_vec(), }; helper.resolve(resolver, out); } }
impl<T, P: DigestPrimitive, C, S> Serialize<S> for Digest<T, P, C> where S: Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized, { fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> { let helper = DigestBytes { bytes: self.bytes.as_slice().to_vec(), }; helper.serialize(serializer) } }
impl<T, P: DigestPrimitive, C, D> Deserialize<Digest<T, P, C>, D> for ArchivedDigestBytes where D: Fallible + ?Sized, D::Error: rkyv::rancor::Source, { #[allow(clippy::expect_used)] // length validated during serialization fn deserialize(&self, deserializer: &mut D) -> Result<Digest<T, P, C>, D::Error> { let helper: DigestBytes = <ArchivedDigestBytes as Deserialize<DigestBytes, D>>::deserialize( self, deserializer, )?; let bytes = Array::try_from(helper.bytes.as_slice()).expect("invalid digest length"); Ok(Digest::new(bytes)) } }}