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