eme2 0.3.0

EME2 (ECB-Mask-ECB) wide-block cipher mode of operation
Documentation
//! Tests for the `extended` feature (`eme2::extended::Eme2<C>`).
//!
//! Two kinds of check:
//! 1. For 128-bit-block ciphers (AES), `extended::Eme2` must be byte-for-byte
//!    identical to the base, already-verified `Eme2` — the algorithm is the
//!    same, only the block-size genericism differs. This is a strong
//!    regression test that needs no new hand-derived vectors.
//! 2. For 256/512/1024-bit ciphers (Threefish), only roundtrip correctness is
//!    checked — there is no independent oracle for these block sizes. See
//!    the module-level warning on `eme2::extended` about the unverified
//!    primitivity of the larger GF(2^n) moduli.

use aes::{Aes128, Aes256};
use cipher::KeyInit;
use eme2::Eme2 as BaseEme2;
use eme2::extended::Eme2 as ExtEme2;

fn fill(buf: &mut [u8], seed: u64) {
    let mut s = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
    for b in buf.iter_mut() {
        s = s
            .wrapping_mul(6_364_136_223_846_793_005)
            .wrapping_add(1_442_695_040_888_963_407);
        *b = (s >> 33) as u8;
    }
}

#[test]
fn extended_matches_base_for_aes256() {
    let key = [0x42u8; 64];
    let base = <BaseEme2<Aes256> as KeyInit>::new(&key.into());
    let ext = <ExtEme2<Aes256> as KeyInit>::new(&key.into());

    let mut lens: Vec<usize> = (16..=200).collect();
    lens.extend([2030, 2048, 2049, 2064]);
    let ad_lens = [0usize, 1, 15, 16, 17, 31, 32, 33];

    for &len in &lens {
        for &al in &ad_lens {
            let mut pt = vec![0u8; len];
            fill(&mut pt, (len * 131 + al) as u64);
            let mut ad = vec![0u8; al];
            fill(&mut ad, (al * 7919 + len) as u64);

            let mut buf_base = pt.clone();
            base.encrypt_with_ad(&ad, &mut buf_base).unwrap();

            let mut buf_ext = pt.clone();
            ext.encrypt_with_ad(&ad, &mut buf_ext).unwrap();

            assert_eq!(
                buf_base, buf_ext,
                "extended::Eme2 diverged from base Eme2: len={len}, ad_len={al}"
            );

            ext.decrypt_with_ad(&ad, &mut buf_ext).unwrap();
            assert_eq!(
                buf_ext, pt,
                "extended::Eme2 roundtrip failed: len={len}, ad_len={al}"
            );
        }
    }
}

#[test]
fn extended_matches_base_for_aes128() {
    let key = [0x24u8; 48];
    let base = <BaseEme2<Aes128> as KeyInit>::new(&key.into());
    let ext = <ExtEme2<Aes128> as KeyInit>::new(&key.into());

    for len in [16, 17, 32, 48, 63, 128] {
        let mut pt = vec![0u8; len];
        fill(&mut pt, len as u64);

        let mut buf_base = pt.clone();
        base.encrypt(&mut buf_base).unwrap();

        let mut buf_ext = pt.clone();
        ext.encrypt(&mut buf_ext).unwrap();

        assert_eq!(buf_base, buf_ext, "mismatch at len={len}");
    }
}

#[test]
fn extended_default_tweak_matches_empty_ad() {
    let key = [0x11u8; 64];
    let cipher = <ExtEme2<Aes256> as KeyInit>::new(&key.into());

    let pt = vec![0xCDu8; 48];
    let mut via_default = pt.clone();
    cipher.encrypt(&mut via_default).unwrap();

    let mut via_empty_ad = pt;
    cipher.encrypt_with_ad(&[], &mut via_empty_ad).unwrap();

    assert_eq!(via_default, via_empty_ad);
}

#[cfg(test)]
mod threefish {
    use super::fill;
    use cipher::KeyInit;
    use eme2::extended::Eme2;
    use threefish::{Threefish256, Threefish512, Threefish1024};

    fn roundtrip<C>(cipher: &Eme2<C>, len: usize, ad: &[u8], seed: u64)
    where
        C: cipher::BlockCipherEncrypt
            + cipher::BlockCipherDecrypt
            + cipher::BlockSizeUser
            + cipher::KeySizeUser,
        C::BlockSize: eme2::extended::EmePoly + core::ops::Add<C::BlockSize>,
        cipher::typenum::Sum<C::BlockSize, C::BlockSize>: hybrid_array::ArraySize,
    {
        let mut pt = vec![0u8; len];
        fill(&mut pt, seed);
        let mut buf = pt.clone();

        cipher
            .encrypt_with_ad(ad, &mut buf)
            .expect("encryption succeeded");
        assert_ne!(
            buf, pt,
            "ciphertext should differ from plaintext (len={len})"
        );

        cipher
            .decrypt_with_ad(ad, &mut buf)
            .expect("decryption succeeded");
        assert_eq!(buf, pt, "roundtrip failed (len={len})");
    }

    #[test]
    fn threefish256_roundtrip() {
        let key = [0x33u8; 96]; // 32 (key1) + 32 (key2) + 32 (key3)
        let cipher = <Eme2<Threefish256> as KeyInit>::new(&key.into());
        for len in [32, 33, 64, 96, 100] {
            roundtrip(&cipher, len, b"associated data", len as u64);
        }
    }

    #[test]
    fn threefish512_roundtrip() {
        let key = [0x55u8; 192]; // 64 + 64 + 64
        let cipher = <Eme2<Threefish512> as KeyInit>::new(&key.into());
        for len in [64, 65, 128, 200] {
            roundtrip(&cipher, len, b"", len as u64);
        }
    }

    #[test]
    fn threefish1024_roundtrip() {
        let key = [0x77u8; 384]; // 128 + 128 + 128
        let cipher = <Eme2<Threefish1024> as KeyInit>::new(&key.into());
        for len in [128, 129, 256, 400] {
            roundtrip(&cipher, len, b"tweak-like AD", len as u64);
        }
    }
}