#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
#![cfg_attr(not(any(feature = "std", test)), deny(clippy::std_instead_of_core))]
mod backend;
pub mod ghash;
mod poly;
use core::slice;
pub use subtle::Choice;
use subtle::ConstantTimeEq;
pub use crate::poly::{Polyval, PolyvalLite};
pub const KEY_SIZE: usize = 16;
pub const BLOCK_SIZE: usize = 16;
#[derive(Copy, Clone, Debug)]
pub struct Tag(pub(crate) [u8; 16]);
impl ConstantTimeEq for Tag {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl From<Tag> for [u8; 16] {
#[inline]
fn from(tag: Tag) -> Self {
tag.0
}
}
#[inline(always)]
const fn as_blocks(blocks: &[u8]) -> (&[[u8; BLOCK_SIZE]], &[u8]) {
#[allow(clippy::arithmetic_side_effects)]
let len_rounded_down = (blocks.len() / BLOCK_SIZE) * BLOCK_SIZE;
let (head, tail) = unsafe { blocks.split_at_unchecked(len_rounded_down) };
let new_len = head.len() / BLOCK_SIZE;
let head = unsafe { slice::from_raw_parts(head.as_ptr().cast(), new_len) };
(head, tail)
}
macro_rules! impl_state {
($name:ident, $endian:ident) => {
#[derive(Clone, Default)]
pub struct $name {
pub(crate) y: $crate::backend::FieldElement,
}
#[cfg(feature = "zeroize")]
#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
impl ::zeroize::ZeroizeOnDrop for $name {}
impl Drop for $name {
#[inline]
fn drop(&mut self) {
cfg_if::cfg_if! {
if #[cfg(feature = "zeroize")] {
::zeroize::Zeroize::zeroize(&mut self.y);
} else {
self.y = ::core::hint::black_box(Default::default());
}
}
}
}
impl ::core::fmt::Debug for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(stringify!($name)).finish_non_exhaustive()
}
}
};
}
pub(crate) use impl_state;
macro_rules! impl_hash {
(
$(#[$meta:meta])*
$vis:vis struct $name:ident($inner:ty);
) => {
$(#[$meta])*
#[derive(Clone)]
$vis struct $name($inner);
impl $name {
#[inline]
pub fn new(key: &[u8; $crate::KEY_SIZE]) -> Option<Self> {
use ::subtle::ConstantTimeEq;
if bool::from(key.ct_eq(&[0; $crate::KEY_SIZE])) {
None
} else {
Some(Self::new_unchecked(key))
}
}
#[inline]
pub fn new_unchecked(key: &[u8; $crate::KEY_SIZE]) -> Self {
Self(<$inner>::new(key))
}
#[inline]
pub fn update_block(&mut self, block: &[u8; $crate::BLOCK_SIZE]) {
self.0.update_block(block);
}
#[inline]
pub fn update_blocks(&mut self, blocks: &[[u8; $crate::BLOCK_SIZE]]) {
self.0.update_blocks(blocks);
}
#[inline]
pub fn update_padded(&mut self, blocks: &[u8]) {
let (head, tail) = $crate::as_blocks(blocks);
if !head.is_empty() {
self.update_blocks(head);
}
if !tail.is_empty() {
let mut block = [0u8; $crate::BLOCK_SIZE];
#[allow(
clippy::indexing_slicing,
reason = "The compiler can prove the slice is in bounds."
)]
block[..tail.len()].copy_from_slice(tail);
self.update_block(&block);
}
}
#[inline]
pub fn tag(self) -> $crate::Tag {
$crate::Tag(self.0.tag())
}
#[inline]
pub fn verify(self, expected_tag: &$crate::Tag) -> ::subtle::Choice {
::subtle::ConstantTimeEq::ct_eq(&self.tag(), expected_tag)
}
#[inline]
#[cfg(feature = "experimental")]
#[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
pub fn export(&self) -> State {
let y = self.0.export();
State { y }
}
#[inline]
#[cfg(feature = "experimental")]
#[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
pub fn reset(&mut self, state: &State) {
self.0.reset(state.y)
}
#[inline]
#[cfg(feature = "experimental")]
#[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
pub fn current_tag(&self) -> $crate::Tag {
$crate::Tag(self.0.tag())
}
}
#[cfg(feature = "zeroize")]
#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
impl ::zeroize::ZeroizeOnDrop for $name {}
impl Drop for $name {
#[inline]
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
unsafe {
zeroize::zeroize_flat_type(self);
}
}
}
impl ::core::fmt::Debug for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(stringify!($name)).finish_non_exhaustive()
}
}
};
}
pub(crate) use impl_hash;