use crate::{
errors::UnknownCryptoError,
util::endianness::{load_u32_le, store_u32_into_le},
};
use fiat_crypto::poly1305_32::{
fiat_poly1305_add, fiat_poly1305_carry, fiat_poly1305_carry_mul, fiat_poly1305_from_bytes,
fiat_poly1305_loose_field_element, fiat_poly1305_selectznz, fiat_poly1305_subborrowx_u26,
fiat_poly1305_tight_field_element, fiat_poly1305_u1,
};
const POLY1305_BLOCKSIZE: usize = 16;
pub const POLY1305_OUTSIZE: usize = 16;
pub const POLY1305_KEYSIZE: usize = 32;
type Poly1305Tag = [u8; POLY1305_OUTSIZE];
construct_secret_key! {
(OneTimeKey, test_one_time_key, POLY1305_KEYSIZE, POLY1305_KEYSIZE, POLY1305_KEYSIZE)
}
impl_from_trait!(OneTimeKey, POLY1305_KEYSIZE);
construct_tag! {
(Tag, test_tag, POLY1305_OUTSIZE, POLY1305_OUTSIZE)
}
impl_from_trait!(Tag, POLY1305_OUTSIZE);
#[derive(Clone)]
pub struct Poly1305 {
a: [u32; 5],
r: [u32; 5],
s: [u32; 4],
leftover: usize,
buffer: [u8; POLY1305_BLOCKSIZE],
is_finalized: bool,
}
impl Drop for Poly1305 {
fn drop(&mut self) {
use zeroize::Zeroize;
self.a.zeroize();
self.r.zeroize();
self.s.zeroize();
self.buffer.zeroize();
}
}
impl core::fmt::Debug for Poly1305 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Poly1305 {{ a: [***OMITTED***], r: [***OMITTED***], s: [***OMITTED***], leftover: [***OMITTED***], buffer: [***OMITTED***], is_finalized: {:?} }}",
self.is_finalized
)
}
}
impl Poly1305 {
const PRIME: [u8; 17] = [
251, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 3,
];
fn process_block(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
if data.len() != POLY1305_BLOCKSIZE {
return Err(UnknownCryptoError);
}
let mut mb = [0u8; 17];
mb[..16].copy_from_slice(data);
mb[16] = if self.is_finalized { 0 } else { 1 };
let mut m: fiat_poly1305_tight_field_element = [0u32; 5];
fiat_poly1305_from_bytes(&mut m, &mb);
let mut h: fiat_poly1305_loose_field_element = [0u32; 5];
fiat_poly1305_add(&mut h, &self.a, &m);
fiat_poly1305_carry_mul(&mut self.a, &h, &self.r);
Ok(())
}
#[rustfmt::skip]
#[allow(clippy::identity_op)]
fn process_end_of_stream(&mut self) {
let mut buf_h: fiat_poly1305_tight_field_element = [0u32; 5];
fiat_poly1305_carry(&mut buf_h, &self.a);
let mut p: fiat_poly1305_tight_field_element = [0u32; 5];
fiat_poly1305_from_bytes(&mut p, &Self::PRIME);
let mut carry: fiat_poly1305_u1 = 0;
let mut g0: u32 = 0; let c = carry; fiat_poly1305_subborrowx_u26(&mut g0, &mut carry, c, buf_h[0], p[0]);
let mut g1: u32 = 0; let c = carry; fiat_poly1305_subborrowx_u26(&mut g1, &mut carry, c, buf_h[1], p[1]);
let mut g2: u32 = 0; let c = carry; fiat_poly1305_subborrowx_u26(&mut g2, &mut carry, c, buf_h[2], p[2]);
let mut g3: u32 = 0; let c = carry; fiat_poly1305_subborrowx_u26(&mut g3, &mut carry, c, buf_h[3], p[3]);
let mut g4: u32 = 0; let c = carry; fiat_poly1305_subborrowx_u26(&mut g4, &mut carry, c, buf_h[4], p[4]);
let mut ret = [0u32; 5];
fiat_poly1305_selectznz(&mut ret, carry,&[g0, g1, g2, g3, g4], &buf_h);
let mut h0 = ret[0];
let mut h1 = ret[1];
let mut h2 = ret[2];
let mut h3 = ret[3];
let h4 = ret[4];
h0 = ((h0) | (h1 << 26)) & 0xffffffff;
h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff;
h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff;
h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff;
let mut f: u64 = (h0 as u64) + (self.s[0] as u64); h0 = f as u32;
f = (h1 as u64) + (self.s[1] as u64) + (f >> 32); h1 = f as u32;
f = (h2 as u64) + (self.s[2] as u64) + (f >> 32); h2 = f as u32;
f = (h3 as u64) + (self.s[3] as u64) + (f >> 32); h3 = f as u32;
self.a[0] = h0;
self.a[1] = h1;
self.a[2] = h2;
self.a[3] = h3;
}
#[allow(clippy::unreadable_literal)]
pub fn new(one_time_key: &OneTimeKey) -> Self {
let mut state = Self {
a: [0u32; 5],
r: [0u32; 5],
s: [0u32; 4],
leftover: 0,
buffer: [0u8; POLY1305_BLOCKSIZE],
is_finalized: false,
};
state.r[0] = (load_u32_le(&one_time_key.unprotected_as_bytes()[0..4])) & 0x3ffffff;
state.r[1] = (load_u32_le(&one_time_key.unprotected_as_bytes()[3..7]) >> 2) & 0x3ffff03;
state.r[2] = (load_u32_le(&one_time_key.unprotected_as_bytes()[6..10]) >> 4) & 0x3ffc0ff;
state.r[3] = (load_u32_le(&one_time_key.unprotected_as_bytes()[9..13]) >> 6) & 0x3f03fff;
state.r[4] = (load_u32_le(&one_time_key.unprotected_as_bytes()[12..16]) >> 8) & 0x00fffff;
state.s[0] = load_u32_le(&one_time_key.unprotected_as_bytes()[16..20]);
state.s[1] = load_u32_le(&one_time_key.unprotected_as_bytes()[20..24]);
state.s[2] = load_u32_le(&one_time_key.unprotected_as_bytes()[24..28]);
state.s[3] = load_u32_le(&one_time_key.unprotected_as_bytes()[28..32]);
state
}
pub(crate) fn process_pad_to_blocksize(
&mut self,
data: &[u8],
) -> Result<(), UnknownCryptoError> {
if self.is_finalized {
return Err(UnknownCryptoError);
}
if data.is_empty() {
return Ok(());
}
let mut blocksize_iter = data.chunks_exact(POLY1305_BLOCKSIZE);
for block in &mut blocksize_iter {
self.process_block(block).unwrap();
}
let remaining = blocksize_iter.remainder();
if !remaining.is_empty() {
let mut pad = [0u8; POLY1305_BLOCKSIZE];
pad[..remaining.len()].copy_from_slice(remaining);
self.process_block(&pad).unwrap();
}
Ok(())
}
pub fn reset(&mut self) {
self.a = [0u32; 5];
self.leftover = 0;
self.is_finalized = false;
self.buffer = [0u8; POLY1305_BLOCKSIZE];
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
if self.is_finalized {
return Err(UnknownCryptoError);
}
if data.is_empty() {
return Ok(());
}
let mut bytes = data;
if self.leftover != 0 {
debug_assert!(self.leftover <= POLY1305_BLOCKSIZE);
let mut want = POLY1305_BLOCKSIZE - self.leftover;
if want > bytes.len() {
want = bytes.len();
}
for (idx, itm) in bytes.iter().enumerate().take(want) {
self.buffer[self.leftover + idx] = *itm;
}
bytes = &bytes[want..];
self.leftover += want;
if self.leftover < POLY1305_BLOCKSIZE {
return Ok(());
}
let tmp = self.buffer;
self.process_block(&tmp)?;
self.leftover = 0;
}
while bytes.len() >= POLY1305_BLOCKSIZE {
self.process_block(&bytes[0..POLY1305_BLOCKSIZE])?;
bytes = &bytes[POLY1305_BLOCKSIZE..];
}
self.buffer[..bytes.len()].copy_from_slice(bytes);
self.leftover = bytes.len();
Ok(())
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
if self.is_finalized {
return Err(UnknownCryptoError);
}
self.is_finalized = true;
let mut local_buffer: Poly1305Tag = self.buffer;
if self.leftover != 0 {
local_buffer[self.leftover] = 1;
for buf_itm in local_buffer
.iter_mut()
.take(POLY1305_BLOCKSIZE)
.skip(self.leftover + 1)
{
*buf_itm = 0u8;
}
self.process_block(&local_buffer)?;
}
self.process_end_of_stream();
store_u32_into_le(&self.a[0..4], &mut local_buffer);
Ok(Tag::from(local_buffer))
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn poly1305(one_time_key: &OneTimeKey, data: &[u8]) -> Result<Tag, UnknownCryptoError> {
let mut poly_1305_state = Self::new(one_time_key);
poly_1305_state.update(data)?;
poly_1305_state.finalize()
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn verify(
expected: &Tag,
one_time_key: &OneTimeKey,
data: &[u8],
) -> Result<(), UnknownCryptoError> {
if &Self::poly1305(one_time_key, data)? == expected {
Ok(())
} else {
Err(UnknownCryptoError)
}
}
}
#[cfg(test)]
mod public {
use super::*;
#[test]
#[cfg(feature = "safe_api")]
fn test_debug_impl() {
let secret_key = OneTimeKey::generate();
let initial_state = Poly1305::new(&secret_key);
let debug = format!("{:?}", initial_state);
let expected = "Poly1305 { a: [***OMITTED***], r: [***OMITTED***], s: [***OMITTED***], leftover: [***OMITTED***], buffer: [***OMITTED***], is_finalized: false }";
assert_eq!(debug, expected);
}
#[cfg(feature = "safe_api")]
mod test_verify {
use super::*;
#[quickcheck]
#[cfg(feature = "safe_api")]
fn prop_verify_diff_key_false(data: Vec<u8>) -> bool {
let sk = OneTimeKey::generate();
let mut state = Poly1305::new(&sk);
state.update(&data[..]).unwrap();
let tag = state.finalize().unwrap();
let bad_sk = OneTimeKey::generate();
Poly1305::verify(&tag, &bad_sk, &data[..]).is_err()
}
}
mod test_streaming_interface {
use super::*;
use crate::test_framework::incremental_interface::{
StreamingContextConsistencyTester, TestableStreamingContext,
};
const KEY: [u8; 32] = [24u8; 32];
impl TestableStreamingContext<Tag> for Poly1305 {
fn reset(&mut self) -> Result<(), UnknownCryptoError> {
self.reset();
Ok(())
}
fn update(&mut self, input: &[u8]) -> Result<(), UnknownCryptoError> {
self.update(input)
}
fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
self.finalize()
}
fn one_shot(input: &[u8]) -> Result<Tag, UnknownCryptoError> {
Poly1305::poly1305(&OneTimeKey::from_slice(&KEY).unwrap(), input)
}
fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> {
Poly1305::verify(expected, &OneTimeKey::from_slice(&KEY).unwrap(), input)
}
fn compare_states(state_1: &Poly1305, state_2: &Poly1305) {
assert_eq!(state_1.a, state_2.a);
assert_eq!(state_1.r, state_2.r);
assert_eq!(state_1.s, state_2.s);
assert_eq!(state_1.leftover, state_2.leftover);
assert_eq!(state_1.buffer[..], state_2.buffer[..]);
assert_eq!(state_1.is_finalized, state_2.is_finalized);
}
}
#[test]
fn default_consistency_tests() {
let initial_state: Poly1305 = Poly1305::new(&OneTimeKey::from_slice(&KEY).unwrap());
let test_runner = StreamingContextConsistencyTester::<Tag, Poly1305>::new(
initial_state,
POLY1305_BLOCKSIZE,
);
test_runner.run_all_tests();
}
#[quickcheck]
#[cfg(feature = "safe_api")]
fn prop_input_to_consistency(data: Vec<u8>) -> bool {
let initial_state: Poly1305 = Poly1305::new(&OneTimeKey::from_slice(&KEY).unwrap());
let test_runner = StreamingContextConsistencyTester::<Tag, Poly1305>::new(
initial_state,
POLY1305_BLOCKSIZE,
);
test_runner.run_all_tests_property(&data);
true
}
}
}
#[cfg(test)]
mod private {
use super::*;
mod test_process_pad_to_blocksize {
use super::*;
#[test]
fn test_process_err_on_finalized() {
let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap();
let mut state = Poly1305::new(&sk);
state.process_pad_to_blocksize(&[0u8; 16]).unwrap();
let _ = state.finalize().unwrap();
assert!(state.process_pad_to_blocksize(&[0u8; 16]).is_err());
}
#[test]
fn test_process_pad_no_pad() {
let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap();
let mut state_pad = Poly1305::new(&sk);
let mut state_no_pad = Poly1305::new(&sk);
state_pad.process_pad_to_blocksize(&[0u8; 17]).unwrap();
state_no_pad.process_pad_to_blocksize(&[0u8; 32]).unwrap();
assert_eq!(
state_no_pad.finalize().unwrap(),
state_pad.finalize().unwrap()
);
}
}
mod test_process_block {
use super::*;
#[test]
fn test_process_block_len() {
let block_0 = [0u8; 0];
let block_1 = [0u8; 15];
let block_2 = [0u8; 17];
let block_3 = [0u8; 16];
let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap();
let mut state = Poly1305::new(&sk);
assert!(state.process_block(&block_0).is_err());
assert!(state.process_block(&block_1).is_err());
assert!(state.process_block(&block_2).is_err());
assert!(state.process_block(&block_3).is_ok());
}
}
mod test_process_end_of_stream {
use super::*;
#[test]
fn test_process_no_panic() {
let block = [0u8; 16];
let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap();
let mut state = Poly1305::new(&sk);
state.process_end_of_stream();
state.reset();
state.process_end_of_stream();
let mut state = Poly1305::new(&sk);
state.process_block(&block).unwrap();
state.process_end_of_stream();
state.reset();
state.process_end_of_stream();
}
}
}