Skip to main content

lib_q_romulus/
libq_aead.rs

1//! `lib_q_core::Aead` wrappers (allocating API) for registry integration.
2
3#![deny(unsafe_code)]
4
5extern crate alloc;
6
7use alloc::string::ToString;
8use alloc::vec::Vec;
9
10use aead::array::Array;
11use aead::{
12    AeadInOut,
13    KeyInit,
14};
15use lib_q_core::{
16    Aead,
17    AeadDecryptSemantic,
18    AeadKey,
19    DecryptSemanticOutcome,
20    Error,
21    Nonce,
22    Result,
23};
24use zeroize::{
25    Zeroize,
26    Zeroizing,
27};
28
29use crate::{
30    RomulusM,
31    RomulusN,
32    stack_secret,
33};
34
35/// Stateless Romulus-N facade using [`lib_q_core::Aead`].
36pub struct RomulusNAead;
37
38impl RomulusNAead {
39    pub const fn new() -> Self {
40        Self
41    }
42
43    pub const fn key_size() -> usize {
44        16
45    }
46
47    pub const fn nonce_size() -> usize {
48        16
49    }
50
51    pub const fn tag_size() -> usize {
52        16
53    }
54}
55
56impl Default for RomulusNAead {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl Aead for RomulusNAead {
63    fn encrypt(
64        &self,
65        key: &AeadKey,
66        nonce: &Nonce,
67        plaintext: &[u8],
68        associated_data: Option<&[u8]>,
69    ) -> Result<Vec<u8>> {
70        let kb = key.as_bytes();
71        if kb.len() != Self::key_size() {
72            return Err(Error::InvalidKeySize {
73                expected: Self::key_size(),
74                actual: kb.len(),
75            });
76        }
77        let nb = nonce.as_bytes();
78        if nb.len() != Self::nonce_size() {
79            return Err(Error::InvalidNonceSize {
80                expected: Self::nonce_size(),
81                actual: nb.len(),
82            });
83        }
84        let ad = associated_data.unwrap_or(&[]);
85        let nonce_z = stack_secret::zeroizing_copy_16(nb);
86        let nonce_arr =
87            Array::try_from(nonce_z.as_slice()).map_err(|_| Error::InvalidNonceSize {
88                expected: Self::nonce_size(),
89                actual: nonce_z.len(),
90            })?;
91        let cipher = {
92            let kz = stack_secret::zeroizing_copy_16(kb);
93            let key_arr = Array::try_from(kz.as_slice()).map_err(|_| Error::InvalidKeySize {
94                expected: Self::key_size(),
95                actual: kz.len(),
96            })?;
97            RomulusN::new(&key_arr)
98        };
99        let mut buf = plaintext.to_vec();
100        let tag = cipher
101            .encrypt_inout_detached(&nonce_arr, ad, buf.as_mut_slice().into())
102            .map_err(|_| Error::EncryptionFailed {
103                operation: "Romulus-N encrypt".to_string(),
104            })?;
105        buf.extend_from_slice(tag.as_slice());
106        Ok(buf)
107    }
108
109    fn decrypt(
110        &self,
111        key: &AeadKey,
112        nonce: &Nonce,
113        ciphertext: &[u8],
114        associated_data: Option<&[u8]>,
115    ) -> Result<Vec<u8>> {
116        let kb = key.as_bytes();
117        if kb.len() != Self::key_size() {
118            return Err(Error::InvalidKeySize {
119                expected: Self::key_size(),
120                actual: kb.len(),
121            });
122        }
123        let nb = nonce.as_bytes();
124        if nb.len() != Self::nonce_size() {
125            return Err(Error::InvalidNonceSize {
126                expected: Self::nonce_size(),
127                actual: nb.len(),
128            });
129        }
130        if ciphertext.len() < Self::tag_size() {
131            return Err(Error::aead_ciphertext_shorter_than_tag(
132                Self::tag_size(),
133                ciphertext.len(),
134            ));
135        }
136        let ad = associated_data.unwrap_or(&[]);
137        let body_len = ciphertext.len() - Self::tag_size();
138        let key_z = stack_secret::zeroizing_copy_16(kb);
139        let nonce_z = stack_secret::zeroizing_copy_16(nb);
140        let tag_arr =
141            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
142                Error::VerificationFailed {
143                    operation: "AEAD tag verification".to_string(),
144                }
145            })?;
146        let mut buf = ciphertext[..body_len].to_vec();
147        crate::romulus_n::romulus_n_decrypt(&key_z, &nonce_z, ad, &mut buf, &tag_arr).map_err(
148            |_| Error::VerificationFailed {
149                operation: "AEAD tag verification".to_string(),
150            },
151        )?;
152        Ok(buf)
153    }
154}
155
156impl AeadDecryptSemantic for RomulusNAead {
157    fn decrypt_semantic(
158        &self,
159        key: &AeadKey,
160        nonce: &Nonce,
161        ciphertext: &[u8],
162        associated_data: Option<&[u8]>,
163    ) -> Result<DecryptSemanticOutcome> {
164        let kb = key.as_bytes();
165        if kb.len() != Self::key_size() {
166            return Err(Error::InvalidKeySize {
167                expected: Self::key_size(),
168                actual: kb.len(),
169            });
170        }
171        let nb = nonce.as_bytes();
172        if nb.len() != Self::nonce_size() {
173            return Err(Error::InvalidNonceSize {
174                expected: Self::nonce_size(),
175                actual: nb.len(),
176            });
177        }
178        if ciphertext.len() < Self::tag_size() {
179            return Err(Error::aead_ciphertext_shorter_than_tag(
180                Self::tag_size(),
181                ciphertext.len(),
182            ));
183        }
184        let ad = associated_data.unwrap_or(&[]);
185        let body_len = ciphertext.len() - Self::tag_size();
186        let key_z = stack_secret::zeroizing_copy_16(kb);
187        let nonce_z = stack_secret::zeroizing_copy_16(nb);
188        let tag_arr =
189            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
190                Error::VerificationFailed {
191                    operation: "AEAD tag verification".to_string(),
192                }
193            })?;
194        let mut buf = ciphertext[..body_len].to_vec();
195        if crate::romulus_n::romulus_n_decrypt_core(&key_z, &nonce_z, ad, &mut buf, &tag_arr) {
196            Ok(DecryptSemanticOutcome::Success(Zeroizing::new(buf)))
197        } else {
198            buf.zeroize();
199            Ok(DecryptSemanticOutcome::AuthenticationFailed)
200        }
201    }
202}
203
204/// Stateless Romulus-M facade using [`lib_q_core::Aead`].
205pub struct RomulusMAead;
206
207impl RomulusMAead {
208    pub const fn new() -> Self {
209        Self
210    }
211
212    pub const fn key_size() -> usize {
213        16
214    }
215
216    pub const fn nonce_size() -> usize {
217        16
218    }
219
220    pub const fn tag_size() -> usize {
221        16
222    }
223
224    /// Allocation-free encrypt: takes key/nonce as byte slices, avoiding the `AeadKey`/`Nonce`
225    /// `Vec` wrappers required by [`Aead::encrypt`]. The trait method forwards here.
226    pub fn encrypt_bytes(
227        &self,
228        key: &[u8],
229        nonce: &[u8],
230        plaintext: &[u8],
231        associated_data: Option<&[u8]>,
232    ) -> Result<Vec<u8>> {
233        let kb = key;
234        if kb.len() != Self::key_size() {
235            return Err(Error::InvalidKeySize {
236                expected: Self::key_size(),
237                actual: kb.len(),
238            });
239        }
240        let nb = nonce;
241        if nb.len() != Self::nonce_size() {
242            return Err(Error::InvalidNonceSize {
243                expected: Self::nonce_size(),
244                actual: nb.len(),
245            });
246        }
247        let ad = associated_data.unwrap_or(&[]);
248        let nonce_z = stack_secret::zeroizing_copy_16(nb);
249        let nonce_arr =
250            Array::try_from(nonce_z.as_slice()).map_err(|_| Error::InvalidNonceSize {
251                expected: Self::nonce_size(),
252                actual: nonce_z.len(),
253            })?;
254        let cipher = {
255            let kz = stack_secret::zeroizing_copy_16(kb);
256            let key_arr = Array::try_from(kz.as_slice()).map_err(|_| Error::InvalidKeySize {
257                expected: Self::key_size(),
258                actual: kz.len(),
259            })?;
260            RomulusM::new(&key_arr)
261        };
262        let mut buf = plaintext.to_vec();
263        let tag = cipher
264            .encrypt_inout_detached(&nonce_arr, ad, buf.as_mut_slice().into())
265            .map_err(|_| Error::EncryptionFailed {
266                operation: "Romulus-M encrypt".to_string(),
267            })?;
268        buf.extend_from_slice(tag.as_slice());
269        Ok(buf)
270    }
271
272    /// Allocation-free Layer A decrypt: byte-slice counterpart to [`Aead::decrypt`].
273    pub fn decrypt_bytes(
274        &self,
275        key: &[u8],
276        nonce: &[u8],
277        ciphertext: &[u8],
278        associated_data: Option<&[u8]>,
279    ) -> Result<Vec<u8>> {
280        let kb = key;
281        if kb.len() != Self::key_size() {
282            return Err(Error::InvalidKeySize {
283                expected: Self::key_size(),
284                actual: kb.len(),
285            });
286        }
287        let nb = nonce;
288        if nb.len() != Self::nonce_size() {
289            return Err(Error::InvalidNonceSize {
290                expected: Self::nonce_size(),
291                actual: nb.len(),
292            });
293        }
294        if ciphertext.len() < Self::tag_size() {
295            return Err(Error::aead_ciphertext_shorter_than_tag(
296                Self::tag_size(),
297                ciphertext.len(),
298            ));
299        }
300        let ad = associated_data.unwrap_or(&[]);
301        let body_len = ciphertext.len() - Self::tag_size();
302        let key_z = stack_secret::zeroizing_copy_16(kb);
303        let nonce_z = stack_secret::zeroizing_copy_16(nb);
304        let tag_arr =
305            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
306                Error::VerificationFailed {
307                    operation: "AEAD tag verification".to_string(),
308                }
309            })?;
310        let mut buf = ciphertext[..body_len].to_vec();
311        crate::romulus_m::romulus_m_decrypt(&key_z, &nonce_z, ad, &mut buf, &tag_arr).map_err(
312            |_| Error::VerificationFailed {
313                operation: "AEAD tag verification".to_string(),
314            },
315        )?;
316        Ok(buf)
317    }
318}
319
320impl Default for RomulusMAead {
321    fn default() -> Self {
322        Self::new()
323    }
324}
325
326impl Aead for RomulusMAead {
327    fn encrypt(
328        &self,
329        key: &AeadKey,
330        nonce: &Nonce,
331        plaintext: &[u8],
332        associated_data: Option<&[u8]>,
333    ) -> Result<Vec<u8>> {
334        self.encrypt_bytes(key.as_bytes(), nonce.as_bytes(), plaintext, associated_data)
335    }
336
337    fn decrypt(
338        &self,
339        key: &AeadKey,
340        nonce: &Nonce,
341        ciphertext: &[u8],
342        associated_data: Option<&[u8]>,
343    ) -> Result<Vec<u8>> {
344        self.decrypt_bytes(
345            key.as_bytes(),
346            nonce.as_bytes(),
347            ciphertext,
348            associated_data,
349        )
350    }
351}
352
353impl AeadDecryptSemantic for RomulusMAead {
354    fn decrypt_semantic(
355        &self,
356        key: &AeadKey,
357        nonce: &Nonce,
358        ciphertext: &[u8],
359        associated_data: Option<&[u8]>,
360    ) -> Result<DecryptSemanticOutcome> {
361        let kb = key.as_bytes();
362        if kb.len() != Self::key_size() {
363            return Err(Error::InvalidKeySize {
364                expected: Self::key_size(),
365                actual: kb.len(),
366            });
367        }
368        let nb = nonce.as_bytes();
369        if nb.len() != Self::nonce_size() {
370            return Err(Error::InvalidNonceSize {
371                expected: Self::nonce_size(),
372                actual: nb.len(),
373            });
374        }
375        if ciphertext.len() < Self::tag_size() {
376            return Err(Error::aead_ciphertext_shorter_than_tag(
377                Self::tag_size(),
378                ciphertext.len(),
379            ));
380        }
381        let ad = associated_data.unwrap_or(&[]);
382        let body_len = ciphertext.len() - Self::tag_size();
383        let key_z = stack_secret::zeroizing_copy_16(kb);
384        let nonce_z = stack_secret::zeroizing_copy_16(nb);
385        let tag_arr =
386            <[u8; stack_secret::LEN]>::try_from(&ciphertext[body_len..]).map_err(|_| {
387                Error::VerificationFailed {
388                    operation: "AEAD tag verification".to_string(),
389                }
390            })?;
391        let mut buf = ciphertext[..body_len].to_vec();
392        if crate::romulus_m::romulus_m_decrypt_core(&key_z, &nonce_z, ad, &mut buf, &tag_arr) {
393            Ok(DecryptSemanticOutcome::Success(Zeroizing::new(buf)))
394        } else {
395            buf.zeroize();
396            Ok(DecryptSemanticOutcome::AuthenticationFailed)
397        }
398    }
399}