use crate::cipher::{Aes128, BlockCipher};
const P36: u64 = 0xf_fffffffb;
const P64: u64 = 0xffff_ffff_ffff_ffc5;
const P128: u128 = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ff61;
const MAXWR_64: u64 = 0xffff_ffff_0000_0000;
const MAXWR_128: u128 = 0xffff_ffff_0000_0000_0000_0000_0000_0000;
const OFFSET_64: u64 = 59;
const OFFSET_128: u128 = 159;
const L1_KEY_LEN: usize = 1024;
const POLY64_OUTPUTS: u32 = 1 << 14;
const MASK64: u64 = 0x01ff_ffff_01ff_ffff;
const MASK128: u128 = 0x01ff_ffff_01ff_ffff_01ff_ffff_01ff_ffff;
fn kdf(aes: &Aes128, index: u64, out: &mut [u8]) {
let mut block = [0u8; 16];
block[0..8].copy_from_slice(&index.to_be_bytes());
let n = out.len().div_ceil(16);
let mut written = 0;
for i in 1..=n as u64 {
block[8..16].copy_from_slice(&i.to_be_bytes());
let mut ct = block;
aes.encrypt_block(&mut ct);
let take = (out.len() - written).min(16);
out[written..written + take].copy_from_slice(&ct[..take]);
written += take;
}
}
fn nh(key: &[u8], data: &[u8]) -> u64 {
let mut y: u64 = 0;
let mut i = 0;
while i < data.len() {
let mut sums = [0u32; 8];
for (j, slot) in sums.iter_mut().enumerate() {
let off = i + j * 4;
let m = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
let k = u32::from_be_bytes([key[off], key[off + 1], key[off + 2], key[off + 3]]);
*slot = m.wrapping_add(k);
}
for j in 0..4 {
y = y.wrapping_add((sums[j] as u64).wrapping_mul(sums[j + 4] as u64));
}
i += 32;
}
y
}
fn l1_chunk(key: &[u8; L1_KEY_LEN], data: &[u8], bit_length: u64) -> u64 {
debug_assert!(data.len() <= L1_KEY_LEN);
let pad_len = data.len().div_ceil(32) * 32;
let pad_len = pad_len.max(32);
let mut buf = [0u8; L1_KEY_LEN];
buf[..data.len()].copy_from_slice(data);
let nh_out = nh(&key[..pad_len], &buf[..pad_len]);
nh_out.wrapping_add(bit_length)
}
#[inline]
fn add_mod_p64(a: u64, b: u64) -> u64 {
let (s, carry) = a.overflowing_add(b);
let s = if carry { s.wrapping_add(OFFSET_64) } else { s };
if s >= P64 { s - P64 } else { s }
}
#[inline]
fn add_mod_p128(a: u128, b: u128) -> u128 {
let (s, carry) = a.overflowing_add(b);
let s = if carry { s.wrapping_add(OFFSET_128) } else { s };
if s >= P128 { s - P128 } else { s }
}
fn mul_mod_p64(a: u64, b: u64) -> u64 {
let prod = (a as u128) * (b as u128);
let lo = prod as u64;
let hi = (prod >> 64) as u64;
let lift1 = (lo as u128) + (OFFSET_64 as u128) * (hi as u128); let lift1_lo = lift1 as u64;
let lift1_hi = (lift1 >> 64) as u64; let (r, carry) = lift1_lo.overflowing_add(OFFSET_64.wrapping_mul(lift1_hi));
let r = if carry { r.wrapping_add(OFFSET_64) } else { r };
if r >= P64 { r - P64 } else { r }
}
fn mul_mod_p128(a: u128, b: u128) -> u128 {
let a_lo = a as u64;
let a_hi = (a >> 64) as u64;
let b_lo = b as u64;
let b_hi = (b >> 64) as u64;
let ll = (a_lo as u128) * (b_lo as u128);
let lh = (a_lo as u128) * (b_hi as u128);
let hl = (a_hi as u128) * (b_lo as u128);
let hh = (a_hi as u128) * (b_hi as u128);
let ll_lo = ll as u64;
let ll_hi = (ll >> 64) as u64;
let lh_lo = lh as u64;
let lh_hi = (lh >> 64) as u64;
let hl_lo = hl as u64;
let hl_hi = (hl >> 64) as u64;
let hh_lo = hh as u64;
let hh_hi = (hh >> 64) as u64;
let w0 = ll_lo;
let s1 = (ll_hi as u128) + (lh_lo as u128) + (hl_lo as u128);
let w1 = s1 as u64;
let c1 = (s1 >> 64) as u64;
let s2 = (lh_hi as u128) + (hl_hi as u128) + (hh_lo as u128) + (c1 as u128);
let w2 = s2 as u64;
let c2 = (s2 >> 64) as u64;
let w3 = hh_hi.wrapping_add(c2);
let r0 = (w0 as u128) + OFFSET_128 * (w2 as u128); let r1 = (w1 as u128) + OFFSET_128 * (w3 as u128);
let r0_lo = r0 as u64;
let r0_hi = (r0 >> 64) as u64; let r1_lo = r1 as u64;
let r1_hi = (r1 >> 64) as u64;
let mid = (r0_hi as u128) + (r1_lo as u128); let mid_lo = mid as u64;
let mid_hi = (mid >> 64) as u64;
let low128: u128 = (r0_lo as u128) | ((mid_lo as u128) << 64);
let high = (mid_hi as u128) + (r1_hi as u128); let extra = high * OFFSET_128;
let (sum, overflow) = low128.overflowing_add(extra);
let sum = if overflow {
sum.wrapping_add(OFFSET_128)
} else {
sum
};
if sum >= P128 { sum - P128 } else { sum }
}
#[inline]
fn poly64_step(k64: u64, acc: &mut u64, m: u64) {
if m >= MAXWR_64 {
*acc = add_mod_p64(mul_mod_p64(k64, *acc), P64 - 1);
*acc = add_mod_p64(mul_mod_p64(k64, *acc), m - OFFSET_64);
} else {
*acc = add_mod_p64(mul_mod_p64(k64, *acc), m);
}
}
#[inline]
fn poly128_step(k128: u128, acc: &mut u128, m: u128) {
if m >= MAXWR_128 {
*acc = add_mod_p128(mul_mod_p128(k128, *acc), P128 - 1);
*acc = add_mod_p128(mul_mod_p128(k128, *acc), m - OFFSET_128);
} else {
*acc = add_mod_p128(mul_mod_p128(k128, *acc), m);
}
}
fn l3_hash(k1_reduced: &[u64; 8], k2: &[u8; 4], b: &[u8; 16]) -> [u8; 4] {
let mut sum: u64 = 0;
for i in 0..8 {
let m_i = u16::from_be_bytes([b[i * 2], b[i * 2 + 1]]) as u64;
sum = sum.wrapping_add(m_i.wrapping_mul(k1_reduced[i]));
}
let y = (sum % P36) as u32; let k2_u32 = u32::from_be_bytes(*k2);
(y ^ k2_u32).to_be_bytes()
}
fn pdf_8(aes_pdf: &Aes128, nonce: &[u8]) -> [u8; 8] {
debug_assert!(!nonce.is_empty() && nonce.len() <= 16);
let mut t = [0u8; 16];
let last = nonce.len() - 1;
let index = nonce[last] & 1;
t[..nonce.len()].copy_from_slice(nonce);
t[last] ^= index; aes_pdf.encrypt_block(&mut t);
let start = (index as usize) * 8;
let mut out = [0u8; 8];
out.copy_from_slice(&t[start..start + 8]);
out
}
fn pdf_16(aes_pdf: &Aes128, nonce: &[u8]) -> [u8; 16] {
debug_assert!(!nonce.is_empty() && nonce.len() <= 16);
let mut t = [0u8; 16];
t[..nonce.len()].copy_from_slice(nonce);
aes_pdf.encrypt_block(&mut t);
t
}
#[derive(Clone)]
struct UmacIter {
poly64_acc: u64,
poly128_acc: u128,
l1_outputs: u32,
pending_first: u64,
transitioned: bool,
poly128_half: [u8; 8],
half_pending: bool,
}
impl UmacIter {
fn new() -> Self {
Self {
poly64_acc: 1, poly128_acc: 1,
l1_outputs: 0,
pending_first: 0,
transitioned: false,
poly128_half: [0; 8],
half_pending: false,
}
}
fn absorb(&mut self, k64: u64, k128: u128, l1_out: u64) {
self.l1_outputs = self.l1_outputs.saturating_add(1);
if self.l1_outputs == 1 {
self.pending_first = l1_out;
return;
}
if self.l1_outputs == 2 {
poly64_step(k64, &mut self.poly64_acc, self.pending_first);
}
if self.l1_outputs <= POLY64_OUTPUTS {
poly64_step(k64, &mut self.poly64_acc, l1_out);
} else {
if !self.transitioned {
let init = self.poly64_acc as u128; poly128_step(k128, &mut self.poly128_acc, init);
self.transitioned = true;
}
let bytes = l1_out.to_be_bytes();
if !self.half_pending {
self.poly128_half.copy_from_slice(&bytes);
self.half_pending = true;
} else {
let mut paired = [0u8; 16];
paired[..8].copy_from_slice(&self.poly128_half);
paired[8..].copy_from_slice(&bytes);
let m = u128::from_be_bytes(paired);
poly128_step(k128, &mut self.poly128_acc, m);
self.half_pending = false;
}
}
}
fn finalize(&mut self, k128: u128) -> [u8; 16] {
if self.l1_outputs == 0 {
return [0u8; 16];
}
if self.l1_outputs == 1 {
let mut b = [0u8; 16];
b[8..].copy_from_slice(&self.pending_first.to_be_bytes());
return b;
}
if !self.transitioned {
let mut b = [0u8; 16];
b[8..].copy_from_slice(&self.poly64_acc.to_be_bytes());
return b;
}
let mut final_block = [0u8; 16];
if self.half_pending {
final_block[..8].copy_from_slice(&self.poly128_half);
final_block[8] = 0x80;
} else {
final_block[0] = 0x80;
}
let m = u128::from_be_bytes(final_block);
poly128_step(k128, &mut self.poly128_acc, m);
self.poly128_acc.to_be_bytes()
}
}
const MAX_ITER: usize = 4;
const MAX_L1_KEY_BUF: usize = L1_KEY_LEN + (MAX_ITER - 1) * 16;
#[derive(Clone)]
struct UmacInner<const ITER: usize> {
pdf_aes: Aes128,
l1_key: [u8; MAX_L1_KEY_BUF],
l2_k64: [u64; ITER],
l2_k128: [u128; ITER],
l3_k1: [[u64; 8]; ITER],
l3_k2: [[u8; 4]; ITER],
chunk: [u8; L1_KEY_LEN],
chunk_off: usize,
total_bytes: u64,
iter_state: [UmacIter; ITER],
}
impl<const ITER: usize> UmacInner<ITER> {
fn new(key: &[u8; 16]) -> Self {
let master = Aes128::new(key);
let mut pdf_key = [0u8; 16];
kdf(&master, 0, &mut pdf_key);
let pdf_aes = Aes128::new(&pdf_key);
let l1_len = L1_KEY_LEN + (ITER - 1) * 16;
let mut l1_key = [0u8; MAX_L1_KEY_BUF];
kdf(&master, 1, &mut l1_key[..l1_len]);
let mut l2_buf = [0u8; 24 * MAX_ITER];
kdf(&master, 2, &mut l2_buf[..24 * ITER]);
let mut l2_k64 = [0u64; ITER];
let mut l2_k128 = [0u128; ITER];
for i in 0..ITER {
let base = i * 24;
let k64_bytes: [u8; 8] = l2_buf[base..base + 8].try_into().unwrap();
let k128_bytes: [u8; 16] = l2_buf[base + 8..base + 24].try_into().unwrap();
l2_k64[i] = u64::from_be_bytes(k64_bytes) & MASK64;
l2_k128[i] = u128::from_be_bytes(k128_bytes) & MASK128;
}
let mut l3k1_buf = [0u8; 64 * MAX_ITER];
kdf(&master, 3, &mut l3k1_buf[..64 * ITER]);
let mut l3_k1 = [[0u64; 8]; ITER];
for (i, words) in l3_k1.iter_mut().enumerate() {
for (j, slot) in words.iter_mut().enumerate() {
let base = i * 64 + j * 8;
let raw: [u8; 8] = l3k1_buf[base..base + 8].try_into().unwrap();
*slot = u64::from_be_bytes(raw) % P36;
}
}
let mut l3k2_buf = [0u8; 4 * MAX_ITER];
kdf(&master, 4, &mut l3k2_buf[..4 * ITER]);
let mut l3_k2 = [[0u8; 4]; ITER];
for i in 0..ITER {
l3_k2[i].copy_from_slice(&l3k2_buf[i * 4..i * 4 + 4]);
}
Self {
pdf_aes,
l1_key,
l2_k64,
l2_k128,
l3_k1,
l3_k2,
chunk: [0u8; L1_KEY_LEN],
chunk_off: 0,
total_bytes: 0,
iter_state: core::array::from_fn(|_| UmacIter::new()),
}
}
fn process_full_chunk(&mut self) {
for i in 0..ITER {
let key_slice = &self.l1_key[i * 16..i * 16 + L1_KEY_LEN];
let key_arr: &[u8; L1_KEY_LEN] = key_slice.try_into().unwrap();
let l1_out = l1_chunk(key_arr, &self.chunk, (L1_KEY_LEN as u64) * 8);
self.iter_state[i].absorb(self.l2_k64[i], self.l2_k128[i], l1_out);
}
}
fn update(&mut self, mut data: &[u8]) {
while !data.is_empty() {
let take = (L1_KEY_LEN - self.chunk_off).min(data.len());
self.chunk[self.chunk_off..self.chunk_off + take].copy_from_slice(&data[..take]);
self.chunk_off += take;
self.total_bytes = self.total_bytes.wrapping_add(take as u64);
data = &data[take..];
if self.chunk_off == L1_KEY_LEN {
self.process_full_chunk();
self.chunk_off = 0;
}
}
}
fn finalize_uhash(&mut self, out: &mut [u8]) {
debug_assert_eq!(out.len(), 4 * ITER);
if self.total_bytes == 0 {
for i in 0..ITER {
let key_slice = &self.l1_key[i * 16..i * 16 + L1_KEY_LEN];
let key_arr: &[u8; L1_KEY_LEN] = key_slice.try_into().unwrap();
let l1_out = l1_chunk(key_arr, &[], 0);
self.iter_state[i].absorb(self.l2_k64[i], self.l2_k128[i], l1_out);
}
} else if self.chunk_off > 0 {
let bit_len = (self.chunk_off as u64) * 8;
for i in 0..ITER {
let key_slice = &self.l1_key[i * 16..i * 16 + L1_KEY_LEN];
let key_arr: &[u8; L1_KEY_LEN] = key_slice.try_into().unwrap();
let l1_out = l1_chunk(key_arr, &self.chunk[..self.chunk_off], bit_len);
self.iter_state[i].absorb(self.l2_k64[i], self.l2_k128[i], l1_out);
}
}
for i in 0..ITER {
let b = self.iter_state[i].finalize(self.l2_k128[i]);
let c = l3_hash(&self.l3_k1[i], &self.l3_k2[i], &b);
out[i * 4..i * 4 + 4].copy_from_slice(&c);
}
}
}
impl<const ITER: usize> Drop for UmacInner<ITER> {
fn drop(&mut self) {
for b in self.l1_key.iter_mut() {
*b = 0;
}
for w in self.l2_k64.iter_mut() {
*w = 0;
}
for w in self.l2_k128.iter_mut() {
*w = 0;
}
for row in self.l3_k1.iter_mut() {
for w in row.iter_mut() {
*w = 0;
}
}
for row in self.l3_k2.iter_mut() {
for b in row.iter_mut() {
*b = 0;
}
}
for b in self.chunk.iter_mut() {
*b = 0;
}
self.chunk_off = 0;
self.total_bytes = 0;
for it in self.iter_state.iter_mut() {
it.poly64_acc = 0;
it.poly128_acc = 0;
it.l1_outputs = 0;
it.pending_first = 0;
it.transitioned = false;
it.poly128_half = [0; 8];
it.half_pending = false;
let _ = core::hint::black_box(&it.poly64_acc);
let _ = core::hint::black_box(&it.poly128_acc);
let _ = core::hint::black_box(&it.pending_first);
let _ = core::hint::black_box(&it.poly128_half);
}
let _ = core::hint::black_box(&self.l1_key);
let _ = core::hint::black_box(&self.l2_k64);
let _ = core::hint::black_box(&self.l2_k128);
let _ = core::hint::black_box(&self.l3_k1);
let _ = core::hint::black_box(&self.l3_k2);
let _ = core::hint::black_box(&self.chunk);
}
}
#[derive(Clone)]
pub struct Umac64 {
inner: UmacInner<2>,
}
impl Umac64 {
pub fn new(key: &[u8; 16]) -> Self {
Self {
inner: UmacInner::new(key),
}
}
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
pub fn finalize(mut self, nonce: &[u8]) -> [u8; 8] {
assert!(
!nonce.is_empty() && nonce.len() <= 16,
"UMAC nonce length must be 1..=16 bytes"
);
let mut tag = [0u8; 8];
self.inner.finalize_uhash(&mut tag);
let pad = pdf_8(&self.inner.pdf_aes, nonce);
for i in 0..8 {
tag[i] ^= pad[i];
}
tag
}
pub fn compute(key: &[u8; 16], data: &[u8], nonce: &[u8]) -> [u8; 8] {
let mut s = Self::new(key);
s.update(data);
s.finalize(nonce)
}
}
#[derive(Clone)]
pub struct Umac128 {
inner: UmacInner<4>,
}
impl Umac128 {
pub fn new(key: &[u8; 16]) -> Self {
Self {
inner: UmacInner::new(key),
}
}
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
pub fn finalize(mut self, nonce: &[u8]) -> [u8; 16] {
assert!(
!nonce.is_empty() && nonce.len() <= 16,
"UMAC nonce length must be 1..=16 bytes"
);
let mut tag = [0u8; 16];
self.inner.finalize_uhash(&mut tag);
let pad = pdf_16(&self.inner.pdf_aes, nonce);
for i in 0..16 {
tag[i] ^= pad[i];
}
tag
}
pub fn compute(key: &[u8; 16], data: &[u8], nonce: &[u8]) -> [u8; 16] {
let mut s = Self::new(key);
s.update(data);
s.finalize(nonce)
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
extern crate alloc;
fn from_hex<const N: usize>(s: &str) -> [u8; N] {
let bytes = s.as_bytes();
assert_eq!(bytes.len(), 2 * N, "hex string has wrong length");
let mut out = [0u8; N];
for i in 0..N {
let hi = (bytes[2 * i] as char).to_digit(16).expect("hex") as u8;
let lo = (bytes[2 * i + 1] as char).to_digit(16).expect("hex") as u8;
out[i] = (hi << 4) | lo;
}
out
}
const RFC_KEY: &[u8; 16] = b"abcdefghijklmnop";
const RFC_NONCE: &[u8; 8] = b"bcdefghi";
struct RfcMsg {
pattern: &'static [u8],
reps: usize,
}
const RFC_VECTORS: [RfcMsg; 8] = [
RfcMsg {
pattern: b"",
reps: 0,
},
RfcMsg {
pattern: b"a",
reps: 3,
},
RfcMsg {
pattern: b"a",
reps: 1 << 10,
},
RfcMsg {
pattern: b"a",
reps: 1 << 15,
},
RfcMsg {
pattern: b"a",
reps: 1 << 20,
},
RfcMsg {
pattern: b"a",
reps: 1 << 25,
},
RfcMsg {
pattern: b"abc",
reps: 1,
},
RfcMsg {
pattern: b"abc",
reps: 500,
},
];
fn feed_pattern_repeated<F: FnMut(&[u8])>(pattern: &[u8], reps: usize, mut f: F) {
if pattern.is_empty() || reps == 0 {
return;
}
let chunk_unit = 8192 / pattern.len().max(1) * pattern.len();
let chunk_unit = chunk_unit.max(pattern.len());
let mut buf = alloc::vec::Vec::with_capacity(chunk_unit);
while buf.len() < chunk_unit {
buf.extend_from_slice(pattern);
}
let total = pattern.len() * reps;
let mut remaining = total;
while remaining > 0 {
let take = remaining.min(buf.len());
f(&buf[..take]);
remaining -= take;
}
}
fn compute_umac64_msg(m: &RfcMsg) -> [u8; 8] {
let mut s = Umac64::new(RFC_KEY);
feed_pattern_repeated(m.pattern, m.reps, |b| s.update(b));
s.finalize(RFC_NONCE)
}
fn compute_umac_iter_msg<const ITER: usize>(m: &RfcMsg) -> [u8; 16] {
let mut inner = UmacInner::<ITER>::new(RFC_KEY);
feed_pattern_repeated(m.pattern, m.reps, |b| inner.update(b));
let mut tag = [0u8; 16];
let n = 4 * ITER;
inner.finalize_uhash(&mut tag[..n]);
match n {
8 => {
let pad = pdf_8(&inner.pdf_aes, RFC_NONCE);
for i in 0..8 {
tag[i] ^= pad[i];
}
}
12 | 16 => {
let pad = pdf_16(&inner.pdf_aes, RFC_NONCE);
for i in 0..n {
tag[i] ^= pad[i];
}
}
_ => unreachable!(),
}
tag
}
fn vector_fits_miri(m: &RfcMsg) -> bool {
m.pattern.len() * m.reps <= 1 << 16
}
#[test]
fn rfc4418_umac64_vectors() {
let expected: [[u8; 8]; 8] = [
from_hex("6E155FAD26900BE1"),
from_hex("44B5CB542F220104"),
from_hex("26BF2F5D60118BD9"),
from_hex("27F8EF643B0D118D"),
from_hex("A4477E87E9F55853"),
from_hex("FACA46F856E9B45F"), from_hex("D4D7B9F6BD4FBFCF"),
from_hex("D4CF26DDEFD5C01A"),
];
for (i, m) in RFC_VECTORS.iter().enumerate() {
if cfg!(miri) && !vector_fits_miri(m) {
continue;
}
let tag = compute_umac64_msg(m);
assert_eq!(
tag,
expected[i],
"UMAC-64 vector {} mismatch ({} x{})",
i,
core::str::from_utf8(m.pattern).unwrap_or("?"),
m.reps,
);
}
}
#[test]
fn rfc4418_umac96_vectors_via_iter3() {
let expected: [[u8; 12]; 8] = [
from_hex("32FEDB100C79AD58F07FF764"),
from_hex("185E4FE905CBA7BD85E4C2DC"),
from_hex("7A54ABE04AF82D60FB298C3C"),
from_hex("7B136BD911E4B734286EF2BE"),
from_hex("F8ACFA3AC31CFEEA047F7B11"),
from_hex("A621C2457C0012E64F3FDAE9"), from_hex("883C3D4B97A61976FFCF2323"),
from_hex("8824A260C53C66A36C9260A6"),
];
for (i, m) in RFC_VECTORS.iter().enumerate() {
if cfg!(miri) && !vector_fits_miri(m) {
continue;
}
let tag = compute_umac_iter_msg::<3>(m);
for j in 0..12 {
assert_eq!(
tag[j],
expected[i][j],
"UMAC-96 vector {} mismatch at byte {} ({} x{})",
i,
j,
core::str::from_utf8(m.pattern).unwrap_or("?"),
m.reps,
);
}
}
}
#[test]
fn streaming_matches_one_shot() {
let key = b"streamtestkey567";
let nonce = b"abcdef01";
let mut data = alloc::vec::Vec::with_capacity(2_500_000);
for i in 0..2_500_000usize {
data.push((i as u8).wrapping_mul(31).wrapping_add(7));
}
let one_shot = Umac64::compute(key, &data, nonce);
let mut state = Umac64::new(key);
let splits: [usize; 9] = [1, 7, 511, 1024, 1023, 4097, 17, 600_000, 999_999];
let mut offset = 0;
let mut idx = 0;
while offset < data.len() {
let take = splits[idx % splits.len()].min(data.len() - offset);
state.update(&data[offset..offset + take]);
offset += take;
idx += 1;
}
let streamed = state.finalize(nonce);
assert_eq!(one_shot, streamed);
}
#[test]
fn umac128_streaming_matches_one_shot() {
let key = b"128streamkey7890";
let nonce = b"01234567";
let mut data = alloc::vec::Vec::with_capacity(50_000);
for i in 0..50_000usize {
data.push((i as u8).wrapping_mul(13).wrapping_add(5));
}
let one_shot = Umac128::compute(key, &data, nonce);
let mut state = Umac128::new(key);
let splits: [usize; 6] = [1, 31, 1024, 17, 10_000, 4097];
let mut offset = 0;
let mut idx = 0;
while offset < data.len() {
let take = splits[idx % splits.len()].min(data.len() - offset);
state.update(&data[offset..offset + take]);
offset += take;
idx += 1;
}
let streamed = state.finalize(nonce);
assert_eq!(one_shot, streamed);
}
}