// MIT License

// Copyright (c) 2018-2021 The orion Developers

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//! # Parameters:
//! - `secret_key`: The secret key.
//! - `nonce`: The nonce value.
//! - `initial_counter`: The initial counter value. In most cases, this is `0`.
//! - `ciphertext`: The encrypted data.
//! - `plaintext`: The data to be encrypted.
//! - `dst_out`: Destination array that will hold the ciphertext/plaintext after
//!   encryption/decryption.
//!
//! `nonce`: "Counters and LFSRs are both acceptable ways of generating unique
//! nonces, as is encrypting a counter using a block cipher with a 64-bit block
//! size such as DES.  Note that it is not acceptable to use a truncation of a
//! counter encrypted with block ciphers with 128-bit or 256-bit blocks,
//! because such a truncation may repeat after a short time." See [RFC]
//! for more information.
//!
//! # Errors:
//! An error will be returned if:
//! - The length of `dst_out` is less than `plaintext` or `ciphertext`.
//! - `plaintext` or `ciphertext` is empty.
//! - The `initial_counter` is high enough to cause a potential overflow.
//!
//! Even though `dst_out` is allowed to be of greater length than `plaintext`,
//! the `ciphertext` produced by `chacha20`/`xchacha20` will always be of the
//! same length as the `plaintext`.
//!
//! # Panics:
//! A panic will occur if:
//! - More than `2^32-1` keystream blocks are processed or more than `2^32-1 * 64`
//!   bytes of data are processed.
//!
//! # Security:
//! - It is critical for security that a given nonce is not re-used with a given
//!   key. Should this happen, the security of all data that has been encrypted
//!   with that given key is compromised.
//! - Functions herein do not provide any data integrity. If you need
//!   data integrity, which is nearly ***always the case***, you should use an
//!   AEAD construction instead. See the [`aead`](super::aead) module for this.
//! - Only a nonce for XChaCha20 is big enough to be randomly generated using a CSPRNG.
//! - To securely generate a strong key, use [`SecretKey::generate()`].
//!
//! # Recommendation:
//! - It is recommended to use [`XChaCha20Poly1305`] when possible.
//!
//! # Example:
//! ```rust
//! use orion::hazardous::stream::chacha20;
//!
//! let secret_key = chacha20::SecretKey::generate();
//!
//! // WARNING: This nonce is only meant for demonstration and should not
//! // be repeated. Please read the security section.
//! let nonce = chacha20::Nonce::from([0u8; 12]);
//! let message = "Data to protect".as_bytes();
//!
//! // The length of this message is 15.
//!
//! let mut dst_out_pt = [0u8; 15];
//! let mut dst_out_ct = [0u8; 15];
//!
//! chacha20::encrypt(&secret_key, &nonce, 0, message, &mut dst_out_ct)?;
//!
//! chacha20::decrypt(&secret_key, &nonce, 0, &dst_out_ct, &mut dst_out_pt)?;
//!
//! assert_eq!(dst_out_pt, message);
//! # Ok::<(), orion::errors::UnknownCryptoError>(())
//! ```
//! [`SecretKey::generate()`]: chacha20::SecretKey::generate()
//! [`XChaCha20Poly1305`]: super::aead::xchacha20poly1305
//! [RFC]: https://tools.ietf.org/html/rfc8439
use crate::errors::UnknownCryptoError;
use crate::util::endianness::load_u32_le;
use crate::util::u32x4::U32x4;
use zeroize::{Zeroize, Zeroizing};

/// The key size for ChaCha20.
pub const CHACHA_KEYSIZE: usize = 32;
/// The nonce size for IETF ChaCha20.
pub const IETF_CHACHA_NONCESIZE: usize = 12;
/// The blocksize which ChaCha20 operates on.
pub(crate) const CHACHA_BLOCKSIZE: usize = 64;
/// The size of the subkey that HChaCha20 returns.
const HCHACHA_OUTSIZE: usize = 32;
/// The nonce size for HChaCha20.
pub(crate) const HCHACHA_NONCESIZE: usize = 16;

construct_secret_key! {
    /// A type to represent the `SecretKey` that Chacha20, XChaCha20, ChaCha20-Poly1305 and
    /// XChaCha20-Poly1305 use.
    ///
    /// # Errors:
    /// An error will be returned if:
    /// - `slice` is not 32 bytes.
    ///
    /// # Panics:
    /// A panic will occur if:
    /// - Failure to generate random bytes securely.
    (SecretKey, test_secret_key, CHACHA_KEYSIZE, CHACHA_KEYSIZE, CHACHA_KEYSIZE)
}

impl_from_trait!(SecretKey, CHACHA_KEYSIZE);

construct_public! {
    /// A type that represents a `Nonce` that ChaCha20 and ChaCha20-Poly1305 use.
    ///
    /// # Errors:
    /// An error will be returned if:
    /// - `slice` is not 12 bytes.
    (Nonce, test_nonce, IETF_CHACHA_NONCESIZE, IETF_CHACHA_NONCESIZE)
}

impl_from_trait!(Nonce, IETF_CHACHA_NONCESIZE);

macro_rules! ROUND {
    ($r0:expr, $r1:expr, $r2:expr, $r3:expr) => {
        $r0 = $r0.wrapping_add($r1);
        $r3 = ($r3 ^ $r0).rotate_left(16);

        $r2 = $r2.wrapping_add($r3);
        $r1 = ($r1 ^ $r2).rotate_left(12);

        $r0 = $r0.wrapping_add($r1);
        $r3 = ($r3 ^ $r0).rotate_left(8);

        $r2 = $r2.wrapping_add($r3);
        $r1 = ($r1 ^ $r2).rotate_left(7);
    };
}

macro_rules! DOUBLE_ROUND {
    ($r0:expr, $r1:expr, $r2:expr, $r3:expr) => {
        ROUND!($r0, $r1, $r2, $r3);

        // Shuffle
        $r1 = $r1.shl_1();
        $r2 = $r2.shl_2();
        $r3 = $r3.shl_3();

        ROUND!($r0, $r1, $r2, $r3);

        // Unshuffle
        $r1 = $r1.shl_3();
        $r2 = $r2.shl_2();
        $r3 = $r3.shl_1();
    };
}
pub(crate) struct ChaCha20 {
    state: [U32x4; 4],
    internal_counter: u32,
    is_ietf: bool,
}

