diff --git a/Cargo.lock b/Cargo.lock index 4a8e630b..16b0b84b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,7 +60,6 @@ dependencies = [ "cpufeatures", "hex-literal", "proptest", - "rand_chacha", "rand_core 0.10.0-rc-4", "zeroize", ] diff --git a/chacha20/Cargo.toml b/chacha20/Cargo.toml index 2d7f48ee..dfcc7bdc 100644 --- a/chacha20/Cargo.toml +++ b/chacha20/Cargo.toml @@ -33,7 +33,6 @@ cpufeatures = "0.2" cipher = { version = "0.5.0-rc.4", features = ["dev"] } hex-literal = "1" proptest = "1" -rand_chacha = "0.9" [features] default = ["cipher"] diff --git a/chacha20/src/rng.rs b/chacha20/src/rng.rs index 1b549a43..6256476a 100644 --- a/chacha20/src/rng.rs +++ b/chacha20/src/rng.rs @@ -553,8 +553,6 @@ impl_chacha_rng!(ChaCha20Rng, ChaCha20Core, R20, abst20); #[cfg(test)] pub(crate) mod tests { - - use hex_literal::hex; use rand_core::RngCore; use super::*; @@ -564,24 +562,6 @@ pub(crate) mod tests { 26, 27, 28, 29, 30, 31, 32, ]; - #[test] - fn test_rng_output() { - let mut rng = ChaCha20Rng::from_seed(KEY); - let mut bytes = [0u8; 13]; - - rng.fill_bytes(&mut bytes); - assert_eq!( - bytes, - [177, 105, 126, 159, 198, 70, 30, 25, 131, 209, 49, 207, 105] - ); - - rng.fill_bytes(&mut bytes); - assert_eq!( - bytes, - [167, 163, 252, 19, 79, 20, 152, 128, 232, 187, 43, 93, 35] - ); - } - #[test] fn test_wrapping_add() { let mut rng = ChaCha20Rng::from_seed(KEY); @@ -631,238 +611,6 @@ pub(crate) mod tests { type ChaChaRng = ChaCha20Rng; - #[test] - fn test_chacha_construction() { - let seed = [ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, - 0, 0, 0, - ]; - let mut rng1 = ChaChaRng::from_seed(seed); - assert_eq!(rng1.next_u32(), 137206642); - - assert_eq!(rng1.get_seed(), seed); - - let mut rng2 = ChaChaRng::from_rng(&mut rng1); - assert_eq!(rng2.next_u32(), 1325750369); - } - - #[test] - fn test_chacha_true_values_a() { - // Test vectors 1 and 2 from - // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 - let seed = [0u8; 32]; - let mut rng = ChaChaRng::from_seed(seed); - - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - } - let expected = [ - 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, - 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 0xf4b8436a, 0x1ca11815, - 0x69b687c3, 0x8665eeb2, - ]; - assert_eq!(results, expected); - - for i in results.iter_mut() { - *i = rng.next_u32(); - } - let expected = [ - 0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 0xa0290fcb, 0x6965e348, 0x3e53c612, - 0xed7aee32, 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, 0x281fed31, 0x45fb0a51, - 0x1f0ae1ac, 0x6f4d794b, - ]; - assert_eq!(results, expected); - } - - #[test] - fn test_chacha_true_values_b() { - // Test vector 3 from - // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 - let seed = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, - ]; - let mut rng = ChaChaRng::from_seed(seed); - - // Skip block 0 - for _ in 0..16 { - rng.next_u32(); - } - - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - } - let expected = [ - 0x2452eb3a, 0x9249f8ec, 0x8d829d9b, 0xddd4ceb1, 0xe8252083, 0x60818b01, 0xf38422b8, - 0x5aaa49c9, 0xbb00ca8e, 0xda3ba7b4, 0xc4b592d1, 0xfdf2732f, 0x4436274e, 0x2561b3c8, - 0xebdd4aa6, 0xa0136c00, - ]; - assert_eq!(results, expected); - } - - #[test] - fn test_chacha_true_values_c() { - // Test vector 4 from - // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 - let seed = [ - 0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ]; - let expected = [ - 0xfb4dd572, 0x4bc42ef1, 0xdf922636, 0x327f1394, 0xa78dea8f, 0x5e269039, 0xa1bebbc1, - 0xcaf09aae, 0xa25ab213, 0x48a6b46c, 0x1b9d9bcb, 0x092c5be6, 0x546ca624, 0x1bec45d5, - 0x87f47473, 0x96f0992e, - ]; - let expected_end = 3 * 16; - let mut results = [0u32; 16]; - - // Test block 2 by skipping block 0 and 1 - let mut rng1 = ChaChaRng::from_seed(seed); - for _ in 0..32 { - rng1.next_u32(); - } - for i in results.iter_mut() { - *i = rng1.next_u32(); - } - assert_eq!(results, expected); - assert_eq!(rng1.get_word_pos(), expected_end); - - // Test block 2 by using `set_word_pos` - let mut rng2 = ChaChaRng::from_seed(seed); - rng2.set_word_pos(2 * 16); - for i in results.iter_mut() { - *i = rng2.next_u32(); - } - assert_eq!(results, expected); - assert_eq!(rng2.get_word_pos(), expected_end); - - // Test block 2 by using `set_block_pos` and u32 - let mut rng3 = ChaChaRng::from_seed(seed); - rng3.set_block_pos(2); - results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng3.next_u32(); - } - assert_eq!(results, expected); - assert_eq!(rng3.get_word_pos(), expected_end); - - // Test block 2 by using `set_block_pos` and [u8; 8] - let mut rng4 = ChaChaRng::from_seed(seed); - rng4.set_block_pos([2, 0, 0, 0, 0, 0, 0, 0]); - results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng4.next_u32(); - } - assert_eq!(results, expected); - assert_eq!(rng4.get_word_pos(), expected_end); - - // Test skipping behaviour with other types - let mut buf = [0u8; 32]; - rng2.fill_bytes(&mut buf[..]); - assert_eq!(rng2.get_word_pos(), expected_end + 8); - rng2.fill_bytes(&mut buf[0..25]); - assert_eq!(rng2.get_word_pos(), expected_end + 15); - rng2.next_u64(); - assert_eq!(rng2.get_word_pos(), expected_end + 17); - rng2.next_u32(); - rng2.next_u64(); - assert_eq!(rng2.get_word_pos(), expected_end + 20); - rng2.fill_bytes(&mut buf[0..1]); - assert_eq!(rng2.get_word_pos(), expected_end + 21); - } - - #[test] - fn test_chacha_multiple_blocks() { - let seed = [ - 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, - 0, 0, 0, - ]; - let mut rng = ChaChaRng::from_seed(seed); - - // Store the 17*i-th 32-bit word, - // i.e., the i-th word of the i-th 16-word block - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - for _ in 0..16 { - rng.next_u32(); - } - } - let expected = [ - 0xf225c81a, 0x6ab1be57, 0x04d42951, 0x70858036, 0x49884684, 0x64efec72, 0x4be2d186, - 0x3615b384, 0x11cfa18e, 0xd3c50049, 0x75c775f6, 0x434c6530, 0x2c5bad8f, 0x898881dc, - 0x5f1c86d9, 0xc1f8e7f4, - ]; - assert_eq!(results, expected); - } - - #[test] - fn test_chacha_true_bytes() { - let seed = [0u8; 32]; - let mut rng = ChaChaRng::from_seed(seed); - let mut results = [0u8; 32]; - rng.fill_bytes(&mut results); - let expected = [ - 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, 83, 134, 189, 40, 189, 210, - 25, 184, 160, 141, 237, 26, 168, 54, 239, 204, 139, 119, 13, 199, - ]; - assert_eq!(results, expected); - } - - #[test] - fn test_chacha_nonce() { - use hex_literal::hex; - // Test vector 5 from - // https://www.rfc-editor.org/rfc/rfc8439#section-2.3.2 - let seed = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); - let mut rng = ChaChaRng::from_seed(seed); - - let stream_id = hex!("0000004a00000000"); - rng.set_stream(stream_id); - rng.set_block_pos(hex!("0000000000000009")); - - // The test vectors omit the first 64-bytes of the keystream - let mut discard_first_64 = [0u8; 64]; - rng.fill_bytes(&mut discard_first_64); - - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - } - let expected = [ - 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, - 0x4e6cd4c3, 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, 0xd19c12b5, 0xb94e16de, - 0xe883d0cb, 0x4e3c50a2, - ]; - - assert_eq!(results, expected); - } - - #[test] - fn test_chacha_nonce_2() { - // Test vector 5 from - // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 - // Although we do not support setting a nonce, we try it here anyway so - // we can use this test vector. - let seed = [0u8; 32]; - let mut rng = ChaChaRng::from_seed(seed); - // 96-bit nonce in LE order is: 0,0,0,0, 0,0,0,0, 0,0,0,2 - rng.set_stream(2u64 << (24 + 32)); - - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - } - let expected = [ - 0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, - 0xc727ee54, 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, 0x99c28f5f, 0x628314e8, - 0x398a19fa, 0x6ded1b53, - ]; - assert_eq!(results, expected); - } - #[test] fn test_chacha_clone_streams() { let seed = [ @@ -927,107 +675,6 @@ pub(crate) mod tests { assert_eq!(rng.get_word_pos(), 0); } - #[test] - /// Testing the edge cases of `fill_bytes()` by brute-forcing it with dest sizes - /// that start at 1, and increase by 1 up to `N`, then they decrease from `N` - /// to 1, and this can repeat multiple times if desired. - /// - /// This test uses `rand_chacha v0.3.1` because this version's API is directly - /// based on `rand_chacha v0.3.1`, and previous versions of `chacha20` could be - /// affected by rust flags for changing the backend. Also, it doesn't seem to work - /// with `chacha20 v0.8` - /// - /// Because this test uses `rand_chacha v0.3.1` which uses a 64-bit counter, these - /// test results should be accurate up to `block_pos = 2^32 - 1`. - fn test_fill_bytes_v2() { - use rand_chacha::ChaCha20Rng as TesterRng; - use rand_chacha::rand_core::{RngCore, SeedableRng}; - - let mut rng = ChaChaRng::from_seed([0u8; 32]); - let mut tester_rng = TesterRng::from_seed([0u8; 32]); - - let num_iterations = 32; - - // If N is too large, it could cause stack overflow. - // With N = 1445, the arrays are 1044735 bytes each, or 0.9963 MiB - const N: usize = 1000; - // compute the sum from 1 to N, with increments of 1 - const LEN: usize = (N * (N + 1)) / 2; - - let mut test_array: [u8; LEN]; - let mut tester_array: [u8; LEN]; - - for _iteration in 0..num_iterations { - test_array = [0u8; LEN]; - tester_array = [0u8; LEN]; - - let mut dest_pos = 0; - // test fill_bytes with lengths starting at 1 byte, increasing by 1, - // up to N bytes - for test_len in 1..=N { - let debug_start_word_pos = rng.get_word_pos(); - let end_pos = dest_pos + test_len; - - // ensure that the current dest_pos index isn't overwritten already - assert_eq!(test_array[dest_pos], 0); - rng.fill_bytes(&mut test_array[dest_pos..end_pos]); - tester_rng.fill_bytes(&mut tester_array[dest_pos..end_pos]); - - if test_array[dest_pos..end_pos] != tester_array[dest_pos..end_pos] { - for (t, (index, expected)) in test_array[dest_pos..end_pos] - .iter() - .zip(tester_array[dest_pos..end_pos].iter().enumerate()) - { - if t != expected { - panic!( - "Failed test at start_word_pos = {},\nfailed index: {:?}\nFailing word_pos = {}", - debug_start_word_pos, - index, - debug_start_word_pos + (index / 4) as u128 - ); - } - } - } - assert_eq!(rng.next_u32(), tester_rng.next_u32()); - - dest_pos = end_pos; - } - test_array = [0u8; LEN]; - tester_array = [0u8; LEN]; - dest_pos = 0; - - // test fill_bytes with lengths starting at `N` bytes, decreasing by 1, - // down to 1 byte - for test_len in 1..=N { - let debug_start_word_pos = rng.get_word_pos(); - let end_pos = dest_pos + N - test_len; - - // ensure that the current dest_pos index isn't overwritten already - assert_eq!(test_array[dest_pos], 0); - rng.fill_bytes(&mut test_array[dest_pos..end_pos]); - tester_rng.fill_bytes(&mut tester_array[dest_pos..end_pos]); - - if test_array[dest_pos..end_pos] != tester_array[dest_pos..end_pos] { - for (t, (index, expected)) in test_array[dest_pos..end_pos] - .iter() - .zip(tester_array[dest_pos..end_pos].iter().enumerate()) - { - if t != expected { - panic!( - "Failed test at start_word_pos = {},\nfailed index: {:?}\nFailing word_pos = {}", - debug_start_word_pos, - index, - debug_start_word_pos + (index / 4) as u128 - ); - } - } - } - assert_eq!(rng.next_u32(), tester_rng.next_u32()); - dest_pos = end_pos; - } - } - } - #[test] #[allow(trivial_casts)] fn test_trait_objects() { @@ -1041,63 +688,6 @@ pub(crate) mod tests { } } - #[test] - fn stream_id_endianness() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - assert_eq!(rng.get_word_pos(), 0); - rng.set_stream([3, 3333]); - assert_eq!(rng.get_word_pos(), 0); - let expected = 1152671828; - assert_eq!(rng.next_u32(), expected); - let mut word_pos = rng.get_word_pos(); - - assert_eq!(word_pos, 1); - - rng.set_stream(1234567); - assert_eq!(rng.get_word_pos(), 0); - let mut block = [0u32; 16]; - for word in 0..block.len() { - block[word] = rng.next_u32(); - } - assert_eq!(rng.get_word_pos(), 16); - assert_eq!(rng.core.word_offset(), 16); - assert_eq!(rng.get_block_pos(), 1); - rng.set_stream(1234567); - let mut block_2 = [0u32; 16]; - for word in 0..block_2.len() { - block_2[word] = rng.next_u32(); - } - assert_eq!(rng.get_word_pos(), 16); - assert_eq!(rng.core.word_offset(), 16); - assert_eq!(rng.get_block_pos(), 1); - assert_eq!(block, block_2); - rng.set_stream(1234567); - assert_eq!(rng.get_block_pos(), 0); - assert_eq!(rng.get_word_pos(), 0); - let _ = rng.next_u32(); - - word_pos = rng.get_word_pos(); - assert_eq!(word_pos, 1); - let test = rng.next_u32(); - let expected = 3110319182; - rng.set_word_pos(65); // old set_stream added 64 to the word_pos - assert!(rng.next_u32() == expected); - rng.set_word_pos(word_pos); - assert_eq!(rng.next_u32(), test); - - word_pos = rng.get_word_pos(); - assert_eq!(word_pos, 2); - rng.set_stream([1, 2, 3, 4, 5, 6, 7, 8]); - rng.next_u32(); - rng.next_u32(); - let test = rng.next_u32(); - rng.set_word_pos(130); // old set_stream added another 64 to the word_pos - let expected = 3790367479; - assert_eq!(rng.next_u32(), expected); - rng.set_word_pos(word_pos); - assert_eq!(rng.next_u32(), test); - } - /// If this test fails, the backend may not be /// performing 64-bit addition. #[test] @@ -1149,87 +739,4 @@ pub(crate) mod tests { ); assert_ne!(&first_blocks[0..64 * 4], &result[64..]); } - - /// Counts how many bytes were incorrect, and returns: - /// - /// (`index_of_first_incorrect_word`, `num_incorrect_bytes`) - fn count_incorrect_bytes(expected: &[u8], output: &[u8]) -> (Option, u32) { - assert_eq!(expected.len(), output.len()); - let mut num_incorrect_bytes = 0; - let mut index_of_first_incorrect_word = None; - expected - .iter() - .enumerate() - .zip(output.iter()) - .for_each(|((i, a), b)| { - if a.ne(b) { - if index_of_first_incorrect_word.is_none() { - index_of_first_incorrect_word = Some(i / 4) - } - num_incorrect_bytes += 1; - } - }); - (index_of_first_incorrect_word, num_incorrect_bytes) - } - - /// Test vector 8 from https://github.com/pyca/cryptography/blob/main/vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt - #[test] - fn counter_overflow_and_diagnostics() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let block_pos = 4294967295; - assert_eq!(block_pos, u32::MAX as u64); - rng.set_block_pos(4294967295); - - let mut output = [0u8; 64 * 4]; - rng.fill_bytes(&mut output[..64 * 3]); - let block_before_overflow = hex!( - "ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d" - ); - let first_block_after_overflow = hex!( - "3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a" - ); - let second_block_after_overflow = hex!( - "46f0f6efee15c8f1b198cb49d92b990867905159440cc723916dc0012826981039ce1766aa2542b05db3bd809ab142489d5dbfe1273e7399637b4b3213768aaa" - ); - assert!( - output[..64].eq(&block_before_overflow), - "The first parblock was incorrect before overflow, indicating that ChaCha was not implemented correctly for this backend. Check the rounds() fn or the functions that it calls" - ); - - rng.set_block_pos(u32::MAX as u64 - 1); - let mut skipped_blocks = [0u8; 64 * 3]; - rng.fill_bytes(&mut skipped_blocks); - rng.fill_bytes(&mut output[64 * 3..]); - - output.chunks_exact(64).enumerate().skip(1).zip(&[first_block_after_overflow, second_block_after_overflow, second_block_after_overflow]).for_each(|((i, a), b)| { - let (index_of_first_incorrect_word, num_incorrect_bytes) = count_incorrect_bytes(a, b); - let msg = if num_incorrect_bytes == 0 { - "The block was correct and this will not be shown" - } else if num_incorrect_bytes > 32 { - "Most of the block was incorrect, indicating an issue with the counter using 32-bit addition towards the beginning of fn rounds()" - } else if num_incorrect_bytes <= 8 && matches!(index_of_first_incorrect_word, Some(12 | 13)) { - "When the state was added to the results/res buffer at the end of fn rounds, the counter was probably incremented in 32-bit fashion for this parblock" - } else { - // this is probably unreachable in the event of a failed assertion, but it depends on the seed - "Some of the block was incorrect" - }; - assert!(a.eq(b), "PARBLOCK #{} uses incorrect counter addition\nDiagnostic = {}\nnum_incorrect_bytes = {}\nindex_of_first_incorrect_word = {:?}", i + 1, msg, num_incorrect_bytes, index_of_first_incorrect_word); - }); - } - - /// Test vector 9 from https://github.com/pyca/cryptography/blob/main/vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt - #[test] - fn counter_wrap_1() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let block_pos = 18446744073709551615; - assert_eq!(block_pos, u64::MAX); - rng.set_block_pos(block_pos); - - let mut output = [0u8; 64 * 3]; - rng.fill_bytes(&mut output); - let expected = hex!( - "d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee65869f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f" - ); - assert_eq!(expected, output); - } } diff --git a/chacha20/tests/kats.rs b/chacha20/tests/kats.rs index 4e4aa33c..0ccc4f66 100644 --- a/chacha20/tests/kats.rs +++ b/chacha20/tests/kats.rs @@ -234,3 +234,384 @@ mod legacy { } } } + +#[cfg(feature = "rng")] +mod rng_tests { + use chacha20::{ + ChaCha20Rng, + rand_core::{RngCore, SeedableRng}, + }; + use hex_literal::hex; + + const KEY: [u8; 32] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, + ]; + + #[test] + fn test_rng_output() { + let mut rng = ChaCha20Rng::from_seed(KEY); + let mut bytes = [0u8; 13]; + + rng.fill_bytes(&mut bytes); + assert_eq!( + bytes, + [177, 105, 126, 159, 198, 70, 30, 25, 131, 209, 49, 207, 105] + ); + + rng.fill_bytes(&mut bytes); + assert_eq!( + bytes, + [167, 163, 252, 19, 79, 20, 152, 128, 232, 187, 43, 93, 35] + ); + } + + #[test] + fn test_chacha_true_values_a() { + // Test vectors 1 and 2 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + let mut results = [0u32; 16]; + for i in results.iter_mut() { + *i = rng.next_u32(); + } + let expected = [ + 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, + 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 0xf4b8436a, 0x1ca11815, + 0x69b687c3, 0x8665eeb2, + ]; + assert_eq!(results, expected); + + for i in results.iter_mut() { + *i = rng.next_u32(); + } + let expected = [ + 0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 0xa0290fcb, 0x6965e348, 0x3e53c612, + 0xed7aee32, 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, 0x281fed31, 0x45fb0a51, + 0x1f0ae1ac, 0x6f4d794b, + ]; + assert_eq!(results, expected); + } + + #[test] + fn test_chacha_true_values_b() { + // Test vector 3 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + let seed = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, + ]; + let mut rng = ChaCha20Rng::from_seed(seed); + + // Skip block 0 + for _ in 0..16 { + rng.next_u32(); + } + + let mut results = [0u32; 16]; + for i in results.iter_mut() { + *i = rng.next_u32(); + } + let expected = [ + 0x2452eb3a, 0x9249f8ec, 0x8d829d9b, 0xddd4ceb1, 0xe8252083, 0x60818b01, 0xf38422b8, + 0x5aaa49c9, 0xbb00ca8e, 0xda3ba7b4, 0xc4b592d1, 0xfdf2732f, 0x4436274e, 0x2561b3c8, + 0xebdd4aa6, 0xa0136c00, + ]; + assert_eq!(results, expected); + } + + #[test] + fn test_chacha_true_values_c() { + // Test vector 4 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + let seed = [ + 0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + let expected = [ + 0xfb4dd572, 0x4bc42ef1, 0xdf922636, 0x327f1394, 0xa78dea8f, 0x5e269039, 0xa1bebbc1, + 0xcaf09aae, 0xa25ab213, 0x48a6b46c, 0x1b9d9bcb, 0x092c5be6, 0x546ca624, 0x1bec45d5, + 0x87f47473, 0x96f0992e, + ]; + let expected_end = 3 * 16; + let mut results = [0u32; 16]; + + // Test block 2 by skipping block 0 and 1 + let mut rng1 = ChaCha20Rng::from_seed(seed); + for _ in 0..32 { + rng1.next_u32(); + } + for i in results.iter_mut() { + *i = rng1.next_u32(); + } + assert_eq!(results, expected); + assert_eq!(rng1.get_word_pos(), expected_end); + + // Test block 2 by using `set_word_pos` + let mut rng2 = ChaCha20Rng::from_seed(seed); + rng2.set_word_pos(2 * 16); + for i in results.iter_mut() { + *i = rng2.next_u32(); + } + assert_eq!(results, expected); + assert_eq!(rng2.get_word_pos(), expected_end); + + // Test block 2 by using `set_block_pos` and u32 + let mut rng3 = ChaCha20Rng::from_seed(seed); + rng3.set_block_pos(2); + results = [0u32; 16]; + for i in results.iter_mut() { + *i = rng3.next_u32(); + } + assert_eq!(results, expected); + assert_eq!(rng3.get_word_pos(), expected_end); + + // Test block 2 by using `set_block_pos` and [u8; 8] + let mut rng4 = ChaCha20Rng::from_seed(seed); + rng4.set_block_pos([2, 0, 0, 0, 0, 0, 0, 0]); + results = [0u32; 16]; + for i in results.iter_mut() { + *i = rng4.next_u32(); + } + assert_eq!(results, expected); + assert_eq!(rng4.get_word_pos(), expected_end); + + // Test skipping behaviour with other types + let mut buf = [0u8; 32]; + rng2.fill_bytes(&mut buf[..]); + assert_eq!(rng2.get_word_pos(), expected_end + 8); + rng2.fill_bytes(&mut buf[0..25]); + assert_eq!(rng2.get_word_pos(), expected_end + 15); + rng2.next_u64(); + assert_eq!(rng2.get_word_pos(), expected_end + 17); + rng2.next_u32(); + rng2.next_u64(); + assert_eq!(rng2.get_word_pos(), expected_end + 20); + rng2.fill_bytes(&mut buf[0..1]); + assert_eq!(rng2.get_word_pos(), expected_end + 21); + } + + #[test] + fn test_chacha_multiple_blocks() { + let seed = [ + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, + 0, 0, 0, + ]; + let mut rng = ChaCha20Rng::from_seed(seed); + + // Store the 17*i-th 32-bit word, + // i.e., the i-th word of the i-th 16-word block + let mut results = [0u32; 16]; + for i in results.iter_mut() { + *i = rng.next_u32(); + for _ in 0..16 { + rng.next_u32(); + } + } + let expected = [ + 0xf225c81a, 0x6ab1be57, 0x04d42951, 0x70858036, 0x49884684, 0x64efec72, 0x4be2d186, + 0x3615b384, 0x11cfa18e, 0xd3c50049, 0x75c775f6, 0x434c6530, 0x2c5bad8f, 0x898881dc, + 0x5f1c86d9, 0xc1f8e7f4, + ]; + assert_eq!(results, expected); + } + + #[test] + fn test_chacha_true_bytes() { + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + let mut results = [0u8; 32]; + rng.fill_bytes(&mut results); + let expected = [ + 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, 83, 134, 189, 40, 189, 210, + 25, 184, 160, 141, 237, 26, 168, 54, 239, 204, 139, 119, 13, 199, + ]; + assert_eq!(results, expected); + } + + #[test] + fn test_chacha_construction() { + let seed = [ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, + ]; + let mut rng1 = ChaCha20Rng::from_seed(seed); + assert_eq!(rng1.next_u32(), 137206642); + + assert_eq!(rng1.get_seed(), seed); + + let mut rng2 = ChaCha20Rng::from_rng(&mut rng1); + assert_eq!(rng2.next_u32(), 1325750369); + } + + #[test] + fn test_chacha_nonce() { + use hex_literal::hex; + // Test vector 5 from + // https://www.rfc-editor.org/rfc/rfc8439#section-2.3.2 + let seed = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + let mut rng = ChaCha20Rng::from_seed(seed); + + let stream_id = hex!("0000004a00000000"); + rng.set_stream(stream_id); + rng.set_block_pos(hex!("0000000000000009")); + + // The test vectors omit the first 64-bytes of the keystream + let mut discard_first_64 = [0u8; 64]; + rng.fill_bytes(&mut discard_first_64); + + let mut results = [0u32; 16]; + for i in results.iter_mut() { + *i = rng.next_u32(); + } + let expected = [ + 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, + 0x4e6cd4c3, 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, 0xd19c12b5, 0xb94e16de, + 0xe883d0cb, 0x4e3c50a2, + ]; + + assert_eq!(results, expected); + } + + #[test] + fn test_chacha_nonce_2() { + // Test vector 5 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + // Although we do not support setting a nonce, we try it here anyway so + // we can use this test vector. + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + // 96-bit nonce in LE order is: 0,0,0,0, 0,0,0,0, 0,0,0,2 + rng.set_stream(2u64 << (24 + 32)); + + let mut results = [0u32; 16]; + for i in results.iter_mut() { + *i = rng.next_u32(); + } + let expected = [ + 0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, + 0xc727ee54, 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, 0x99c28f5f, 0x628314e8, + 0x398a19fa, 0x6ded1b53, + ]; + assert_eq!(results, expected); + } + + #[test] + fn stream_id_endianness() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + assert_eq!(rng.get_word_pos(), 0); + rng.set_stream([3, 3333]); + assert_eq!(rng.get_word_pos(), 0); + let expected = 1152671828; + assert_eq!(rng.next_u32(), expected); + assert_eq!(rng.get_word_pos(), 1); + + rng.set_stream(1234567); + // these `word_pos == 0` might need to be changed if set_stream changes again + assert_eq!(rng.get_word_pos(), 0); + let mut block = [0u32; 16]; + for word in 0..block.len() { + block[word] = rng.next_u32(); + } + assert_eq!(rng.get_word_pos(), 16); + // new `get_block_pos` + assert_eq!(rng.get_block_pos(), 1); + rng.set_stream(1234567); + assert_eq!(rng.get_block_pos(), 0); + assert_eq!(rng.get_word_pos(), 0); + + let expected = 3110319182; + rng.set_word_pos(65); // old set_stream added 64 to the word_pos + assert!(rng.next_u32() == expected); + + rng.set_stream([1, 2, 3, 4, 5, 6, 7, 8]); + rng.set_word_pos(130); // old set_stream added another 64 to the word_pos + let expected = 3790367479; + assert_eq!(rng.next_u32(), expected); + } + + /// Test vector 9 from https://github.com/pyca/cryptography/blob/main/vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt + #[test] + fn counter_wrap_1() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let block_pos = 18446744073709551615; + assert_eq!(block_pos, u64::MAX); + rng.set_block_pos(block_pos); + + let mut output = [0u8; 64 * 3]; + rng.fill_bytes(&mut output); + let expected = hex!( + "d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee65869f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f" + ); + assert_eq!(expected, output); + } + + /// Counts how many bytes were incorrect, and returns: + /// + /// (`index_of_first_incorrect_word`, `num_incorrect_bytes`) + fn count_incorrect_bytes(expected: &[u8], output: &[u8]) -> (Option, u32) { + assert_eq!(expected.len(), output.len()); + let mut num_incorrect_bytes = 0; + let mut index_of_first_incorrect_word = None; + expected + .iter() + .enumerate() + .zip(output.iter()) + .for_each(|((i, a), b)| { + if a.ne(b) { + if index_of_first_incorrect_word.is_none() { + index_of_first_incorrect_word = Some(i / 4) + } + num_incorrect_bytes += 1; + } + }); + (index_of_first_incorrect_word, num_incorrect_bytes) + } + + /// Test vector 8 from https://github.com/pyca/cryptography/blob/main/vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt + #[test] + fn counter_overflow_and_diagnostics() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let block_pos = 4294967295; + assert_eq!(block_pos, u32::MAX as u64); + rng.set_block_pos(4294967295); + + let mut output = [0u8; 64 * 4]; + rng.fill_bytes(&mut output[..64 * 3]); + let block_before_overflow = hex!( + "ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d" + ); + let first_block_after_overflow = hex!( + "3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a" + ); + let second_block_after_overflow = hex!( + "46f0f6efee15c8f1b198cb49d92b990867905159440cc723916dc0012826981039ce1766aa2542b05db3bd809ab142489d5dbfe1273e7399637b4b3213768aaa" + ); + assert!( + output[..64].eq(&block_before_overflow), + "The first parblock was incorrect before overflow, indicating that ChaCha was not implemented correctly for this backend. Check the rounds() fn or the functions that it calls" + ); + + rng.set_block_pos(u32::MAX as u64 - 1); + let mut skipped_blocks = [0u8; 64 * 3]; + rng.fill_bytes(&mut skipped_blocks); + rng.fill_bytes(&mut output[64 * 3..]); + + output.chunks_exact(64).enumerate().skip(1).zip(&[first_block_after_overflow, second_block_after_overflow, second_block_after_overflow]).for_each(|((i, a), b)| { + let (index_of_first_incorrect_word, num_incorrect_bytes) = count_incorrect_bytes(a, b); + let msg = if num_incorrect_bytes == 0 { + "The block was correct and this will not be shown" + } else if num_incorrect_bytes > 32 { + "Most of the block was incorrect, indicating an issue with the counter using 32-bit addition towards the beginning of fn rounds()" + } else if num_incorrect_bytes <= 8 && matches!(index_of_first_incorrect_word, Some(12 | 13)) { + "When the state was added to the results/res buffer at the end of fn rounds, the counter was probably incremented in 32-bit fashion for this parblock" + } else { + // this is probably unreachable in the event of a failed assertion, but it depends on the seed + "Some of the block was incorrect" + }; + assert!(a.eq(b), "PARBLOCK #{} uses incorrect counter addition\nDiagnostic = {}\nnum_incorrect_bytes = {}\nindex_of_first_incorrect_word = {:?}", i + 1, msg, num_incorrect_bytes, index_of_first_incorrect_word); + }); + } +} diff --git a/chacha20/tests/rng.rs b/chacha20/tests/rng.rs deleted file mode 100644 index 1d44e26b..00000000 --- a/chacha20/tests/rng.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Equivalence tests between `rand_chacha` and `chacha20` RNGs. - -#![cfg(feature = "rand_core")] - -use proptest::prelude::*; -use rand_chacha::rand_core::{RngCore as OldRngCore, SeedableRng as OldSeedableRng}; -use rand_core::{RngCore, SeedableRng}; - -// Number of reads to perform from the RNG in equivalence tests -const NREADS: usize = 16; - -type Seed = ::Seed; - -proptest! { - #[test] - fn rand_chacha_equivalence( - seed in any::(), - reads in any::<[u16; NREADS]>() - ) { - let mut rng = chacha20::ChaCha20Rng::from_seed(seed); - let mut reference_rng = rand_chacha::ChaCha20Rng::from_seed(seed); - - for nbytes in reads { - let nbytes = nbytes as usize; - - let mut expected = [0u8; u16::MAX as usize]; - reference_rng.fill_bytes(&mut expected[..nbytes]); - - let mut actual = [0u8; u16::MAX as usize]; - rng.fill_bytes(&mut actual[..nbytes]); - - prop_assert_eq!(&expected[..nbytes], &actual[..nbytes]); - } - } -}