Skip to main content

libcrux_aead/
multiplexed.rs

1use libcrux_secrets::U8;
2use libcrux_traits::aead::{slice::KeyGenError, typed_refs::KeyMut};
3#[cfg(any(feature = "chacha20poly1305", feature = "xchacha20poly1305"))]
4use libcrux_traits::{
5    aead,
6    aead::typed_refs::{DecryptError, EncryptError, Multiplexes},
7};
8
9/// A multiplexed AEAD, allowing algorithm selection at run time.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum Aead {
12    /// The ChaCha20Poly1305 AEAD algorithm.
13    #[cfg(feature = "chacha20poly1305")]
14    ChaCha20Poly1305,
15
16    /// The XChaCha20Poly1305 AEAD algorithm.
17    #[cfg(feature = "xchacha20poly1305")]
18    XChaCha20Poly1305,
19
20    /// The AES-GCM 128 AEAD algorithm.
21    #[cfg(feature = "aesgcm128")]
22    AesGcm128,
23
24    /// The AES-GCM 256 AEAD algorithm.
25    #[cfg(feature = "aesgcm256")]
26    AesGcm256,
27}
28
29/// A reference to a multiplexed AEAD key.
30#[cfg(any(
31    feature = "chacha20poly1305",
32    feature = "xchacha20poly1305",
33    feature = "aesgcm128",
34    feature = "aesgcm256"
35))]
36pub type KeyRef<'a> = aead::typed_refs::KeyRef<'a, Aead>;
37/// A reference to a multiplexed AEAD tag.
38#[cfg(any(
39    feature = "chacha20poly1305",
40    feature = "xchacha20poly1305",
41    feature = "aesgcm128",
42    feature = "aesgcm256"
43))]
44pub type TagRef<'a> = aead::typed_refs::TagRef<'a, Aead>;
45/// A mutable reference to a multiplexed AEAD tag.
46#[cfg(any(
47    feature = "chacha20poly1305",
48    feature = "xchacha20poly1305",
49    feature = "aesgcm128",
50    feature = "aesgcm256"
51))]
52pub type TagMut<'a> = aead::typed_refs::TagMut<'a, Aead>;
53/// A reference to a multiplexed AEAD nonce.
54#[cfg(any(
55    feature = "chacha20poly1305",
56    feature = "xchacha20poly1305",
57    feature = "aesgcm128",
58    feature = "aesgcm256"
59))]
60pub type NonceRef<'a> = aead::typed_refs::NonceRef<'a, Aead>;
61
62#[cfg(feature = "chacha20poly1305")]
63impl Multiplexes<crate::chacha20poly1305::ChaCha20Poly1305> for Aead {
64    fn mux_algo(&self) -> Option<crate::chacha20poly1305::ChaCha20Poly1305> {
65        matches!(self, Self::ChaCha20Poly1305).then_some(crate::chacha20poly1305::ChaCha20Poly1305)
66    }
67    fn wrap_algo(_algo: crate::chacha20poly1305::ChaCha20Poly1305) -> Self {
68        Self::ChaCha20Poly1305
69    }
70}
71
72#[cfg(feature = "xchacha20poly1305")]
73impl Multiplexes<crate::xchacha20poly1305::XChaCha20Poly1305> for Aead {
74    fn mux_algo(&self) -> Option<crate::xchacha20poly1305::XChaCha20Poly1305> {
75        matches!(self, Self::XChaCha20Poly1305)
76            .then_some(crate::xchacha20poly1305::XChaCha20Poly1305)
77    }
78    fn wrap_algo(_algo: crate::xchacha20poly1305::XChaCha20Poly1305) -> Self {
79        Self::XChaCha20Poly1305
80    }
81}
82
83#[cfg(feature = "aesgcm128")]
84impl Multiplexes<crate::aesgcm128::AesGcm128> for Aead {
85    fn mux_algo(&self) -> Option<crate::aesgcm128::AesGcm128> {
86        matches!(self, Self::AesGcm128).then_some(crate::aesgcm128::AesGcm128)
87    }
88    fn wrap_algo(_algo: crate::aesgcm128::AesGcm128) -> Self {
89        Self::AesGcm128
90    }
91}
92
93#[cfg(feature = "aesgcm256")]
94impl Multiplexes<crate::aesgcm256::AesGcm256> for Aead {
95    fn mux_algo(&self) -> Option<crate::aesgcm256::AesGcm256> {
96        matches!(self, Self::AesGcm256).then_some(crate::aesgcm256::AesGcm256)
97    }
98    fn wrap_algo(_algo: crate::aesgcm256::AesGcm256) -> Self {
99        Self::AesGcm256
100    }
101}
102
103#[cfg(any(
104    feature = "chacha20poly1305",
105    feature = "xchacha20poly1305",
106    feature = "aesgcm128",
107    feature = "aesgcm256"
108))]
109impl aead::typed_refs::Aead for Aead {
110    fn key_len(&self) -> usize {
111        match *self {
112            #[cfg(feature = "chacha20poly1305")]
113            Aead::ChaCha20Poly1305 => 32,
114            #[cfg(feature = "xchacha20poly1305")]
115            Aead::XChaCha20Poly1305 => 32,
116            #[cfg(feature = "aesgcm128")]
117            Aead::AesGcm128 => 16,
118            #[cfg(feature = "aesgcm256")]
119            Aead::AesGcm256 => 32,
120        }
121    }
122
123    fn tag_len(&self) -> usize {
124        16
125    }
126
127    fn nonce_len(&self) -> usize {
128        match *self {
129            #[cfg(feature = "chacha20poly1305")]
130            Aead::ChaCha20Poly1305 => 12,
131            #[cfg(feature = "xchacha20poly1305")]
132            Aead::XChaCha20Poly1305 => 24,
133            #[cfg(feature = "aesgcm128")]
134            Aead::AesGcm128 => 12,
135            #[cfg(feature = "aesgcm256")]
136            Aead::AesGcm256 => 12,
137        }
138    }
139
140    fn keygen<'a>(&self, key: KeyMut<'a, Self>, rand: &[U8]) -> Result<(), KeyGenError> {
141        match *self {
142            #[cfg(feature = "chacha20poly1305")]
143            Aead::ChaCha20Poly1305 => {
144                use crate::chacha20poly1305::ChaCha20Poly1305;
145
146                let key = Self::mux_key_mut(key).ok_or(KeyGenError::WrongKeyLength)?;
147                ChaCha20Poly1305.keygen(key, rand)
148            }
149            #[cfg(feature = "xchacha20poly1305")]
150            Aead::XChaCha20Poly1305 => {
151                use crate::xchacha20poly1305::XChaCha20Poly1305;
152
153                let key = Self::mux_key_mut(key).ok_or(KeyGenError::WrongKeyLength)?;
154                XChaCha20Poly1305.keygen(key, rand)
155            }
156            #[cfg(feature = "aesgcm128")]
157            Aead::AesGcm128 => {
158                use crate::aesgcm128::AesGcm128;
159
160                let key = Self::mux_key_mut(key).ok_or(KeyGenError::WrongKeyLength)?;
161
162                AesGcm128.keygen(key, rand)
163            }
164            #[cfg(feature = "aesgcm256")]
165            Aead::AesGcm256 => {
166                use crate::aesgcm256::AesGcm256;
167
168                let key = Self::mux_key_mut(key).ok_or(KeyGenError::WrongKeyLength)?;
169
170                AesGcm256.keygen(key, rand)
171            }
172        }
173    }
174
175    fn encrypt<'a>(
176        &self,
177        ciphertext: &mut [u8],
178        tag: TagMut<'a>,
179        key: KeyRef<'a>,
180        nonce: NonceRef<'a>,
181        aad: &[u8],
182        plaintext: &[U8],
183    ) -> Result<(), aead::typed_refs::EncryptError> {
184        match *self {
185            #[cfg(feature = "chacha20poly1305")]
186            Aead::ChaCha20Poly1305 => {
187                use crate::chacha20poly1305::ChaCha20Poly1305;
188
189                let key = Self::mux_key(key).ok_or(EncryptError::WrongKey)?;
190                let tag = Self::mux_tag_mut(tag).ok_or(EncryptError::WrongTag)?;
191                let nonce = Self::mux_nonce(nonce).ok_or(EncryptError::WrongNonce)?;
192                ChaCha20Poly1305.encrypt(ciphertext, tag, key, nonce, aad, plaintext)
193            }
194            #[cfg(feature = "xchacha20poly1305")]
195            Aead::XChaCha20Poly1305 => {
196                use crate::xchacha20poly1305::XChaCha20Poly1305;
197
198                let key = Self::mux_key(key).ok_or(EncryptError::WrongKey)?;
199                let tag = Self::mux_tag_mut(tag).ok_or(EncryptError::WrongTag)?;
200                let nonce = Self::mux_nonce(nonce).ok_or(EncryptError::WrongNonce)?;
201                XChaCha20Poly1305.encrypt(ciphertext, tag, key, nonce, aad, plaintext)
202            }
203            #[cfg(feature = "aesgcm128")]
204            Aead::AesGcm128 => {
205                use crate::aesgcm128::AesGcm128;
206
207                let key = Self::mux_key(key).ok_or(EncryptError::WrongKey)?;
208                let tag = Self::mux_tag_mut(tag).ok_or(EncryptError::WrongTag)?;
209                let nonce = Self::mux_nonce(nonce).ok_or(EncryptError::WrongNonce)?;
210                AesGcm128.encrypt(ciphertext, tag, key, nonce, aad, plaintext)
211            }
212            #[cfg(feature = "aesgcm256")]
213            Aead::AesGcm256 => {
214                use crate::aesgcm256::AesGcm256;
215
216                let key = Self::mux_key(key).ok_or(EncryptError::WrongKey)?;
217                let tag = Self::mux_tag_mut(tag).ok_or(EncryptError::WrongTag)?;
218                let nonce = Self::mux_nonce(nonce).ok_or(EncryptError::WrongNonce)?;
219                AesGcm256.encrypt(ciphertext, tag, key, nonce, aad, plaintext)
220            }
221        }
222    }
223
224    fn decrypt<'a>(
225        &self,
226        plaintext: &mut [U8],
227        key: KeyRef<'a>,
228        nonce: NonceRef<'a>,
229        aad: &[u8],
230        ciphertext: &[u8],
231        tag: TagRef<'a>,
232    ) -> Result<(), aead::typed_refs::DecryptError> {
233        match *self {
234            #[cfg(feature = "chacha20poly1305")]
235            Aead::ChaCha20Poly1305 => {
236                use crate::chacha20poly1305::ChaCha20Poly1305;
237
238                let key = Self::mux_key(key).ok_or(DecryptError::WrongKey)?;
239                let tag = Self::mux_tag(tag).ok_or(DecryptError::WrongTag)?;
240                let nonce = Self::mux_nonce(nonce).ok_or(DecryptError::WrongNonce)?;
241                ChaCha20Poly1305.decrypt(plaintext, key, nonce, aad, ciphertext, tag)
242            }
243            #[cfg(feature = "xchacha20poly1305")]
244            Aead::XChaCha20Poly1305 => {
245                use crate::xchacha20poly1305::XChaCha20Poly1305;
246
247                let key = Self::mux_key(key).ok_or(DecryptError::WrongKey)?;
248                let tag = Self::mux_tag(tag).ok_or(DecryptError::WrongTag)?;
249                let nonce = Self::mux_nonce(nonce).ok_or(DecryptError::WrongNonce)?;
250                XChaCha20Poly1305.decrypt(plaintext, key, nonce, aad, ciphertext, tag)
251            }
252            #[cfg(feature = "aesgcm128")]
253            Aead::AesGcm128 => {
254                use crate::aesgcm128::AesGcm128;
255
256                let key = Self::mux_key(key).ok_or(DecryptError::WrongKey)?;
257                let tag = Self::mux_tag(tag).ok_or(DecryptError::WrongTag)?;
258                let nonce = Self::mux_nonce(nonce).ok_or(DecryptError::WrongNonce)?;
259                AesGcm128.decrypt(plaintext, key, nonce, aad, ciphertext, tag)
260            }
261            #[cfg(feature = "aesgcm256")]
262            Aead::AesGcm256 => {
263                use crate::aesgcm256::AesGcm256;
264
265                let key = Self::mux_key(key).ok_or(DecryptError::WrongKey)?;
266                let tag = Self::mux_tag(tag).ok_or(DecryptError::WrongTag)?;
267                let nonce = Self::mux_nonce(nonce).ok_or(DecryptError::WrongNonce)?;
268                AesGcm256.decrypt(plaintext, key, nonce, aad, ciphertext, tag)
269            }
270        }
271    }
272}
273
274#[cfg(any(
275    feature = "chacha20poly1305",
276    feature = "xchacha20poly1305",
277    feature = "aesgcm128",
278    feature = "aesgcm256"
279))]
280#[cfg(test)]
281mod tests {
282    use libcrux_traits::aead::typed_refs;
283    use typed_refs::Aead as _;
284
285    use super::Aead;
286
287    #[test]
288    #[cfg(feature = "chacha20poly1305")]
289    fn test_key_centric_multiplexed_chachapoly() {
290        use libcrux_traits::libcrux_secrets::{Classify, ClassifyRef, DeclassifyRef};
291
292        let algo = Aead::ChaCha20Poly1305;
293
294        algo.new_key(&[0.classify(); 33])
295            .expect_err("length should mismatch");
296
297        let mut tag_bytes = [0.classify(); 16];
298        let key_bytes = [0.classify(); 32];
299        let nonce_bytes = [0.classify(); 12];
300
301        let key = algo.new_key(&key_bytes).expect("length should match");
302        let nonce = algo.new_nonce(&nonce_bytes).expect("length should match");
303        let tag = algo
304            .new_tag_mut(&mut tag_bytes)
305            .expect("length should match");
306
307        let pt = b"the quick brown fox jumps over the lazy dog".classify_ref();
308        let mut ct = [0; 43];
309        let mut pt_out = [0.classify(); 43];
310
311        key.encrypt(&mut ct, tag, nonce, b"", pt).unwrap();
312        let tag = algo.new_tag(&tag_bytes).unwrap();
313        key.decrypt(&mut pt_out, nonce, b"", &ct, tag).unwrap();
314        assert_eq!(pt.declassify_ref(), pt_out.declassify_ref());
315    }
316
317    #[test]
318    #[cfg(feature = "xchacha20poly1305")]
319    fn test_key_centric_multiplexed_xchachapoly() {
320        use libcrux_traits::libcrux_secrets::{Classify, ClassifyRef, DeclassifyRef};
321
322        let algo = Aead::XChaCha20Poly1305;
323
324        let wrong_length_key_bytes = [0.classify(); 33];
325        algo.new_key(&wrong_length_key_bytes)
326            .expect_err("length should mismatch");
327
328        let mut tag_bytes = [0.classify(); 16];
329
330        let key_bytes = [0.classify(); 32];
331        let nonce_bytes = [0.classify(); 24];
332
333        let key = algo.new_key(&key_bytes).expect("length should match");
334        let nonce = algo.new_nonce(&nonce_bytes).expect("length should match");
335        let tag = algo
336            .new_tag_mut(&mut tag_bytes)
337            .expect("length should match");
338
339        let pt = b"the quick brown fox jumps over the lazy dog".classify_ref();
340        let mut ct = [0; 43];
341        let mut pt_out = [0.classify(); 43];
342
343        key.encrypt(&mut ct, tag, nonce, b"", pt).unwrap();
344        let tag = algo.new_tag(&tag_bytes).unwrap();
345        key.decrypt(&mut pt_out, nonce, b"", &ct, tag).unwrap();
346        assert_eq!(pt.declassify_ref(), pt_out.declassify_ref());
347    }
348
349    #[test]
350    #[cfg(feature = "aesgcm128")]
351    fn test_key_centric_multiplexed_aesgcm128() {
352        use libcrux_traits::libcrux_secrets::{Classify, ClassifyRef, DeclassifyRef};
353
354        let algo = Aead::AesGcm128;
355
356        algo.new_key(&[0.classify(); 33])
357            .expect_err("length should mismatch");
358
359        let mut tag_bytes = [0.classify(); 16];
360        let key_bytes = [0.classify(); 16];
361        let nonce_bytes = [0.classify(); 12];
362
363        let key = algo.new_key(&key_bytes).expect("length should match");
364        let nonce = algo.new_nonce(&nonce_bytes).expect("length should match");
365        let tag = algo
366            .new_tag_mut(&mut tag_bytes)
367            .expect("length should match");
368
369        let pt = b"the quick brown fox jumps over the lazy dog".classify_ref();
370        let mut ct = [0; 43];
371        let mut pt_out = [0.classify(); 43];
372
373        key.encrypt(&mut ct, tag, nonce, b"", pt).unwrap();
374        let tag = algo.new_tag(&tag_bytes).unwrap();
375        key.decrypt(&mut pt_out, nonce, b"", &ct, tag).unwrap();
376        assert_eq!(pt.declassify_ref(), pt_out.declassify_ref());
377    }
378
379    #[test]
380    #[cfg(feature = "aesgcm256")]
381    fn test_key_centric_multiplexed_aesgcm256() {
382        use libcrux_traits::libcrux_secrets::{Classify, ClassifyRef, DeclassifyRef};
383
384        let algo = Aead::AesGcm256;
385
386        algo.new_key(&[0.classify(); 33])
387            .expect_err("length should mismatch");
388
389        let mut tag_bytes = [0.classify(); 16];
390        let key_bytes = [0.classify(); 32];
391        let nonce_bytes = [0.classify(); 12];
392
393        let key = algo.new_key(&key_bytes).expect("length should match");
394        let nonce = algo.new_nonce(&nonce_bytes).expect("length should match");
395        let tag = algo
396            .new_tag_mut(&mut tag_bytes)
397            .expect("length should match");
398
399        let pt = b"the quick brown fox jumps over the lazy dog".classify_ref();
400        let mut ct = [0; 43];
401        let mut pt_out = [0.classify(); 43];
402
403        key.encrypt(&mut ct, tag, nonce, b"", pt).unwrap();
404        let tag = algo.new_tag(&tag_bytes).unwrap();
405        key.decrypt(&mut pt_out, nonce, b"", &ct, tag).unwrap();
406        assert_eq!(pt.declassify_ref(), pt_out.declassify_ref());
407    }
408}