isap-aead 0.3.0

Pure Rust implementation of ISAP v2 with Ascon and Keccak
// Copyright 2022-2026 Sebastian Ramacher
// SPDX-License-Identifier: MIT

use aead::{
    KeySizeUser, TagPosition,
    array::{Array, typenum::Unsigned},
    consts::{U1, U6, U8, U12, U16, U40, U64, U128},
    inout::{InOut, InOutBuf},
};
use ascon_core::State;

use crate::{AbsorbingState, AeadCore, AeadInOut, Isap, Key, KeyInit, Nonce, Result, Tag};

#[derive(Debug, Default)]
#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
pub(crate) struct AsconState {
    state: State,
    #[cfg_attr(feature = "zeroize", zeroize(skip))]
    idx: usize,
}

impl AbsorbingState for AsconState {
    const RATE: usize = 8;
    type StateSize = U40;

    fn absorb_byte<R: Unsigned>(&mut self, byte: u8) {
        self.state[0] ^= (byte as u64) << ((7 - self.idx) * 8);
        self.idx += 1;
        if self.idx == Self::RATE {
            self.permute_n::<R>();
        }
    }

    fn absorb_bytes<R: Unsigned>(&mut self, mut bytes: &[u8]) {
        // process until full block reached
        while self.idx != 0 && !bytes.is_empty() {
            self.absorb_byte::<R>(bytes[0]);
            bytes = &bytes[1..];
        }

        // process full blocks
        let chunks = bytes.chunks_exact(Self::RATE);
        let reminder = chunks.remainder();
        for chunk in chunks {
            self.state[0] ^= u64::from_be_bytes(chunk.try_into().unwrap());
            self.permute_n::<R>();
        }

        // process remaining bytes
        if !reminder.is_empty() {
            let mut tmp = [0u8; 8];
            tmp[0..reminder.len()].copy_from_slice(reminder);
            self.state[0] ^= u64::from_be_bytes(tmp);
            self.idx = reminder.len();
        }
    }

    fn permute_n<R: Unsigned>(&mut self) {
        match R::USIZE {
            12 => self.state.permute_12(),
            8 => self.state.permute_8(),
            6 => self.state.permute_6(),
            1 => self.state.permute_1(),
            _ => unreachable!(),
        }
        self.idx = 0;
    }

    fn permute_n_if<R: Unsigned>(&mut self) {
        if self.idx != 0 {
            self.permute_n::<R>();
        }
    }

    fn seperate_domains(&mut self) {
        self.state[4] ^= 0x1;
    }

    fn extract_bytes<const LEN: usize>(&self) -> [u8; LEN] {
        debug_assert!(LEN % 8 == 0 && LEN <= 40);

        let mut ret = [0u8; LEN];
        for (idx, chunk) in ret.chunks_exact_mut(8).enumerate() {
            chunk.copy_from_slice(&u64::to_be_bytes(self.state[idx]));
        }
        ret
    }

    fn overwrite_bytes<const LEN: usize, O: Unsigned>(&mut self, bytes: &[u8; LEN]) {
        debug_assert!(LEN % 8 == 0);
        debug_assert!(O::USIZE % 8 == 0);
        debug_assert!(LEN + O::USIZE <= 40);

        for (idx, chunk) in bytes.chunks_exact(8).enumerate() {
            self.state[idx + O::USIZE / 8] = u64::from_be_bytes(chunk.try_into().unwrap());
        }
    }
}

/// ISAP-Ascon128
#[derive(Clone, Debug)]
#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
pub struct IsapAscon128 {
    k: [u8; 16],
}

impl Isap for IsapAscon128 {
    type KeySizeBits = U128;
    type RateBits = U64;
    type RateBytes = U8;
    type RateSessionKeyBits = U1;
    type RoundsKey = U12;
    type RoundsBit = U12;
    type RoundsEncryption = U12;
    type RoundsMAC = U12;
    type State = AsconState;

    fn isap_enc_process_block(
        state: &Self::State,
        mut buffer: InOut<'_, '_, Array<u8, Self::RateBytes>>,
    ) {
        let t = u64::from_ne_bytes(state.extract_bytes())
            ^ u64::from_ne_bytes(buffer.get_in()[..8].try_into().unwrap());
        buffer.get_out()[..8].copy_from_slice(&u64::to_ne_bytes(t));
    }

