polyhash/
lib.rs

1//! POLYVAL per [RFC 8452].
2//!
3//! [RFC 8452]: https://datatracker.ietf.org/doc/html/rfc8452
4
5#![cfg_attr(docsrs, feature(doc_cfg))]
6#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
7#![cfg_attr(not(any(feature = "std", test)), deny(clippy::std_instead_of_core))]
8
9mod backend;
10pub mod ghash;
11mod poly;
12
13use core::slice;
14
15pub use subtle::Choice;
16use subtle::ConstantTimeEq;
17
18pub use crate::poly::{Polyval, PolyvalLite};
19
20/// The size in bytes of a POLYVAL (or GHASH) key.
21pub const KEY_SIZE: usize = 16;
22
23/// The size in bytes of a POLYVAL (or GHASH) block.
24pub const BLOCK_SIZE: usize = 16;
25
26/// An authentication tag.
27#[derive(Copy, Clone, Debug)]
28pub struct Tag(pub(crate) [u8; 16]);
29
30impl ConstantTimeEq for Tag {
31    #[inline]
32    fn ct_eq(&self, other: &Self) -> Choice {
33        self.0.ct_eq(&other.0)
34    }
35}
36
37impl From<Tag> for [u8; 16] {
38    #[inline]
39    fn from(tag: Tag) -> Self {
40        tag.0
41    }
42}
43
44// See https://doc.rust-lang.org/std/primitive.slice.html#method.as_chunks
45#[inline(always)]
46const fn as_blocks(blocks: &[u8]) -> (&[[u8; BLOCK_SIZE]], &[u8]) {
47    #[allow(clippy::arithmetic_side_effects)]
48    let len_rounded_down = (blocks.len() / BLOCK_SIZE) * BLOCK_SIZE;
49    // SAFETY: The rounded-down value is always the same or
50    // smaller than the original length, and thus must be
51    // in-bounds of the slice.
52    let (head, tail) = unsafe { blocks.split_at_unchecked(len_rounded_down) };
53    let new_len = head.len() / BLOCK_SIZE;
54    // SAFETY: We cast a slice of `new_len * N` elements into
55    // a slice of `new_len` many `N` elements chunks.
56    let head = unsafe { slice::from_raw_parts(head.as_ptr().cast(), new_len) };
57    (head, tail)
58}
59
60macro_rules! impl_state {
61    ($name:ident, $endian:ident) => {
62        /// Saved hash state.
63        #[derive(Clone, Default)]
64        pub struct $name {
65            pub(crate) y: $crate::backend::FieldElement,
66        }
67
68        #[cfg(feature = "zeroize")]
69        #[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
70        impl ::zeroize::ZeroizeOnDrop for $name {}
71
72        impl Drop for $name {
73            #[inline]
74            fn drop(&mut self) {
75                cfg_if::cfg_if! {
76                    if #[cfg(feature = "zeroize")] {
77                        ::zeroize::Zeroize::zeroize(&mut self.y);
78                    } else {
79                        self.y = ::core::hint::black_box(Default::default());
80                    }
81                }
82            }
83        }
84
85        impl ::core::fmt::Debug for $name {
86            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
87                f.debug_struct(stringify!($name)).finish_non_exhaustive()
88            }
89        }
90    };
91}
92pub(crate) use impl_state;
93
94macro_rules! impl_hash {
95    (
96        $(#[$meta:meta])*
97        $vis:vis struct $name:ident($inner:ty);
98    ) => {
99        $(#[$meta])*
100        #[derive(Clone)]
101        $vis struct $name($inner);
102
103        impl $name {
104            /// Creates a new hash instance.
105            ///
106            /// It returns `None` if `key` is all zeroes.
107            #[inline]
108            pub fn new(key: &[u8; $crate::KEY_SIZE]) -> Option<Self> {
109                use ::subtle::ConstantTimeEq;
110
111                if bool::from(key.ct_eq(&[0; $crate::KEY_SIZE])) {
112                    None
113                } else {
114                    Some(Self::new_unchecked(key))
115                }
116            }
117
118            /// Creates a hash instance from a known non-zero
119            /// key.
120            ///
121            /// # Warning
122            ///
123            /// Only use this method if `key` is known to be
124            /// non-zero. Using an all zero key fixes the output
125            /// to zero, regardless of the input.
126            #[inline]
127            pub fn new_unchecked(key: &[u8; $crate::KEY_SIZE]) -> Self {
128                Self(<$inner>::new(key))
129            }
130
131            /// Writes a single block to the running hash.
132            #[inline]
133            pub fn update_block(&mut self, block: &[u8; $crate::BLOCK_SIZE]) {
134                self.0.update_block(block);
135            }
136
137            /// Writes one or more blocks to the running hash.
138            #[inline]
139            pub fn update_blocks(&mut self, blocks: &[[u8; $crate::BLOCK_SIZE]]) {
140                self.0.update_blocks(blocks);
141            }
142
143            /// Writes one or more blocks to the running hash.
144            ///
145            /// If the length of `blocks` is non-zero, it's
146            /// padded to the lowest multiple of
147            /// [`BLOCK_SIZE`][crate::BLOCK_SIZE].
148            #[inline]
149            pub fn update_padded(&mut self, blocks: &[u8]) {
150                let (head, tail) = $crate::as_blocks(blocks);
151                if !head.is_empty() {
152                    self.update_blocks(head);
153                }
154                if !tail.is_empty() {
155                    let mut block = [0u8; $crate::BLOCK_SIZE];
156                    #[allow(
157                        clippy::indexing_slicing,
158                        reason = "The compiler can prove the slice is in bounds."
159                    )]
160                    block[..tail.len()].copy_from_slice(tail);
161                    self.update_block(&block);
162                }
163            }
164
165            /// Returns the current authentication tag.
166            #[inline]
167            pub fn tag(self) -> $crate::Tag {
168                $crate::Tag(self.0.tag())
169            }
170
171            /// Reports whether the current authentication tag matches
172            /// `expected_tag`.
173            #[inline]
174            pub fn verify(self, expected_tag: &$crate::Tag) -> ::subtle::Choice {
175                ::subtle::ConstantTimeEq::ct_eq(&self.tag(), expected_tag)
176            }
177
178            /// Exports the current state.
179            #[inline]
180            #[cfg(feature = "experimental")]
181            #[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
182            pub fn export(&self) -> State {
183                let y = self.0.export();
184                State { y }
185            }
186
187            /// Resets the hash to `state`.
188            #[inline]
189            #[cfg(feature = "experimental")]
190            #[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
191            pub fn reset(&mut self, state: &State) {
192                self.0.reset(state.y)
193            }
194
195            /// Returns the current authentication tag without
196            /// consuming the hash.
197            #[inline]
198            #[cfg(feature = "experimental")]
199            #[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
200            pub fn current_tag(&self) -> $crate::Tag {
201                $crate::Tag(self.0.tag())
202            }
203        }
204
205        #[cfg(feature = "zeroize")]
206        #[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
207        impl ::zeroize::ZeroizeOnDrop for $name {}
208
209        impl Drop for $name {
210            #[inline]
211            fn drop(&mut self) {
212                #[cfg(feature = "zeroize")]
213                // SAFETY: `self` is "flat" data and is not used
214                // after this point.
215                unsafe {
216                    zeroize::zeroize_flat_type(self);
217                }
218            }
219        }
220
221        impl ::core::fmt::Debug for $name {
222            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
223                f.debug_struct(stringify!($name)).finish_non_exhaustive()
224            }
225        }
226    };
227}
228pub(crate) use impl_hash;