impl Drop for ChaCha20 {
    fn drop(&mut self) {
        self.state.iter_mut().zeroize();
    }
}

impl ChaCha20 {
    #[allow(clippy::unreadable_literal)]
    /// Initialize either a ChaCha or HChaCha state with a `secret_key` and
    /// `nonce`.
    pub(crate) fn new(sk: &[u8], n: &[u8], is_ietf: bool) -> Result<Self, UnknownCryptoError> {
        debug_assert!(sk.len() == CHACHA_KEYSIZE);
        if (n.len() != IETF_CHACHA_NONCESIZE) && is_ietf {
            return Err(UnknownCryptoError);
        }
        if (n.len() != HCHACHA_NONCESIZE) && !is_ietf {
            return Err(UnknownCryptoError);
        }

        // Row 0 with constants.
        let r0 = U32x4(0x61707865, 0x3320646e, 0x79622d32, 0x6b206574);

        // Row 1 and 2 with secret key.
        let r1 = U32x4(
            load_u32_le(&sk[0..4]),
            load_u32_le(&sk[4..8]),
            load_u32_le(&sk[8..12]),
            load_u32_le(&sk[12..16]),
        );

        let r2 = U32x4(
            load_u32_le(&sk[16..20]),
            load_u32_le(&sk[20..24]),
            load_u32_le(&sk[24..28]),
            load_u32_le(&sk[28..32]),
        );

        // Row 3 with counter and nonce if IETF,
        // but only nonce if HChaCha20.
        let r3 = if is_ietf {
            U32x4(
                0, // Default counter
                load_u32_le(&n[0..4]),
                load_u32_le(&n[4..8]),
                load_u32_le(&n[8..12]),
            )
        } else {
            U32x4(
                load_u32_le(&n[0..4]),
                load_u32_le(&n[4..8]),
                load_u32_le(&n[8..12]),
                load_u32_le(&n[12..16]),
            )
        };

        Ok(Self {
            state: [r0, r1, r2, r3],
            internal_counter: 0,
            is_ietf,
        })
    }

    /// Process the next keystream and copy into destination array.
    pub(crate) fn keystream_block(&mut self, block_counter: u32, inplace: &mut [u8]) {
        debug_assert!(if self.is_ietf {
            inplace.len() == CHACHA_BLOCKSIZE
        } else {
            inplace.len() == HCHACHA_OUTSIZE
        });

        if self.is_ietf {
            self.state[3].0 = block_counter;
        }

        // If this panics, max amount of keystream blocks
        // have been retrieved.
        self.internal_counter = self.internal_counter.checked_add(1).unwrap();

        let mut wr0 = self.state[0];
        let mut wr1 = self.state[1];
        let mut wr2 = self.state[2];
        let mut wr3 = self.state[3];

        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);
        DOUBLE_ROUND!(wr0, wr1, wr2, wr3);

        let mut iter = inplace.chunks_exact_mut(16);

        if self.is_ietf {
            wr0 = wr0.wrapping_add(self.state[0]);
            wr1 = wr1.wrapping_add(self.state[1]);
            wr2 = wr2.wrapping_add(self.state[2]);
            wr3 = wr3.wrapping_add(self.state[3]);

            wr0.store_into_le(iter.next().unwrap());
            wr1.store_into_le(iter.next().unwrap());
            wr2.store_into_le(iter.next().unwrap());
            wr3.store_into_le(iter.next().unwrap());
        } else {
            wr0.store_into_le(iter.next().unwrap());
            wr3.store_into_le(iter.next().unwrap());
        }
    }
}

/// XOR keystream into destination array using a temporary buffer for each keystream block.
pub(crate) fn xor_keystream(
    ctx: &mut ChaCha20,
    initial_counter: u32,
    tmp_block: &mut [u8],
    bytes: &mut [u8],
) -> Result<(), UnknownCryptoError> {
    debug_assert!(tmp_block.len() == CHACHA_BLOCKSIZE);
    if bytes.is_empty() {
        return Err(UnknownCryptoError);
    }

    for (ctr, out_block) in bytes.chunks_mut(CHACHA_BLOCKSIZE).enumerate() {
        match initial_counter.checked_add(ctr as u32) {
            Some(counter) => {
                ctx.keystream_block(counter, tmp_block);
                xor_slices!(tmp_block, out_block);
            }
            None => return Err(UnknownCryptoError),
        }
    }

    Ok(())
}

/// In-place IETF ChaCha20 encryption as specified in the [RFC 8439](https://tools.ietf.org/html/rfc8439).
pub(crate) fn encrypt_in_place(
    secret_key: &SecretKey,
    nonce: &Nonce,
    initial_counter: u32,
    bytes: &mut [u8],
) -> Result<(), UnknownCryptoError> {
    if bytes.is_empty() {
        return Err(UnknownCryptoError);
    }

    let mut ctx = ChaCha20::new(secret_key.unprotected_as_bytes(), nonce.as_ref(), true)?;
    let mut keystream_block = Zeroizing::new([0u8; CHACHA_BLOCKSIZE]);
    xor_keystream(&mut ctx, initial_counter, keystream_block.as_mut(), bytes)
}

#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
/// IETF ChaCha20 encryption as specified in the [RFC 8439](https://tools.ietf.org/html/rfc8439).
pub fn encrypt(
    secret_key: &SecretKey,
    nonce: &Nonce,
    initial_counter: u32,
    plaintext: &[u8],
    dst_out: &mut [u8],
) -> Result<(), UnknownCryptoError> {
    if dst_out.len() < plaintext.len() {
        return Err(UnknownCryptoError);
    }
    if plaintext.is_empty() {
        return Err(UnknownCryptoError);
    }

    dst_out[..plaintext.len()].copy_from_slice(plaintext);
    encrypt_in_place(
        secret_key,
        nonce,
        initial_counter,
        &mut dst_out[..plaintext.len()],
    )
}