    fn isap_enc_process_bytes(state: Self::State, mut buffer: InOutBuf<'_, '_, u8>) {
        let mut tmp = [0u8; 8];
        let buf_len = buffer.len();
        tmp[0..buf_len].copy_from_slice(buffer.get_in());
        buffer.get_out().copy_from_slice(
            &u64::to_ne_bytes(u64::from_ne_bytes(state.extract_bytes()) ^ u64::from_ne_bytes(tmp))
                [0..buf_len],
        );
    }
}

impl AeadCore for IsapAscon128 {
    type NonceSize = U16;
    type TagSize = U16;
    const TAG_POSITION: TagPosition = TagPosition::Postfix;
}

impl KeySizeUser for IsapAscon128 {
    type KeySize = U16;
}

impl KeyInit for IsapAscon128 {
    fn new(key: &Key<Self>) -> Self {
        Self { k: (*key).into() }
    }
}

impl AeadInOut for IsapAscon128 {
    fn encrypt_inout_detached(
        &self,
        nonce: &Nonce<Self>,
        associated_data: &[u8],
        buffer: InOutBuf<'_, '_, u8>,
    ) -> Result<Tag<Self>> {
        Self::encrypt_impl(&self.k, nonce, associated_data, buffer).map(Into::into)
    }

    fn decrypt_inout_detached(
        &self,
        nonce: &Nonce<Self>,
        associated_data: &[u8],
        buffer: InOutBuf<'_, '_, u8>,
        tag: &Tag<Self>,
    ) -> Result<()> {
        Self::decrypt_impl(&self.k, nonce, associated_data, buffer, tag)
    }
}

/// ISAP-Ascon128A
#[derive(Clone, Debug)]
#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
pub struct IsapAscon128A {
    k: [u8; 16],
}

impl Isap for IsapAscon128A {
    type KeySizeBits = U128;
    type RateBits = U64;
    type RateBytes = U8;
    type RateSessionKeyBits = U1;
    type RoundsKey = U12;
    type RoundsBit = U1;
    type RoundsEncryption = U6;
    type RoundsMAC = U12;
    type State = AsconState;

    fn isap_enc_process_block(
        state: &Self::State,
        mut buffer: InOut<'_, '_, Array<u8, Self::RateBytes>>,
    ) {
        let t = u64::from_ne_bytes(state.extract_bytes())
            ^ u64::from_ne_bytes(buffer.get_in()[..8].try_into().unwrap());
        buffer.get_out()[..8].copy_from_slice(&u64::to_ne_bytes(t));
    }

    fn isap_enc_process_bytes(state: Self::State, mut buffer: InOutBuf<'_, '_, u8>) {
        let mut tmp = [0u8; 8];
        let buf_len = buffer.len();
        tmp[0..buf_len].copy_from_slice(buffer.get_in());
        buffer.get_out().copy_from_slice(
            &u64::to_ne_bytes(u64::from_ne_bytes(state.extract_bytes()) ^ u64::from_ne_bytes(tmp))
                [0..buf_len],
        );
    }
}

impl AeadCore for IsapAscon128A {
    type NonceSize = U16;
    type TagSize = U16;
    const TAG_POSITION: TagPosition = TagPosition::Postfix;
}

impl KeySizeUser for IsapAscon128A {
    type KeySize = U16;
}

impl KeyInit for IsapAscon128A {
    fn new(key: &Key<Self>) -> Self {
        Self { k: (*key).into() }
    }
}

impl AeadInOut for IsapAscon128A {
    fn encrypt_inout_detached(
        &self,
        nonce: &Nonce<Self>,
        associated_data: &[u8],
        buffer: InOutBuf<'_, '_, u8>,
    ) -> Result<Tag<Self>> {
        Self::encrypt_impl(&self.k, nonce, associated_data, buffer).map(Into::into)
    }

    fn decrypt_inout_detached(
        &self,
        nonce: &Nonce<Self>,
        associated_data: &[u8],
        buffer: InOutBuf<'_, '_, u8>,
        tag: &Tag<Self>,
    ) -> Result<()> {
        Self::decrypt_impl(&self.k, nonce, associated_data, buffer, tag)
    }
}