Skip to main content

dryoc/classic/
crypto_secretbox.rs

1//! # Secret-key authenticated encryption
2//!
3//! Implements libsodium's `crypto_secretbox_*` functions. These functions
4//! encrypt a message with a shared secret key and detect tampering.
5//!
6//! Nonces are public, but a nonce must never repeat with the same key. See the
7//! [libsodium documentation](https://doc.libsodium.org/secret-key_cryptography/secretbox)
8//! for details.
9//!
10//! ## Classic API example
11//!
12//! ```
13//! # #[cfg(feature = "alloc")]
14//! # {
15//! use dryoc::classic::crypto_secretbox::{
16//!     Key, Nonce, crypto_secretbox_easy, crypto_secretbox_keygen, crypto_secretbox_open_easy,
17//! };
18//! use dryoc::constants::{CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_NONCEBYTES};
19//! use dryoc::rng::randombytes_buf;
20//! use dryoc::types::*;
21//!
22//! let key: Key = crypto_secretbox_keygen();
23//! let nonce = Nonce::generate();
24//!
25//! let message = "A message to encrypt";
26//!
27//! // Encrypt
28//! let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
29//! crypto_secretbox_easy(&mut ciphertext, message.as_bytes(), &nonce, &key)
30//!     .expect("encrypt failed");
31//!
32//! // Decrypt
33//! let mut decrypted = vec![0u8; ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES];
34//! crypto_secretbox_open_easy(&mut decrypted, &ciphertext, &nonce, &key).expect("decrypt failed");
35//!
36//! assert_eq!(decrypted, message.as_bytes());
37//! # }
38//! ```
39
40use crate::classic::crypto_secretbox_impl::*;
41use crate::constants::{
42    CRYPTO_SECRETBOX_KEYBYTES, CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
43    CRYPTO_SECRETBOX_NONCEBYTES,
44};
45use crate::error::Error;
46use crate::rng::copy_randombytes;
47use crate::types::*;
48
49/// Secret box message authentication code.
50pub type Mac = [u8; CRYPTO_SECRETBOX_MACBYTES];
51/// Nonce for secret key authenticated boxes.
52pub type Nonce = [u8; CRYPTO_SECRETBOX_NONCEBYTES];
53/// Key (or secret) for secret key authenticated boxes.
54pub type Key = [u8; CRYPTO_SECRETBOX_KEYBYTES];
55
56fn validate_message_len(message_len: usize, context: crate::ErrorContext) -> Result<(), Error> {
57    validate_length!(max CRYPTO_SECRETBOX_MESSAGEBYTES_MAX, message_len, context);
58    Ok(())
59}
60
61/// In-place variant of [`crypto_secretbox_keygen`]
62pub fn crypto_secretbox_keygen_inplace(key: &mut Key) {
63    copy_randombytes(key)
64}
65
66/// Generates a random key using
67/// [`copy_randombytes`].
68#[must_use]
69pub fn crypto_secretbox_keygen() -> Key {
70    Key::generate()
71}
72
73/// Detached version of [`crypto_secretbox_easy`].
74///
75/// Compatible with libsodium's `crypto_secretbox_detached`.
76///
77/// # Errors
78///
79/// Returns an error if `message` is too long or `ciphertext` is shorter than
80/// `message`.
81pub fn crypto_secretbox_detached(
82    ciphertext: &mut [u8],
83    mac: &mut Mac,
84    message: &[u8],
85    nonce: &Nonce,
86    key: &Key,
87) -> Result<(), Error> {
88    validate_message_len(message.len(), crate::ErrorContext::Message)?;
89    validate_length!(min message.len(), ciphertext.len(), crate::ErrorContext::Ciphertext);
90
91    crypto_secretbox_detached_b2b(&mut ciphertext[..message.len()], mac, message, nonce, key);
92    Ok(())
93}
94
95/// Detached version of [`crypto_secretbox_open_easy`].
96///
97/// Compatible with libsodium's `crypto_secretbox_open_detached`.
98///
99/// # Errors
100///
101/// Returns an error if `ciphertext` is too long, `message` is shorter than
102/// `ciphertext`, or authentication fails.
103pub fn crypto_secretbox_open_detached(
104    message: &mut [u8],
105    ciphertext: &[u8],
106    mac: &Mac,
107    nonce: &Nonce,
108    key: &Key,
109) -> Result<(), Error> {
110    let c_len = ciphertext.len();
111    validate_message_len(c_len, crate::ErrorContext::Ciphertext)?;
112    validate_length!(min c_len, message.len(), crate::ErrorContext::Message);
113
114    crypto_secretbox_open_detached_b2b(&mut message[..c_len], ciphertext, mac, nonce, key)
115}
116
117/// Encrypts `message` with `nonce` and `key`.
118///
119/// Compatible with libsodium's `crypto_secretbox_easy`.
120///
121/// # Errors
122///
123/// Returns an error if `message` is too long or `ciphertext` is not exactly one
124/// authentication tag longer than `message`.
125pub fn crypto_secretbox_easy(
126    ciphertext: &mut [u8],
127    message: &[u8],
128    nonce: &Nonce,
129    key: &Key,
130) -> Result<(), Error> {
131    validate_message_len(message.len(), crate::ErrorContext::Message)?;
132
133    let expected_len = message.len() + CRYPTO_SECRETBOX_MACBYTES;
134    validate_length!(exact expected_len, ciphertext.len(), crate::ErrorContext::Ciphertext);
135
136    let mut mac = Mac::default();
137    crypto_secretbox_detached(
138        &mut ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
139        &mut mac,
140        message,
141        nonce,
142        key,
143    )?;
144
145    ciphertext[..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(&mac);
146
147    Ok(())
148}
149
150/// Decrypts `ciphertext` with `nonce` and `key`.
151///
152/// Compatible with libsodium's `crypto_secretbox_open_easy`.
153///
154/// # Errors
155///
156/// Returns an error if `ciphertext` is shorter than an authentication tag,
157/// `message` has the wrong length, or authentication fails.
158pub fn crypto_secretbox_open_easy(
159    message: &mut [u8],
160    ciphertext: &[u8],
161    nonce: &Nonce,
162    key: &Key,
163) -> Result<(), Error> {
164    validate_length!(
165        min CRYPTO_SECRETBOX_MACBYTES,
166        ciphertext.len(),
167        crate::ErrorContext::Ciphertext
168    );
169    validate_length!(
170        exact ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES,
171        message.len(),
172        crate::ErrorContext::Message
173    );
174
175    let (mac, ciphertext) = ciphertext
176        .split_first_chunk::<CRYPTO_SECRETBOX_MACBYTES>()
177        .expect("validated ciphertext length");
178    crypto_secretbox_open_detached(message, ciphertext, mac, nonce, key)
179}
180
181/// Encrypts `message` with `nonce` and `key` in-place, without allocating
182/// additional memory for ciphertext.
183///
184/// # Errors
185///
186/// Returns an error if `data` is shorter than an authentication tag.
187pub fn crypto_secretbox_easy_inplace(
188    data: &mut [u8],
189    nonce: &Nonce,
190    key: &Key,
191) -> Result<(), Error> {
192    validate_length!(min CRYPTO_SECRETBOX_MACBYTES, data.len(), crate::ErrorContext::Data);
193    data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
194    let (mac, data) = data
195        .split_first_chunk_mut::<CRYPTO_SECRETBOX_MACBYTES>()
196        .expect("validated data length");
197
198    crypto_secretbox_detached_inplace(data, mac, nonce, key);
199
200    Ok(())
201}
202
203/// Decrypts `ciphertext` with `nonce` and `key` in-place, without allocating
204/// additional memory for the message.
205///
206/// # Errors
207///
208/// Returns an error if `ciphertext` is shorter than an authentication tag or
209/// authentication fails.
210pub fn crypto_secretbox_open_easy_inplace(
211    ciphertext: &mut [u8],
212    nonce: &Nonce,
213    key: &Key,
214) -> Result<(), Error> {
215    validate_length!(
216        min CRYPTO_SECRETBOX_MACBYTES,
217        ciphertext.len(),
218        crate::ErrorContext::Ciphertext
219    );
220
221    let (mac, data) = ciphertext
222        .split_first_chunk_mut::<CRYPTO_SECRETBOX_MACBYTES>()
223        .expect("validated ciphertext length");
224    let mac = &*mac;
225
226    crypto_secretbox_open_detached_inplace(data, mac, nonce, key)?;
227
228    ciphertext.rotate_left(CRYPTO_SECRETBOX_MACBYTES);
229
230    Ok(())
231}
232
233#[cfg(test)]
234mod tests {
235    use crate::test_prelude::*;
236
237    #[cfg(all(feature = "nightly", dryoc_native_tests))]
238    extern crate test;
239
240    use super::*;
241
242    #[test]
243    fn rejects_lengths_above_the_libsodium_limit() {
244        let too_long = CRYPTO_SECRETBOX_MESSAGEBYTES_MAX + 1;
245
246        assert!(matches!(
247            validate_message_len(too_long, crate::ErrorContext::Message),
248            Err(Error::InvalidLength {
249                context: crate::ErrorContext::Message,
250                actual,
251                constraint: crate::LengthConstraint::AtMost(CRYPTO_SECRETBOX_MESSAGEBYTES_MAX),
252            }) if actual == too_long
253        ));
254    }
255
256    #[test]
257    fn test_crypto_secretbox_rejects_invalid_buffer_lengths_without_mutation() {
258        let key = Key::default();
259        let nonce = Nonce::default();
260        let message = b"buffer length validation";
261
262        let mut short_detached = vec![0xa5; message.len() - 1];
263        let original_short_detached = short_detached.clone();
264        let mut mac = [0x5a; CRYPTO_SECRETBOX_MACBYTES];
265        let original_mac = mac;
266        assert!(
267            crypto_secretbox_detached(&mut short_detached, &mut mac, message, &nonce, &key)
268                .is_err()
269        );
270        assert_eq!(short_detached, original_short_detached);
271        assert_eq!(mac, original_mac);
272
273        for output_len in [
274            message.len() + CRYPTO_SECRETBOX_MACBYTES - 1,
275            message.len() + CRYPTO_SECRETBOX_MACBYTES + 1,
276        ] {
277            let mut output = vec![0xa5; output_len];
278            let original = output.clone();
279            assert!(crypto_secretbox_easy(&mut output, message, &nonce, &key).is_err());
280            assert_eq!(output, original);
281        }
282
283        let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
284        crypto_secretbox_easy(&mut ciphertext, message, &nonce, &key).expect("encrypt failed");
285
286        for output_len in [message.len() - 1, message.len() + 1] {
287            let mut output = vec![0xa5; output_len];
288            let original = output.clone();
289            assert!(crypto_secretbox_open_easy(&mut output, &ciphertext, &nonce, &key).is_err());
290            assert_eq!(output, original);
291        }
292
293        let mut short_open = vec![0xa5; message.len() - 1];
294        let original_short_open = short_open.clone();
295        assert!(
296            crypto_secretbox_open_detached(
297                &mut short_open,
298                &ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
299                ciphertext.first_chunk().expect("sealed ciphertext"),
300                &nonce,
301                &key,
302            )
303            .is_err()
304        );
305        assert_eq!(short_open, original_short_open);
306
307        let mut too_short_inplace = vec![0xa5; CRYPTO_SECRETBOX_MACBYTES - 1];
308        let original_too_short_inplace = too_short_inplace.clone();
309        assert!(crypto_secretbox_easy_inplace(&mut too_short_inplace, &nonce, &key).is_err());
310        assert_eq!(too_short_inplace, original_too_short_inplace);
311    }
312
313    #[cfg(dryoc_native_tests)]
314    #[test]
315    fn test_crypto_secretbox_easy() {
316        for i in 0..20 {
317            use base64::Engine as _;
318            use base64::engine::general_purpose;
319
320            use crate::native_test_util::{secretbox_easy, secretbox_open_easy};
321
322            let key: Key = crypto_secretbox_keygen();
323            let nonce = Nonce::generate();
324
325            let words = vec!["love Doge".to_string(); i];
326            let message = words.join(" <3 ");
327
328            let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
329            crypto_secretbox_easy(&mut ciphertext, message.as_bytes(), &nonce, &key)
330                .expect("encrypt failed");
331            let so_ciphertext = secretbox_easy(message.as_bytes(), &nonce, &key);
332            assert_eq!(
333                general_purpose::STANDARD.encode(&ciphertext),
334                general_purpose::STANDARD.encode(&so_ciphertext)
335            );
336
337            let mut decrypted = vec![0u8; message.len()];
338            crypto_secretbox_open_easy(&mut decrypted, &ciphertext, &nonce, &key)
339                .expect("decrypt failed");
340            let so_decrypted = secretbox_open_easy(&ciphertext, &nonce, &key).unwrap();
341
342            assert_eq!(decrypted, message.as_bytes());
343            assert_eq!(decrypted, so_decrypted);
344        }
345    }
346
347    #[cfg(dryoc_native_tests)]
348    #[test]
349    fn test_crypto_secretbox_easy_inplace() {
350        for i in 0..20 {
351            use base64::Engine as _;
352            use base64::engine::general_purpose;
353
354            use crate::native_test_util::{secretbox_easy, secretbox_open_easy};
355
356            let key = crypto_secretbox_keygen();
357            let nonce = Nonce::generate();
358
359            let words = vec!["love Doge".to_string(); i];
360            let message: Vec<u8> = words.join(" <3 ").into();
361            let message_copy = message.clone();
362
363            let mut ciphertext = message.clone();
364            ciphertext.resize(message.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
365            crypto_secretbox_easy_inplace(&mut ciphertext, &nonce, &key).expect("encrypt failed");
366            let so_ciphertext = secretbox_easy(&message_copy, &nonce, &key);
367            assert_eq!(
368                general_purpose::STANDARD.encode(&ciphertext),
369                general_purpose::STANDARD.encode(&so_ciphertext)
370            );
371
372            let mut decrypted = ciphertext.clone();
373            crypto_secretbox_open_easy_inplace(&mut decrypted, &nonce, &key)
374                .expect("decrypt failed");
375            decrypted.resize(ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES, 0);
376            let so_decrypted =
377                secretbox_open_easy(&ciphertext, &nonce, &key).expect("decrypt failed");
378
379            assert_eq!(&decrypted, &message_copy);
380            assert_eq!(decrypted, so_decrypted);
381        }
382    }
383
384    #[test]
385    fn test_crypto_secretbox_detached_only_touches_message_len() {
386        let key = crypto_secretbox_keygen();
387        let nonce = Nonce::generate();
388        let message = b"detached secretbox buffer prefix";
389        let mut ciphertext = vec![0xa5; message.len() + 8];
390        let mut mac = Mac::default();
391
392        crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
393            .expect("encrypt failed");
394
395        assert_eq!(&ciphertext[message.len()..], &[0xa5; 8]);
396
397        let mut decrypted = vec![0x5a; message.len() + 8];
398        crypto_secretbox_open_detached(
399            &mut decrypted,
400            &ciphertext[..message.len()],
401            &mac,
402            &nonce,
403            &key,
404        )
405        .expect("decrypt failed");
406
407        assert_eq!(&decrypted[..message.len()], message);
408        assert_eq!(&decrypted[message.len()..], &[0x5a; 8]);
409    }
410
411    #[test]
412    fn test_crypto_secretbox_open_failure_keeps_output() {
413        let key = crypto_secretbox_keygen();
414        let nonce = Nonce::generate();
415        let message = b"authenticated plaintext";
416        let mut ciphertext = vec![0u8; message.len()];
417        let mut mac = Mac::default();
418
419        crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
420            .expect("encrypt failed");
421        mac[0] ^= 1;
422
423        let mut decrypted = vec![0x5a; message.len()];
424        let original_decrypted = decrypted.clone();
425        assert!(
426            crypto_secretbox_open_detached(&mut decrypted, &ciphertext, &mac, &nonce, &key)
427                .is_err()
428        );
429        assert_eq!(decrypted, original_decrypted);
430
431        let mut inplace = ciphertext.clone();
432        assert!(crypto_secretbox_open_detached_inplace(&mut inplace, &mac, &nonce, &key).is_err());
433        assert_eq!(inplace, ciphertext);
434    }
435
436    /// A deterministic key, nonce and message of `len` bytes.
437    fn fixture(len: usize) -> (Key, Nonce, Vec<u8>) {
438        let mut rng = crate::utils::test_util::XorShift64::new(0x5ec2_e7b0_1d3a_9f41);
439        let key = rng.next_bytes32();
440        let nonce: Nonce = rng.next_bytes32()[..CRYPTO_SECRETBOX_NONCEBYTES]
441            .try_into()
442            .unwrap();
443        let message = (0..len)
444            .map(|i| (i as u8).wrapping_mul(31) ^ 0x3c)
445            .collect();
446        (key, nonce, message)
447    }
448
449    /// Every failing `crypto_secretbox_open_easy_inplace` (and `open_easy`)
450    /// must leave its whole buffer as it found it: a flipped bit in the
451    /// first and last tag byte, in the first and last ciphertext byte, and
452    /// a buffer one byte short of a tag.
453    #[test]
454    fn test_open_easy_inplace_failures_leave_buffer_untouched() {
455        let (key, nonce, message) = fixture(100);
456        let mut sealed = message.clone();
457        sealed.resize(message.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
458        crypto_secretbox_easy_inplace(&mut sealed, &nonce, &key).expect("encrypt failed");
459
460        let mut cases = Vec::new();
461        for (name, index) in [
462            ("first tag byte", 0),
463            ("last tag byte", CRYPTO_SECRETBOX_MACBYTES - 1),
464            ("first ciphertext byte", CRYPTO_SECRETBOX_MACBYTES),
465            ("last ciphertext byte", sealed.len() - 1),
466        ] {
467            let mut tampered = sealed.clone();
468            tampered[index] ^= 1;
469            cases.push((name, tampered));
470        }
471        for (name, tampered) in cases {
472            let mut buffer = tampered.clone();
473            assert!(
474                matches!(
475                    crypto_secretbox_open_easy_inplace(&mut buffer, &nonce, &key),
476                    Err(Error::AuthenticationFailed)
477                ),
478                "{name}: in place"
479            );
480            assert_eq!(buffer, tampered, "{name}: in place buffer");
481
482            let mut output = vec![0xa5u8; message.len()];
483            assert!(
484                matches!(
485                    crypto_secretbox_open_easy(&mut output, &tampered, &nonce, &key),
486                    Err(Error::AuthenticationFailed)
487                ),
488                "{name}: b2b"
489            );
490            assert_eq!(output, vec![0xa5u8; message.len()], "{name}: b2b output");
491        }
492
493        let mut short = sealed[..CRYPTO_SECRETBOX_MACBYTES - 1].to_vec();
494        let original = short.clone();
495        assert!(matches!(
496            crypto_secretbox_open_easy_inplace(&mut short, &nonce, &key),
497            Err(Error::InvalidLength { .. })
498        ));
499        assert_eq!(short, original);
500        let mut output = [0xa5u8];
501        assert!(matches!(
502            crypto_secretbox_open_easy(&mut output, &original, &nonce, &key),
503            Err(Error::InvalidLength { .. })
504        ));
505        assert_eq!(output, [0xa5]);
506
507        let mut buffer = sealed.clone();
508        crypto_secretbox_open_easy_inplace(&mut buffer, &nonce, &key).expect("decrypt failed");
509        assert_eq!(&buffer[..message.len()], message);
510    }
511
512    /// Message lengths around the Poly1305 and Salsa20 blocks, and around
513    /// the XSalsa20 kernel chunks (5, 8 and 16 blocks): the message's
514    /// keystream starts 32 bytes into block 0, so a chunk boundary falls 32
515    /// bytes before a multiple of the chunk.
516    #[cfg(dryoc_native_tests)]
517    fn boundary_lens() -> Vec<usize> {
518        let mut lens = vec![0, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65];
519        for chunk in [5 * 64, 8 * 64, 16 * 64] {
520            lens.extend([
521                chunk - 33,
522                chunk - 32,
523                chunk - 31,
524                chunk - 1,
525                chunk,
526                chunk + 1,
527                2 * chunk - 33,
528                2 * chunk - 32,
529                2 * chunk - 31,
530            ]);
531        }
532        lens.sort_unstable();
533        lens.dedup();
534        lens
535    }
536
537    /// Every seal function must produce libsodium's `crypto_secretbox_easy`
538    /// and `crypto_secretbox_detached` output, and every open function must
539    /// recover the message from it, at every length in [`boundary_lens`].
540    #[cfg(dryoc_native_tests)]
541    #[test]
542    fn test_matches_libsodium_at_boundary_lengths() {
543        use libc::c_ulonglong;
544
545        crate::native_test_util::init();
546
547        for len in boundary_lens() {
548            let (key, nonce, message) = fixture(len);
549            let mut expected = vec![0u8; len];
550            let mut expected_mac = Mac::default();
551            let mut expected_easy = vec![0u8; len + CRYPTO_SECRETBOX_MACBYTES];
552            // SAFETY: every buffer is valid for the length passed beside it;
553            // `mac`, `nonce` and `key` are exact-size arrays.
554            unsafe {
555                assert_eq!(
556                    libsodium_sys::crypto_secretbox_detached(
557                        expected.as_mut_ptr(),
558                        expected_mac.as_mut_ptr(),
559                        message.as_ptr(),
560                        len as c_ulonglong,
561                        nonce.as_ptr(),
562                        key.as_ptr(),
563                    ),
564                    0
565                );
566                assert_eq!(
567                    libsodium_sys::crypto_secretbox_easy(
568                        expected_easy.as_mut_ptr(),
569                        message.as_ptr(),
570                        len as c_ulonglong,
571                        nonce.as_ptr(),
572                        key.as_ptr(),
573                    ),
574                    0
575                );
576            }
577            assert_eq!(expected_easy[..CRYPTO_SECRETBOX_MACBYTES], expected_mac);
578            assert_eq!(expected_easy[CRYPTO_SECRETBOX_MACBYTES..], expected);
579
580            let mut ciphertext = vec![0u8; len];
581            let mut mac = Mac::default();
582            crypto_secretbox_detached(&mut ciphertext, &mut mac, &message, &nonce, &key)
583                .expect("detached");
584            assert_eq!(
585                (&ciphertext, mac),
586                (&expected, expected_mac),
587                "len {len}: detached"
588            );
589
590            let mut data = message.clone();
591            let mut mac = Mac::default();
592            crypto_secretbox_detached_inplace(&mut data, &mut mac, &nonce, &key);
593            assert_eq!(
594                (&data, mac),
595                (&expected, expected_mac),
596                "len {len}: detached in place"
597            );
598
599            let mut easy = vec![0u8; len + CRYPTO_SECRETBOX_MACBYTES];
600            crypto_secretbox_easy(&mut easy, &message, &nonce, &key).expect("easy");
601            assert_eq!(easy, expected_easy, "len {len}: easy");
602
603            let mut data = message.clone();
604            data.resize(len + CRYPTO_SECRETBOX_MACBYTES, 0);
605            crypto_secretbox_easy_inplace(&mut data, &nonce, &key).expect("easy in place");
606            assert_eq!(data, expected_easy, "len {len}: easy in place");
607
608            let mut output = vec![0u8; len];
609            crypto_secretbox_open_detached(&mut output, &expected, &expected_mac, &nonce, &key)
610                .expect("open detached");
611            assert_eq!(output, message, "len {len}: open detached");
612
613            let mut data = expected.clone();
614            crypto_secretbox_open_detached_inplace(&mut data, &expected_mac, &nonce, &key)
615                .expect("open detached in place");
616            assert_eq!(data, message, "len {len}: open detached in place");
617
618            let mut output = vec![0u8; len];
619            crypto_secretbox_open_easy(&mut output, &expected_easy, &nonce, &key)
620                .expect("open easy");
621            assert_eq!(output, message, "len {len}: open easy");
622
623            let mut data = expected_easy.clone();
624            crypto_secretbox_open_easy_inplace(&mut data, &nonce, &key)
625                .expect("open easy in place");
626            assert_eq!(&data[..len], message, "len {len}: open easy in place");
627        }
628    }
629
630    #[cfg(all(feature = "nightly", dryoc_native_tests))]
631    fn bench_crypto_secretbox_detached(b: &mut test::Bencher, message_len: usize) {
632        let key: Key = crypto_secretbox_keygen();
633        let nonce = Nonce::generate();
634        let mut message = vec![0u8; message_len];
635        crate::rng::copy_randombytes(&mut message);
636        let mut ciphertext = vec![0u8; message_len];
637        let mut mac = Mac::default();
638
639        b.bytes = message_len as u64;
640        b.iter(|| {
641            crypto_secretbox_detached(
642                test::black_box(&mut ciphertext),
643                test::black_box(&mut mac),
644                test::black_box(&message),
645                test::black_box(&nonce),
646                test::black_box(&key),
647            )
648            .expect("encrypt failed");
649        });
650    }
651
652    #[cfg(all(feature = "nightly", dryoc_native_tests))]
653    #[bench]
654    fn crypto_secretbox_detached_64b_bench(b: &mut test::Bencher) {
655        bench_crypto_secretbox_detached(b, 64);
656    }
657
658    #[cfg(all(feature = "nightly", dryoc_native_tests))]
659    #[bench]
660    fn crypto_secretbox_detached_1kib_bench(b: &mut test::Bencher) {
661        bench_crypto_secretbox_detached(b, 1024);
662    }
663
664    #[cfg(all(feature = "nightly", dryoc_native_tests))]
665    #[bench]
666    fn crypto_secretbox_detached_16kib_bench(b: &mut test::Bencher) {
667        bench_crypto_secretbox_detached(b, 16 * 1024);
668    }
669
670    #[cfg(all(feature = "nightly", dryoc_native_tests))]
671    #[bench]
672    fn crypto_secretbox_detached_1mib_bench(b: &mut test::Bencher) {
673        bench_crypto_secretbox_detached(b, 1024 * 1024);
674    }
675
676    /// libsodium's `crypto_secretbox_detached` with the same buffers as
677    /// `bench_crypto_secretbox_detached`, so the two rows are directly
678    /// comparable.
679    #[cfg(all(feature = "nightly", dryoc_native_tests))]
680    fn bench_libsodium_secretbox_detached(b: &mut test::Bencher, message_len: usize) {
681        crate::native_test_util::init();
682
683        let key: Key = crypto_secretbox_keygen();
684        let nonce = Nonce::generate();
685        let mut message = vec![0u8; message_len];
686        crate::rng::copy_randombytes(&mut message);
687        let mut ciphertext = vec![0u8; message_len];
688        let mut mac = Mac::default();
689
690        b.bytes = message_len as u64;
691        b.iter(|| {
692            // SAFETY: `ciphertext` and `message` are both `message_len` bytes,
693            // and `mac`, `nonce` and `key` are exact-size arrays.
694            let rc = unsafe {
695                libsodium_sys::crypto_secretbox_detached(
696                    ciphertext.as_mut_ptr(),
697                    mac.as_mut_ptr(),
698                    test::black_box(message.as_ptr()),
699                    message_len as u64,
700                    nonce.as_ptr(),
701                    key.as_ptr(),
702                )
703            };
704            assert_eq!(rc, 0);
705            test::black_box((&ciphertext, &mac));
706        });
707    }
708
709    #[cfg(all(feature = "nightly", dryoc_native_tests))]
710    #[bench]
711    fn libsodium_secretbox_detached_64b_bench(b: &mut test::Bencher) {
712        bench_libsodium_secretbox_detached(b, 64);
713    }
714
715    #[cfg(all(feature = "nightly", dryoc_native_tests))]
716    #[bench]
717    fn libsodium_secretbox_detached_1kib_bench(b: &mut test::Bencher) {
718        bench_libsodium_secretbox_detached(b, 1024);
719    }
720
721    #[cfg(all(feature = "nightly", dryoc_native_tests))]
722    #[bench]
723    fn libsodium_secretbox_detached_16kib_bench(b: &mut test::Bencher) {
724        bench_libsodium_secretbox_detached(b, 16 * 1024);
725    }
726
727    #[cfg(all(feature = "nightly", dryoc_native_tests))]
728    #[bench]
729    fn libsodium_secretbox_detached_1mib_bench(b: &mut test::Bencher) {
730        bench_libsodium_secretbox_detached(b, 1024 * 1024);
731    }
732}