#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
/// IETF ChaCha20 decryption as specified in the [RFC 8439](https://tools.ietf.org/html/rfc8439).
pub fn decrypt(
    secret_key: &SecretKey,
    nonce: &Nonce,
    initial_counter: u32,
    ciphertext: &[u8],
    dst_out: &mut [u8],
) -> Result<(), UnknownCryptoError> {
    encrypt(secret_key, nonce, initial_counter, ciphertext, dst_out)
}

/// HChaCha20 as specified in the [draft-RFC](https://github.com/bikeshedders/xchacha-rfc/blob/master).
pub(super) fn hchacha20(
    secret_key: &SecretKey,
    nonce: &[u8],
) -> Result<[u8; HCHACHA_OUTSIZE], UnknownCryptoError> {
    let mut chacha_state = ChaCha20::new(secret_key.unprotected_as_bytes(), nonce, false)?;
    let mut keystream_block = [0u8; HCHACHA_OUTSIZE];
    chacha_state.keystream_block(0, &mut keystream_block);

    Ok(keystream_block)
}

// Testing public functions in the module.
#[cfg(test)]
mod public {
    use super::*;

    #[cfg(feature = "safe_api")]
    mod test_encrypt_decrypt {
        use super::*;
        use crate::test_framework::streamcipher_interface::*;

        impl TestingRandom for SecretKey {
            fn gen() -> Self {
                Self::generate()
            }
        }

        impl TestingRandom for Nonce {
            fn gen() -> Self {
                let mut n = [0u8; IETF_CHACHA_NONCESIZE];
                crate::util::secure_rand_bytes(&mut n).unwrap();
                Self::from_slice(&n).unwrap()
            }
        }

        #[quickcheck]
        fn prop_streamcipher_interface(input: Vec<u8>, counter: u32) -> bool {
            let secret_key = SecretKey::generate();
            let nonce = Nonce::from_slice(&[0u8; IETF_CHACHA_NONCESIZE]).unwrap();
            StreamCipherTestRunner(encrypt, decrypt, secret_key, nonce, counter, &input, None);
            test_diff_params_diff_output(&encrypt, &decrypt);

            true
        }
    }

    // hex crate uses Vec<u8>, so we need std.
    mod test_hchacha20 {
        use super::*;

        use hex::decode;

