use wolf_crypto_sys::{
Poly1305 as wc_Poly1305,
wc_Poly1305SetKey, wc_Poly1305Update, wc_Poly1305Final,
wc_Poly1305_Pad, wc_Poly1305_EncodeSizes,
wc_Poly1305_MAC
};
mod key;
pub mod state;
pub use key::{GenericKey, Key, KeyRef, KEY_SIZE};
use key::KEY_SIZE_U32;
use core::mem::MaybeUninit;
use core::ptr::addr_of_mut;
use state::{Poly1305State, Init, Ready, Streaming};
use crate::opaque_res::Res;
use crate::{can_cast_u32, to_u32, Unspecified};
use core::marker::PhantomData;
use crate::aead::{Aad, Tag};
use crate::ct;
#[repr(transparent)]
pub struct Poly1305<State: Poly1305State = Init> {
inner: wc_Poly1305,
_state: PhantomData<State>
}
impl<State: Poly1305State> From<Poly1305<State>> for Unspecified {
#[inline]
fn from(_value: Poly1305<State>) -> Self {
Self
}
}
opaque_dbg! { Poly1305 }
impl Poly1305<Init> {
pub fn new<K: GenericKey>(key: K) -> Poly1305<Ready> {
let mut poly1305 = MaybeUninit::<wc_Poly1305>::uninit();
unsafe {
let _res = wc_Poly1305SetKey(
poly1305.as_mut_ptr(),
key.ptr(),
KEY_SIZE_U32
);
debug_assert_eq!(_res, 0);
Poly1305::<Ready> {
inner: poly1305.assume_init(),
_state: PhantomData
}
}
}
}
impl<State: Poly1305State> Poly1305<State> {
#[inline]
const fn with_state<N: Poly1305State>(self) -> Poly1305<N> {
unsafe { core::mem::transmute(self) }
}
#[inline]
unsafe fn update_unchecked(&mut self, input: &[u8]) {
let _res = wc_Poly1305Update(
addr_of_mut!(self.inner),
input.as_ptr(),
input.len() as u32
);
debug_assert_eq!(_res, 0);
}
}
impl Poly1305<Ready> {
unsafe fn mac_unchecked<A: Aad>(mut self, input: &[u8], aad: A) -> Tag {
debug_assert!(can_cast_u32(input.len()));
debug_assert!(aad.is_valid_size());
let mut tag = Tag::new_zeroed();
let _res = wc_Poly1305_MAC(
addr_of_mut!(self.inner),
aad.ptr(),
aad.size(),
input.as_ptr(),
input.len() as u32,
tag.as_mut_ptr(),
Tag::SIZE
);
assert_eq!(_res, 0);
debug_assert_eq!(_res, 0);
tag
}
#[inline]
pub fn mac<A: Aad>(self, input: &[u8], aad: A) -> Result<Tag, Unspecified> {
if can_cast_u32(input.len()) && aad.is_valid_size() {
Ok(unsafe { self.mac_unchecked(input, aad) })
} else {
Err(Unspecified)
}
}
pub const fn aead_padding(self) -> StreamPoly1305Aead {
StreamPoly1305Aead::from_parts(self.with_state(), 0)
}
pub const fn normal(self) -> StreamPoly1305 {
StreamPoly1305::from_parts(self.with_state(), 0)
}
pub const fn aead_padding_ct(self) -> CtPoly1305Aead {
CtPoly1305Aead::from_parts(self.with_state(), Res::OK, 0)
}
pub const fn normal_ct(self) -> CtPoly1305 {
CtPoly1305::from_parts(self.with_state(), Res::OK, 0)
}
#[inline]
pub fn update(mut self, input: &[u8]) -> Result<StreamPoly1305, Unspecified> {
to_u32(input.len()).map_or(
Err(Unspecified),
|len| unsafe {
self.update_unchecked(input);
Ok(StreamPoly1305::from_parts(self.with_state(), len))
}
)
}
pub fn update_ct(mut self, input: &[u8]) -> CtPoly1305 {
let (adjusted, res) = adjust_slice(input);
unsafe { self.update_unchecked(adjusted) };
CtPoly1305::from_parts(self.with_state(), res, adjusted.len() as u32)
}
}
#[inline]
fn finalize_aead<S: Poly1305State>(mut poly: Poly1305<S>, accum_len: u32) -> Tag {
unsafe {
let _res = wc_Poly1305_Pad(
addr_of_mut!(poly.inner),
accum_len
);
debug_assert_eq!(_res, 0);
let _res = wc_Poly1305_EncodeSizes(
addr_of_mut!(poly.inner),
0u32,
accum_len
);
debug_assert_eq!(_res, 0);
finalize_no_pad(poly)
}
}
#[inline]
fn finalize<S: Poly1305State>(mut poly: Poly1305<S>, to_pad: u32) -> Tag {
unsafe {
let _res = wc_Poly1305_Pad(
addr_of_mut!(poly.inner),
to_pad
);
debug_assert_eq!(_res, 0);
finalize_no_pad(poly)
}
}
#[inline]
fn finalize_no_pad<S: Poly1305State>(mut poly: Poly1305<S>) -> Tag {
unsafe {
let mut tag = Tag::new_zeroed();
let _res = wc_Poly1305Final(
addr_of_mut!(poly.inner),
tag.as_mut_ptr()
);
debug_assert_eq!(_res, 0);
tag
}
}
#[inline(always)]
#[must_use]
const fn update_to_pad(to_pad: u8, new_len: u32) -> u8 {
to_pad.wrapping_add((new_len & 15) as u8) & 15
}
pub struct StreamPoly1305Aead {
poly1305: Poly1305<Streaming>,
accum_len: u32
}
impl From<StreamPoly1305Aead> for Unspecified {
#[inline]
fn from(value: StreamPoly1305Aead) -> Self {
value.poly1305.into()
}
}
opaque_dbg! { StreamPoly1305Aead }
impl StreamPoly1305Aead {
const fn from_parts(poly1305: Poly1305<Streaming>, accum_len: u32) -> Self {
Self { poly1305, accum_len }
}
#[inline(always)]
fn incr_accum(&mut self, len: u32) -> Res {
let (accum_len, res) = ct::add_no_wrap(self.accum_len, len);
self.accum_len = accum_len;
res
}
pub fn update(mut self, input: &[u8]) -> Result<Self, Self> {
if let Some(input_len) = to_u32(input.len()) {
into_result!(self.incr_accum(input_len),
ok => {
unsafe { self.poly1305.update_unchecked(input) };
self
},
err => self
)
} else {
Err(self)
}
}
pub fn finalize(self) -> Tag {
finalize_aead(self.poly1305, self.accum_len)
}
}
pub struct StreamPoly1305 {
poly1305: Poly1305<Streaming>,
to_pad: u8
}
impl From<StreamPoly1305> for Unspecified {
#[inline]
fn from(value: StreamPoly1305) -> Self {
value.poly1305.into()
}
}
opaque_dbg! { StreamPoly1305 }
impl StreamPoly1305 {
const fn from_parts(poly1305: Poly1305<Streaming>, len: u32) -> Self {
Self { poly1305, to_pad: update_to_pad(0, len) }
}
pub fn update(mut self, input: &[u8]) -> Result<Self, Self> {
if let Some(len) = to_u32(input.len()) {
self.to_pad = update_to_pad(self.to_pad, len);
unsafe { self.poly1305.update_unchecked(input) };
Ok(self)
} else {
Err(self)
}
}
#[inline]
pub fn finalize(self) -> Tag {
finalize(self.poly1305, self.to_pad as u32)
}
#[inline]
pub fn finalize_no_padding(self) -> Tag {
finalize_no_pad(self.poly1305)
}
}
#[must_use]
pub struct CtPoly1305Aead {
poly1305: Poly1305<Streaming>,
result: Res,
accum_len: u32
}
opaque_dbg! { CtPoly1305Aead }
#[inline(always)]
#[must_use]
const fn slice_len_mask(len: usize) -> usize {
(can_cast_u32(len) as usize).wrapping_neg()
}
#[inline(always)]
fn adjust_slice(slice: &[u8]) -> (&[u8], Res) {
let mask = slice_len_mask(slice.len());
(&slice[..(slice.len() & mask)], Res(mask != 0))
}
impl CtPoly1305Aead {
const fn from_parts(poly1305: Poly1305<Streaming>, result: Res, accum_len: u32) -> Self {
Self {
poly1305,
result,
accum_len
}
}
#[inline(always)]
fn incr_accum(&mut self, len: u32) -> Res {
let (accum_len, res) = ct::add_no_wrap(self.accum_len, len);
self.accum_len = accum_len;
res
}
pub fn update_ct(mut self, input: &[u8]) -> Self {
let (adjusted, mut res) = adjust_slice(input);
res.ensure(self.incr_accum(adjusted.len() as u32));
unsafe { self.poly1305.update_unchecked(adjusted) };
self.result.ensure(res);
self
}
#[must_use]
pub const fn is_ok(&self) -> bool {
self.result.is_ok()
}
#[must_use]
pub const fn is_err(&self) -> bool {
self.result.is_err()
}
pub fn finalize(self) -> Result<Tag, Unspecified> {
let tag = finalize_aead(self.poly1305, self.accum_len);
self.result.unit_err(tag)
}
}
#[must_use]
pub struct CtPoly1305 {
poly1305: Poly1305<Streaming>,
result: Res,
to_pad: u8
}
opaque_dbg! { CtPoly1305 }
impl CtPoly1305 {
const fn from_parts(poly1305: Poly1305<Streaming>, result: Res, len: u32) -> Self {
Self {
poly1305,
result,
to_pad: update_to_pad(0, len)
}
}
pub fn update_ct(mut self, input: &[u8]) -> Self {
let (adjusted, res) = adjust_slice(input);
self.to_pad = update_to_pad(self.to_pad, adjusted.len() as u32);
unsafe { self.poly1305.update_unchecked(adjusted) };
self.result.ensure(res);
self
}
#[must_use]
pub const fn is_ok(&self) -> bool { self.result.is_ok() }
#[must_use]
pub const fn is_err(&self) -> bool {
self.result.is_err()
}
pub fn finalize(self) -> Result<Tag, Unspecified> {
let tag = finalize(self.poly1305, self.to_pad as u32);
self.result.unit_err(tag)
}
pub fn finalize_no_padding(self) -> Result<Tag, Unspecified> {
let tag = finalize_no_pad(self.poly1305);
self.result.unit_err(tag)
}
}
#[cfg(test)]
use poly1305::universal_hash::generic_array::{GenericArray, ArrayLength};
#[cfg(test)]
const fn rc_to_blocks<T, N: ArrayLength<T>>(data: &[T]) -> (&[GenericArray<T, N>], &[T]) {
let nb = data.len() / N::USIZE;
let (left, right) = data.split_at(nb * N::USIZE);
let p = left.as_ptr().cast::<GenericArray<T, N>>();
#[allow(unsafe_code)]
let blocks = unsafe { core::slice::from_raw_parts(p, nb) };
(blocks, right)
}
#[cfg(test)]
use poly1305::{Poly1305 as rc_Poly1305, universal_hash::{KeyInit, UniversalHash}};
#[cfg(test)]
fn construct_polys(key: [u8; 32]) -> (rc_Poly1305, Poly1305<Ready>) {
let rc_key = poly1305::Key::from_slice(key.as_slice());
(rc_Poly1305::new(rc_key), Poly1305::new(KeyRef::new(&key)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke() {
let key: Key = [42u8; 32].into();
let tag = Poly1305::new(key.as_ref())
.aead_padding_ct()
.update_ct(b"hello world")
.update_ct(b"good day to you")
.update_ct(b"mmm yes mm yes indeed mm")
.update_ct(b"hmm...")
.finalize()
.unwrap();
let o_tag = Poly1305::new(key.as_ref())
.mac(b"hello worldgood day to yoummm yes mm yes indeed mmhmm...", b"")
.unwrap();
assert_eq!(tag, o_tag);
}
#[test]
fn rust_crypto_aligned() {
let key = [42u8; 32];
let (mut rc, wc) = construct_polys(key);
let data = b"hello world we operate equivalen";
let (blocks, _rem) = rc_to_blocks(data);
rc.update(blocks);
let rc_out = rc.finalize();
let wc_out = wc.update(data).unwrap().finalize();
assert_eq!(wc_out, rc_out.as_slice());
}
#[test]
fn rust_crypto_unaligned() {
let key = [42u8; 32];
let (mut rc, wc) = construct_polys(key);
let data = b"hello world we operate equivalently";
rc.update_padded(data);
let rc_tag = rc.finalize();
let tag = wc.update(data).unwrap().finalize();
assert_eq!(tag, rc_tag.as_slice());
}
}
#[cfg(kani)]
const fn wc_to_pad(len_to_pad: u32) -> u32 {
((-(len_to_pad as isize)) & 15) as u32
}
#[cfg(kani)]
const fn wc_to_pad_64(len_to_pad: u64) -> u32 {
((-(len_to_pad as i128)) & 15) as u32
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
use crate::aes::test_utils::{BoundList, AnyList};
proptest! {
#![proptest_config(ProptestConfig::with_cases(5_000))]
#[test]
fn wc_poly_is_eq_to_rc_poly(
key in any::<[u8; 32]>(),
data in any::<BoundList<1024>>()
) {
let (mut rc, wc) = construct_polys(key);
rc.update_padded(data.as_slice());
let tag = wc.update(data.as_slice()).unwrap().finalize();
let rc_tag = rc.finalize();
prop_assert_eq!(tag, rc_tag.as_slice());
}
#[test]
fn wc_poly_multi_updates_is_eq_to_rc_poly_oneshot(
key in any::<[u8; 32]>(),
data in any::<AnyList<32, BoundList<256>>>()
) {
let (mut rc, wc) = construct_polys(key);
let mut wc = wc.normal();
for input in data.as_slice() {
wc = wc.update(input.as_slice()).unwrap();
}
let joined = data.join();
rc.update_padded(joined.as_slice());
let tag = wc.finalize();
let rc_tag = rc.finalize();
prop_assert_eq!(tag, rc_tag.as_slice());
}
#[test]
fn multi_updates_is_eq_to_oneshot_tls_aead_scheme(
key in any::<Key>(),
data in any::<AnyList<32, BoundList<256>>>()
) {
let mut updates = Poly1305::new(key.as_ref()).aead_padding();
for input in data.as_slice() {
updates = updates.update(input.as_slice()).unwrap();
}
let tag = updates.finalize();
let joined = data.join();
let m_tag = Poly1305::new(key).mac(joined.as_slice(), ()).unwrap();
prop_assert_eq!(tag, m_tag);
}
#[test]
fn multi_updates_ct_is_eq_to_normal(
key in any::<Key>(),
data in any::<AnyList<32, BoundList<256>>>()
) {
let mut poly = Poly1305::new(key.as_ref()).normal();
let mut poly_ct = Poly1305::new(key.as_ref()).normal_ct();
for input in data.as_slice() {
poly = poly.update(input.as_slice()).unwrap();
poly_ct = poly_ct.update_ct(input.as_slice());
}
let tag = poly.finalize();
let tag_ct = poly_ct.finalize().unwrap();
prop_assert_eq!(tag, tag_ct);
}
#[test]
fn multi_updates_is_eq_oneshot(
key in any::<Key>(),
data in any::<AnyList<32, BoundList<256>>>()
) {
let mut poly = Poly1305::new(key.as_ref()).normal();
for input in data.as_slice() {
poly = poly.update(input.as_slice()).unwrap();
}
let tag = poly.finalize();
let joined = data.join();
let o_tag = Poly1305::new(key)
.update(joined.as_slice()).unwrap()
.finalize();
prop_assert_eq!(tag, o_tag);
}
#[test]
fn multi_updates_ct_is_eq_oneshot(
key in any::<Key>(),
data in any::<AnyList<32, BoundList<256>>>()
) {
let mut poly = Poly1305::new(key.as_ref()).normal_ct();
for input in data.as_slice() {
poly = poly.update_ct(input.as_slice());
}
let tag = poly.finalize().unwrap();
let joined = data.join();
let o_tag = Poly1305::new(key)
.update(joined.as_slice()).unwrap()
.finalize();
prop_assert_eq!(tag, o_tag);
}
}
}
#[cfg(kani)]
mod proofs {
use kani::proof;
use super::*;
#[proof]
fn univ_update_to_pad_is_no_wrap_mask() {
let existing: u32 = kani::any();
let to_add: u32 = kani::any();
let utp = update_to_pad((existing & 15) as u8, to_add) as u64;
let genuine = ((existing as u64) + (to_add as u64)) & 15;
kani::assert(
utp == genuine,
"The wrapping addition must always be equivalent to non-wrapping output"
);
}
#[proof]
fn univ_update_to_pad_holds_for_wc_pad_algo() {
let start: u32 = kani::any();
let end: u32 = kani::any();
let utp = update_to_pad((start & 15) as u8, end);
kani::assert(
wc_to_pad(utp as u32) == wc_to_pad_64((start as u64) + (end as u64)),
"update_to_pad must be equivalent to the total length in the eyes of the wolfcrypt \
padding algorithm."
);
}
#[proof]
fn univ_mask_is_no_mask_to_wc_pad_algo() {
let some_num: u64 = kani::any();
kani::assert(
wc_to_pad_64(some_num & 15) == wc_to_pad_64(some_num),
"wolfcrypt's to pad must result in the same output for the input mask 15 and raw input"
)
}
}