Skip to main content

lib_q_romulus/
romulus_m.rs

1//! Romulus-M: nonce-misuse-resistant AEAD (Romulus v1.3).
2
3#![deny(unsafe_code)]
4
5use aead::consts::U16;
6use aead::inout::InOutBuf;
7use aead::{
8    AeadCore,
9    AeadInOut,
10    Error,
11    Key,
12    KeyInit,
13    KeySizeUser,
14    Nonce,
15    Tag,
16    TagPosition,
17};
18use subtle::ConstantTimeEq;
19use zeroize::Zeroize;
20
21use crate::backend::{
22    AD_BLK_EVN,
23    AD_BLK_ODD,
24    MSG_BLK,
25    ad_encryption,
26    ad2msg_encryption,
27    g8a,
28    lfsr_gf56,
29    msg_decryption_m_inplace,
30    msg_encryption_m_inplace,
31    nonce_encryption,
32    reset_lfsr_gf56,
33    rho,
34    romulus_m_compute_w,
35};
36
37/// Romulus-M AEAD with 128-bit key, 128-bit nonce, 128-bit tag.
38#[derive(Clone)]
39pub struct RomulusM {
40    key: Key<Self>,
41}
42
43impl Drop for RomulusM {
44    fn drop(&mut self) {
45        self.key.as_mut_slice().zeroize();
46    }
47}
48
49impl KeySizeUser for RomulusM {
50    type KeySize = U16;
51}
52
53impl KeyInit for RomulusM {
54    fn new(key: &Key<Self>) -> Self {
55        Self { key: *key }
56    }
57}
58
59impl AeadCore for RomulusM {
60    type NonceSize = U16;
61    type TagSize = U16;
62    const TAG_POSITION: TagPosition = TagPosition::Postfix;
63}
64
65impl AeadInOut for RomulusM {
66    fn encrypt_inout_detached(
67        &self,
68        nonce: &Nonce<Self>,
69        associated_data: &[u8],
70        buffer: InOutBuf<'_, '_, u8>,
71    ) -> Result<Tag<Self>, Error> {
72        let k = crate::stack_secret::zeroizing_copy_16(self.key.as_slice());
73        let n = crate::stack_secret::zeroizing_copy_16(nonce.as_slice());
74        // Romulus is an in-place AEAD: aead's higher-level paths always hand us an aliased
75        // (in == out) buffer, so the output slice already holds the plaintext.
76        let buffer = buffer.into_out();
77        let tag = romulus_m_encrypt(&k, &n, associated_data, buffer)?;
78        Ok(Tag::<Self>::from(tag))
79    }
80
81    fn decrypt_inout_detached(
82        &self,
83        nonce: &Nonce<Self>,
84        associated_data: &[u8],
85        buffer: InOutBuf<'_, '_, u8>,
86        tag: &Tag<Self>,
87    ) -> Result<(), Error> {
88        let k = crate::stack_secret::zeroizing_copy_16(self.key.as_slice());
89        let n = crate::stack_secret::zeroizing_copy_16(nonce.as_slice());
90        let tg = crate::stack_secret::zeroizing_copy_16(tag.as_slice());
91        let buffer = buffer.into_out();
92        romulus_m_decrypt(&k, &n, associated_data, buffer, &tg)
93    }
94}
95
96/// Encrypt plaintext in `buf` in place to ciphertext; return tag.
97pub(crate) fn romulus_m_encrypt(
98    key: &[u8; 16],
99    nonce: &[u8; 16],
100    ad: &[u8],
101    buf: &mut [u8],
102) -> Result<[u8; 16], Error> {
103    let mut s = [0u8; 16];
104    let mut cnt = [0u8; 7];
105    reset_lfsr_gf56(&mut cnt);
106    let n_ad = AD_BLK_ODD;
107    let t_ad = AD_BLK_EVN;
108    let mlen_u = buf.len() as u64;
109    let xlen_init = mlen_u;
110    let mut ad_off = 0usize;
111    let mut adlen = ad.len() as u64;
112
113    let w = romulus_m_compute_w(adlen, xlen_init, n_ad, t_ad);
114
115    if adlen == 0 {
116        lfsr_gf56(&mut cnt);
117    } else {
118        while adlen > 0 {
119            adlen = ad_encryption(
120                ad,
121                &mut ad_off,
122                &mut s,
123                key,
124                adlen,
125                &mut cnt,
126                40,
127                n_ad,
128                t_ad,
129            );
130        }
131    }
132
133    let mut mac_off = 0usize;
134    let mut xlen = mlen_u;
135
136    if w & 8 == 0 {
137        xlen = ad2msg_encryption(buf, &mut mac_off, &mut cnt, &mut s, key, t_ad, 44, xlen);
138    } else if mlen_u == 0 {
139        lfsr_gf56(&mut cnt);
140    }
141
142    while xlen > 0 {
143        xlen = ad_encryption(
144            buf,
145            &mut mac_off,
146            &mut s,
147            key,
148            xlen,
149            &mut cnt,
150            44,
151            n_ad,
152            t_ad,
153        );
154    }
155
156    nonce_encryption(nonce, &mut cnt, &mut s, key, t_ad, w);
157
158    let mut tag = [0u8; 16];
159    g8a(&s, &mut tag);
160
161    reset_lfsr_gf56(&mut cnt);
162    s.copy_from_slice(&tag);
163
164    let msg_n = MSG_BLK;
165    let mut enc_off = 0usize;
166
167    if mlen_u > 0 {
168        nonce_encryption(nonce, &mut cnt, &mut s, key, t_ad, 36);
169        let mut rem = mlen_u;
170        while rem > msg_n as u64 {
171            rem = msg_encryption_m_inplace(
172                buf,
173                &mut enc_off,
174                nonce,
175                &mut cnt,
176                &mut s,
177                key,
178                msg_n,
179                t_ad,
180                36,
181                rem,
182            );
183        }
184        let r = rem as usize;
185        let mut last = [0u8; 16];
186        last[..r].copy_from_slice(&buf[enc_off..enc_off + r]);
187        let mut ctmp = [0u8; 16];
188        rho(&last[..r], &mut ctmp, &mut s, r, 16);
189        buf[enc_off..enc_off + r].copy_from_slice(&ctmp[..r]);
190    }
191
192    Ok(tag)
193}
194
195/// Decrypt ciphertext in `buffer` in place; verify `tag`.
196///
197/// On failure, `buffer` is zeroized. For Layer B semantic mapping, see [`romulus_m_decrypt_core`].
198pub(crate) fn romulus_m_decrypt(
199    key: &[u8; 16],
200    nonce: &[u8; 16],
201    ad: &[u8],
202    ct: &mut [u8],
203    tag: &[u8; 16],
204) -> Result<(), Error> {
205    let ok = romulus_m_decrypt_core(key, nonce, ad, ct, tag);
206    if ok {
207        Ok(())
208    } else {
209        ct.zeroize();
210        Err(Error)
211    }
212}
213
214/// In-place Romulus-M decrypt; returns whether `tag` matches after the decrypt schedule.
215pub(crate) fn romulus_m_decrypt_core(
216    key: &[u8; 16],
217    nonce: &[u8; 16],
218    ad: &[u8],
219    ct: &mut [u8],
220    tag: &[u8; 16],
221) -> bool {
222    let body_len = ct.len();
223    let xlen = body_len as u64;
224
225    let mut s = [0u8; 16];
226    let mut cnt = [0u8; 7];
227    reset_lfsr_gf56(&mut cnt);
228    let n_ad = AD_BLK_ODD;
229    let t_ad = AD_BLK_EVN;
230
231    s.copy_from_slice(tag);
232
233    let msg_n = MSG_BLK;
234    let mut clen = body_len as u64;
235    let mut off = 0usize;
236
237    if clen > 0 {
238        nonce_encryption(nonce, &mut cnt, &mut s, key, t_ad, 36);
239        while clen > msg_n as u64 {
240            clen = msg_decryption_m_inplace(
241                ct, &mut off, nonce, &mut cnt, &mut s, key, msg_n, t_ad, 36, clen,
242            );
243        }
244        let r = clen as usize;
245        let mut tmp = [0u8; 16];
246        tmp[..r].copy_from_slice(&ct[off..off + r]);
247        let mut ptmp = [0u8; 16];
248        crate::backend::irho(&mut ptmp, &tmp[..r], &mut s, r, 16);
249        ct[off..off + r].copy_from_slice(&ptmp[..r]);
250    }
251
252    s.fill(0);
253    reset_lfsr_gf56(&mut cnt);
254
255    let mut ad_off = 0usize;
256    let mut adlen = ad.len() as u64;
257    let w = romulus_m_compute_w(adlen, xlen, n_ad, t_ad);
258
259    if adlen == 0 {
260        lfsr_gf56(&mut cnt);
261    } else {
262        while adlen > 0 {
263            adlen = ad_encryption(
264                ad,
265                &mut ad_off,
266                &mut s,
267                key,
268                adlen,
269                &mut cnt,
270                40,
271                n_ad,
272                t_ad,
273            );
274        }
275    }
276
277    let mut mac_off = 0usize;
278    let mut xrem = xlen;
279
280    if w & 8 == 0 {
281        xrem = ad2msg_encryption(ct, &mut mac_off, &mut cnt, &mut s, key, t_ad, 44, xrem);
282    } else if body_len == 0 {
283        lfsr_gf56(&mut cnt);
284    }
285
286    while xrem > 0 {
287        xrem = ad_encryption(
288            ct,
289            &mut mac_off,
290            &mut s,
291            key,
292            xrem,
293            &mut cnt,
294            44,
295            n_ad,
296            t_ad,
297        );
298    }
299
300    nonce_encryption(nonce, &mut cnt, &mut s, key, t_ad, w);
301
302    let mut calc = [0u8; 16];
303    g8a(&s, &mut calc);
304    bool::from(calc.ct_eq(tag))
305}