diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 209850cd..267274a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,7 @@ jobs: cargo test --target ${{ matrix.target }} --benches - name: Test run: | + cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml --all-features cargo test --target ${{ matrix.target }} --manifest-path rand_hc/Cargo.toml --all-features cargo test --target ${{ matrix.target }} --manifest-path rand_isaac/Cargo.toml --all-features cargo test --target ${{ matrix.target }} --manifest-path rand_xorshift/Cargo.toml --all-features @@ -108,6 +109,7 @@ jobs: run: cargo install cross || true - name: Test run: | + cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml --all-features cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_hc/Cargo.toml --all-features cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_isaac/Cargo.toml --all-features cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_xorshift/Cargo.toml --all-features @@ -125,6 +127,7 @@ jobs: rustup component add miri - name: Test rand run: | + cargo miri test --manifest-path rand_chacha/Cargo.toml --all-features cargo miri test --manifest-path rand_hc/Cargo.toml --all-features cargo miri test --manifest-path rand_isaac/Cargo.toml --all-features cargo miri test --manifest-path rand_xorshift/Cargo.toml --all-features diff --git a/Cargo.toml b/Cargo.toml index f9cc4350..58645f77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "rand_chacha", "rand_jitter", "rand_isaac", "rand_xorshift", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index d6bcd302..9c8df5a2 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -9,6 +9,7 @@ publish = false criterion = "0.5.0" criterion-cycles-per-byte = "0.6" rand_core = { version = "0.9.0", features = ["os_rng"] } +rand_chacha = { path = "../rand_chacha", version = "0.9" } rand_xoshiro = { path = "../rand_xoshiro", version = "0.7" } rand_isaac = { path = "../rand_isaac", version = "0.4" } rand_xorshift = { path = "../rand_xorshift", version = "0.4" } @@ -18,7 +19,3 @@ rand_hc = { path = "../rand_hc", version = "0.4" } name = "mod" path = "mod.rs" harness = false - -[patch.crates-io.rand_core] -git = "https://github.com/rust-random/rand.git" -branch = "master" diff --git a/benches/mod.rs b/benches/mod.rs index 7a4ce084..f83578c1 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -18,6 +18,7 @@ use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use criterion_cycles_per_byte::CyclesPerByte; use rand_core::{RngCore, SeedableRng}; +use rand_chacha::{ChaCha12Rng, ChaCha20Rng, ChaCha8Rng}; use rand_hc::Hc128Rng; use rand_isaac::{Isaac64Rng, IsaacRng}; use rand_xorshift::XorShiftRng; @@ -64,6 +65,9 @@ fn gen_bytes(c: &mut Criterion) { gen_bytes!("hc128", Hc128Rng::from_os_rng()); gen_bytes!("isaac", IsaacRng::from_os_rng()); gen_bytes!("isaac64", Isaac64Rng::from_os_rng()); + gen_bytes!("chacha8", ChaCha8Rng::from_os_rng()); + gen_bytes!("chacha12", ChaCha12Rng::from_os_rng()); + gen_bytes!("chacha20", ChaCha20Rng::from_os_rng()); } // Save a dependency on Rand: @@ -150,6 +154,9 @@ fn gen_uint(c: &mut Criterion) { gen_uint!(g, "hc128", u32, Hc128Rng::from_os_rng()); gen_uint!(g, "isaac", u32, IsaacRng::from_os_rng()); gen_uint!(g, "isaac64", u32, Isaac64Rng::from_os_rng()); + gen_uint!(g, "chacha8", u32, ChaCha8Rng::from_os_rng()); + gen_uint!(g, "chacha12", u32, ChaCha12Rng::from_os_rng()); + gen_uint!(g, "chacha20", u32, ChaCha20Rng::from_os_rng()); } { @@ -201,6 +208,9 @@ fn gen_uint(c: &mut Criterion) { gen_uint!(g, "hc128", u64, Hc128Rng::from_os_rng()); gen_uint!(g, "isaac", u64, IsaacRng::from_os_rng()); gen_uint!(g, "isaac64", u64, Isaac64Rng::from_os_rng()); + gen_uint!(g, "chacha8", u64, ChaCha8Rng::from_os_rng()); + gen_uint!(g, "chacha12", u64, ChaCha12Rng::from_os_rng()); + gen_uint!(g, "chacha20", u64, ChaCha20Rng::from_os_rng()); } } @@ -231,6 +241,9 @@ fn init(c: &mut Criterion) { init_gen!("hc128", Hc128Rng); init_gen!("isaac", IsaacRng); init_gen!("isaac64", Isaac64Rng); + init_gen!("chacha8", ChaCha8Rng); + init_gen!("chacha12", ChaCha12Rng); + init_gen!("chacha20", ChaCha20Rng); } criterion_group! { diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md new file mode 100644 index 00000000..7965cf76 --- /dev/null +++ b/rand_chacha/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.9.0] - 2025-01-27 +### Dependencies and features +- Update to `rand_core` v0.9.0 (#1558) +- Feature `std` now implies feature `rand_core/std` (#1153) +- Rename feature `serde1` to `serde` (#1477) +- Rename feature `getrandom` to `os_rng` (#1537) + +### Other changes +- Remove usage of `unsafe` in `fn generate` (#1181) then optimise for AVX2 (~4-7%) (#1192) +- Revise crate docs (#1454) + +## [0.3.1] - 2021-06-09 +- add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) +- add serde support, gated by the `serde1` feature (#1124) +- ensure expected layout via `repr(transparent)` (#1120) + +## [0.3.0] - 2020-12-08 +- Bump `rand_core` version to 0.6.0 +- Bump MSRV to 1.36 (#1011) +- Remove usage of deprecated feature "simd" of `ppv-lite86` (#979), then revert + this change (#1023) since SIMD is only enabled by default from `ppv-lite86 v0.2.10` +- impl PartialEq+Eq for ChaChaXRng and ChaChaXCore (#979) +- Fix panic on block counter wrap that was occurring in debug builds (#980) + +## [0.2.2] - 2020-03-09 +- Integrate `c2-chacha`, reducing dependency count (#931) +- Add CryptoRng to ChaChaXCore (#944) + +## [0.2.1] - 2019-07-22 +- Force enable the `simd` feature of `c2-chacha` (#845) + +## [0.2.0] - 2019-06-06 +- Rewrite based on the much faster `c2-chacha` crate (#789) + +## [0.1.1] - 2019-01-04 +- Disable `i128` and `u128` if the `target_os` is `emscripten` (#671: work-around Emscripten limitation) +- Update readme and doc links + +## [0.1.0] - 2018-10-17 +- Pulled out of the Rand crate diff --git a/rand_chacha/COPYRIGHT b/rand_chacha/COPYRIGHT new file mode 100644 index 00000000..468d907c --- /dev/null +++ b/rand_chacha/COPYRIGHT @@ -0,0 +1,12 @@ +Copyrights in the Rand project are retained by their contributors. No +copyright assignment is required to contribute to the Rand project. + +For full authorship information, see the version control history. + +Except as otherwise noted (below and/or in individual files), Rand is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. + +The Rand project includes code from the Rust project +published under these same licenses. diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml new file mode 100644 index 00000000..5f3a8676 --- /dev/null +++ b/rand_chacha/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "rand_chacha" +version = "0.9.0" +authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/rust-random/rand" +documentation = "https://docs.rs/rand_chacha" +homepage = "https://rust-random.github.io/book" +description = """ +ChaCha random number generator +""" +keywords = ["random", "rng", "chacha"] +categories = ["algorithms", "no-std"] +edition = "2021" +rust-version = "1.63" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--generate-link-to-definition"] + +[dependencies] +rand_core = "0.9.0" +ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } +serde = { version = "1.0", features = ["derive"], optional = true } + +[dev-dependencies] +# Only to test serde +serde_json = "1.0.120" +rand_core = { version = "0.9.0", features = ["os_rng"] } + +[features] +default = ["std"] +os_rng = ["rand_core/os_rng"] +std = ["ppv-lite86/std", "rand_core/std"] +serde = ["dep:serde"] diff --git a/rand_chacha/LICENSE-APACHE b/rand_chacha/LICENSE-APACHE new file mode 100644 index 00000000..494ad3bf --- /dev/null +++ b/rand_chacha/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/rand_chacha/LICENSE-MIT b/rand_chacha/LICENSE-MIT new file mode 100644 index 00000000..d93b5baf --- /dev/null +++ b/rand_chacha/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright 2018 Developers of the Rand project +Copyright (c) 2014 The Rust Project 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. diff --git a/rand_chacha/README.md b/rand_chacha/README.md new file mode 100644 index 00000000..19cfafac --- /dev/null +++ b/rand_chacha/README.md @@ -0,0 +1,51 @@ +# rand_chacha + +[![Test Status](https://github.com/rust-random/rand/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/rust-random/rand/actions) +[![Latest version](https://img.shields.io/crates/v/rand_chacha.svg)](https://crates.io/crates/rand_chacha) +[![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) +[![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) +[![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) + +A cryptographically secure random number generator that uses the ChaCha +algorithm. + +This implementation of ChaCha RNGs is built over `ppv-lite86` by Kaz Wesley, +and was formerly the implementation behind `rand::rngs::StdRng`. + +ChaCha is a stream cipher designed by Daniel J. Bernstein[^1], that we use +as an RNG. It is an improved variant of the Salsa20 cipher family, which was +selected as one of the "stream ciphers suitable for widespread adoption" by +eSTREAM[^2]. + +The RNGs provided by this crate are implemented via the fast stream ciphers of +the [`c2-chacha`](https://crates.io/crates/c2-chacha) crate. + +Links: + +- [API documentation (master)](https://rust-random.github.io/rand/rand_chacha) +- [API documentation (docs.rs)](https://docs.rs/rand_chacha) +- [Changelog](https://github.com/rust-random/rand/blob/master/rand_chacha/CHANGELOG.md) + +[rand]: https://crates.io/crates/rand +[^1]: D. J. Bernstein, [*ChaCha, a variant of Salsa20*]( + https://cr.yp.to/chacha.html) + +[^2]: [eSTREAM: the ECRYPT Stream Cipher Project]( + http://www.ecrypt.eu.org/stream/) + + +## Crate Features + +`rand_chacha` is `no_std` compatible when disabling default features; the `std` +feature can be explicitly required to re-enable `std` support. Using `std` +allows detection of CPU features and thus better optimisation. Using `std` +also enables `os_rng` functionality, such as `ChaCha20Rng::from_os_rng()`. + + +# License + +`rand_chacha` is distributed under the terms of both the MIT license and the +Apache License (Version 2.0). + +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and +[COPYRIGHT](COPYRIGHT) for details. diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs new file mode 100644 index 00000000..91d3cd62 --- /dev/null +++ b/rand_chacha/src/chacha.rs @@ -0,0 +1,651 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The ChaCha random number generator. + +use crate::guts::ChaCha; +use core::fmt; +use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; +use rand_core::{CryptoRng, RngCore, SeedableRng}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +// NB. this must remain consistent with some currently hard-coded numbers in this module +const BUF_BLOCKS: u8 = 4; +// number of 32-bit words per ChaCha block (fixed by algorithm definition) +const BLOCK_WORDS: u8 = 16; + +#[repr(transparent)] +pub struct Array64([T; 64]); +impl Default for Array64 +where + T: Default, +{ + #[rustfmt::skip] + fn default() -> Self { + Self([ + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), + ]) + } +} +impl AsRef<[T]> for Array64 { + fn as_ref(&self) -> &[T] { + &self.0 + } +} +impl AsMut<[T]> for Array64 { + fn as_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} +impl Clone for Array64 +where + T: Copy + Default, +{ + fn clone(&self) -> Self { + let mut new = Self::default(); + new.0.copy_from_slice(&self.0); + new + } +} +impl fmt::Debug for Array64 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Array64 {{}}") + } +} + +macro_rules! chacha_impl { + ($ChaChaXCore:ident, $ChaChaXRng:ident, $rounds:expr, $doc:expr, $abst:ident,) => { + #[doc=$doc] + #[derive(Clone, PartialEq, Eq)] + pub struct $ChaChaXCore { + state: ChaCha, + } + + // Custom Debug implementation that does not expose the internal state + impl fmt::Debug for $ChaChaXCore { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ChaChaXCore {{}}") + } + } + + impl BlockRngCore for $ChaChaXCore { + type Item = u32; + type Results = Array64; + + #[inline] + fn generate(&mut self, r: &mut Self::Results) { + self.state.refill4($rounds, &mut r.0); + } + } + + impl SeedableRng for $ChaChaXCore { + type Seed = [u8; 32]; + + #[inline] + fn from_seed(seed: Self::Seed) -> Self { + $ChaChaXCore { + state: ChaCha::new(&seed, &[0u8; 8]), + } + } + } + + impl CryptoBlockRng for $ChaChaXCore {} + + /// A cryptographically secure random number generator that uses the ChaCha algorithm. + /// + /// ChaCha is a stream cipher designed by Daniel J. Bernstein[^1], that we use as an RNG. It is + /// an improved variant of the Salsa20 cipher family, which was selected as one of the "stream + /// ciphers suitable for widespread adoption" by eSTREAM[^2]. + /// + /// ChaCha uses add-rotate-xor (ARX) operations as its basis. These are safe against timing + /// attacks, although that is mostly a concern for ciphers and not for RNGs. We provide a SIMD + /// implementation to support high throughput on a variety of common hardware platforms. + /// + /// With the ChaCha algorithm it is possible to choose the number of rounds the core algorithm + /// should run. The number of rounds is a tradeoff between performance and security, where 8 + /// rounds is the minimum potentially secure configuration, and 20 rounds is widely used as a + /// conservative choice. + /// + /// We use a 64-bit counter and 64-bit stream identifier as in Bernstein's implementation[^1] + /// except that we use a stream identifier in place of a nonce. A 64-bit counter over 64-byte + /// (16 word) blocks allows 1 ZiB of output before cycling, and the stream identifier allows + /// 264 unique streams of output per seed. Both counter and stream are initialized + /// to zero but may be set via the `set_word_pos` and `set_stream` methods. + /// + /// The word layout is: + /// + /// ```text + /// constant constant constant constant + /// seed seed seed seed + /// seed seed seed seed + /// counter counter stream_id stream_id + /// ``` + /// + /// This implementation uses an output buffer of sixteen `u32` words, and uses + /// [`BlockRng`] to implement the [`RngCore`] methods. + /// + /// [^1]: D. J. Bernstein, [*ChaCha, a variant of Salsa20*]( + /// https://cr.yp.to/chacha.html) + /// + /// [^2]: [eSTREAM: the ECRYPT Stream Cipher Project]( + /// http://www.ecrypt.eu.org/stream/) + #[derive(Clone, Debug)] + pub struct $ChaChaXRng { + rng: BlockRng<$ChaChaXCore>, + } + + impl SeedableRng for $ChaChaXRng { + type Seed = [u8; 32]; + + #[inline] + fn from_seed(seed: Self::Seed) -> Self { + let core = $ChaChaXCore::from_seed(seed); + Self { + rng: BlockRng::new(core), + } + } + } + + impl RngCore for $ChaChaXRng { + #[inline] + fn next_u32(&mut self) -> u32 { + self.rng.next_u32() + } + + #[inline] + fn next_u64(&mut self) -> u64 { + self.rng.next_u64() + } + + #[inline] + fn fill_bytes(&mut self, bytes: &mut [u8]) { + self.rng.fill_bytes(bytes) + } + } + + impl $ChaChaXRng { + // The buffer is a 4-block window, i.e. it is always at a block-aligned position in the + // stream but if the stream has been sought it may not be self-aligned. + + /// Get the offset from the start of the stream, in 32-bit words. + /// + /// Since the generated blocks are 16 words (24) long and the + /// counter is 64-bits, the offset is a 68-bit number. Sub-word offsets are + /// not supported, hence the result can simply be multiplied by 4 to get a + /// byte-offset. + #[inline] + pub fn get_word_pos(&self) -> u128 { + let buf_start_block = { + let buf_end_block = self.rng.core.state.get_block_pos(); + u64::wrapping_sub(buf_end_block, BUF_BLOCKS.into()) + }; + let (buf_offset_blocks, block_offset_words) = { + let buf_offset_words = self.rng.index() as u64; + let blocks_part = buf_offset_words / u64::from(BLOCK_WORDS); + let words_part = buf_offset_words % u64::from(BLOCK_WORDS); + (blocks_part, words_part) + }; + let pos_block = u64::wrapping_add(buf_start_block, buf_offset_blocks); + let pos_block_words = u128::from(pos_block) * u128::from(BLOCK_WORDS); + pos_block_words + u128::from(block_offset_words) + } + + /// Set the offset from the start of the stream, in 32-bit words. + /// + /// As with `get_word_pos`, we use a 68-bit number. Since the generator + /// simply cycles at the end of its period (1 ZiB), we ignore the upper + /// 60 bits. + #[inline] + pub fn set_word_pos(&mut self, word_offset: u128) { + let block = (word_offset / u128::from(BLOCK_WORDS)) as u64; + self.rng.core.state.set_block_pos(block); + self.rng + .generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); + } + + /// Set the stream number. + /// + /// This is initialized to zero; 264 unique streams of output + /// are available per seed/key. + /// + /// Note that in order to reproduce ChaCha output with a specific 64-bit + /// nonce, one can convert that nonce to a `u64` in little-endian fashion + /// and pass to this function. In theory a 96-bit nonce can be used by + /// passing the last 64-bits to this function and using the first 32-bits as + /// the most significant half of the 64-bit counter (which may be set + /// indirectly via `set_word_pos`), but this is not directly supported. + #[inline] + pub fn set_stream(&mut self, stream: u64) { + self.rng.core.state.set_nonce(stream); + if self.rng.index() != 64 { + let wp = self.get_word_pos(); + self.set_word_pos(wp); + } + } + + /// Get the stream number. + #[inline] + pub fn get_stream(&self) -> u64 { + self.rng.core.state.get_nonce() + } + + /// Get the seed. + #[inline] + pub fn get_seed(&self) -> [u8; 32] { + self.rng.core.state.get_seed() + } + } + + impl CryptoRng for $ChaChaXRng {} + + impl From<$ChaChaXCore> for $ChaChaXRng { + fn from(core: $ChaChaXCore) -> Self { + $ChaChaXRng { + rng: BlockRng::new(core), + } + } + } + + impl PartialEq<$ChaChaXRng> for $ChaChaXRng { + fn eq(&self, rhs: &$ChaChaXRng) -> bool { + let a: $abst::$ChaChaXRng = self.into(); + let b: $abst::$ChaChaXRng = rhs.into(); + a == b + } + } + impl Eq for $ChaChaXRng {} + + #[cfg(feature = "serde")] + impl Serialize for $ChaChaXRng { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + $abst::$ChaChaXRng::from(self).serialize(s) + } + } + #[cfg(feature = "serde")] + impl<'de> Deserialize<'de> for $ChaChaXRng { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + $abst::$ChaChaXRng::deserialize(d).map(|x| Self::from(&x)) + } + } + + mod $abst { + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; + + // The abstract state of a ChaCha stream, independent of implementation choices. The + // comparison and serialization of this object is considered a semver-covered part of + // the API. + #[derive(Debug, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + pub(crate) struct $ChaChaXRng { + seed: [u8; 32], + stream: u64, + word_pos: u128, + } + + impl From<&super::$ChaChaXRng> for $ChaChaXRng { + // Forget all information about the input except what is necessary to determine the + // outputs of any sequence of pub API calls. + fn from(r: &super::$ChaChaXRng) -> Self { + Self { + seed: r.get_seed(), + stream: r.get_stream(), + word_pos: r.get_word_pos(), + } + } + } + + impl From<&$ChaChaXRng> for super::$ChaChaXRng { + // Construct one of the possible concrete RNGs realizing an abstract state. + fn from(a: &$ChaChaXRng) -> Self { + use rand_core::SeedableRng; + let mut r = Self::from_seed(a.seed); + r.set_stream(a.stream); + r.set_word_pos(a.word_pos); + r + } + } + } + }; +} + +chacha_impl!( + ChaCha20Core, + ChaCha20Rng, + 10, + "ChaCha with 20 rounds", + abstract20, +); +chacha_impl!( + ChaCha12Core, + ChaCha12Rng, + 6, + "ChaCha with 12 rounds", + abstract12, +); +chacha_impl!( + ChaCha8Core, + ChaCha8Rng, + 4, + "ChaCha with 8 rounds", + abstract8, +); + +#[cfg(test)] +mod test { + use rand_core::{RngCore, SeedableRng}; + + #[cfg(feature = "serde")] + use super::{ChaCha12Rng, ChaCha20Rng, ChaCha8Rng}; + + type ChaChaRng = super::ChaCha20Rng; + + #[cfg(feature = "serde")] + #[test] + fn test_chacha_serde_roundtrip() { + let seed = [ + 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, 0, 0, + 0, 0, 0, 2, 92, + ]; + let mut rng1 = ChaCha20Rng::from_seed(seed); + let mut rng2 = ChaCha12Rng::from_seed(seed); + let mut rng3 = ChaCha8Rng::from_seed(seed); + + let encoded1 = serde_json::to_string(&rng1).unwrap(); + let encoded2 = serde_json::to_string(&rng2).unwrap(); + let encoded3 = serde_json::to_string(&rng3).unwrap(); + + let mut decoded1: ChaCha20Rng = serde_json::from_str(&encoded1).unwrap(); + let mut decoded2: ChaCha12Rng = serde_json::from_str(&encoded2).unwrap(); + let mut decoded3: ChaCha8Rng = serde_json::from_str(&encoded3).unwrap(); + + assert_eq!(rng1, decoded1); + assert_eq!(rng2, decoded2); + assert_eq!(rng3, decoded3); + + assert_eq!(rng1.next_u32(), decoded1.next_u32()); + assert_eq!(rng2.next_u32(), decoded2.next_u32()); + assert_eq!(rng3.next_u32(), decoded3.next_u32()); + } + + // This test validates that: + // 1. a hard-coded serialization demonstrating the format at time of initial release can still + // be deserialized to a ChaChaRng + // 2. re-serializing the resultant object produces exactly the original string + // + // Condition 2 is stronger than necessary: an equivalent serialization (e.g. with field order + // permuted, or whitespace differences) would also be admissible, but would fail this test. + // However testing for equivalence of serialized data is difficult, and there shouldn't be any + // reason we need to violate the stronger-than-needed condition, e.g. by changing the field + // definition order. + #[cfg(feature = "serde")] + #[test] + fn test_chacha_serde_format_stability() { + let j = r#"{"seed":[4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8],"stream":27182818284,"word_pos":314159265359}"#; + let r: ChaChaRng = serde_json::from_str(j).unwrap(); + let j1 = serde_json::to_string(&r).unwrap(); + assert_eq!(j, j1); + } + + #[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); + + 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 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() { + // 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 = [ + 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); + let mut clone = rng.clone(); + for _ in 0..16 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + + rng.set_stream(51); + for _ in 0..7 { + assert!(rng.next_u32() != clone.next_u32()); + } + clone.set_stream(51); // switch part way through block + for _ in 7..16 { + assert_eq!(rng.next_u32(), clone.next_u32()); + } + } + + #[test] + fn test_chacha_word_pos_wrap_exact() { + use super::{BLOCK_WORDS, BUF_BLOCKS}; + let mut rng = ChaChaRng::from_seed(Default::default()); + // refilling the buffer in set_word_pos will wrap the block counter to 0 + let last_block = (1 << 68) - u128::from(BUF_BLOCKS * BLOCK_WORDS); + rng.set_word_pos(last_block); + assert_eq!(rng.get_word_pos(), last_block); + } + + #[test] + fn test_chacha_word_pos_wrap_excess() { + use super::BLOCK_WORDS; + let mut rng = ChaChaRng::from_seed(Default::default()); + // refilling the buffer in set_word_pos will wrap the block counter past 0 + let last_block = (1 << 68) - u128::from(BLOCK_WORDS); + rng.set_word_pos(last_block); + assert_eq!(rng.get_word_pos(), last_block); + } + + #[test] + fn test_chacha_word_pos_zero() { + let mut rng = ChaChaRng::from_seed(Default::default()); + assert_eq!(rng.get_word_pos(), 0); + rng.set_word_pos(0); + assert_eq!(rng.get_word_pos(), 0); + } + + #[test] + fn test_trait_objects() { + use rand_core::CryptoRng; + + let mut rng1 = ChaChaRng::from_seed(Default::default()); + let rng2 = &mut rng1.clone() as &mut dyn CryptoRng; + for _ in 0..1000 { + assert_eq!(rng1.next_u64(), rng2.next_u64()); + } + } +} diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs new file mode 100644 index 00000000..d077225c --- /dev/null +++ b/rand_chacha/src/guts.rs @@ -0,0 +1,287 @@ +// Copyright 2019 The CryptoCorrosion Contributors +// Copyright 2020 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The ChaCha random number generator. + +use ppv_lite86::{dispatch, dispatch_light128}; + +pub use ppv_lite86::Machine; +use ppv_lite86::{ + vec128_storage, ArithOps, BitOps32, LaneWords4, MultiLane, StoreBytes, Vec4, Vec4Ext, Vector, +}; + +pub(crate) const BLOCK: usize = 16; +pub(crate) const BLOCK64: u64 = BLOCK as u64; +const LOG2_BUFBLOCKS: u64 = 2; +const BUFBLOCKS: u64 = 1 << LOG2_BUFBLOCKS; +pub(crate) const BUFSZ64: u64 = BLOCK64 * BUFBLOCKS; +pub(crate) const BUFSZ: usize = BUFSZ64 as usize; + +const STREAM_PARAM_NONCE: u32 = 1; +const STREAM_PARAM_BLOCK: u32 = 0; + +#[derive(Clone, PartialEq, Eq)] +pub struct ChaCha { + pub(crate) b: vec128_storage, + pub(crate) c: vec128_storage, + pub(crate) d: vec128_storage, +} + +#[derive(Clone)] +pub struct State { + pub(crate) a: V, + pub(crate) b: V, + pub(crate) c: V, + pub(crate) d: V, +} + +#[inline(always)] +pub(crate) fn round(mut x: State) -> State { + x.a += x.b; + x.d = (x.d ^ x.a).rotate_each_word_right16(); + x.c += x.d; + x.b = (x.b ^ x.c).rotate_each_word_right20(); + x.a += x.b; + x.d = (x.d ^ x.a).rotate_each_word_right24(); + x.c += x.d; + x.b = (x.b ^ x.c).rotate_each_word_right25(); + x +} + +#[inline(always)] +pub(crate) fn diagonalize(mut x: State) -> State { + x.b = x.b.shuffle_lane_words3012(); + x.c = x.c.shuffle_lane_words2301(); + x.d = x.d.shuffle_lane_words1230(); + x +} +#[inline(always)] +pub(crate) fn undiagonalize(mut x: State) -> State { + x.b = x.b.shuffle_lane_words1230(); + x.c = x.c.shuffle_lane_words2301(); + x.d = x.d.shuffle_lane_words3012(); + x +} + +impl ChaCha { + #[inline(always)] + pub fn new(key: &[u8; 32], nonce: &[u8]) -> Self { + init_chacha(key, nonce) + } + + /// Produce 4 blocks of output, advancing the state + #[inline(always)] + pub fn refill4(&mut self, drounds: u32, out: &mut [u32; BUFSZ]) { + refill_wide(self, drounds, out) + } + + #[inline(always)] + pub fn set_block_pos(&mut self, value: u64) { + set_stream_param(self, STREAM_PARAM_BLOCK, value) + } + + #[inline(always)] + pub fn get_block_pos(&self) -> u64 { + get_stream_param(self, STREAM_PARAM_BLOCK) + } + + #[inline(always)] + pub fn set_nonce(&mut self, value: u64) { + set_stream_param(self, STREAM_PARAM_NONCE, value) + } + + #[inline(always)] + pub fn get_nonce(&self) -> u64 { + get_stream_param(self, STREAM_PARAM_NONCE) + } + + #[inline(always)] + pub fn get_seed(&self) -> [u8; 32] { + get_seed(self) + } +} + +// This implementation is platform-independent. +#[inline(always)] +#[cfg(target_endian = "big")] +fn add_pos(_m: Mach, d0: Mach::u32x4, i: u64) -> Mach::u32x4 { + let pos0 = ((d0.extract(1) as u64) << 32) | d0.extract(0) as u64; + let pos = pos0.wrapping_add(i); + d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0) +} +#[inline(always)] +#[cfg(target_endian = "big")] +fn d0123(m: Mach, d: vec128_storage) -> Mach::u32x4x4 { + let d0: Mach::u32x4 = m.unpack(d); + let mut pos = ((d0.extract(1) as u64) << 32) | d0.extract(0) as u64; + pos = pos.wrapping_add(1); + let d1 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); + pos = pos.wrapping_add(1); + let d2 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); + pos = pos.wrapping_add(1); + let d3 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); + Mach::u32x4x4::from_lanes([d0, d1, d2, d3]) +} + +// Pos is packed into the state vectors as a little-endian u64, +// so on LE platforms we can use native vector ops to increment it. +#[inline(always)] +#[cfg(target_endian = "little")] +fn add_pos(m: Mach, d: Mach::u32x4, i: u64) -> Mach::u32x4 { + let d0: Mach::u64x2 = m.unpack(d.into()); + let incr = m.vec([i, 0]); + m.unpack((d0 + incr).into()) +} +#[inline(always)] +#[cfg(target_endian = "little")] +fn d0123(m: Mach, d: vec128_storage) -> Mach::u32x4x4 { + let d0: Mach::u64x2 = m.unpack(d); + let incr = + Mach::u64x2x4::from_lanes([m.vec([0, 0]), m.vec([1, 0]), m.vec([2, 0]), m.vec([3, 0])]); + m.unpack((Mach::u64x2x4::from_lanes([d0, d0, d0, d0]) + incr).into()) +} + +#[allow(clippy::many_single_char_names)] +#[inline(always)] +fn refill_wide_impl( + m: Mach, + state: &mut ChaCha, + drounds: u32, + out: &mut [u32; BUFSZ], +) { + let k = m.vec([0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]); + let b = m.unpack(state.b); + let c = m.unpack(state.c); + let mut x = State { + a: Mach::u32x4x4::from_lanes([k, k, k, k]), + b: Mach::u32x4x4::from_lanes([b, b, b, b]), + c: Mach::u32x4x4::from_lanes([c, c, c, c]), + d: d0123(m, state.d), + }; + for _ in 0..drounds { + x = round(x); + x = undiagonalize(round(diagonalize(x))); + } + let kk = Mach::u32x4x4::from_lanes([k, k, k, k]); + let sb = m.unpack(state.b); + let sb = Mach::u32x4x4::from_lanes([sb, sb, sb, sb]); + let sc = m.unpack(state.c); + let sc = Mach::u32x4x4::from_lanes([sc, sc, sc, sc]); + let sd = d0123(m, state.d); + let results = Mach::u32x4x4::transpose4(x.a + kk, x.b + sb, x.c + sc, x.d + sd); + out[0..16].copy_from_slice(&results.0.to_scalars()); + out[16..32].copy_from_slice(&results.1.to_scalars()); + out[32..48].copy_from_slice(&results.2.to_scalars()); + out[48..64].copy_from_slice(&results.3.to_scalars()); + state.d = add_pos(m, sd.to_lanes()[0], 4).into(); +} + +dispatch!(m, Mach, { + fn refill_wide(state: &mut ChaCha, drounds: u32, out: &mut [u32; BUFSZ]) { + refill_wide_impl(m, state, drounds, out); + } +}); + +// Single-block, rounds-only; shared by try_apply_keystream for tails shorter than BUFSZ +// and XChaCha's setup step. +dispatch!(m, Mach, { + fn refill_narrow_rounds(state: &mut ChaCha, drounds: u32) -> State { + let k: Mach::u32x4 = m.vec([0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]); + let mut x = State { + a: k, + b: m.unpack(state.b), + c: m.unpack(state.c), + d: m.unpack(state.d), + }; + for _ in 0..drounds { + x = round(x); + x = undiagonalize(round(diagonalize(x))); + } + State { + a: x.a.into(), + b: x.b.into(), + c: x.c.into(), + d: x.d.into(), + } + } +}); + +dispatch_light128!(m, Mach, { + fn set_stream_param(state: &mut ChaCha, param: u32, value: u64) { + let d: Mach::u32x4 = m.unpack(state.d); + state.d = d + .insert((value >> 32) as u32, (param << 1) | 1) + .insert(value as u32, param << 1) + .into(); + } +}); + +dispatch_light128!(m, Mach, { + fn get_stream_param(state: &ChaCha, param: u32) -> u64 { + let d: Mach::u32x4 = m.unpack(state.d); + ((d.extract((param << 1) | 1) as u64) << 32) | d.extract(param << 1) as u64 + } +}); + +dispatch_light128!(m, Mach, { + fn get_seed(state: &ChaCha) -> [u8; 32] { + let b: Mach::u32x4 = m.unpack(state.b); + let c: Mach::u32x4 = m.unpack(state.c); + let mut key = [0u8; 32]; + b.write_le(&mut key[..16]); + c.write_le(&mut key[16..]); + key + } +}); + +fn read_u32le(xs: &[u8]) -> u32 { + assert_eq!(xs.len(), 4); + u32::from(xs[0]) | (u32::from(xs[1]) << 8) | (u32::from(xs[2]) << 16) | (u32::from(xs[3]) << 24) +} + +dispatch_light128!(m, Mach, { + fn init_chacha(key: &[u8; 32], nonce: &[u8]) -> ChaCha { + let ctr_nonce = [ + 0, + if nonce.len() == 12 { + read_u32le(&nonce[0..4]) + } else { + 0 + }, + read_u32le(&nonce[nonce.len() - 8..nonce.len() - 4]), + read_u32le(&nonce[nonce.len() - 4..]), + ]; + let key0: Mach::u32x4 = m.read_le(&key[..16]); + let key1: Mach::u32x4 = m.read_le(&key[16..]); + ChaCha { + b: key0.into(), + c: key1.into(), + d: ctr_nonce.into(), + } + } +}); + +dispatch_light128!(m, Mach, { + fn init_chacha_x(key: &[u8; 32], nonce: &[u8; 24], rounds: u32) -> ChaCha { + let key0: Mach::u32x4 = m.read_le(&key[..16]); + let key1: Mach::u32x4 = m.read_le(&key[16..]); + let nonce0: Mach::u32x4 = m.read_le(&nonce[..16]); + let mut state = ChaCha { + b: key0.into(), + c: key1.into(), + d: nonce0.into(), + }; + let x = refill_narrow_rounds(&mut state, rounds); + let ctr_nonce1 = [0, 0, read_u32le(&nonce[16..20]), read_u32le(&nonce[20..24])]; + state.b = x.a; + state.c = x.d; + state.d = ctr_nonce1.into(); + state + } +}); diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs new file mode 100644 index 00000000..24ddd601 --- /dev/null +++ b/rand_chacha/src/lib.rs @@ -0,0 +1,106 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The ChaCha random number generators. +//! +//! These are native Rust implementations of RNGs derived from the +//! [ChaCha stream ciphers] by D J Bernstein. +//! +//! ## Generators +//! +//! This crate provides 8-, 12- and 20-round variants of generators via a "core" +//! implementation (of [`BlockRngCore`]), each with an associated "RNG" type +//! (implementing [`RngCore`]). +//! +//! These generators are all deterministic and portable (see [Reproducibility] +//! in the book), with testing against reference vectors. +//! +//! ## Cryptographic (secure) usage +//! +//! Where secure unpredictable generators are required, it is suggested to use +//! [`ChaCha12Rng`] or [`ChaCha20Rng`] and to seed via +//! [`SeedableRng::from_os_rng`]. +//! +//! See also the [Security] chapter in the rand book. The crate is provided +//! "as is", without any form of guarantee, and without a security audit. +//! +//! ## Seeding (construction) +//! +//! Generators implement the [`SeedableRng`] trait. Any method may be used, +//! but note that `seed_from_u64` is not suitable for usage where security is +//! important. Some suggestions: +//! +//! 1. With a fresh seed, **direct from the OS** (implies a syscall): +//! ``` +//! # use {rand_core::SeedableRng, rand_chacha::ChaCha12Rng}; +//! let rng = ChaCha12Rng::from_os_rng(); +//! # let _: ChaCha12Rng = rng; +//! ``` +//! 2. **From a master generator.** This could be [`rand::rng`] +//! (effectively a fresh seed without the need for a syscall on each usage) +//! or a deterministic generator such as [`ChaCha20Rng`]. +//! Beware that should a weak master generator be used, correlations may be +//! detectable between the outputs of its child generators. +//! ```ignore +//! let rng = ChaCha12Rng::from_rng(&mut rand::rng()); +//! ``` +//! +//! See also [Seeding RNGs] in the book. +//! +//! ## Generation +//! +//! Generators implement [`RngCore`], whose methods may be used directly to +//! generate unbounded integer or byte values. +//! ``` +//! use rand_core::{SeedableRng, RngCore}; +//! use rand_chacha::ChaCha12Rng; +//! +//! let mut rng = ChaCha12Rng::from_seed(Default::default()); +//! let x = rng.next_u64(); +//! assert_eq!(x, 0x53f955076a9af49b); +//! ``` +//! +//! It is often more convenient to use the [`rand::Rng`] trait, which provides +//! further functionality. See also the [Random Values] chapter in the book. +//! +//! [ChaCha stream ciphers]: https://cr.yp.to/chacha.html +//! [Reproducibility]: https://rust-random.github.io/book/crate-reprod.html +//! [Seeding RNGs]: https://rust-random.github.io/book/guide-seeding.html +//! [Security]: https://rust-random.github.io/book/guide-rngs.html#security +//! [Random Values]: https://rust-random.github.io/book/guide-values.html +//! [`BlockRngCore`]: rand_core::block::BlockRngCore +//! [`RngCore`]: rand_core::RngCore +//! [`SeedableRng`]: rand_core::SeedableRng +//! [`SeedableRng::from_os_rng`]: rand_core::SeedableRng::from_os_rng +//! [`rand::rng`]: https://docs.rs/rand/latest/rand/fn.rng.html +//! [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html + +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://rust-random.github.io/rand/" +)] +#![forbid(unsafe_code)] +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![doc(test(attr(allow(unused_variables), deny(warnings))))] +#![cfg_attr(not(feature = "std"), no_std)] + +pub use rand_core; + +mod chacha; +mod guts; + +pub use crate::chacha::{ + ChaCha12Core, ChaCha12Rng, ChaCha20Core, ChaCha20Rng, ChaCha8Core, ChaCha8Rng, +}; + +/// ChaCha with 20 rounds +pub type ChaChaRng = ChaCha20Rng; +/// ChaCha with 20 rounds, low-level interface +pub type ChaChaCore = ChaCha20Core;