Skip to main content

dryoc/classic/
crypto_secretstream_xchacha20poly1305.rs

1//! # Secret stream functions
2//!
3//! Implements authenticated encrypted streams as per
4//! <https://libsodium.gitbook.io/doc/secret-key_cryptography/secretstream>.
5//!
6//! This API is compatible with libsodium's implementation.
7//!
8//! # Classic API example
9//!
10//! ```
11//! use dryoc::classic::crypto_secretstream_xchacha20poly1305::*;
12//! use dryoc::constants::{
13//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
14//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL,
15//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
16//! };
17//! let message1 = b"Arbitrary data to encrypt";
18//! let message2 = b"split into";
19//! let message3 = b"three messages";
20//!
21//! // Generate a key
22//! let mut key = Key::default();
23//! crypto_secretstream_xchacha20poly1305_keygen(&mut key);
24//!
25//! // Create stream push state
26//! let mut state = State::new();
27//! let mut header = Header::default();
28//! crypto_secretstream_xchacha20poly1305_init_push(&mut state, &mut header, &key);
29//!
30//! let (mut c1, mut c2, mut c3) = (
31//!     vec![0u8; message1.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES],
32//!     vec![0u8; message2.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES],
33//!     vec![0u8; message3.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES],
34//! );
35//! // Encrypt a series of messages
36//! crypto_secretstream_xchacha20poly1305_push(
37//!     &mut state,
38//!     &mut c1,
39//!     message1,
40//!     None,
41//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
42//! )
43//! .expect("Encrypt failed");
44//! crypto_secretstream_xchacha20poly1305_push(
45//!     &mut state,
46//!     &mut c2,
47//!     message2,
48//!     None,
49//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
50//! )
51//! .expect("Encrypt failed");
52//! crypto_secretstream_xchacha20poly1305_push(
53//!     &mut state,
54//!     &mut c3,
55//!     message3,
56//!     None,
57//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL,
58//! )
59//! .expect("Encrypt failed");
60//!
61//! // Create stream pull state, using the same key as above with a new state.
62//! let mut state = State::new();
63//! crypto_secretstream_xchacha20poly1305_init_pull(&mut state, &header, &key);
64//!
65//! let (mut m1, mut m2, mut m3) = (
66//!     vec![0u8; message1.len()],
67//!     vec![0u8; message2.len()],
68//!     vec![0u8; message3.len()],
69//! );
70//! let (mut tag1, mut tag2, mut tag3) = (0u8, 0u8, 0u8);
71//!
72//! // Decrypt the stream of messages
73//! crypto_secretstream_xchacha20poly1305_pull(&mut state, &mut m1, &mut tag1, &c1, None)
74//!     .expect("Decrypt failed");
75//! crypto_secretstream_xchacha20poly1305_pull(&mut state, &mut m2, &mut tag2, &c2, None)
76//!     .expect("Decrypt failed");
77//! crypto_secretstream_xchacha20poly1305_pull(&mut state, &mut m3, &mut tag3, &c3, None)
78//!     .expect("Decrypt failed");
79//!
80//! assert_eq!(message1, m1.as_slice());
81//! assert_eq!(message2, m2.as_slice());
82//! assert_eq!(message3, m3.as_slice());
83//!
84//! assert_eq!(tag1, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE);
85//! assert_eq!(tag2, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE);
86//! assert_eq!(tag3, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
87//! ```
88
89use subtle::ConstantTimeEq;
90use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
91
92use crate::classic::crypto_core::{HChaCha20Key, crypto_core_hchacha20};
93use crate::constants::{
94    CRYPTO_CORE_HCHACHA20_INPUTBYTES, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
95    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES,
96    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
97    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES,
98    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
99    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
100    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY, CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES,
101    CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
102};
103use crate::error::*;
104use crate::rng::copy_randombytes;
105use crate::types::*;
106use crate::utils::{increment_bytes, pad16, xor_buf};
107
108/// A secret for authenticated secret streams.
109pub type Key = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES];
110/// A nonce for authenticated secret streams.
111pub type Nonce = [u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES];
112/// Container for stream header data
113pub type Header = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
114
115/// Stream state data
116#[derive(PartialEq, Eq, Clone, Default, Zeroize, ZeroizeOnDrop)]
117pub struct State {
118    k: Key,
119    nonce: Nonce,
120}
121
122impl State {
123    /// Returns a new stream state with an empty key and nonce.
124    pub fn new() -> Self {
125        Self::default()
126    }
127}
128
129/// Generates a random stream key using [crate::rng::copy_randombytes].
130pub fn crypto_secretstream_xchacha20poly1305_keygen(key: &mut Key) {
131    copy_randombytes(key);
132}
133
134fn state_counter(nonce: &mut Nonce) -> &mut [u8] {
135    &mut nonce[..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
136}
137
138fn state_inonce(nonce: &mut Nonce) -> &mut [u8] {
139    &mut nonce[CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
140        ..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
141            + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
142}
143
144fn secretstream_length_block(associated_data_len: usize, message_len: usize) -> [u8; 16] {
145    let mut lengths = [0u8; 16];
146    lengths[..8].copy_from_slice(&(associated_data_len as u64).to_le_bytes());
147    lengths[8..].copy_from_slice(&(64u64 + message_len as u64).to_le_bytes());
148    lengths
149}
150
151fn _crypto_secretstream_xchacha20poly1305_counter_reset(state: &mut State) {
152    let counter = state_counter(&mut state.nonce);
153    counter.fill(0);
154    counter[0] = 1;
155}
156
157/// Initializes a push stream for streaming encryption.
158///
159/// Initializes a push stream into `state` using `key` and returns a stream
160/// header. The stream header can be used to initialize a pull stream using the
161/// same key (i.e., using [crypto_secretstream_xchacha20poly1305_init_pull]).
162///
163/// Compatible with libsodium's
164/// `crypto_secretstream_xchacha20poly1305_init_push`.
165pub fn crypto_secretstream_xchacha20poly1305_init_push(
166    state: &mut State,
167    header: &mut Header,
168    key: &Key,
169) {
170    copy_randombytes(header);
171
172    let mut k = Zeroizing::new(HChaCha20Key::default());
173    crypto_core_hchacha20(
174        k.as_mut_array(),
175        ByteArray::as_array(&header[..16]),
176        key,
177        None,
178    );
179    // Copy key into state
180    state.k.copy_from_slice(&*k);
181    _crypto_secretstream_xchacha20poly1305_counter_reset(state);
182
183    let inonce = state_inonce(&mut state.nonce);
184    inonce.copy_from_slice(
185        &header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
186            ..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
187                + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
188    );
189}
190
191/// Initializes a pull stream for streaming decryption.
192///
193/// Initializes `state` using `key` and a `header` returned by
194/// [`crypto_secretstream_xchacha20poly1305_init_push`].
195///
196/// Compatible with libsodium's
197/// `crypto_secretstream_xchacha20poly1305_init_pull`.
198pub fn crypto_secretstream_xchacha20poly1305_init_pull(
199    state: &mut State,
200    header: &Header,
201    key: &Key,
202) {
203    let mut k = Zeroizing::new(HChaCha20Key::default());
204    crypto_core_hchacha20(
205        k.as_mut_array(),
206        ByteArray::as_array(&header[0..16]),
207        key,
208        None,
209    );
210    state.k.copy_from_slice(&*k);
211
212    _crypto_secretstream_xchacha20poly1305_counter_reset(state);
213
214    let inonce = state_inonce(&mut state.nonce);
215    inonce.copy_from_slice(
216        &header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
217            ..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
218                + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
219    );
220}
221
222/// Manually rekeys a stream.
223///
224/// Compatible with libsodium's
225/// `crypto_secretstream_xchacha20poly1305_rekey`.
226pub fn crypto_secretstream_xchacha20poly1305_rekey(state: &mut State) {
227    use chacha20::ChaCha20;
228    use chacha20::cipher::{KeyIvInit, StreamCipher};
229
230    let mut new_state = Zeroizing::new(
231        [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES
232            + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES],
233    );
234
235    new_state[..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES].copy_from_slice(&state.k);
236    new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]
237        .copy_from_slice(state_inonce(&mut state.nonce));
238
239    let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
240    cipher.apply_keystream(&mut *new_state);
241
242    state
243        .k
244        .copy_from_slice(&new_state[0..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES]);
245    state_inonce(&mut state.nonce)
246        .copy_from_slice(&new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]);
247
248    _crypto_secretstream_xchacha20poly1305_counter_reset(state);
249}
250
251/// Encrypts `message` from the stream for `state`, with `tag` and optional
252/// `associated_data`, placing the result into `ciphertext`.
253///
254/// Compatible with libsodium's `crypto_secretstream_xchacha20poly1305_push`.
255///
256/// NOTE: The libsodium version of this function contains an alignment bug which
257/// was left in place, and is reflected in this implementation for compatibility
258/// purposes. Refer to [commit
259/// 290197ba3ee72245fdab5e971c8de43a82b19874](https://github.com/jedisct1/libsodium/commit/290197ba3ee72245fdab5e971c8de43a82b19874#diff-dbd9b6026ac3fd057df0ddf00e4d671af16e5df99b4cc7d08b73b61f193d10f5)
260///
261/// # Errors
262///
263/// Returns an error if `message` exceeds the maximum supported length or
264/// `ciphertext` is not exactly one authentication tag longer than `message`.
265pub fn crypto_secretstream_xchacha20poly1305_push(
266    state: &mut State,
267    ciphertext: &mut [u8],
268    message: &[u8],
269    associated_data: Option<&[u8]>,
270    tag: u8,
271) -> Result<(), Error> {
272    use chacha20::ChaCha20;
273    use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
274
275    use crate::poly1305::Poly1305;
276
277    if message.len() > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
278        return Err(length_error!(
279            crate::ErrorContext::Message,
280            message.len(),
281            max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
282        ));
283    }
284
285    let expected_ciphertext_len = message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
286    if ciphertext.len() != expected_ciphertext_len {
287        return Err(length_error!(
288            crate::ErrorContext::Ciphertext,
289            ciphertext.len(),
290            exact expected_ciphertext_len
291        ));
292    }
293
294    let associated_data = associated_data.unwrap_or(&[]);
295
296    let mut mac_key = crate::poly1305::Key::new();
297    let _pad0 = [0u8; 16];
298
299    let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
300
301    cipher.apply_keystream(&mut mac_key);
302    let mut mac = Zeroizing::new(Poly1305::new(&mac_key));
303    mac_key.zeroize();
304
305    mac.update(associated_data);
306    mac.update(&_pad0[..pad16(associated_data.len())]);
307
308    let mut block = Zeroizing::new([0u8; 64]);
309    block[0] = tag;
310    cipher.seek(64);
311    cipher.apply_keystream(&mut *block);
312    mac.update(&*block);
313
314    let mlen = message.len();
315    ciphertext[0] = block[0];
316    ciphertext[1..(1 + mlen)].copy_from_slice(message);
317
318    cipher.seek(128);
319    cipher.apply_keystream(&mut ciphertext[1..(1 + mlen)]);
320
321    let size_data = secretstream_length_block(associated_data.len(), mlen);
322
323    mac.update(&ciphertext[1..(1 + mlen)]);
324    // this is to workaround an unfortunate padding bug in libsodium, there's a
325    // note in commit 290197ba3ee72245fdab5e971c8de43a82b19874. There's no
326    // safety issue, so we can just pretend it's not a bug.
327    let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
328    mac.update(&_pad0[0..buffer_mac_pad]);
329    mac.update(&size_data);
330
331    mac.finalize(&mut ciphertext[1 + mlen..]);
332
333    let inonce = state_inonce(&mut state.nonce);
334    xor_buf(inonce, &ciphertext[1 + mlen..]);
335
336    let counter = state_counter(&mut state.nonce);
337    increment_bytes(counter);
338
339    if tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
340        == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
341        || state_counter(&mut state.nonce)
342            .ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
343            .unwrap_u8()
344            == 1
345    {
346        crypto_secretstream_xchacha20poly1305_rekey(state);
347    }
348
349    Ok(())
350}
351
352/// Decrypts `ciphertext` from the stream for `state` with optional
353/// `additional_data`, placing the result into `message` (which must be manually
354/// resized) and `tag`. Returns the length of the message.
355///
356/// Due to a quirk in libsodium's implementation, you need to manually resize
357/// `message` to the message length after decrypting when using this function.
358///
359/// Compatible with libsodium's `crypto_secretstream_xchacha20poly1305_pull`.
360///
361/// NOTE: The libsodium version of this function contains an alignment bug which
362/// was left in place, and is reflected in this implementation for compatibility
363/// purposes. Refer to [commit
364/// 290197ba3ee72245fdab5e971c8de43a82b19874](https://github.com/jedisct1/libsodium/commit/290197ba3ee72245fdab5e971c8de43a82b19874#diff-dbd9b6026ac3fd057df0ddf00e4d671af16e5df99b4cc7d08b73b61f193d10f5)
365///
366/// # Errors
367///
368/// Returns an error if `ciphertext` is too short or too long, `message` lacks
369/// space for the plaintext, or authentication fails.
370pub fn crypto_secretstream_xchacha20poly1305_pull(
371    state: &mut State,
372    message: &mut [u8],
373    tag: &mut u8,
374    ciphertext: &[u8],
375    associated_data: Option<&[u8]>,
376) -> Result<usize, Error> {
377    use chacha20::ChaCha20;
378    use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
379
380    use crate::poly1305::Poly1305;
381
382    let _pad0 = [0u8; 16];
383
384    if ciphertext.len() < CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
385        return Err(length_error!(
386            crate::ErrorContext::Ciphertext,
387            ciphertext.len(),
388            min CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
389        ));
390    }
391
392    let mlen = ciphertext.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
393
394    if mlen > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
395        return Err(length_error!(
396            crate::ErrorContext::Ciphertext,
397            ciphertext.len(),
398            max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
399                + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
400        ));
401    }
402
403    if message.len() < mlen {
404        return Err(length_error!(
405            crate::ErrorContext::Message,
406            message.len(),
407            min mlen
408        ));
409    }
410
411    let associated_data = associated_data.unwrap_or(&[]);
412
413    let mut mac_key = crate::poly1305::Key::new();
414
415    let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
416
417    cipher.apply_keystream(&mut mac_key);
418    let mut mac = Zeroizing::new(Poly1305::new(&mac_key));
419    mac_key.zeroize();
420
421    mac.update(associated_data);
422    mac.update(&_pad0[..pad16(associated_data.len())]);
423
424    let mut block = Zeroizing::new([0u8; 64]);
425    block[0] = ciphertext[0];
426
427    cipher.seek(64);
428    cipher.apply_keystream(&mut *block);
429
430    let decrypted_tag = block[0];
431    block[0] = ciphertext[0];
432
433    mac.update(&*block);
434
435    // this is to workaround an unfortunate padding bug in libsodium, there's a
436    // note in commit 290197ba3ee72245fdab5e971c8de43a82b19874. There's no
437    // safety issue, so we can just pretend it's not a bug.
438    let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
439    mac.update(&ciphertext[1..1 + mlen]);
440    mac.update(&_pad0[..buffer_mac_pad]);
441
442    let size_data = secretstream_length_block(associated_data.len(), mlen);
443    mac.update(&size_data);
444    let mac = Zeroizing::new(mac.finalize_to_array());
445
446    if ciphertext[1 + mlen..].ct_eq(mac.as_slice()).unwrap_u8() == 0 {
447        return Err(Error::AuthenticationFailed);
448    }
449
450    message[..mlen].copy_from_slice(&ciphertext[1..1 + mlen]);
451    cipher.seek(128);
452    cipher.apply_keystream(&mut message[..mlen]);
453    *tag = decrypted_tag;
454
455    let inonce = state_inonce(&mut state.nonce);
456    xor_buf(inonce, &*mac);
457
458    let counter = state_counter(&mut state.nonce);
459    increment_bytes(counter);
460
461    if decrypted_tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
462        == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
463        || state_counter(&mut state.nonce)
464            .ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
465            .unwrap_u8()
466            == 1
467    {
468        crypto_secretstream_xchacha20poly1305_rekey(state);
469    }
470
471    Ok(mlen)
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477    use crate::dryocstream::Tag;
478
479    #[test]
480    fn push_and_pull_reject_invalid_buffer_lengths() {
481        let mut state = State::new();
482        let mut tag = 0;
483
484        let error = crypto_secretstream_xchacha20poly1305_push(
485            &mut state,
486            &mut [],
487            b"message",
488            None,
489            Tag::MESSAGE.bits(),
490        )
491        .expect_err("ciphertext must include secretstream overhead");
492        assert!(matches!(
493            error,
494            Error::InvalidLength {
495                context: crate::ErrorContext::Ciphertext,
496                ..
497            }
498        ));
499
500        let short_ciphertext = [0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES - 1];
501        let error = crypto_secretstream_xchacha20poly1305_pull(
502            &mut state,
503            &mut [],
504            &mut tag,
505            &short_ciphertext,
506            None,
507        )
508        .expect_err("ciphertext must include secretstream overhead");
509        assert!(matches!(
510            error,
511            Error::InvalidLength {
512                context: crate::ErrorContext::Ciphertext,
513                ..
514            }
515        ));
516
517        let ciphertext = [0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES + 1];
518        let error = crypto_secretstream_xchacha20poly1305_pull(
519            &mut state,
520            &mut [],
521            &mut tag,
522            &ciphertext,
523            None,
524        )
525        .expect_err("the message buffer must hold the plaintext");
526        assert!(matches!(
527            error,
528            Error::InvalidLength {
529                context: crate::ErrorContext::Message,
530                ..
531            }
532        ));
533    }
534
535    #[test]
536    fn pull_authenticates_before_mutating_outputs_or_state() {
537        let key = Key::default();
538        let mut push_state = State::new();
539        let mut header = Header::default();
540        crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut header, &key);
541
542        let plaintext = b"do not publish unauthenticated plaintext";
543        let mut ciphertext =
544            vec![0u8; plaintext.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
545        crypto_secretstream_xchacha20poly1305_push(
546            &mut push_state,
547            &mut ciphertext,
548            plaintext,
549            Some(b"associated data"),
550            Tag::FINAL.bits(),
551        )
552        .expect("push failed");
553
554        let mut pull_state = State::new();
555        crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &header, &key);
556        let original_state = pull_state.clone();
557        let mut tampered = ciphertext.clone();
558        *tampered.last_mut().expect("authentication tag") ^= 1;
559        let mut output = vec![0xa5; plaintext.len()];
560        let original_output = output.clone();
561        let mut tag = 0x5a;
562
563        assert!(matches!(
564            crypto_secretstream_xchacha20poly1305_pull(
565                &mut pull_state,
566                &mut output,
567                &mut tag,
568                &tampered,
569                Some(b"associated data"),
570            ),
571            Err(Error::AuthenticationFailed)
572        ));
573        assert_eq!(output, original_output);
574        assert_eq!(tag, 0x5a);
575        assert!(pull_state == original_state);
576
577        crypto_secretstream_xchacha20poly1305_pull(
578            &mut pull_state,
579            &mut output,
580            &mut tag,
581            &ciphertext,
582            Some(b"associated data"),
583        )
584        .expect("state must remain usable after authentication failure");
585        assert_eq!(output, plaintext);
586        assert_eq!(tag, Tag::FINAL.bits());
587    }
588
589    #[test]
590    fn length_block_uses_fixed_width_little_endian_values() {
591        let lengths = secretstream_length_block(0x0102_0304, 0x0506_0708);
592
593        assert_eq!(&lengths[..8], &0x0102_0304u64.to_le_bytes());
594        assert_eq!(&lengths[8..], &(64u64 + 0x0506_0708).to_le_bytes());
595    }
596
597    #[test]
598    fn test_sizes() {
599        use crate::constants::*;
600
601        const _: () = assert!(
602            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
603                == CRYPTO_CORE_HCHACHA20_INPUTBYTES
604                    + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
605        );
606
607        const _: () = assert!(
608            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
609                == CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES
610        );
611
612        const _: () = assert!(
613            CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES
614                == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
615                    + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
616        );
617
618        const _: () = assert!(
619            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
620                <= CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
621        );
622
623        const _: () = assert!(
624            CRYPTO_ONETIMEAUTH_POLY1305_BYTES >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
625        );
626
627        #[cfg(target_pointer_width = "32")]
628        {
629            assert_eq!(
630                CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
631                usize::MAX - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
632            );
633            assert_eq!(
634                CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
635                usize::MAX - CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES
636            );
637            assert_eq!(
638                CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
639                usize::MAX - CRYPTO_SECRETBOX_MACBYTES
640            );
641        }
642    }
643
644    #[test]
645    fn test_secretstream_large_aad() {
646        let mut key = Key::default();
647        crypto_secretstream_xchacha20poly1305_keygen(&mut key);
648
649        let mut push_state = State::new();
650        let mut push_header = Header::default();
651        crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
652
653        let message = b"hello world";
654        let large_aad = vec![0x42u8; 328]; // 328 bytes of 0x42
655
656        let mut ciphertext =
657            vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
658        crypto_secretstream_xchacha20poly1305_push(
659            &mut push_state,
660            &mut ciphertext,
661            message,
662            Some(&large_aad),
663            Tag::MESSAGE.bits(),
664        )
665        .expect("push failed");
666
667        let mut pull_state = State::new();
668        crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
669
670        let mut decrypted = vec![0u8; message.len()];
671        let mut tag = 0u8;
672
673        crypto_secretstream_xchacha20poly1305_pull(
674            &mut pull_state,
675            &mut decrypted,
676            &mut tag,
677            &ciphertext,
678            Some(&large_aad),
679        )
680        .expect("pull failed");
681
682        assert_eq!(message.as_slice(), decrypted.as_slice());
683        assert_eq!(tag, Tag::MESSAGE.bits());
684
685        // Test with wrong AAD should fail
686        let mut wrong_aad = large_aad.clone();
687        wrong_aad[100] = 0x43; // Change one byte
688
689        let mut wrong_aad_pull_state = State::new();
690        crypto_secretstream_xchacha20poly1305_init_pull(
691            &mut wrong_aad_pull_state,
692            &push_header,
693            &key,
694        );
695
696        let mut decrypted = vec![0u8; message.len()];
697        let mut tag = 0u8;
698
699        assert!(
700            crypto_secretstream_xchacha20poly1305_pull(
701                &mut wrong_aad_pull_state,
702                &mut decrypted,
703                &mut tag,
704                &ciphertext,
705                Some(&wrong_aad),
706            )
707            .is_err()
708        );
709    }
710
711    #[test]
712    fn test_secretstream_small_aad() {
713        let mut key = Key::default();
714        crypto_secretstream_xchacha20poly1305_keygen(&mut key);
715
716        let mut push_state = State::new();
717        let mut push_header = Header::default();
718        crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
719
720        let message = b"hello world";
721        let small_aad = b"abc"; // 3 bytes of AAD
722
723        let mut ciphertext =
724            vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
725        crypto_secretstream_xchacha20poly1305_push(
726            &mut push_state,
727            &mut ciphertext,
728            message,
729            Some(small_aad),
730            Tag::MESSAGE.bits(),
731        )
732        .expect("push failed");
733
734        let mut pull_state = State::new();
735        crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
736
737        let mut decrypted = vec![0u8; message.len()];
738        let mut tag = 0u8;
739
740        crypto_secretstream_xchacha20poly1305_pull(
741            &mut pull_state,
742            &mut decrypted,
743            &mut tag,
744            &ciphertext,
745            Some(small_aad),
746        )
747        .expect("pull failed");
748
749        assert_eq!(message.as_slice(), decrypted.as_slice());
750        assert_eq!(tag, Tag::MESSAGE.bits());
751
752        // Test with wrong AAD should fail
753        let wrong_aad = b"xyz"; // Different 3 byte AAD
754
755        let mut wrong_aad_pull_state = State::new();
756        crypto_secretstream_xchacha20poly1305_init_pull(
757            &mut wrong_aad_pull_state,
758            &push_header,
759            &key,
760        );
761
762        let mut decrypted = vec![0u8; message.len()];
763        let mut tag = 0u8;
764
765        assert!(
766            crypto_secretstream_xchacha20poly1305_pull(
767                &mut wrong_aad_pull_state,
768                &mut decrypted,
769                &mut tag,
770                &ciphertext,
771                Some(wrong_aad),
772            )
773            .is_err()
774        );
775    }
776
777    #[cfg(dryoc_native_tests)]
778    mod native_tests {
779        use super::*;
780
781        #[test]
782        fn test_secretstream_basic_push() {
783            use base64::Engine as _;
784            use base64::engine::general_purpose;
785            use libsodium_sys::{
786                crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
787                crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
788                crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
789                crypto_secretstream_xchacha20poly1305_state,
790            };
791
792            use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
793            use crate::dryocstream::Tag;
794
795            let mut key = Key::default();
796            crypto_secretstream_xchacha20poly1305_keygen(&mut key);
797
798            let mut push_state = State::new();
799            let mut push_header = Header::default();
800            crypto_secretstream_xchacha20poly1305_init_push(
801                &mut push_state,
802                &mut push_header,
803                &key,
804            );
805            let push_state_init = push_state.clone();
806
807            let message = b"hello";
808            let mut output =
809                vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
810            let aad = b"";
811            let tag = Tag::MESSAGE.bits();
812            crypto_secretstream_xchacha20poly1305_push(
813                &mut push_state,
814                &mut output,
815                message,
816                Some(aad),
817                tag,
818            )
819            .expect("push failed");
820
821            let mut so_output = output.clone();
822            unsafe {
823                use libc::{c_uchar, c_ulonglong};
824                let mut so_state = crypto_secretstream_xchacha20poly1305_state {
825                    k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
826                    nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
827                    _pad: [0u8; 8],
828                };
829                so_state.k.copy_from_slice(&push_state_init.k);
830                so_state.nonce.copy_from_slice(&push_state_init.nonce);
831                let mut clen_p: c_ulonglong = 0;
832                let ret = so_crypto_secretstream_xchacha20poly1305_push(
833                    &mut so_state,
834                    so_output.as_mut_ptr(),
835                    &mut clen_p,
836                    message.as_ptr(),
837                    message.len() as u64,
838                    aad.as_ptr(),
839                    aad.len() as u64,
840                    0,
841                );
842                assert_eq!(ret, 0);
843                so_output.resize(clen_p as usize, 0);
844                assert_eq!(
845                    general_purpose::STANDARD.encode(&so_output),
846                    general_purpose::STANDARD.encode(&output)
847                );
848                assert_eq!(
849                    general_purpose::STANDARD.encode(so_state.k),
850                    general_purpose::STANDARD.encode(push_state.k)
851                );
852                assert_eq!(
853                    general_purpose::STANDARD.encode(so_state.nonce),
854                    general_purpose::STANDARD.encode(push_state.nonce)
855                );
856
857                let mut so_state = crypto_secretstream_xchacha20poly1305_state {
858                    k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
859                    nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
860                    _pad: [0u8; 8],
861                };
862                let mut mlen_p: c_ulonglong = 0;
863                let mut tag_p: c_uchar = 0;
864                let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
865                    &mut so_state,
866                    push_header.as_ptr(),
867                    key.as_ptr(),
868                );
869                assert_eq!(ret, 0);
870                assert_eq!(
871                    general_purpose::STANDARD.encode(so_state.k),
872                    general_purpose::STANDARD.encode(push_state_init.k)
873                );
874                assert_eq!(
875                    general_purpose::STANDARD.encode(so_state.nonce),
876                    general_purpose::STANDARD.encode(push_state_init.nonce)
877                );
878                assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
879                let ret = so_crypto_secretstream_xchacha20poly1305_pull(
880                    &mut so_state,
881                    so_output.as_mut_ptr(),
882                    &mut mlen_p,
883                    &mut tag_p,
884                    output.as_ptr(),
885                    output.len() as u64,
886                    aad.as_ptr(),
887                    aad.len() as u64,
888                );
889                assert_eq!(ret, 0);
890                so_output.resize(mlen_p as usize, 0);
891            }
892            assert_eq!(
893                general_purpose::STANDARD.encode(message),
894                general_purpose::STANDARD.encode(&so_output)
895            );
896
897            let mut pull_state = State::default();
898            crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
899
900            assert_eq!(
901                general_purpose::STANDARD.encode(pull_state.k),
902                general_purpose::STANDARD.encode(push_state_init.k)
903            );
904            assert_eq!(
905                general_purpose::STANDARD.encode(pull_state.nonce),
906                general_purpose::STANDARD.encode(push_state_init.nonce)
907            );
908
909            let mut pull_result_message =
910                vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
911            let mut pull_result_tag = 0u8;
912            crypto_secretstream_xchacha20poly1305_pull(
913                &mut pull_state,
914                &mut pull_result_message,
915                &mut pull_result_tag,
916                &output,
917                Some(&[]),
918            )
919            .expect("pull failed");
920
921            assert_eq!(Tag::MESSAGE, Tag::from_bits(tag).expect("tag"));
922            assert_eq!(
923                general_purpose::STANDARD.encode(&pull_result_message),
924                general_purpose::STANDARD.encode(message)
925            );
926        }
927
928        #[test]
929        fn test_rekey() {
930            use base64::Engine as _;
931            use base64::engine::general_purpose;
932            use libsodium_sys::{
933                crypto_secretstream_xchacha20poly1305_rekey as so_crypto_secretstream_xchacha20poly1305_rekey,
934                crypto_secretstream_xchacha20poly1305_state,
935            };
936
937            use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
938
939            let mut key = Key::default();
940            crypto_secretstream_xchacha20poly1305_keygen(&mut key);
941
942            let mut push_state = State::default();
943            let mut push_header: Header = Header::default();
944            crypto_secretstream_xchacha20poly1305_init_push(
945                &mut push_state,
946                &mut push_header,
947                &key,
948            );
949            let push_state_init = push_state.clone();
950
951            crypto_secretstream_xchacha20poly1305_rekey(&mut push_state);
952
953            let mut so_state = crypto_secretstream_xchacha20poly1305_state {
954                k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
955                nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
956                _pad: [0u8; 8],
957            };
958            so_state.k.copy_from_slice(&push_state_init.k);
959            so_state.nonce.copy_from_slice(&push_state_init.nonce);
960            unsafe {
961                so_crypto_secretstream_xchacha20poly1305_rekey(&mut so_state);
962            }
963            assert_eq!(
964                general_purpose::STANDARD.encode(so_state.k),
965                general_purpose::STANDARD.encode(push_state.k)
966            );
967            assert_eq!(
968                general_purpose::STANDARD.encode(so_state.nonce),
969                general_purpose::STANDARD.encode(push_state.nonce)
970            );
971        }
972
973        #[test]
974        fn test_secretstream_lots_of_messages_push() {
975            use base64::Engine as _;
976            use base64::engine::general_purpose;
977            use libc::{c_uchar, c_ulonglong};
978            use libsodium_sys::{
979                crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
980                crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
981                crypto_secretstream_xchacha20poly1305_state,
982            };
983
984            use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
985            use crate::dryocstream::Tag;
986
987            let mut key = Key::default();
988            crypto_secretstream_xchacha20poly1305_keygen(&mut key);
989
990            let mut push_state = State::new();
991            let mut push_header = Header::default();
992            crypto_secretstream_xchacha20poly1305_init_push(
993                &mut push_state,
994                &mut push_header,
995                &key,
996            );
997            let push_state_init = push_state.clone();
998
999            let mut pull_state = State::default();
1000            crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
1001
1002            assert_eq!(
1003                general_purpose::STANDARD.encode(pull_state.k),
1004                general_purpose::STANDARD.encode(push_state_init.k)
1005            );
1006            assert_eq!(
1007                general_purpose::STANDARD.encode(pull_state.nonce),
1008                general_purpose::STANDARD.encode(push_state_init.nonce)
1009            );
1010
1011            let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1012                k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1013                nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1014                _pad: [0u8; 8],
1015            };
1016            so_state.k.copy_from_slice(&push_state_init.k);
1017            so_state.nonce.copy_from_slice(&push_state_init.nonce);
1018
1019            let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1020                k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1021                nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1022                _pad: [0u8; 8],
1023            };
1024            let mut mlen_p: c_ulonglong = 0;
1025            let mut tag_p: c_uchar = 0;
1026            unsafe {
1027                let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
1028                    &mut so_state,
1029                    push_header.as_ptr(),
1030                    key.as_ptr(),
1031                );
1032                assert_eq!(ret, 0);
1033            }
1034            assert_eq!(
1035                general_purpose::STANDARD.encode(so_state.k),
1036                general_purpose::STANDARD.encode(push_state_init.k)
1037            );
1038            assert_eq!(
1039                general_purpose::STANDARD.encode(so_state.nonce),
1040                general_purpose::STANDARD.encode(push_state_init.nonce)
1041            );
1042
1043            for i in 0..100 {
1044                let message = format!("hello {}", i);
1045                let aad = format!("aad {}", i);
1046                let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
1047
1048                let mut output =
1049                    vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1050                crypto_secretstream_xchacha20poly1305_push(
1051                    &mut push_state,
1052                    &mut output,
1053                    message.as_bytes(),
1054                    Some(aad.as_bytes()),
1055                    tag.bits(),
1056                )
1057                .expect("push failed");
1058
1059                let mut so_output = output.clone();
1060                unsafe {
1061                    assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
1062                    let ret = so_crypto_secretstream_xchacha20poly1305_pull(
1063                        &mut so_state,
1064                        so_output.as_mut_ptr(),
1065                        &mut mlen_p,
1066                        &mut tag_p,
1067                        output.as_ptr(),
1068                        output.len() as u64,
1069                        aad.as_ptr(),
1070                        aad.len() as u64,
1071                    );
1072                    assert_eq!(ret, 0);
1073                    so_output.resize(mlen_p as usize, 0);
1074                }
1075                assert_eq!(
1076                    general_purpose::STANDARD.encode(&message),
1077                    general_purpose::STANDARD.encode(&so_output)
1078                );
1079
1080                let mut pull_result_message =
1081                    vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1082                let mut pull_result_tag = 0u8;
1083                crypto_secretstream_xchacha20poly1305_pull(
1084                    &mut pull_state,
1085                    &mut pull_result_message,
1086                    &mut pull_result_tag,
1087                    &output,
1088                    Some(aad.as_bytes()),
1089                )
1090                .expect("pull failed");
1091
1092                assert_eq!(tag, Tag::from_bits(pull_result_tag).expect("tag"));
1093                assert_eq!(
1094                    general_purpose::STANDARD.encode(&pull_result_message),
1095                    general_purpose::STANDARD.encode(&message)
1096                );
1097            }
1098        }
1099
1100        #[test]
1101        fn test_secretstream_basic_pull() {
1102            use base64::Engine as _;
1103            use base64::engine::general_purpose;
1104            use libc::c_ulonglong;
1105            use libsodium_sys::{
1106                crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
1107                crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
1108                crypto_secretstream_xchacha20poly1305_state,
1109            };
1110
1111            use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1112
1113            let mut key = Key::default();
1114            crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1115
1116            let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1117                k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1118                nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1119                _pad: [0u8; 8],
1120            };
1121            let mut so_header = Header::default();
1122            unsafe {
1123                so_crypto_secretstream_xchacha20poly1305_init_push(
1124                    &mut so_state,
1125                    so_header.as_mut_ptr(),
1126                    key.as_ptr(),
1127                );
1128            }
1129
1130            let mut pull_state = State::new();
1131            crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
1132
1133            let message = b"hello";
1134            let aad = b"aad";
1135            let mut so_output =
1136                vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1137            let mut clen_p: c_ulonglong = 0;
1138
1139            unsafe {
1140                let ret = so_crypto_secretstream_xchacha20poly1305_push(
1141                    &mut so_state,
1142                    so_output.as_mut_ptr(),
1143                    &mut clen_p,
1144                    message.as_ptr(),
1145                    message.len() as u64,
1146                    aad.as_ptr(),
1147                    aad.len() as u64,
1148                    0,
1149                );
1150                assert_eq!(ret, 0);
1151                so_output.resize(clen_p as usize, 0);
1152            }
1153
1154            let mut output = vec![0u8; so_output.len()];
1155            let mut tag = 0u8;
1156            let mlen = crypto_secretstream_xchacha20poly1305_pull(
1157                &mut pull_state,
1158                &mut output,
1159                &mut tag,
1160                &so_output,
1161                Some(aad),
1162            )
1163            .expect("decrypt failed");
1164            output.resize(mlen, 0);
1165
1166            assert_eq!(
1167                general_purpose::STANDARD.encode(&output),
1168                general_purpose::STANDARD.encode(message)
1169            );
1170            assert_eq!(tag, 0);
1171        }
1172
1173        #[test]
1174        fn test_secretstream_lots_of_messages_pull() {
1175            use base64::Engine as _;
1176            use base64::engine::general_purpose;
1177            use libc::c_ulonglong;
1178            use libsodium_sys::{
1179                crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
1180                crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
1181                crypto_secretstream_xchacha20poly1305_state,
1182            };
1183
1184            use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1185            use crate::dryocstream::Tag;
1186
1187            let mut key = Key::default();
1188            crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1189
1190            let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1191                k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1192                nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1193                _pad: [0u8; 8],
1194            };
1195            let mut so_header = Header::default();
1196            unsafe {
1197                so_crypto_secretstream_xchacha20poly1305_init_push(
1198                    &mut so_state,
1199                    so_header.as_mut_ptr(),
1200                    key.as_ptr(),
1201                );
1202            }
1203
1204            let mut pull_state = State::new();
1205            crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
1206
1207            for i in 0..100 {
1208                let message = format!("hello {}", i);
1209                let aad = format!("aad {}", i);
1210                let mut so_output =
1211                    vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1212                let mut clen_p: c_ulonglong = 0;
1213
1214                let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
1215
1216                unsafe {
1217                    let ret = so_crypto_secretstream_xchacha20poly1305_push(
1218                        &mut so_state,
1219                        so_output.as_mut_ptr(),
1220                        &mut clen_p,
1221                        message.as_ptr(),
1222                        message.len() as u64,
1223                        aad.as_ptr(),
1224                        aad.len() as u64,
1225                        tag.bits(),
1226                    );
1227                    assert_eq!(ret, 0);
1228                    so_output.resize(clen_p as usize, 0);
1229                }
1230
1231                let mut output =
1232                    vec![0u8; so_output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1233                let mut outtag = 0u8;
1234                crypto_secretstream_xchacha20poly1305_pull(
1235                    &mut pull_state,
1236                    &mut output,
1237                    &mut outtag,
1238                    &so_output,
1239                    Some(aad.as_bytes()),
1240                )
1241                .expect("decrypt failed");
1242
1243                assert_eq!(
1244                    general_purpose::STANDARD.encode(so_state.k),
1245                    general_purpose::STANDARD.encode(pull_state.k)
1246                );
1247                assert_eq!(
1248                    general_purpose::STANDARD.encode(so_state.nonce),
1249                    general_purpose::STANDARD.encode(pull_state.nonce)
1250                );
1251
1252                assert_eq!(
1253                    general_purpose::STANDARD.encode(&output),
1254                    general_purpose::STANDARD.encode(&message)
1255                );
1256                assert_eq!(outtag, tag.bits());
1257            }
1258        }
1259    }
1260}