lib-q-romulus 0.0.8

Romulus-N and Romulus-M AEAD (SKINNY-128-384+), no_std / wasm-friendly
Documentation
//! `lib_q_core::Aead` wrappers (allocating API) for registry integration.

#![deny(unsafe_code)]

extern crate alloc;

use alloc::string::ToString;
use alloc::vec::Vec;

use aead::array::Array;
use aead::{
    AeadInOut,
    KeyInit,
};
use lib_q_core::{
    Aead,
    AeadDecryptSemantic,
    AeadKey,
    DecryptSemanticOutcome,
    Error,
    Nonce,
    Result,
};
use zeroize::{
    Zeroize,
    Zeroizing,
};

use crate::{
    RomulusM,
    RomulusN,
    stack_secret,
};

/// Stateless Romulus-N facade using [`lib_q_core::Aead`].
pub struct RomulusNAead;

impl RomulusNAead {
    pub const fn new() -> Self {
        Self
    }

    pub const fn key_size() -> usize {
        16
    }

    pub const fn nonce_size() -> usize {
        16
    }

    pub const fn tag_size() -> usize {
        16
    }
}

impl Default for RomulusNAead {
    fn default() -> Self {
        Self::new()
    }
}

impl Aead for RomulusNAead {
    fn encrypt(
        &self,
        key: &AeadKey,
        nonce: &Nonce,
        plaintext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        let kb = key.as_bytes();
        if kb.len() != Self::key_size() {
            return Err(Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kb.len(),
            });
        }
        let nb = nonce.as_bytes();
        if nb.len() != Self::nonce_size() {
            return Err(Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nb.len(),
            });
        }
        let ad = associated_data.unwrap_or(&[]);
        let nonce_z = stack_secret::zeroizing_copy_16(nb);
        let nonce_arr =
            Array::try_from(nonce_z.as_slice()).map_err(|_| Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nonce_z.len(),
            })?;
        let cipher = {
            let kz = stack_secret::zeroizing_copy_16(kb);
            let key_arr = Array::try_from(kz.as_slice()).map_err(|_| Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kz.len(),
            })?;
            RomulusN::new(&key_arr)
        };
        let mut buf = plaintext.to_vec();
        let tag = cipher
            .encrypt_inout_detached(&nonce_arr, ad, buf.as_mut_slice().into())
            .map_err(|_| Error::EncryptionFailed {
                operation: "Romulus-N encrypt".to_string(),
            })?;
        buf.extend_from_slice(tag.as_slice());
        Ok(buf)
    }

    fn decrypt(
        &self,
        key: &AeadKey,
        nonce: &Nonce,
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        let kb = key.as_bytes();
        if kb.len() != Self::key_size() {
            return Err(Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kb.len(),
            });
        }
        let nb = nonce.as_bytes();
        if nb.len() != Self::nonce_size() {
            return Err(Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nb.len(),
            });
        }
        if ciphertext.len() < Self::tag_size() {
            return Err(Error::aead_ciphertext_shorter_than_tag(
                Self::tag_size(),
                ciphertext.len(),
            ));
        }
        let ad = associated_data.unwrap_or(&[]);
        let body_len = ciphertext.len() - Self::tag_size();
        let key_z = stack_secret::zeroizing_copy_16(kb);
        let nonce_z = stack_secret::zeroizing_copy_16(nb);
        let tag_arr =
            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
                Error::VerificationFailed {
                    operation: "AEAD tag verification".to_string(),
                }
            })?;
        let mut buf = ciphertext[..body_len].to_vec();
        crate::romulus_n::romulus_n_decrypt(&key_z, &nonce_z, ad, &mut buf, &tag_arr).map_err(
            |_| Error::VerificationFailed {
                operation: "AEAD tag verification".to_string(),
            },
        )?;
        Ok(buf)
    }
}

impl AeadDecryptSemantic for RomulusNAead {
    fn decrypt_semantic(
        &self,
        key: &AeadKey,
        nonce: &Nonce,
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<DecryptSemanticOutcome> {
        let kb = key.as_bytes();
        if kb.len() != Self::key_size() {
            return Err(Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kb.len(),
            });
        }
        let nb = nonce.as_bytes();
        if nb.len() != Self::nonce_size() {
            return Err(Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nb.len(),
            });
        }
        if ciphertext.len() < Self::tag_size() {
            return Err(Error::aead_ciphertext_shorter_than_tag(
                Self::tag_size(),
                ciphertext.len(),
            ));
        }
        let ad = associated_data.unwrap_or(&[]);
        let body_len = ciphertext.len() - Self::tag_size();
        let key_z = stack_secret::zeroizing_copy_16(kb);
        let nonce_z = stack_secret::zeroizing_copy_16(nb);
        let tag_arr =
            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
                Error::VerificationFailed {
                    operation: "AEAD tag verification".to_string(),
                }
            })?;
        let mut buf = ciphertext[..body_len].to_vec();
        if crate::romulus_n::romulus_n_decrypt_core(&key_z, &nonce_z, ad, &mut buf, &tag_arr) {
            Ok(DecryptSemanticOutcome::Success(Zeroizing::new(buf)))
        } else {
            buf.zeroize();
            Ok(DecryptSemanticOutcome::AuthenticationFailed)
        }
    }
}

/// Stateless Romulus-M facade using [`lib_q_core::Aead`].
pub struct RomulusMAead;