        #[test]
        fn test_nonce_length() {
            assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16],).is_ok());
            assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 17],).is_err());
            assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 15],).is_err());
            assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 0],).is_err());
        }

        #[test]
        fn test_diff_keys_diff_output() {
            let keystream1 =
                hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap();

            let keystream2 =
                hchacha20(&SecretKey::from_slice(&[1u8; 32]).unwrap(), &[0u8; 16]).unwrap();

            assert!(keystream1 != keystream2);
        }

        #[test]
        fn test_diff_nonce_diff_output() {
            let keystream1 =
                hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap();

            let keystream2 =
                hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[1u8; 16]).unwrap();

            assert!(keystream1 != keystream2);
        }

        pub fn hchacha_test_runner(key: &str, nonce: &str, output_expected: &str) {
            let actual: [u8; 32] = hchacha20(
                &SecretKey::from_slice(&decode(key).unwrap()).unwrap(),
                &decode(nonce).unwrap(),
            )
            .unwrap();

            assert_eq!(&actual, &decode(output_expected).unwrap()[..]);
        }

        // Testing against Monocypher-generated test vectors
        // https://github.com/LoupVaillant/Monocypher/tree/master/tests/gen
        // Pulled at commit: https://github.com/LoupVaillant/Monocypher/commit/39b164a5bf715d1a62689203b059144df76d98e2

        #[test]
        fn test_case_0() {
            let key = "e4e4c4054fe35a75d9c0f679ad8770d8227e68e4c1e68ce67ee88e6be251a207";
            let nonce = "48b3753cff3a6d990163e6b60da1e4e5";
            let expected_output =
                "d805447c583fd97a07a2b7ab66be621ad0fa32d63d86ac20588da90b87c1907b";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_1() {
            let key = "d6a2df78c16c96a52d4fb01ea4ecf70e81ac001b08d6577bd91ce991c4c45c46";
            let nonce = "bc84d5465fc9139bf17042ae7313181f";
            let expected_output =
                "66d1fd5e89a564b55ccf0c339455449c20dfbc9d17081c85fbb430a157777be9";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_2() {
            let key = "7afb217bd1eceeac1e133aaa9edb441fa88ea3ae0eaa06cb9911b6d218570f92";
            let nonce = "4a70a7e992b43e0b18578e892e954c40";
            let expected_output =
                "41119e28a00a9d3f24b1910495f3058f9db83cbcf12889de84a2fcd7de8dc31b";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_3() {
            let key = "a51abdb5a85d300c32f391c45d6ef4db043ddcf4214f24ea6ef6b181071f299a";
            let nonce = "a254a4606ab6a058e0c6fb5598218db7";
            let expected_output =
                "04c2f31fdcc7013ac7d10ec82e8d3628c9ab23b08bbf95d6d77ad2dec7e865d6";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_4() {
            let key = "1deb473f7d04c152e7e857736715dc7b788aca39a3c96a878019e8999c815c57";
            let nonce = "23dbfbde05e6c71f118afc0dedb5b9f8";
            let expected_output =
                "75e9a94daf28b6b8634823325c61cdcb2beeb17a8f7554cc6d5b1b1d2e3592cf";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_5() {
            let key = "dea398b2d764bca68dfc023a9821939d389e38a072cf1b413bb1517c3fe83abe";
            let nonce = "bb1cdf3a218abb1b0c01da64c24f59ee";
            let expected_output =
                "65a20993e8e69de41d38e94c0796cb7baccd6d80a6e4084e65d0d574fbcb7311";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_6() {
            let key = "d19cfb8cb3940aba546f0be57895e2cc869fe55aab069c5abcf9e7ba6444a846";
            let nonce = "e5d73f1c8c5376c1220ff3d9d53eeb65";
            let expected_output =
                "a345f5f10ec20b4a744634fbb94e94c9425699b4d57ffeab5403b8fbfb85bae7";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_7() {
            let key = "cc53599f40d6c8348c353b00172655236cddcd1879ca1f04b35f91adab70b81f";
            let nonce = "504035fc169964a5ae985e6c11b0b7bb";
            let expected_output =
                "11dda56dce88c92641177e2a6e21b11c5ca794912b3bceb9ccb375c87bcc7968";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_8() {
            let key = "18a51fd77fbffd722aa220efdd8947ca5a5c7fb1c2ebdb9ad1f603801ff22e80";
            let nonce = "314f716af9c22022fa159dbb4b4d3153";
            let expected_output =
                "14759f0e978a9f45a4696739fecb590b4ba6f06536384225333cccba074c8a68";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_9() {
            let key = "f999b20ab4769eb1d01c057c5295ed042b4536561dce32478b113adb5b605cac";
            let nonce = "75bcfcacb5e3e811b78e72e398fdd118";
            let expected_output =
                "564eb6b2ac2b92270af7c0b054cc7a721313e4ed3651b0970db9dfcdfda27220";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_10() {
            let key = "bf04c6a7ed0756a3533e3dca02109e1830b739210bd8bffe6a8a542980bd73e9";
            let nonce = "ca43cdd4eb7173476862df6d2458d6c7";
            let expected_output =
                "4f8975d01fb3525a60de55c61190471e86b95cb3e835374d58b003f55eb9819a";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_11() {
            let key = "4739a0ad2169b9c89edd74e16fbcecc748c25dc338041fc34af0f1bda20eaf3f";
            let nonce = "ff7b372aa801eb98a1298bc610280737";
            let expected_output =
                "06ccde41d10d6466859927bfc9a476dbc84064838ec721261cb548c18bd14c67";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_12() {
            let key = "50831c8cb43cd6822bf3f6fae0801cb6c843d8066b07346635365fb7d6ee54e5";
            let nonce = "c9cd6f05d76b2bd4caec8d80b58235cb";
            let expected_output =
                "6ed040d7721395fb2c74c8afe252a169ded78e6f2f889e8fb0ec1490533a8154";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_13() {
            let key = "4268543ab0eb865a948cc5b5f6e31f05f8146bd9495acc459d6d200005ee72c3";
            let nonce = "bc3e4ae3badfd79adfe46b2ae1045f78";
            let expected_output =
                "19b839a6d3424cf2a52d301e70e76cb77368cf9f60945bf43ce4c657aeb1d157";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_14() {
            let key = "382e04c969df1a2d6a963a79c58401770a383248b5d70bb4adedcbe520fed634";
            let nonce = "f513b8c2ea6ab37fe633ba7302a5db6c";
            let expected_output =
                "fd0739819bae6c98cbde7cb50a80e8d0b359567c50cec1ca7e985745c1cedb3a";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_15() {
            let key = "2aa209e24478fa1bd6f6ffabe98555e034342cbec07364c54d1e407e282ef08e";
            let nonce = "dbfdbde936c9d42df58ae15889f5c939";
            let expected_output =
                "f5047baa0acf9a603415a09b64268d77712ae902c73490e9c53db593765726db";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_16() {
            let key = "a3087eaeac1f2a58e2c2763d01b55744c4a65f4db93adff0078c63f090fb607a";
            let nonce = "90c87defd622e5f55977877cec9ed883";
            let expected_output =
                "1d882fa80248882c6bc311a693ebd06b8c09aa2776e6e90df523d12bfeeed77a";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_17() {
            let key = "12b0411228540cd6dde6e84cd2da59b1871db119e3298e3c12fe8200a47eddf0";
            let nonce = "49c971cd99f694e3b2a5e25fa37aedf0";
            let expected_output =
                "69bb83ccb7bc4deaf60cfe168cb11fad4257222c3523c2d08922564ac0fb74d2";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_18() {
            let key = "1bf32e7c679a3187e22a635d301ce98ad000ca301049f2e891e403250c3358fc";
            let nonce = "2030b227bb96e93b88f419afe9f9d660";
            let expected_output =
                "d0ed414a875a81db1e4cff7609afdbb2ffcdd575ebc17543fb92de53c6487efb";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_19() {
            let key = "e013761228051ec5a8f0c093b33fc60e2cd7a9c845434e95d4319d79d1bdaa8f";
            let nonce = "73853fbd9958e9ffc23a0ecbb7b48dbb";
            let expected_output =
                "e3f6c6da6c0300103d665dd877a8b62e23b1361bf3af5bbc2310502131d69be8";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_20() {
            let key = "a63672d582bb83d92249800324cbc9a6e5b37d36887e7c79093f58ef8f1a0015";
            let nonce = "85321bfee1714260dd6130cc768d20b1";
            let expected_output =
                "97e05360aca70058389d93be38d49fa26df01a4d3b4c4f10c3ec31e0ed64f08e";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_21() {
            let key = "4d3850f0eec0f8f349110e751c16cdb5ed05516df17479937d942c90eb1fb181";
            let nonce = "3062bd3f3f6b7668cd8fd3afce0cc752";
            let expected_output =
                "77513195542b2ab157cb2e6870c5b1ba143a8423ad276a64152ab923c6f54c06";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_22() {
            let key = "9b87dfc58eceb951e1e53d9e94793329199c42d004bc0f0dab3adf0cd702e99e";
            let nonce = "fa5ef6e59d3b201680f8e2d5a4ef7f23";
            let expected_output =
                "56a208bd87c5b486b5de50fbe4c1c476532f874147eba529cbb0cbeae8f09b94";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_23() {
            let key = "f1b6a8e102670a3829a995ae23fbc3a5639e028cd2b5f71bb90c7a1e4a8a0501";
            let nonce = "7d26e3afc3a88541f6c3f45d71f8a3cc";
            let expected_output =
                "a02140057f889e7ab36b4a5066e376dff248d13bd8072c384e23bd8fe4bf7047";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_24() {
            let key = "31a063ea4aad1b4d00db6f5228e9b9b1561a7f61812b8b79e6af4292580d02ea";
            let nonce = "4f6266d04244303304510272e383eaa5";
            let expected_output =
                "d610d44b8b3c14c7d3782f73405637fd14b7fada717665a9acbd4df6daa89adc";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_25() {
            let key = "1a8ea7099a74bafa3375b210653a0d2f40b15afd725cf5065066be1cb803dc15";
            let nonce = "8865ed8d7cca72dcf2b7c6b5d0d045bf";
            let expected_output =
                "f10cce296197a056bedbee166183ad6aaa56bdb21c3459296ca54c0bb78317d1";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_26() {
            let key = "32b063d3da484ba1843e071b61c49ce7f30ba18a4f7ef2730ecd785494839966";
            let nonce = "f593168e17311913753c59593fc66cb6";
            let expected_output =
                "f18115a9568724c25184728f563b65b737219cb0df1b3ce19a8bdcbdf7b8b2be";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_27() {
            let key = "64c1572251132fc28bf37fd8e96f2327cf7948a1126fd37175a91f483d6b3ad9";
            let nonce = "2308df7e6daa8bf3efde75f80ad72a49";
            let expected_output =
                "06a24cb90abe94cf3ee8e429d8197bc42bc769fbe81119156274f9692aa017a2";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_28() {
            let key = "ae0794009e21ad33fa4141fe5fa79fed12f6a20f51614dc130f45598e92549b1";
            let nonce = "13ed6185724507e7fa5a7e8a75b2c7a3";
            let expected_output =
                "51d1aec8d64d20e448a377bfa83ccbf71a73a3ad00d062bf6b83c549a7296ef1";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_29() {
            let key = "ad700919f36a46ea0ffa680857e30188f8a03c7c4b6c11bc39aececec2668723";
            let nonce = "3682d31887277028e2fd286f2654c681";
            let expected_output =
                "a24610a94968df2dc9d197cd0bc55cab08c9dabd444c0efcd2a47fd37016382e";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_30() {
            let key = "efd9e7ed6b340874e897337d4dcc672811a6cf4b69086e0a57c266424dc1d10e";
            let nonce = "cbaf0c822cce9e4f17b19e0ece39c180";
            let expected_output =
                "6f94a0f8ed7f3fe5ebaa3b8caba016ab64373ffc3c7b1c86e6787f31b4a905ec";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_31() {
            let key = "a4c756c03c19900280ff6cdebe5174d507c6e0860c38c3537176c58965b74a56";
            let nonce = "c52b3151bb8a149cf4f82158d57c823f";
            let expected_output =
                "50ea3d4f6a45e4a062b2d966e63cac51e093dfb6ab9df6d16bb109bc177b0a38";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_32() {
            let key = "3a90c6b427912226ff604d9abee1fb8c8d35530a0cd5808e53e308ac580f7318";
            let nonce = "fe2ab2a4933b5d90db718aa3440fbe9b";
            let expected_output =
                "2b57adcc5d26060383c87ef7e055f9aca4addcb2646cbf2cff4edc3f17b72ad5";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_33() {
            let key = "a17f09716219bdffc93a189e410a6a3e6477fbb05c7c35956c3c0c5f342355fa";
            let nonce = "0850307998642501c025e3873ebac3cc";
            let expected_output =
                "d3a58c49e9fe1ecf2eca169f4d4131cde27279053d562d0429a08ec701aaa39e";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_34() {
            let key = "d749d8379ae6d830f785ec104897bd723d34ad20c9d36bfe371df46aebc6d459";
            let nonce = "5d490a770bee4dd0be6a5a0b5e95645c";
            let expected_output =
                "c278c0079bd656f1dadf3dec692f19f25339c6557542181716d2a41379740bf2";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_35() {
            let key = "7dcbc03c27010df3320fe75b0a3ecc8983ad94217e80348fd0f3f54e54b95bb5";
            let nonce = "48dc2225a264443732b41b861590358d";
            let expected_output =
                "b244c408c74f3dcb8bcb72f834a054c554edad0363d761847003dab003ac6848";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_36() {
            let key = "543894006b73f3d70fc04b15d0c2a5dfa650be5044fb5061811b866be7f9d623";
            let nonce = "fcb077ee19421610aeb263c57faef006";
            let expected_output =
                "fb20ea177cb7225c87122f285d92faf0c2033e2497575f74505255b6d3dfcb96";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_37() {
            let key = "62d424c07a7aa5005068b262251c0667a4e2e4b12f5df7f509564517887e370b";
            let nonce = "425fabab1ce9e733ab2911b42074414e";
            let expected_output =
                "3a5eb5552cdd267c05c1e4fe936ce8f0eaf7279ff328ed9a42d6d83f7b30416c";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_38() {
            let key = "387d7247fa5055489bbd4b7d4de256de723566c1c2d3ecee8c10e7d98233dbef";
            let nonce = "90494951ec91a843f6701f8216a7326b";
            let expected_output =
                "8c4bc60a1e05004ec93aef4ae162aeff43d679ea1ba048739c700d6a168bc6cc";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_39() {
            let key = "241fd57f32e09976de4054797b9aee820e0de381d02852ac13f511918267b703";
            let nonce = "7330e60ba1c5875a0275f8ccc75cbe98";
            let expected_output =
                "9e724c5b0321e2528278a501108f1ae8a14dffaea9b6b138eacef3bd8d4dda41";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_40() {
            let key = "7c12457eb5614f87f1fdc40118906d02c602059d48ae05ae62d3d607d6bf63c6";
            let nonce = "760b802483b0e3aaa9dd4f79c6c5e93e";
            let expected_output =
                "e5b86f76fbc1f488c44e4d7f304736b752ab6cfb99fcf6910668eeefa4b67c2a";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_41() {
            let key = "6b51da45018c6bde108f81f9abfa23640b83cfe3fed34bcf6640bf0baf647daf";
            let nonce = "e9bc99acee972b5a152efa3e69e50f34";
            let expected_output =
                "1032b5d539b1c8cd6e0be96db443a08fc759bea8988384435c03b5f00b6e485f";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_42() {
            let key = "3bc12887fec8e70db73b4b48dce564d83786aca4c6b7e224163ea928771fde37";
            let nonce = "78c453b35d98deced812fc5685843565";
            let expected_output =
                "2279b063dab4c73a96abe02175e694662c65d09eb5889234293c7a1f2911e13d";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_43() {
            let key = "b73d097601d3558278bd9d7327de5fdaa2b842050b370e837ef811a496169d5f";
            let nonce = "f768878766c08c45561fdc2aad6469c1";
            let expected_output =
                "a8e85a6ab627f08ad415649a9cf9998f4b1065030f3c844e31c8185036af7558";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_44() {
            let key = "1380c3d3f873c7233c541ea4c43824ecd8bf7e11ac8486208fb685218d46736e";
            let nonce = "51103d1fae0e8e368f25480ee7328381";
            let expected_output =
                "9b84e50804449b594a54240741e21d75d31050d2612f4cbc651fea2f25bd9c1f";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_45() {
            let key = "c2f8b252a18a29c44dbfbb62cbe6c3dfd4db55378734d8110b8f20f1d1ada6dd";
            let nonce = "d4da48fb09c06580eb46bbc5ca62bfab";
            let expected_output =
                "315c3fe1009e438762a72f27e7a68b8ccb2c0b60bf79cb6e48123db0c42d4aeb";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_46() {
            let key = "40b184271b73b710d40cb63435042c9b526d1e5c3a77bfc516a2bcb4cc27ecae";
            let nonce = "b3451318590c84e311dd1e876f527d81";
            let expected_output =
                "cbbde3a3412504c1f684aa273ee691159edc9f44e306360278d63d4ee2f1faa4";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_47() {
            let key = "ec81df06c7e426b729aebb02be30c846eb228490df4a0e6c688aaaa6bf05d144";
            let nonce = "28335f2652926bfdfe32dfd789173ba8";
            let expected_output =
                "522b522e4cf9aa1e80126a446ed7b9665af3e781a3d5afdce43a5fe0cdbd4351";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_48() {
            let key = "60fa0114802ee333d7c49ccaad8108db470c882514716592e57aba26bb75049b";
            let nonce = "75db088bd1a89c6a67fb76b96c987478";
            let expected_output =
                "e004cc12dfdb74268e59958385e2a1c6ff31e31664838971629f5bbf88f4ed51";
            hchacha_test_runner(key, nonce, expected_output);
        }
        #[test]
        fn test_case_49() {
            let key = "bfba2449a607f3cca1c911d3b7d9cb972bcd84b0246189c7820032e031949f1e";
            let nonce = "97e8ad5eb5a75cc805900850969de48e";
            let expected_output =
                "19faebfbb954552fcfbf9b91f271c9397a15c641733c394a9cb731c286c68645";
            hchacha_test_runner(key, nonce, expected_output);
        }
    }
}

// Testing private functions in the module.
#[cfg(test)]
mod private {
    use super::*;

    mod test_init_state {
        use super::*;

        #[test]
        fn test_nonce_length() {
            assert!(ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; 15], true).is_err());
            assert!(ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; 10], true).is_err());
            assert!(
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE], true).is_ok()
            );

            assert!(ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; 15], false).is_err());
            assert!(ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; 17], false).is_err());
            assert!(
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; HCHACHA_NONCESIZE], false).is_ok()
            );
        }

        #[quickcheck]
        #[cfg(feature = "safe_api")]
        fn prop_test_nonce_length_ietf(nonce: Vec<u8>) -> bool {
            if nonce.len() == IETF_CHACHA_NONCESIZE {
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &nonce[..], true).is_ok()
            } else {
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &nonce[..], true).is_err()
            }
        }

        #[quickcheck]
        #[cfg(feature = "safe_api")]
        // Always fail to initialize state while the nonce is not
        // the correct length. If it is correct length, never panic.
        fn prop_test_nonce_length_hchacha(nonce: Vec<u8>) -> bool {
            if nonce.len() == HCHACHA_NONCESIZE {
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &nonce, false).is_ok()
            } else {
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &nonce, false).is_err()
            }
        }
    }

    mod test_encrypt_in_place {
        use super::*;

        #[test]
        #[should_panic]
        #[cfg(debug_assertions)]
        fn test_xor_keystream_err_bad_tmp() {
            let mut ctx =
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE], true).unwrap();
            let mut tmp = [0u8; CHACHA_BLOCKSIZE - 1];
            let mut out = [0u8; CHACHA_BLOCKSIZE];
            xor_keystream(&mut ctx, 0, &mut tmp, &mut out).unwrap();
        }

        #[test]
        fn test_xor_keystream_err_empty_input() {
            let mut ctx =
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE], true).unwrap();
            let mut tmp = [0u8; CHACHA_BLOCKSIZE];
            let mut out = [0u8; 0];
            assert!(xor_keystream(&mut ctx, 0, &mut tmp, &mut out).is_err());
        }

        #[test]
        fn test_enc_in_place_err_empty_input() {
            let n = Nonce::from([0u8; IETF_CHACHA_NONCESIZE]);
            let sk = SecretKey::from([0u8; CHACHA_KEYSIZE]);
            let mut out = [0u8; 0];
            assert!(encrypt_in_place(&sk, &n, 0, &mut out).is_err());
        }
    }

    mod test_keystream_block {
        use super::*;

        #[test]
        fn test_xor_keystream_block_ignore_counter_when_hchacha() {
            let mut chacha_state_hchacha =
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; HCHACHA_NONCESIZE], false).unwrap();

            let mut hchacha_keystream_block_zero = [0u8; HCHACHA_OUTSIZE];
            let mut hchacha_keystream_block_max = [0u8; HCHACHA_OUTSIZE];

            chacha_state_hchacha.keystream_block(0, &mut hchacha_keystream_block_zero);
            chacha_state_hchacha.keystream_block(u32::MAX, &mut hchacha_keystream_block_max);

            assert_eq!(hchacha_keystream_block_zero, hchacha_keystream_block_max);
        }

        #[cfg(debug_assertions)]
        #[test]
        #[should_panic]
        fn test_xor_keystream_block_invalid_blocksize_ietf() {
            let mut chacha_state_ietf =
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE], true).unwrap();

            let mut ietf_keystream_block = [0u8; CHACHA_BLOCKSIZE];
            let mut hchacha_keystream_block = [0u8; HCHACHA_OUTSIZE];

            chacha_state_ietf.keystream_block(0, &mut ietf_keystream_block);
            chacha_state_ietf.keystream_block(0, &mut hchacha_keystream_block);
        }

        #[cfg(debug_assertions)]
        #[test]
        #[should_panic]
        fn test_xor_keystream_block_invalid_blocksize_hchacha() {
            let mut chacha_state_hchacha =
                ChaCha20::new(&[0u8; CHACHA_KEYSIZE], &[0u8; HCHACHA_NONCESIZE], false).unwrap();

            let mut ietf_keystream_block = [0u8; CHACHA_BLOCKSIZE];
            let mut hchacha_keystream_block = [0u8; HCHACHA_OUTSIZE];

            chacha_state_hchacha.keystream_block(0, &mut hchacha_keystream_block);
            chacha_state_hchacha.keystream_block(0, &mut ietf_keystream_block);
        }

        #[test]
        #[should_panic]
        fn test_xor_keystream_panic_on_too_much_keystream_data_ietf() {
            let mut chacha_state_ietf = ChaCha20 {
                state: [
                    U32x4(0, 0, 0, 0),
                    U32x4(0, 0, 0, 0),
                    U32x4(0, 0, 0, 0),
                    U32x4(0, 0, 0, 0),
                ],
                internal_counter: (u32::MAX - 128),
                is_ietf: true,
            };

            let mut keystream_block = [0u8; CHACHA_BLOCKSIZE];

            for amount in 0..(128 + 1) {
                chacha_state_ietf.keystream_block(amount, &mut keystream_block);
            }
        }

        #[test]
        #[should_panic]
        fn test_xor_keystream_panic_on_too_much_keystream_data_hchacha() {
            let mut chacha_state_ietf = ChaCha20 {
                state: [
                    U32x4(0, 0, 0, 0),
                    U32x4(0, 0, 0, 0),
                    U32x4(0, 0, 0, 0),
                    U32x4(0, 0, 0, 0),
                ],
                internal_counter: (u32::MAX - 128),
                is_ietf: false,
            };

            let mut keystream_block = [0u8; HCHACHA_OUTSIZE];

            for _ in 0..(128 + 1) {
                chacha_state_ietf.keystream_block(0, &mut keystream_block);
            }
        }
    }
}

// Testing any test vectors that aren't put into library's /tests folder.
#[cfg(test)]
mod test_vectors {
    use super::*;

    // NOTE: These PartialEq implementation should only be available in testing.
    #[cfg(test)]
    impl PartialEq for U32x4 {
        fn eq(&self, other: &Self) -> bool {
            self.0 == other.0 && self.1 == other.1 && self.2 == other.2 && self.3 == other.3
        }
    }

    // Convenience function for testing.
    fn init(key: &[u8], nonce: &[u8]) -> Result<ChaCha20, UnknownCryptoError> {
        Ok(ChaCha20::new(key, nonce, true)?)
    }
    #[test]
    fn rfc8439_chacha20_block_results() {
        let key = [
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
            0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
            0x1c, 0x1d, 0x1e, 0x1f,
        ];
        let nonce = [
            0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
        ];
        let expected = [
            0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20,
            0x71, 0xc4, 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a,
            0xc3, 0xd4, 0x6c, 0x4e, 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2,
            0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2, 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9,
            0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e,
        ];

        let expected_init = [
            U32x4(0x61707865, 0x3320646e, 0x79622d32, 0x6b206574),
            U32x4(0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c),
            U32x4(0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c),
            U32x4(0x00000001, 0x09000000, 0x4a000000, 0x00000000),
        ];
        // Test initial key-setup
        let mut state = init(&key, &nonce).unwrap();
        // Set block counter
        state.state[3].0 = 1;
        assert!(state.state[..] == expected_init[..]);

        let mut kb = [0u8; 64];
        state.keystream_block(1, &mut kb);

        assert_eq!(kb[..], expected[..]);
    }

    #[test]
    fn rfc8439_chacha20_block_test_1() {
        let key = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ];
        let nonce = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];
        let expected = [
            0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86,
            0xbd, 0x28, 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc,
            0x8b, 0x77, 0x0d, 0xc7, 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24,
            0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
            0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,
        ];

        let mut state = init(&key, &nonce).unwrap();
        let mut kb = [0u8; 64];
        state.keystream_block(0, &mut kb);

        assert_eq!(kb[..], expected[..]);
    }

    #[test]
    fn rfc8439_chacha20_block_test_2() {
        let key = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ];
        let nonce = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];
        let expected = [
            0x9f, 0x07, 0xe7, 0xbe, 0x55, 0x51, 0x38, 0x7a, 0x98, 0xba, 0x97, 0x7c, 0x73, 0x2d,
            0x08, 0x0d, 0xcb, 0x0f, 0x29, 0xa0, 0x48, 0xe3, 0x65, 0x69, 0x12, 0xc6, 0x53, 0x3e,
            0x32, 0xee, 0x7a, 0xed, 0x29, 0xb7, 0x21, 0x76, 0x9c, 0xe6, 0x4e, 0x43, 0xd5, 0x71,
            0x33, 0xb0, 0x74, 0xd8, 0x39, 0xd5, 0x31, 0xed, 0x1f, 0x28, 0x51, 0x0a, 0xfb, 0x45,
            0xac, 0xe1, 0x0a, 0x1f, 0x4b, 0x79, 0x4d, 0x6f,
        ];

        let mut state = init(&key, &nonce).unwrap();
        let mut kb = [0u8; 64];
        state.keystream_block(1, &mut kb);

        assert_eq!(kb[..], expected[..]);
    }

    #[test]
    fn rfc8439_chacha20_block_test_3() {
        let key = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x01,
        ];
        let nonce = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];
        let expected = [
            0x3a, 0xeb, 0x52, 0x24, 0xec, 0xf8, 0x49, 0x92, 0x9b, 0x9d, 0x82, 0x8d, 0xb1, 0xce,
            0xd4, 0xdd, 0x83, 0x20, 0x25, 0xe8, 0x01, 0x8b, 0x81, 0x60, 0xb8, 0x22, 0x84, 0xf3,
            0xc9, 0x49, 0xaa, 0x5a, 0x8e, 0xca, 0x00, 0xbb, 0xb4, 0xa7, 0x3b, 0xda, 0xd1, 0x92,
            0xb5, 0xc4, 0x2f, 0x73, 0xf2, 0xfd, 0x4e, 0x27, 0x36, 0x44, 0xc8, 0xb3, 0x61, 0x25,
            0xa6, 0x4a, 0xdd, 0xeb, 0x00, 0x6c, 0x13, 0xa0,
        ];

        let mut state = init(&key, &nonce).unwrap();
        let mut kb = [0u8; 64];
        state.keystream_block(1, &mut kb);

        assert_eq!(kb[..], expected[..]);
    }

    #[test]
    fn rfc8439_chacha20_block_test_4() {
        let key = [
            0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ];
        let nonce = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];
        let expected = [
            0x72, 0xd5, 0x4d, 0xfb, 0xf1, 0x2e, 0xc4, 0x4b, 0x36, 0x26, 0x92, 0xdf, 0x94, 0x13,
            0x7f, 0x32, 0x8f, 0xea, 0x8d, 0xa7, 0x39, 0x90, 0x26, 0x5e, 0xc1, 0xbb, 0xbe, 0xa1,
            0xae, 0x9a, 0xf0, 0xca, 0x13, 0xb2, 0x5a, 0xa2, 0x6c, 0xb4, 0xa6, 0x48, 0xcb, 0x9b,
            0x9d, 0x1b, 0xe6, 0x5b, 0x2c, 0x09, 0x24, 0xa6, 0x6c, 0x54, 0xd5, 0x45, 0xec, 0x1b,
            0x73, 0x74, 0xf4, 0x87, 0x2e, 0x99, 0xf0, 0x96,
        ];

        let mut state = init(&key, &nonce).unwrap();
        let mut kb = [0u8; 64];
        state.keystream_block(2, &mut kb);

        assert_eq!(kb[..], expected[..]);
    }

    #[test]
    fn rfc8439_chacha20_block_test_5() {
        let key = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ];
        let nonce = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
        ];
        let expected = [
            0xc2, 0xc6, 0x4d, 0x37, 0x8c, 0xd5, 0x36, 0x37, 0x4a, 0xe2, 0x04, 0xb9, 0xef, 0x93,
            0x3f, 0xcd, 0x1a, 0x8b, 0x22, 0x88, 0xb3, 0xdf, 0xa4, 0x96, 0x72, 0xab, 0x76, 0x5b,
            0x54, 0xee, 0x27, 0xc7, 0x8a, 0x97, 0x0e, 0x0e, 0x95, 0x5c, 0x14, 0xf3, 0xa8, 0x8e,
            0x74, 0x1b, 0x97, 0xc2, 0x86, 0xf7, 0x5f, 0x8f, 0xc2, 0x99, 0xe8, 0x14, 0x83, 0x62,
            0xfa, 0x19, 0x8a, 0x39, 0x53, 0x1b, 0xed, 0x6d,
        ];

        let mut state = init(&key, &nonce).unwrap();
        let mut kb = [0u8; 64];
        state.keystream_block(0, &mut kb);

        assert_eq!(kb[..], expected[..]);
    }

    #[test]
    fn rfc8439_key_schedule() {
        let key = [
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
            0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
            0x1c, 0x1d, 0x1e, 0x1f,
        ];
        let nonce = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
        ];
        // First block setup expected
        let first_state = [
            U32x4(0x61707865, 0x3320646e, 0x79622d32, 0x6b206574),
            U32x4(0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c),
            U32x4(0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c),
            U32x4(0x00000001, 0x00000000, 0x4a000000, 0x00000000),
        ];
        // Second block setup expected
        let second_state = [
            U32x4(0x61707865, 0x3320646e, 0x79622d32, 0x6b206574),
            U32x4(0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c),
            U32x4(0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c),
            U32x4(0x00000002, 0x00000000, 0x4a000000, 0x00000000),
        ];

        // Expected keystream
        let expected_keystream = [
            0x22, 0x4f, 0x51, 0xf3, 0x40, 0x1b, 0xd9, 0xe1, 0x2f, 0xde, 0x27, 0x6f, 0xb8, 0x63,
            0x1d, 0xed, 0x8c, 0x13, 0x1f, 0x82, 0x3d, 0x2c, 0x06, 0xe2, 0x7e, 0x4f, 0xca, 0xec,
            0x9e, 0xf3, 0xcf, 0x78, 0x8a, 0x3b, 0x0a, 0xa3, 0x72, 0x60, 0x0a, 0x92, 0xb5, 0x79,
            0x74, 0xcd, 0xed, 0x2b, 0x93, 0x34, 0x79, 0x4c, 0xba, 0x40, 0xc6, 0x3e, 0x34, 0xcd,
            0xea, 0x21, 0x2c, 0x4c, 0xf0, 0x7d, 0x41, 0xb7, 0x69, 0xa6, 0x74, 0x9f, 0x3f, 0x63,
            0x0f, 0x41, 0x22, 0xca, 0xfe, 0x28, 0xec, 0x4d, 0xc4, 0x7e, 0x26, 0xd4, 0x34, 0x6d,
            0x70, 0xb9, 0x8c, 0x73, 0xf3, 0xe9, 0xc5, 0x3a, 0xc4, 0x0c, 0x59, 0x45, 0x39, 0x8b,
            0x6e, 0xda, 0x1a, 0x83, 0x2c, 0x89, 0xc1, 0x67, 0xea, 0xcd, 0x90, 0x1d, 0x7e, 0x2b,
            0xf3, 0x63,
        ];

        let mut state = init(&key, &nonce).unwrap();
        let mut actual_keystream = [0u8; 128];

        state.keystream_block(1, &mut actual_keystream[..64]);
        assert!(first_state == state.state);

        state.keystream_block(2, &mut actual_keystream[64..]);
        assert!(second_state == state.state);

        assert_eq!(
            actual_keystream[..expected_keystream.len()].as_ref(),
            expected_keystream.as_ref()
        );
    }
}
