Skip to main content

dryoc/classic/
crypto_aead_chacha20poly1305_ietf.rs

1//! # ChaCha20-Poly1305-IETF authenticated encryption
2//!
3//! Implements libsodium's `crypto_aead_chacha20poly1305_ietf_*` functions.
4//! This construction authenticates optional additional data, appends the
5//! authentication tag in combined mode, and uses 96-bit public nonces as
6//! specified by RFC 8439. This is not the legacy 64-bit-nonce construction.
7//!
8//! ## Classic API example
9//!
10//! ```
11//! use dryoc::classic::crypto_aead_chacha20poly1305_ietf::*;
12//! use dryoc::constants::{
13//!     CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES, CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES,
14//! };
15//! use dryoc::types::*;
16//!
17//! let key = crypto_aead_chacha20poly1305_ietf_keygen();
18//! // This 96-bit nonce must be unique for every message encrypted with `key`.
19//! let nonce = [0u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES];
20//! let message =
21//!     b"Our doubts are traitors, and make us lose the good we oft might win, by fearing to attempt.";
22//! let aad = b"metadata";
23//!
24//! let mut ciphertext = vec![0u8; message.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
25//! crypto_aead_chacha20poly1305_ietf_encrypt(&mut ciphertext, message, Some(aad), &nonce, &key)
26//!     .expect("encrypt failed");
27//!
28//! let mut decrypted = vec![0u8; message.len()];
29//! crypto_aead_chacha20poly1305_ietf_decrypt(&mut decrypted, &ciphertext, Some(aad), &nonce, &key)
30//!     .expect("decrypt failed");
31//!
32//! assert_eq!(message, decrypted.as_slice());
33//! ```
34
35use chacha20::cipher::array::Array;
36use chacha20::cipher::consts::U64;
37use chacha20::cipher::{Block, KeyIvInit, StreamCipherCore};
38use chacha20::variants::Ietf;
39use chacha20::{ChaChaCore, R20};
40use subtle::ConstantTimeEq;
41use zeroize::Zeroize;
42
43use crate::constants::{
44    CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES, CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES,
45    CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
46    CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES,
47};
48use crate::error::Error;
49use crate::poly1305::{Key as Poly1305Key, Poly1305};
50use crate::rng::copy_randombytes;
51use crate::types::*;
52use crate::utils::pad16;
53
54/// Authentication tag for ChaCha20-Poly1305-IETF AEAD.
55pub type Mac = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
56/// Public nonce for ChaCha20-Poly1305-IETF AEAD.
57pub type Nonce = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES];
58/// Secret key for ChaCha20-Poly1305-IETF AEAD.
59pub type Key = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES];
60
61const PAD0: [u8; 16] = [0u8; 16];
62
63/// In-place variant of [`crypto_aead_chacha20poly1305_ietf_keygen`].
64pub fn crypto_aead_chacha20poly1305_ietf_keygen_inplace(key: &mut Key) {
65    copy_randombytes(key)
66}
67
68/// Generates a random key using [`copy_randombytes`].
69pub fn crypto_aead_chacha20poly1305_ietf_keygen() -> Key {
70    Key::generate()
71}
72
73fn validate_message_len(message_len: usize) -> Result<(), Error> {
74    if message_len > CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX {
75        Err(length_error!(
76            crate::ErrorContext::Message,
77            message_len,
78            max CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
79        ))
80    } else {
81        Ok(())
82    }
83}
84
85fn validate_output_len(
86    output_len: usize,
87    expected_len: usize,
88    context: crate::ErrorContext,
89) -> Result<(), Error> {
90    if output_len != expected_len {
91        Err(length_error!(context, output_len, exact expected_len))
92    } else {
93        Ok(())
94    }
95}
96
97fn message_len_from_combined_len(
98    combined_len: usize,
99    context: crate::ErrorContext,
100) -> Result<usize, Error> {
101    if combined_len < CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES {
102        Err(length_error!(context, combined_len, min CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES))
103    } else {
104        let message_len = combined_len - CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES;
105        validate_message_len(message_len)?;
106        Ok(message_len)
107    }
108}
109
110type ChaCha20IetfCore = ChaChaCore<R20, Ietf>;
111
112fn apply_chacha20_ietf_keystream(data: &mut [u8], counter: u32, nonce: &Nonce, key: &Key) {
113    let available_blocks = u64::from(u32::MAX) - u64::from(counter) + 1;
114    debug_assert!((data.len() as u64) <= available_blocks * 64);
115
116    let mut cipher = ChaCha20IetfCore::new(key.into(), nonce.into());
117    cipher.set_block_pos(counter);
118
119    // The core instance is local and discarded after this call, so allowing
120    // the final counter block to wrap the internal position cannot cause
121    // keystream reuse. The slice-based wrapper intentionally rejects that
122    // block because it supports subsequent calls on the same instance.
123    let (blocks, tail) = Array::<u8, U64>::slice_as_chunks_mut(data);
124    cipher.apply_keystream_blocks(blocks);
125    if !tail.is_empty() {
126        let mut block = Block::<ChaCha20IetfCore>::default();
127        cipher.write_keystream_block(&mut block);
128        for (byte, keystream_byte) in tail.iter_mut().zip(block.iter()) {
129            *byte ^= keystream_byte;
130        }
131        block.zeroize();
132    }
133}
134
135fn poly1305_key(nonce: &Nonce, key: &Key) -> Poly1305Key {
136    let mut mac_key = Poly1305Key::new();
137    apply_chacha20_ietf_keystream(&mut mac_key, 0, nonce, key);
138    mac_key
139}
140
141fn compute_mac(mac: &mut Mac, mac_key: &mut Poly1305Key, ciphertext: &[u8], ad: &[u8]) {
142    let mut state = Poly1305::new(mac_key);
143    mac_key.zeroize();
144
145    state.update(ad);
146    state.update(&PAD0[..pad16(ad.len())]);
147    state.update(ciphertext);
148    state.update(&PAD0[..pad16(ciphertext.len())]);
149    state.update(&(ad.len() as u64).to_le_bytes());
150    state.update(&(ciphertext.len() as u64).to_le_bytes());
151    state.finalize(mac);
152}
153
154fn compute_mac_to_array(mac_key: &mut Poly1305Key, ciphertext: &[u8], ad: &[u8]) -> Mac {
155    let mut mac = Mac::default();
156    compute_mac(&mut mac, mac_key, ciphertext, ad);
157    mac
158}
159
160fn verify_mac(mac: &Mac, computed_mac: &Mac) -> Result<(), Error> {
161    if mac.ct_eq(computed_mac).unwrap_u8() == 1 {
162        Ok(())
163    } else {
164        Err(Error::AuthenticationFailed)
165    }
166}
167
168/// Detached version of [`crypto_aead_chacha20poly1305_ietf_encrypt`].
169///
170/// Compatible with libsodium's
171/// `crypto_aead_chacha20poly1305_ietf_encrypt_detached`.
172///
173/// # Errors
174///
175/// Returns an error if `message` exceeds the maximum supported length or
176/// `ciphertext.len()` does not equal `message.len()`.
177pub fn crypto_aead_chacha20poly1305_ietf_encrypt_detached(
178    ciphertext: &mut [u8],
179    mac: &mut Mac,
180    message: &[u8],
181    associated_data: Option<&[u8]>,
182    nonce: &Nonce,
183    key: &Key,
184) -> Result<(), Error> {
185    validate_message_len(message.len())?;
186    validate_output_len(
187        ciphertext.len(),
188        message.len(),
189        crate::ErrorContext::Ciphertext,
190    )?;
191
192    let associated_data = associated_data.unwrap_or(&[]);
193    let mut mac_key = poly1305_key(nonce, key);
194
195    ciphertext.copy_from_slice(message);
196    apply_chacha20_ietf_keystream(ciphertext, 1, nonce, key);
197
198    compute_mac(mac, &mut mac_key, ciphertext, associated_data);
199    Ok(())
200}
201
202/// In-place detached variant of
203/// [`crypto_aead_chacha20poly1305_ietf_encrypt_detached`].
204///
205/// # Errors
206///
207/// Returns an error if `data` exceeds the maximum supported message length.
208pub fn crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace(
209    data: &mut [u8],
210    mac: &mut Mac,
211    associated_data: Option<&[u8]>,
212    nonce: &Nonce,
213    key: &Key,
214) -> Result<(), Error> {
215    validate_message_len(data.len())?;
216
217    let associated_data = associated_data.unwrap_or(&[]);
218    let mut mac_key = poly1305_key(nonce, key);
219
220    apply_chacha20_ietf_keystream(data, 1, nonce, key);
221
222    compute_mac(mac, &mut mac_key, data, associated_data);
223    Ok(())
224}
225
226/// Detached version of [`crypto_aead_chacha20poly1305_ietf_decrypt`].
227///
228/// Compatible with libsodium's
229/// `crypto_aead_chacha20poly1305_ietf_decrypt_detached`.
230///
231/// # Errors
232///
233/// Returns an error if `ciphertext` is too long, `message.len()` does not equal
234/// `ciphertext.len()`, or authentication fails.
235pub fn crypto_aead_chacha20poly1305_ietf_decrypt_detached(
236    message: &mut [u8],
237    ciphertext: &[u8],
238    mac: &Mac,
239    associated_data: Option<&[u8]>,
240    nonce: &Nonce,
241    key: &Key,
242) -> Result<(), Error> {
243    validate_message_len(ciphertext.len())?;
244    validate_output_len(
245        message.len(),
246        ciphertext.len(),
247        crate::ErrorContext::Message,
248    )?;
249
250    let associated_data = associated_data.unwrap_or(&[]);
251    let mut mac_key = poly1305_key(nonce, key);
252    let computed_mac = compute_mac_to_array(&mut mac_key, ciphertext, associated_data);
253
254    verify_mac(mac, &computed_mac)?;
255    message.copy_from_slice(ciphertext);
256    apply_chacha20_ietf_keystream(message, 1, nonce, key);
257    Ok(())
258}
259
260/// In-place detached variant of
261/// [`crypto_aead_chacha20poly1305_ietf_decrypt_detached`].
262///
263/// # Errors
264///
265/// Returns an error if `data` exceeds the maximum supported message length or
266/// authentication fails.
267pub fn crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace(
268    data: &mut [u8],
269    mac: &Mac,
270    associated_data: Option<&[u8]>,
271    nonce: &Nonce,
272    key: &Key,
273) -> Result<(), Error> {
274    validate_message_len(data.len())?;
275
276    let associated_data = associated_data.unwrap_or(&[]);
277    let mut mac_key = poly1305_key(nonce, key);
278    let computed_mac = compute_mac_to_array(&mut mac_key, data, associated_data);
279
280    verify_mac(mac, &computed_mac)?;
281    apply_chacha20_ietf_keystream(data, 1, nonce, key);
282    Ok(())
283}
284
285/// Encrypts `message` with `nonce`, `key`, and optional associated data.
286///
287/// Compatible with libsodium's `crypto_aead_chacha20poly1305_ietf_encrypt`.
288///
289/// # Errors
290///
291/// Returns an error if `message` exceeds the maximum supported length or
292/// `ciphertext` is not exactly one authentication tag longer than `message`.
293pub fn crypto_aead_chacha20poly1305_ietf_encrypt(
294    ciphertext: &mut [u8],
295    message: &[u8],
296    associated_data: Option<&[u8]>,
297    nonce: &Nonce,
298    key: &Key,
299) -> Result<(), Error> {
300    validate_message_len(message.len())?;
301    validate_output_len(
302        ciphertext.len(),
303        message.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES,
304        crate::ErrorContext::Ciphertext,
305    )?;
306
307    let (ciphertext, mac) = ciphertext.split_at_mut(message.len());
308    let mac = MutByteArray::as_mut_array(mac);
309    crypto_aead_chacha20poly1305_ietf_encrypt_detached(
310        ciphertext,
311        mac,
312        message,
313        associated_data,
314        nonce,
315        key,
316    )
317}
318
319/// Decrypts `ciphertext` with `nonce`, `key`, and optional associated data.
320///
321/// Compatible with libsodium's `crypto_aead_chacha20poly1305_ietf_decrypt`.
322///
323/// # Errors
324///
325/// Returns an error if `ciphertext` is shorter than an authentication tag,
326/// `message` has the wrong length, or authentication fails.
327pub fn crypto_aead_chacha20poly1305_ietf_decrypt(
328    message: &mut [u8],
329    ciphertext: &[u8],
330    associated_data: Option<&[u8]>,
331    nonce: &Nonce,
332    key: &Key,
333) -> Result<(), Error> {
334    let message_len =
335        message_len_from_combined_len(ciphertext.len(), crate::ErrorContext::Ciphertext)?;
336    validate_output_len(message.len(), message_len, crate::ErrorContext::Message)?;
337
338    let (ciphertext, mac) = ciphertext.split_at(message_len);
339    let mac = ByteArray::as_array(mac);
340    crypto_aead_chacha20poly1305_ietf_decrypt_detached(
341        message,
342        ciphertext,
343        mac,
344        associated_data,
345        nonce,
346        key,
347    )
348}
349
350/// Encrypts `data` in place and appends the authentication tag.
351///
352/// The last [`CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES`] bytes are reserved
353/// for the tag and are ignored as plaintext input.
354///
355/// # Errors
356///
357/// Returns an error if `data` is shorter than an authentication tag or its
358/// plaintext portion exceeds the maximum supported message length.
359pub fn crypto_aead_chacha20poly1305_ietf_encrypt_inplace(
360    data: &mut [u8],
361    associated_data: Option<&[u8]>,
362    nonce: &Nonce,
363    key: &Key,
364) -> Result<(), Error> {
365    let message_len = message_len_from_combined_len(data.len(), crate::ErrorContext::Data)?;
366    let (data, mac) = data.split_at_mut(message_len);
367    let mac = MutByteArray::as_mut_array(mac);
368    crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace(
369        data,
370        mac,
371        associated_data,
372        nonce,
373        key,
374    )
375}
376
377/// Decrypts `data` in place after verifying the appended authentication tag.
378///
379/// After success, the first `data.len() -
380/// CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES` bytes contain the plaintext.
381///
382/// # Errors
383///
384/// Returns an error if `data` is shorter than an authentication tag or
385/// authentication fails.
386pub fn crypto_aead_chacha20poly1305_ietf_decrypt_inplace(
387    data: &mut [u8],
388    associated_data: Option<&[u8]>,
389    nonce: &Nonce,
390    key: &Key,
391) -> Result<(), Error> {
392    let message_len = message_len_from_combined_len(data.len(), crate::ErrorContext::Data)?;
393    let (data, mac) = data.split_at_mut(message_len);
394    let mac = ByteArray::as_array(mac);
395    crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace(
396        data,
397        mac,
398        associated_data,
399        nonce,
400        key,
401    )
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    const MESSAGE: &[u8] =
409        b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
410    const AD: &[u8] = &[
411        0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
412    ];
413    const KEY: Key = [
414        0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
415        0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
416        0x9e, 0x9f,
417    ];
418    const NONCE: Nonce = [
419        0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
420    ];
421
422    fn expected() -> Vec<u8> {
423        hex::decode(concat!(
424            "d31a8d34648e60db7b86afbc53ef7ec2",
425            "a4aded51296e08fea9e2b5a736ee62d6",
426            "3dbea45e8ca9671282fafb69da92728b",
427            "1a71de0a9e060b2905d6a5b67ecd3b36",
428            "92ddbd7f2d778b8c9803aee328091b58",
429            "fab324e4fad675945585808b4831d7bc",
430            "3ff4def08e4b7a9de576d26586cec64b",
431            "61161ae10b594f09e26a7e902ecbd0600691"
432        ))
433        .expect("valid test vector")
434    }
435
436    #[test]
437    fn test_rfc_8439_known_answer() {
438        let mut ciphertext = vec![0u8; MESSAGE.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
439        crypto_aead_chacha20poly1305_ietf_encrypt(&mut ciphertext, MESSAGE, Some(AD), &NONCE, &KEY)
440            .expect("encrypt");
441        assert_eq!(ciphertext, expected());
442
443        let mut decrypted = vec![0u8; MESSAGE.len()];
444        crypto_aead_chacha20poly1305_ietf_decrypt(
445            &mut decrypted,
446            &ciphertext,
447            Some(AD),
448            &NONCE,
449            &KEY,
450        )
451        .expect("decrypt");
452        assert_eq!(decrypted, MESSAGE);
453    }
454
455    #[test]
456    fn test_detached_and_inplace_match_combined() {
457        let expected = expected();
458        let mut detached = MESSAGE.to_vec();
459        let mut mac = Mac::default();
460        crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace(
461            &mut detached,
462            &mut mac,
463            Some(AD),
464            &NONCE,
465            &KEY,
466        )
467        .expect("detached encrypt");
468        assert_eq!(detached, expected[..MESSAGE.len()]);
469        assert_eq!(mac, expected[MESSAGE.len()..]);
470
471        crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace(
472            &mut detached,
473            &mac,
474            Some(AD),
475            &NONCE,
476            &KEY,
477        )
478        .expect("detached decrypt");
479        assert_eq!(detached, MESSAGE);
480
481        let mut combined = MESSAGE.to_vec();
482        combined.resize(MESSAGE.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES, 0);
483        crypto_aead_chacha20poly1305_ietf_encrypt_inplace(&mut combined, Some(AD), &NONCE, &KEY)
484            .expect("in-place encrypt");
485        assert_eq!(combined, expected);
486    }
487
488    #[test]
489    fn test_authentication_failure_does_not_mutate_output() {
490        let mut ciphertext = expected();
491        ciphertext[0] ^= 1;
492        let mut plaintext = vec![0xa5; MESSAGE.len()];
493        let original = plaintext.clone();
494
495        assert!(matches!(
496            crypto_aead_chacha20poly1305_ietf_decrypt(
497                &mut plaintext,
498                &ciphertext,
499                Some(AD),
500                &NONCE,
501                &KEY,
502            ),
503            Err(Error::AuthenticationFailed)
504        ));
505        assert_eq!(plaintext, original);
506
507        let mut inplace = ciphertext;
508        let original = inplace.clone();
509        assert!(matches!(
510            crypto_aead_chacha20poly1305_ietf_decrypt_inplace(&mut inplace, Some(AD), &NONCE, &KEY,),
511            Err(Error::AuthenticationFailed)
512        ));
513        assert_eq!(inplace, original);
514
515        let mut plaintext = vec![0xa5; MESSAGE.len()];
516        assert!(matches!(
517            crypto_aead_chacha20poly1305_ietf_decrypt(
518                &mut plaintext,
519                &expected(),
520                Some(b"wrong associated data"),
521                &NONCE,
522                &KEY,
523            ),
524            Err(Error::AuthenticationFailed)
525        ));
526        assert_eq!(plaintext, vec![0xa5; MESSAGE.len()]);
527    }
528
529    #[test]
530    fn test_empty_message_and_length_errors() {
531        let mut ciphertext = [0u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
532        crypto_aead_chacha20poly1305_ietf_encrypt(&mut ciphertext, &[], None, &NONCE, &KEY)
533            .expect("empty encrypt");
534        crypto_aead_chacha20poly1305_ietf_decrypt(&mut [], &ciphertext, None, &NONCE, &KEY)
535            .expect("empty decrypt");
536
537        assert!(
538            crypto_aead_chacha20poly1305_ietf_decrypt(
539                &mut [],
540                &[0u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES - 1],
541                None,
542                &NONCE,
543                &KEY,
544            )
545            .is_err()
546        );
547        assert!(
548            crypto_aead_chacha20poly1305_ietf_encrypt(&mut [0u8; 1], &[], None, &NONCE, &KEY,)
549                .is_err()
550        );
551    }
552
553    #[test]
554    fn test_final_counter_block_is_available() {
555        let mut block = [0u8; 64];
556        apply_chacha20_ietf_keystream(&mut block, u32::MAX, &NONCE, &KEY);
557        assert_ne!(block, [0u8; 64]);
558
559        apply_chacha20_ietf_keystream(&mut block, u32::MAX, &NONCE, &KEY);
560        assert_eq!(block, [0u8; 64]);
561    }
562
563    #[cfg(dryoc_native_tests)]
564    #[test]
565    fn test_final_counter_block_matches_libsodium() {
566        use libsodium_sys::crypto_stream_chacha20_ietf_xor_ic;
567
568        let message = [0xa5u8; 64];
569        let mut actual = message;
570        apply_chacha20_ietf_keystream(&mut actual, u32::MAX, &NONCE, &KEY);
571
572        let mut expected = [0u8; 64];
573        // SAFETY: All pointers reference initialized, correctly sized arrays
574        // that remain valid and non-overlapping for the duration of the call.
575        let result = unsafe {
576            crypto_stream_chacha20_ietf_xor_ic(
577                expected.as_mut_ptr(),
578                message.as_ptr(),
579                message.len() as u64,
580                NONCE.as_ptr(),
581                u32::MAX,
582                KEY.as_ptr(),
583            )
584        };
585        assert_eq!(result, 0);
586        assert_eq!(actual, expected);
587    }
588
589    #[cfg(dryoc_native_tests)]
590    #[test]
591    fn test_libsodium_constants() {
592        use libsodium_sys::{
593            crypto_aead_chacha20poly1305_ietf_abytes, crypto_aead_chacha20poly1305_ietf_keybytes,
594            crypto_aead_chacha20poly1305_ietf_messagebytes_max,
595            crypto_aead_chacha20poly1305_ietf_npubbytes,
596            crypto_aead_chacha20poly1305_ietf_nsecbytes,
597        };
598
599        use crate::constants::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES;
600
601        // SAFETY: These parameter-free libsodium functions only return compile-time
602        // constants.
603        unsafe {
604            assert_eq!(
605                crypto_aead_chacha20poly1305_ietf_keybytes(),
606                CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES
607            );
608            assert_eq!(
609                crypto_aead_chacha20poly1305_ietf_nsecbytes(),
610                CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES
611            );
612            assert_eq!(
613                crypto_aead_chacha20poly1305_ietf_npubbytes(),
614                CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES
615            );
616            assert_eq!(
617                crypto_aead_chacha20poly1305_ietf_abytes(),
618                CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES
619            );
620            assert_eq!(
621                crypto_aead_chacha20poly1305_ietf_messagebytes_max(),
622                CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
623            );
624        }
625    }
626
627    #[cfg(dryoc_native_tests)]
628    #[test]
629    fn test_sodiumoxide_interop() {
630        use sodiumoxide::crypto::aead::chacha20poly1305_ietf::{
631            Key as SodiumKey, Nonce as SodiumNonce, open, seal,
632        };
633
634        let sodium_key = SodiumKey::from_slice(&KEY).expect("key");
635        let sodium_nonce = SodiumNonce::from_slice(&NONCE).expect("nonce");
636        let ciphertext = expected();
637        assert_eq!(
638            open(&ciphertext, Some(AD), &sodium_nonce, &sodium_key).expect("sodiumoxide open"),
639            MESSAGE
640        );
641
642        let sodium_ciphertext = seal(MESSAGE, Some(AD), &sodium_nonce, &sodium_key);
643        let mut plaintext = vec![0u8; MESSAGE.len()];
644        crypto_aead_chacha20poly1305_ietf_decrypt(
645            &mut plaintext,
646            &sodium_ciphertext,
647            Some(AD),
648            &NONCE,
649            &KEY,
650        )
651        .expect("dryoc decrypt");
652        assert_eq!(plaintext, MESSAGE);
653    }
654}