impl RomulusMAead {
    pub const fn new() -> Self {
        Self
    }

    pub const fn key_size() -> usize {
        16
    }

    pub const fn nonce_size() -> usize {
        16
    }

    pub const fn tag_size() -> usize {
        16
    }

    /// Allocation-free encrypt: takes key/nonce as byte slices, avoiding the `AeadKey`/`Nonce`
    /// `Vec` wrappers required by [`Aead::encrypt`]. The trait method forwards here.
    pub fn encrypt_bytes(
        &self,
        key: &[u8],
        nonce: &[u8],
        plaintext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        let kb = key;
        if kb.len() != Self::key_size() {
            return Err(Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kb.len(),
            });
        }
        let nb = nonce;
        if nb.len() != Self::nonce_size() {
            return Err(Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nb.len(),
            });
        }
        let ad = associated_data.unwrap_or(&[]);
        let nonce_z = stack_secret::zeroizing_copy_16(nb);
        let nonce_arr =
            Array::try_from(nonce_z.as_slice()).map_err(|_| Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nonce_z.len(),
            })?;
        let cipher = {
            let kz = stack_secret::zeroizing_copy_16(kb);
            let key_arr = Array::try_from(kz.as_slice()).map_err(|_| Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kz.len(),
            })?;
            RomulusM::new(&key_arr)
        };
        let mut buf = plaintext.to_vec();
        let tag = cipher
            .encrypt_inout_detached(&nonce_arr, ad, buf.as_mut_slice().into())
            .map_err(|_| Error::EncryptionFailed {
                operation: "Romulus-M encrypt".to_string(),
            })?;
        buf.extend_from_slice(tag.as_slice());
        Ok(buf)
    }

    /// Allocation-free Layer A decrypt: byte-slice counterpart to [`Aead::decrypt`].
    pub fn decrypt_bytes(
        &self,
        key: &[u8],
        nonce: &[u8],
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        let kb = key;
        if kb.len() != Self::key_size() {
            return Err(Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kb.len(),
            });
        }
        let nb = nonce;
        if nb.len() != Self::nonce_size() {
            return Err(Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nb.len(),
            });
        }
        if ciphertext.len() < Self::tag_size() {
            return Err(Error::aead_ciphertext_shorter_than_tag(
                Self::tag_size(),
                ciphertext.len(),
            ));
        }
        let ad = associated_data.unwrap_or(&[]);
        let body_len = ciphertext.len() - Self::tag_size();
        let key_z = stack_secret::zeroizing_copy_16(kb);
        let nonce_z = stack_secret::zeroizing_copy_16(nb);
        let tag_arr =
            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
                Error::VerificationFailed {
                    operation: "AEAD tag verification".to_string(),
                }
            })?;
        let mut buf = ciphertext[..body_len].to_vec();
        crate::romulus_m::romulus_m_decrypt(&key_z, &nonce_z, ad, &mut buf, &tag_arr).map_err(
            |_| Error::VerificationFailed {
                operation: "AEAD tag verification".to_string(),
            },
        )?;
        Ok(buf)
    }
}

impl Default for RomulusMAead {
    fn default() -> Self {
        Self::new()
    }
}

impl Aead for RomulusMAead {
    fn encrypt(
        &self,
        key: &AeadKey,
        nonce: &Nonce,
        plaintext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        self.encrypt_bytes(key.as_bytes(), nonce.as_bytes(), plaintext, associated_data)
    }

    fn decrypt(
        &self,
        key: &AeadKey,
        nonce: &Nonce,
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        self.decrypt_bytes(
            key.as_bytes(),
            nonce.as_bytes(),
            ciphertext,
            associated_data,
        )
    }
}

impl AeadDecryptSemantic for RomulusMAead {
    fn decrypt_semantic(
        &self,
        key: &AeadKey,
        nonce: &Nonce,
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<DecryptSemanticOutcome> {
        let kb = key.as_bytes();
        if kb.len() != Self::key_size() {
            return Err(Error::InvalidKeySize {
                expected: Self::key_size(),
                actual: kb.len(),
            });
        }
        let nb = nonce.as_bytes();
        if nb.len() != Self::nonce_size() {
            return Err(Error::InvalidNonceSize {
                expected: Self::nonce_size(),
                actual: nb.len(),
            });
        }
        if ciphertext.len() < Self::tag_size() {
            return Err(Error::aead_ciphertext_shorter_than_tag(
                Self::tag_size(),
                ciphertext.len(),
            ));
        }
        let ad = associated_data.unwrap_or(&[]);
        let body_len = ciphertext.len() - Self::tag_size();
        let key_z = stack_secret::zeroizing_copy_16(kb);
        let nonce_z = stack_secret::zeroizing_copy_16(nb);
        let tag_arr =
            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
                Error::VerificationFailed {
                    operation: "AEAD tag verification".to_string(),
                }
            })?;
        let mut buf = ciphertext[..body_len].to_vec();
        if crate::romulus_m::romulus_m_decrypt_core(&key_z, &nonce_z, ad, &mut buf, &tag_arr) {
            Ok(DecryptSemanticOutcome::Success(Zeroizing::new(buf)))
        } else {
            buf.zeroize();
            Ok(DecryptSemanticOutcome::AuthenticationFailed)
        }
    }
}