Something went wrong. Try again.
Typelevel cryptographic algo tracking crates.io/crates/evidence
typelevel cryptography util
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422//! Message Authentication Codes with type-level tracking.//!//! [`Mac<T, M, C>`] represents data with a MAC tag attached. Like [`Signed`](crate::signed::Signed),//! the payload is inaccessible until verification succeeds — but MACs use symmetric//! keys rather than public-key cryptography.//!//! # Type Parameters//!//! - `T`: The payload type that was authenticated//! - `M`: The MAC primitive (e.g., [`HmacSha256`](hmac::HmacSha256))//! - `C`: The codec used to serialize the payload//!//! # Example//!//! ```//! # #[cfg(feature = "hmac")]//! # {//! use evidence::{codec::Identity, mac::{Mac, hmac::HmacSha256}};//!//! let key = hmac::digest::Key::<hmac::Hmac<sha2::Sha256>>::from_slice(&[0u8; 64]);//! let data = b"hello world";//!//! let mac: Mac<[u8; 11], HmacSha256, Identity> = Mac::tag(key, data);//!//! // Payload is NOT accessible here — must verify first//! let payload = mac.try_verify(key).unwrap();//! assert_eq!(payload, *data);//! # }//! ```
use alloc::vec::Vec;use core::{fmt::Debug, marker::PhantomData};
use crate::codec::{Decode, Encode};
/// A MAC primitive that can authenticate and verify messages.////// This trait abstracts over different MAC schemes (HMAC, KMAC, Poly1305, etc.)/// similar to how [`SignaturePrimitive`](crate::signature::SignaturePrimitive) abstracts/// over signature algorithms — but MACs use symmetric keys.pub trait MacPrimitive { /// The secret key type (same key for tagging and verification). type Key;
/// The MAC tag type. type Tag: Clone + Eq;
/// Error type returned when verification fails. type Error: Debug;
/// Compute a MAC tag over the message. fn mac(key: &Self::Key, message: &[u8]) -> Self::Tag;
/// Verify a MAC tag against a message. /// /// # Errors /// /// Returns an error if the MAC tag does not match. fn verify(key: &Self::Key, message: &[u8], tag: &Self::Tag) -> Result<(), Self::Error>;}
/// Authenticated data with a MAC tag.////// This type deliberately does _not_ provide access to the payload./// You must call [`try_verify`](Self::try_verify) to obtain the payload,/// which ensures you cannot accidentally use unauthenticated data.////// # Construction////// Use [`tag`](Self::tag) to create an authenticated payload.////// For deserialization from untrusted sources, import the/// [`MacUnchecked`] extension trait.pub struct Mac<T, M: MacPrimitive, C> { tag: M::Tag, encoded_payload: Vec<u8>, _marker: PhantomData<fn() -> (T, C)>,}
impl<T, M: MacPrimitive, C> Clone for Mac<T, M, C>where M::Tag: Clone,{ fn clone(&self) -> Self { Self { tag: self.tag.clone(), encoded_payload: self.encoded_payload.clone(), _marker: PhantomData, } }}
impl<T, M: MacPrimitive, C> PartialEq for Mac<T, M, C>where M::Tag: PartialEq,{ fn eq(&self, other: &Self) -> bool { self.tag == other.tag && self.encoded_payload == other.encoded_payload }}
impl<T, M: MacPrimitive, C> Eq for Mac<T, M, C> where M::Tag: Eq {}
impl<T, M: MacPrimitive, C> core::fmt::Debug for Mac<T, M, C>where M::Tag: core::fmt::Debug,{ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Mac") .field("tag", &self.tag) .field("encoded_payload", &self.encoded_payload) .finish() }}
impl<T, M: MacPrimitive, C> Mac<T, M, C> { /// Create a MAC from its components. /// /// This is `pub(crate)` — external users should use [`tag`](Self::tag) /// or the [`MacUnchecked`] extension trait. #[must_use] pub(crate) fn new(tag: M::Tag, encoded_payload: Vec<u8>) -> Self { Self { tag, encoded_payload, _marker: PhantomData, } }
/// Compute a MAC tag over a payload. /// /// The payload is encoded using codec `C`, then authenticated with key. /// /// # Panics /// /// Panics if the codec fails to encode the payload. #[must_use] #[allow(clippy::expect_used)] // documented panic on encode failure pub fn tag(key: &M::Key, payload: &T) -> Self where C: Encode<T>, { let encoded = C::encode(payload).expect("encoding failed"); let tag = M::mac(key, &encoded); Self::new(tag, encoded) }
/// Verify the MAC and decode the payload. /// /// # Errors /// /// Returns [`MacError::InvalidMac`] if the tag does not verify. /// /// Returns [`MacError::DecodeError`] if the payload cannot be decoded. pub fn try_verify(&self, key: &M::Key) -> Result<T, MacError> where C: Decode<T>, { M::verify(key, &self.encoded_payload, &self.tag).map_err(|_| MacError::InvalidMac)?;
C::decode(&self.encoded_payload).map_err(|_| MacError::DecodeError) }
/// Get the MAC tag. #[must_use] pub const fn mac_tag(&self) -> &M::Tag { &self.tag }
/// Get the encoded payload bytes. /// /// Note: This returns the raw encoded bytes, _not_ the decoded payload. /// The decoded payload is only accessible after verification via /// [`try_verify`](Self::try_verify). #[must_use] pub fn encoded_payload(&self) -> &[u8] { &self.encoded_payload }}
/// Error returned when MAC verification fails.////// Details of _why_ verification failed are intentionally hidden/// to avoid leaking information.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum MacError { /// MAC tag did not verify. InvalidMac,
/// Payload could not be decoded. DecodeError,}
impl core::fmt::Display for MacError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::InvalidMac => write!(f, "invalid MAC"), Self::DecodeError => write!(f, "payload decode error"), } }}
/// Extension trait for constructing [`Mac`] from raw components.////// This trait is _intentionally_ not in the prelude. Importing it is an explicit/// acknowledgment that you are bypassing the normal authentication flow.////// # When to use////// - Deserializing authenticated data from storage or network/// - Interoperating with external systems/// - Testing////// # Example////// ```/// # #[cfg(feature = "hmac")]/// # {/// use evidence::{codec::Identity, mac::{Mac, MacUnchecked, hmac::HmacSha256}};////// // Reconstruct from deserialized components/// let tag: [u8; 32] = [0u8; 32];/// let encoded = vec![1, 2, 3, 4];////// let mac: Mac<Vec<u8>, HmacSha256, Identity> = Mac::from_unchecked_parts(tag, encoded);/// # }/// ```pub trait MacUnchecked<T, M: MacPrimitive, C> { /// Create a MAC from raw components. /// /// # Safety (logical) /// /// This does not perform any verification. The caller must ensure /// the components represent valid authenticated data. fn from_unchecked_parts(tag: M::Tag, encoded_payload: Vec<u8>) -> Self;}
impl<T, M: MacPrimitive, C> MacUnchecked<T, M, C> for Mac<T, M, C> { fn from_unchecked_parts(tag: M::Tag, encoded_payload: Vec<u8>) -> Self { Self::new(tag, encoded_payload) }}
#[cfg(feature = "serde")]impl<T, M: MacPrimitive, C> serde::Serialize for Mac<T, M, C>where M::Tag: serde::Serialize,{ fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { use serde::ser::SerializeStruct; let mut state = serializer.serialize_struct("Mac", 2)?; state.serialize_field("tag", &self.tag)?; state.serialize_field("encoded_payload", &self.encoded_payload)?; state.end() }}
#[cfg(feature = "serde")]impl<'de, T, M: MacPrimitive, C> serde::Deserialize<'de> for Mac<T, M, C>where M::Tag: serde::Deserialize<'de>,{ fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { use serde::de::{MapAccess, Visitor};
struct MacVisitor<T, M: MacPrimitive, C>(PhantomData<(T, M, C)>);
impl<'de, T, M: MacPrimitive, C> Visitor<'de> for MacVisitor<T, M, C> where M::Tag: serde::Deserialize<'de>, { type Value = Mac<T, M, C>;
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { formatter.write_str("struct Mac") }
fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Mac<T, M, C>, V::Error> { let mut tag = None; let mut encoded_payload = None;
while let Some(key) = map.next_key::<&str>()? { match key { "tag" => tag = Some(map.next_value()?), "encoded_payload" => encoded_payload = Some(map.next_value()?), _ => { let _: serde::de::IgnoredAny = map.next_value()?; } } }
let tag = tag.ok_or_else(|| serde::de::Error::missing_field("tag"))?; let encoded_payload = encoded_payload .ok_or_else(|| serde::de::Error::missing_field("encoded_payload"))?;
Ok(Mac::new(tag, encoded_payload)) } }
const FIELDS: &[&str] = &["tag", "encoded_payload"]; deserializer.deserialize_struct("Mac", FIELDS, MacVisitor(PhantomData)) }}
#[cfg(feature = "arbitrary")]impl<'a, T, M: MacPrimitive, C> arbitrary::Arbitrary<'a> for Mac<T, M, C>where M::Tag: arbitrary::Arbitrary<'a>,{ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { let tag = M::Tag::arbitrary(u)?; let encoded_payload = Vec::arbitrary(u)?; Ok(Self::new(tag, encoded_payload)) }}
#[cfg(feature = "bolero")]impl<T: 'static, M: MacPrimitive + 'static, C: 'static> bolero_generator::TypeGenerator for Mac<T, M, C>where M::Tag: bolero_generator::TypeGenerator,{ fn generate<D: bolero_generator::Driver>(driver: &mut D) -> Option<Self> { let tag = M::Tag::generate(driver)?; let encoded_payload = Vec::generate(driver)?; Some(Self::new(tag, encoded_payload)) }}
#[cfg(feature = "proptest")]impl<T: 'static, M: MacPrimitive + 'static, C: 'static> proptest::arbitrary::Arbitrary for Mac<T, M, C>where M::Tag: proptest::arbitrary::Arbitrary + 'static,{ type Parameters = (); type Strategy = proptest::strategy::BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy { use proptest::prelude::*; ( any::<M::Tag>(), proptest::collection::vec(any::<u8>(), 0..256), ) .prop_map(|(tag, encoded_payload)| Self::new(tag, encoded_payload)) .boxed() }}
#[cfg(feature = "rkyv")]/// Zero-copy [`rkyv`] serialization support for [`Mac`].pub mod archive { use super::{Mac, MacPrimitive}; use alloc::vec::Vec; use rkyv::{Archive, Archived, Deserialize, Serialize, rancor::Fallible};
impl<T, M: MacPrimitive, C> Archive for Mac<T, M, C> where M::Tag: Archive, { type Archived = ArchivedMac<M::Tag>; type Resolver = MacResolver<M::Tag>;
fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) { let helper = MacHelper { tag: self.tag.clone(), encoded_payload: self.encoded_payload.clone(), }; helper.resolve(resolver, out); } }
impl<T, M: MacPrimitive, C, S> Serialize<S> for Mac<T, M, C> where M::Tag: Serialize<S>, S: Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized, { fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> { let helper = MacHelper { tag: self.tag.clone(), encoded_payload: self.encoded_payload.clone(), }; helper.serialize(serializer) } }
impl<T, M: MacPrimitive, C, D> Deserialize<Mac<T, M, C>, D> for ArchivedMac<M::Tag> where M::Tag: Archive, Archived<M::Tag>: Deserialize<M::Tag, D>, D: Fallible + ?Sized, D::Error: rkyv::rancor::Source, { fn deserialize(&self, deserializer: &mut D) -> Result<Mac<T, M, C>, D::Error> { let helper: MacHelper<M::Tag> = <ArchivedMac<M::Tag> as Deserialize< MacHelper<M::Tag>, D, >>::deserialize(self, deserializer)?; Ok(Mac::new(helper.tag, helper.encoded_payload)) } }
/// Helper struct for rkyv serialization. /// /// The phantom type parameters from [`Mac`] are erased in the archived /// form since they only matter at compile time. #[derive(Debug, Archive, Serialize, Deserialize)] pub struct MacHelper<Tag> { tag: Tag, encoded_payload: Vec<u8>, }
/// Type alias for the archived form of [`Mac`]. pub type ArchivedMac<Tag> = ArchivedMacHelper<Tag>;
/// Type alias for the resolver of [`Mac`]. pub type MacResolver<Tag> = MacHelperResolver<Tag>;}
#[cfg(feature = "hmac")]pub mod hmac;