use crate::error::{validate, Result};
use crate::mac::MacAlgorithm;
use crate::types::Tag;
use dcrypt_common::security::{SecretBuffer, SecretVec};
use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
pub const POLY1305_KEY_SIZE: usize = 32;
pub const POLY1305_TAG_SIZE: usize = 16;
pub enum Poly1305Algorithm {}
impl MacAlgorithm for Poly1305Algorithm {
const KEY_SIZE: usize = POLY1305_KEY_SIZE;
const TAG_SIZE: usize = POLY1305_TAG_SIZE;
const BLOCK_SIZE: usize = 16;
fn name() -> &'static str {
"Poly1305"
}
}
pub struct Poly1305 {
r: SecretBuffer<24>, s: SecretBuffer<16>, data: SecretVec, }
impl Zeroize for Poly1305 {
fn zeroize(&mut self) {
self.r.zeroize();
self.s.zeroize();
self.data.zeroize();
}
}
impl Drop for Poly1305 {
fn drop(&mut self) {
self.zeroize();
}
}
impl ZeroizeOnDrop for Poly1305 {}
impl Poly1305 {
pub fn new(key: &[u8]) -> Result<Self> {
validate::length("Poly1305 key", key.len(), POLY1305_KEY_SIZE)?;
let mut r_bytes = Zeroizing::new([0u8; 16]);
r_bytes.copy_from_slice(&key[..16]);
r_bytes[3] &= 15;
r_bytes[7] &= 15;
r_bytes[11] &= 15;
r_bytes[15] &= 15;
r_bytes[4] &= 252;
r_bytes[8] &= 252;
r_bytes[12] &= 252;
let mut r = SecretBuffer::<24>::zeroed();
r.as_mut()[..16].copy_from_slice(r_bytes.as_ref());
let mut s = SecretBuffer::<16>::zeroed();
s.as_mut().copy_from_slice(&key[16..32]);
Ok(Self {
r,
s,
data: SecretVec::empty(),
})
}
fn get_r(&self) -> Zeroizing<[u64; 3]> {
let bytes = self.r.as_ref();
let mut words = Zeroizing::new([0u64; 3]);
for (word_index, word) in words.iter_mut().enumerate() {
for byte_index in 0..8 {
*word |= u64::from(bytes[word_index * 8 + byte_index]) << (byte_index * 8);
}
}
words
}
fn get_s(&self) -> Zeroizing<[u64; 2]> {
let bytes = self.s.as_ref();
let mut words = Zeroizing::new([0u64; 2]);
for (word_index, word) in words.iter_mut().enumerate() {
for byte_index in 0..8 {
*word |= u64::from(bytes[word_index * 8 + byte_index]) << (byte_index * 8);
}
}
words
}
pub fn update(&mut self, chunk: &[u8]) -> Result<()> {
if !chunk.is_empty() {
self.data.extend_from_slice(chunk);
}
Ok(())
}
pub fn finalize(self) -> Tag<POLY1305_TAG_SIZE> {
let mut h = Zeroizing::new([0u64; 3]);
let r = self.get_r();
for block in self.data.chunks(16) {
let mut buf = Zeroizing::new([0u8; 16]);
buf[..block.len()].copy_from_slice(block);
let n2 = if block.len() == 16 {
1
} else {
buf[block.len()] = 1;
0
};
let mut n = Zeroizing::new([0u64; 2]);
for (word_index, word) in n.iter_mut().enumerate() {
for byte_index in 0..8 {
*word |= u64::from(buf[word_index * 8 + byte_index]) << (byte_index * 8);
}
}
let mut sum = Zeroizing::new(u128::from(h[0]) + u128::from(n[0]));
h[0] = *sum as u64;
*sum = u128::from(h[1]) + u128::from(n[1]) + (*sum >> 64);
h[1] = *sum as u64;
h[2] = h[2].wrapping_add(n2).wrapping_add((*sum >> 64) as u64);
let reduced = mul_reduce(&h, &r);
h.copy_from_slice(&*reduced);
}
const P0: u64 = 0xffff_ffff_ffff_fffb;
const P1: u64 = 0xffff_ffff_ffff_ffff;
const P2: u64 = 3;
let mut g = Zeroizing::new([0u64; 3]);
let mut borrow = Zeroizing::new(0u64);
let mut wide = Zeroizing::new((1u128 << 64) + u128::from(h[0]) - u128::from(P0));
g[0] = *wide as u64;
*borrow = 1 - (*wide >> 64) as u64;
*wide = (1u128 << 64) + u128::from(h[1]) - u128::from(P1) - u128::from(*borrow);
g[1] = *wide as u64;
*borrow = 1 - (*wide >> 64) as u64;
*wide = (1u128 << 64) + u128::from(h[2]) - u128::from(P2) - u128::from(*borrow);
g[2] = *wide as u64;
*borrow = 1 - (*wide >> 64) as u64;
let mask = Zeroizing::new(borrow.wrapping_sub(1));
h[0] = (h[0] & !*mask) | (g[0] & *mask);
h[1] = (h[1] & !*mask) | (g[1] & *mask);
h[2] = (h[2] & !*mask) | (g[2] & *mask);
let s = self.get_s();
*wide = u128::from(h[0]) + u128::from(s[0]);
let mut tag_words = Zeroizing::new([0u64; 2]);
tag_words[0] = *wide as u64;
*wide = u128::from(h[1]) + u128::from(s[1]) + (*wide >> 64);
tag_words[1] = *wide as u64;
let mut out = [0u8; POLY1305_TAG_SIZE];
for (word_index, word) in tag_words.iter().enumerate() {
for byte_index in 0..8 {
out[word_index * 8 + byte_index] = (word >> (byte_index * 8)) as u8;
}
}
Tag::new(out)
}
}
fn mul_reduce(h: &[u64; 3], r: &[u64; 3]) -> Zeroizing<[u64; 3]> {
let operands = Zeroizing::new([
u128::from(h[0]),
u128::from(h[1]),
u128::from(h[2]),
u128::from(r[0]),
u128::from(r[1]),
u128::from(r[2]),
]);
let mut products = Zeroizing::new([0u128; 5]);
products[0] = operands[0] * operands[3];
products[1] = operands[0] * operands[4] + operands[1] * operands[3];
products[2] = operands[0] * operands[5] + operands[1] * operands[4] + operands[2] * operands[3];
products[3] = operands[1] * operands[5] + operands[2] * operands[4];
products[4] = operands[2] * operands[5];
for limb in 0..4 {
products[limb + 1] += products[limb] >> 64;
products[limb] &= u128::from(u64::MAX);
}
products[4] &= u128::from(u64::MAX);
let high = Zeroizing::new((products[2] >> 2) + (products[3] << 62) + (products[4] << 126));
let mut reduced = Zeroizing::new([0u128; 3]);
reduced[0] = products[0] + *high * 5;
reduced[1] = products[1];
reduced[2] = products[2] & 0x3;
reduced[1] += reduced[0] >> 64;
reduced[0] &= u128::from(u64::MAX);
reduced[2] += reduced[1] >> 64;
reduced[1] &= u128::from(u64::MAX);
reduced[2] &= 0x3fff_ffff_ffff_ffff;
let mut result = Zeroizing::new([0u64; 3]);
for (output, value) in result.iter_mut().zip(reduced.iter()) {
*output = *value as u64;
}
result
}
#[cfg(test)]
mod tests;