mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-08 22:28:01 -05:00
chore(csprng): add code base taken from concrete-core repo
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["tfhe", "tasks", "apps/trivium"]
|
||||
members = ["tfhe", "tasks", "apps/trivium", "concrete-csprng"]
|
||||
|
||||
[profile.bench]
|
||||
lto = "fat"
|
||||
|
||||
51
concrete-csprng/Cargo.toml
Normal file
51
concrete-csprng/Cargo.toml
Normal file
@@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "concrete-csprng"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
license = "BSD-3-Clause-Clear"
|
||||
description = "Cryptographically Secure PRNG used in the TFHE-rs library."
|
||||
homepage = "https://zama.ai/"
|
||||
documentation = "https://docs.zama.ai/tfhe-rs"
|
||||
repository = "https://github.com/zama-ai/tfhe-rs"
|
||||
readme = "README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
|
||||
[dependencies]
|
||||
aes = "0.8.2"
|
||||
rayon = { version = "1.5.0", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
libc = "0.2.133"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.3"
|
||||
criterion = "0.3"
|
||||
|
||||
[features]
|
||||
parallel = ["rayon"]
|
||||
seeder_x86_64_rdseed = []
|
||||
seeder_unix = []
|
||||
generator_x86_64_aesni = []
|
||||
generator_fallback = []
|
||||
generator_aarch64_aes = []
|
||||
|
||||
x86_64 = [
|
||||
"parallel",
|
||||
"seeder_x86_64_rdseed",
|
||||
"generator_x86_64_aesni",
|
||||
"generator_fallback",
|
||||
]
|
||||
x86_64-unix = ["x86_64", "seeder_unix"]
|
||||
aarch64 = ["parallel", "generator_aarch64_aes", "generator_fallback"]
|
||||
aarch64-unix = ["aarch64", "seeder_unix"]
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
path = "benches/benchmark.rs"
|
||||
harness = false
|
||||
required-features = ["seeder_x86_64_rdseed", "generator_x86_64_aesni"]
|
||||
|
||||
[[bin]]
|
||||
name = "generate"
|
||||
path = "src/main.rs"
|
||||
required-features = ["seeder_unix", "generator_fallback"]
|
||||
28
concrete-csprng/LICENSE
Normal file
28
concrete-csprng/LICENSE
Normal file
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause Clear License
|
||||
|
||||
Copyright © 2023 ZAMA.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
23
concrete-csprng/README.md
Normal file
23
concrete-csprng/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Concrete CSPRNG
|
||||
|
||||
This crate contains a fast *Cryptographically Secure Pseudoramdon Number Generator*, used in the
|
||||
['concrete-core'](https://crates.io/crates/concrete-core) library, you can find it [here](../concrete-core/) in this repo.
|
||||
|
||||
The implementation is based on the AES blockcipher used in CTR mode, as described in the ISO/IEC
|
||||
18033-4 standard.
|
||||
|
||||
Two implementations are available, an accelerated one on x86_64 CPUs with the `aes` feature and the `sse2` feature, and a pure software one that can be used on other platforms.
|
||||
|
||||
The crate also makes two seeders available, one needing the x86_64 feature `rdseed` and another one based on the Unix random device `/dev/random` the latter requires the user to provide a secret.
|
||||
|
||||
## Running the benchmarks
|
||||
|
||||
To execute the benchmarks on an x86_64 platform:
|
||||
```shell
|
||||
RUSTFLAGS="-Ctarget-cpu=native" cargo bench --features=seeder_x86_64_rdseed,generator_x86_64_aesni
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This software is distributed under the BSD-3-Clause-Clear license. If you have any questions,
|
||||
please contact us at `hello@zama.ai`.
|
||||
54
concrete-csprng/benches/benchmark.rs
Normal file
54
concrete-csprng/benches/benchmark.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use concrete_csprng::generators::{
|
||||
AesniRandomGenerator, BytesPerChild, ChildrenCount, RandomGenerator,
|
||||
};
|
||||
use concrete_csprng::seeders::{RdseedSeeder, Seeder};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
// The number of bytes to generate during one benchmark iteration.
|
||||
const N_GEN: usize = 1_000_000;
|
||||
|
||||
fn parent_generate(c: &mut Criterion) {
|
||||
let mut seeder = RdseedSeeder;
|
||||
let mut generator = AesniRandomGenerator::new(seeder.seed());
|
||||
c.bench_function("parent_generate", |b| {
|
||||
b.iter(|| {
|
||||
(0..N_GEN).for_each(|_| {
|
||||
generator.next();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn child_generate(c: &mut Criterion) {
|
||||
let mut seeder = RdseedSeeder;
|
||||
let mut generator = AesniRandomGenerator::new(seeder.seed());
|
||||
let mut generator = generator
|
||||
.try_fork(ChildrenCount(1), BytesPerChild(N_GEN * 10_000))
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
c.bench_function("child_generate", |b| {
|
||||
b.iter(|| {
|
||||
(0..N_GEN).for_each(|_| {
|
||||
generator.next();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn fork(c: &mut Criterion) {
|
||||
let mut seeder = RdseedSeeder;
|
||||
let mut generator = AesniRandomGenerator::new(seeder.seed());
|
||||
c.bench_function("fork", |b| {
|
||||
b.iter(|| {
|
||||
black_box(
|
||||
generator
|
||||
.try_fork(ChildrenCount(2048), BytesPerChild(2048))
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, parent_generate, child_generate, fork);
|
||||
criterion_main!(benches);
|
||||
112
concrete-csprng/build.rs
Normal file
112
concrete-csprng/build.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
// To have clear error messages during compilation about why some piece of code may not be available
|
||||
// we decided to check the features compatibility with the target configuration in this script.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
|
||||
// See https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch for various
|
||||
// compilation configuration
|
||||
|
||||
// Can be easily extended if needed
|
||||
pub struct FeatureRequirement {
|
||||
pub feature_name: &'static str,
|
||||
// target_arch requirement
|
||||
pub feature_req_target_arch: Option<&'static str>,
|
||||
// target_family requirement
|
||||
pub feature_req_target_family: Option<&'static str>,
|
||||
}
|
||||
|
||||
// We implement a version of default that is const which is not possible through the Default trait
|
||||
impl FeatureRequirement {
|
||||
// As we cannot use cfg!(feature = "feature_name") with something else than a literal, we need
|
||||
// a reference to the HashMap we populate with the enabled features
|
||||
fn is_activated(&self, build_activated_features: &HashMap<&'static str, bool>) -> bool {
|
||||
*build_activated_features.get(self.feature_name).unwrap()
|
||||
}
|
||||
|
||||
// panics if the requirements are not met
|
||||
fn check_requirements(&self) {
|
||||
let target_arch = get_target_arch_cfg();
|
||||
if let Some(feature_req_target_arch) = self.feature_req_target_arch {
|
||||
if feature_req_target_arch != target_arch {
|
||||
panic!(
|
||||
"Feature `{}` requires target_arch `{}`, current cfg: `{}`",
|
||||
self.feature_name, feature_req_target_arch, target_arch
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let target_family = get_target_family_cfg();
|
||||
if let Some(feature_req_target_family) = self.feature_req_target_family {
|
||||
if feature_req_target_family != target_family {
|
||||
panic!(
|
||||
"Feature `{}` requires target_family `{}`, current cfg: `{}`",
|
||||
self.feature_name, feature_req_target_family, target_family
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const vecs are not yet a thing so use a fixed size array (update the array size when adding
|
||||
// requirements)
|
||||
static FEATURE_REQUIREMENTS: [FeatureRequirement; 4] = [
|
||||
FeatureRequirement {
|
||||
feature_name: "seeder_x86_64_rdseed",
|
||||
feature_req_target_arch: Some("x86_64"),
|
||||
feature_req_target_family: None,
|
||||
},
|
||||
FeatureRequirement {
|
||||
feature_name: "generator_x86_64_aesni",
|
||||
feature_req_target_arch: Some("x86_64"),
|
||||
feature_req_target_family: None,
|
||||
},
|
||||
FeatureRequirement {
|
||||
feature_name: "seeder_unix",
|
||||
feature_req_target_arch: None,
|
||||
feature_req_target_family: Some("unix"),
|
||||
},
|
||||
FeatureRequirement {
|
||||
feature_name: "generator_aarch64_aes",
|
||||
feature_req_target_arch: Some("aarch64"),
|
||||
feature_req_target_family: None,
|
||||
},
|
||||
];
|
||||
|
||||
// For a "feature_name" feature_cfg!("feature_name") expands to
|
||||
// ("feature_name", cfg!(feature = "feature_name"))
|
||||
macro_rules! feature_cfg {
|
||||
($feat_name:literal) => {
|
||||
($feat_name, cfg!(feature = $feat_name))
|
||||
};
|
||||
}
|
||||
|
||||
// Static HashMap would require an additional crate (phf or lazy static e.g.), so we just write a
|
||||
// function that returns the HashMap we are interested in
|
||||
fn get_feature_enabled_status() -> HashMap<&'static str, bool> {
|
||||
HashMap::from([
|
||||
feature_cfg!("seeder_x86_64_rdseed"),
|
||||
feature_cfg!("generator_x86_64_aesni"),
|
||||
feature_cfg!("seeder_unix"),
|
||||
feature_cfg!("generator_aarch64_aes"),
|
||||
])
|
||||
}
|
||||
|
||||
// See https://stackoverflow.com/a/43435335/18088947 for the inspiration of this code
|
||||
fn get_target_arch_cfg() -> String {
|
||||
env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH is not set")
|
||||
}
|
||||
|
||||
fn get_target_family_cfg() -> String {
|
||||
env::var("CARGO_CFG_TARGET_FAMILY").expect("CARGO_CFG_TARGET_FAMILY is not set")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let feature_enabled_status = get_feature_enabled_status();
|
||||
|
||||
// This will panic if some requirements for a feature are not met
|
||||
FEATURE_REQUIREMENTS
|
||||
.iter()
|
||||
.filter(|&req| FeatureRequirement::is_activated(req, &feature_enabled_status))
|
||||
.for_each(FeatureRequirement::check_requirements);
|
||||
}
|
||||
20
concrete-csprng/src/generators/aes_ctr/block_cipher.rs
Normal file
20
concrete-csprng/src/generators/aes_ctr/block_cipher.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use crate::generators::aes_ctr::index::AesIndex;
|
||||
use crate::generators::aes_ctr::BYTES_PER_BATCH;
|
||||
|
||||
/// Represents a key used in the AES block cipher.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AesKey(pub u128);
|
||||
|
||||
/// A trait for AES block ciphers.
|
||||
///
|
||||
/// Note:
|
||||
/// -----
|
||||
///
|
||||
/// The block cipher is used in a batched manner (to reduce amortized cost on special hardware).
|
||||
/// For this reason we only expose a `generate_batch` method.
|
||||
pub trait AesBlockCipher: Clone + Send + Sync {
|
||||
/// Instantiate a new generator from a secret key.
|
||||
fn new(key: AesKey) -> Self;
|
||||
/// Generates the batch corresponding to the given index.
|
||||
fn generate_batch(&mut self, index: AesIndex) -> [u8; BYTES_PER_BATCH];
|
||||
}
|
||||
379
concrete-csprng/src/generators/aes_ctr/generic.rs
Normal file
379
concrete-csprng/src/generators/aes_ctr/generic.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
use crate::generators::aes_ctr::block_cipher::{AesBlockCipher, AesKey};
|
||||
use crate::generators::aes_ctr::index::TableIndex;
|
||||
use crate::generators::aes_ctr::states::{BufferPointer, ShiftAction, State};
|
||||
use crate::generators::aes_ctr::BYTES_PER_BATCH;
|
||||
use crate::generators::{ByteCount, BytesPerChild, ChildrenCount, ForkError};
|
||||
|
||||
// Usually, to work with iterators and parallel iterators, we would use opaque types such as
|
||||
// `impl Iterator<..>`. Unfortunately, it is not yet possible to return existential types in
|
||||
// traits, which we would need for `RandomGenerator`. For this reason, we have to use the
|
||||
// full type name where needed. Hence the following trait aliases definition:
|
||||
|
||||
/// A type alias for the children iterator closure type.
|
||||
pub type ChildrenClosure<BlockCipher> =
|
||||
fn((usize, (Box<BlockCipher>, TableIndex, BytesPerChild))) -> AesCtrGenerator<BlockCipher>;
|
||||
|
||||
/// A type alias for the children iterator type.
|
||||
pub type ChildrenIterator<BlockCipher> = std::iter::Map<
|
||||
std::iter::Zip<
|
||||
std::ops::Range<usize>,
|
||||
std::iter::Repeat<(Box<BlockCipher>, TableIndex, BytesPerChild)>,
|
||||
>,
|
||||
ChildrenClosure<BlockCipher>,
|
||||
>;
|
||||
|
||||
/// A type implementing the `RandomGenerator` api using the AES block cipher in counter mode.
|
||||
#[derive(Clone)]
|
||||
pub struct AesCtrGenerator<BlockCipher: AesBlockCipher> {
|
||||
// The block cipher used in the background
|
||||
pub(crate) block_cipher: Box<BlockCipher>,
|
||||
// The state corresponding to the latest outputted byte.
|
||||
pub(crate) state: State,
|
||||
// The last legal index. This makes bound check faster.
|
||||
pub(crate) last: TableIndex,
|
||||
// The buffer containing the current batch of aes calls.
|
||||
pub(crate) buffer: [u8; BYTES_PER_BATCH],
|
||||
}
|
||||
|
||||
#[allow(unused)] // to please clippy when tests are not activated
|
||||
impl<BlockCipher: AesBlockCipher> AesCtrGenerator<BlockCipher> {
|
||||
/// Generates a new csprng.
|
||||
///
|
||||
/// Note :
|
||||
/// ------
|
||||
///
|
||||
/// The `start_index` given as input, points to the first byte that will be outputted by the
|
||||
/// generator. If not given, this one is automatically set to the second table index. The
|
||||
/// first table index is not used to prevent an edge case from happening: since `state` is
|
||||
/// supposed to contain the index of the previous byte, the initial value must be decremented.
|
||||
/// Using the second value prevents wrapping to the max index, which would make the bound
|
||||
/// checking fail.
|
||||
///
|
||||
/// The `bound_index` given as input, points to the first byte that can __not__ be legally
|
||||
/// outputted by the generator. If not given, the bound is automatically set to the last
|
||||
/// table index.
|
||||
pub fn new(
|
||||
key: AesKey,
|
||||
start_index: Option<TableIndex>,
|
||||
bound_index: Option<TableIndex>,
|
||||
) -> AesCtrGenerator<BlockCipher> {
|
||||
AesCtrGenerator::from_block_cipher(
|
||||
Box::new(BlockCipher::new(key)),
|
||||
start_index.unwrap_or(TableIndex::SECOND),
|
||||
bound_index.unwrap_or(TableIndex::LAST),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates a csprng from an existing block cipher.
|
||||
pub fn from_block_cipher(
|
||||
block_cipher: Box<BlockCipher>,
|
||||
start_index: TableIndex,
|
||||
bound_index: TableIndex,
|
||||
) -> AesCtrGenerator<BlockCipher> {
|
||||
assert!(start_index < bound_index);
|
||||
let last = bound_index.decremented();
|
||||
let buffer = [0u8; BYTES_PER_BATCH];
|
||||
let state = State::new(start_index);
|
||||
AesCtrGenerator {
|
||||
block_cipher,
|
||||
state,
|
||||
last,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the table index related to the previous random byte.
|
||||
pub fn table_index(&self) -> TableIndex {
|
||||
self.state.table_index()
|
||||
}
|
||||
|
||||
/// Returns the bound of the generator if any.
|
||||
///
|
||||
/// The bound is the table index of the first byte that can not be outputted by the generator.
|
||||
pub fn get_bound(&self) -> TableIndex {
|
||||
self.last.incremented()
|
||||
}
|
||||
|
||||
/// Returns whether the generator is bounded or not.
|
||||
pub fn is_bounded(&self) -> bool {
|
||||
self.get_bound() != TableIndex::LAST
|
||||
}
|
||||
|
||||
/// Computes the number of bytes that can still be outputted by the generator.
|
||||
///
|
||||
/// Note :
|
||||
/// ------
|
||||
///
|
||||
/// Note that `ByteCount` uses the `u128` datatype to store the byte count. Unfortunately, the
|
||||
/// number of remaining bytes is in ⟦0;2¹³² -1⟧. When the number is greater than 2¹²⁸ - 1,
|
||||
/// we saturate the count at 2¹²⁸ - 1.
|
||||
pub fn remaining_bytes(&self) -> ByteCount {
|
||||
TableIndex::distance(&self.last, &self.state.table_index()).unwrap()
|
||||
}
|
||||
|
||||
/// Outputs the next random byte.
|
||||
pub fn generate_next(&mut self) -> u8 {
|
||||
self.next()
|
||||
.expect("Tried to generate a byte after the bound.")
|
||||
}
|
||||
|
||||
/// Tries to fork the current generator into `n_child` generators each able to output
|
||||
/// `child_bytes` random bytes.
|
||||
pub fn try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<ChildrenIterator<BlockCipher>, ForkError> {
|
||||
if n_children.0 == 0 {
|
||||
return Err(ForkError::ZeroChildrenCount);
|
||||
}
|
||||
if n_bytes.0 == 0 {
|
||||
return Err(ForkError::ZeroBytesPerChild);
|
||||
}
|
||||
if !self.is_fork_in_bound(n_children, n_bytes) {
|
||||
return Err(ForkError::ForkTooLarge);
|
||||
}
|
||||
|
||||
// The state currently stored in the parent generator points to the table index of the last
|
||||
// generated byte. The first index to be generated is the next one:
|
||||
let first_index = self.state.table_index().incremented();
|
||||
let output = (0..n_children.0)
|
||||
.zip(std::iter::repeat((
|
||||
self.block_cipher.clone(),
|
||||
first_index,
|
||||
n_bytes,
|
||||
)))
|
||||
.map(
|
||||
// This map is a little weird because we need to cast the closure to a fn pointer
|
||||
// that matches the signature of `ChildrenIterator<BlockCipher>`.
|
||||
// Unfortunately, the compiler does not manage to coerce this one
|
||||
// automatically.
|
||||
(|(i, (block_cipher, first_index, n_bytes))| {
|
||||
// The first index to be outputted by the child is the `first_index` shifted by
|
||||
// the proper amount of `child_bytes`.
|
||||
let child_first_index = first_index.increased(n_bytes.0 * i);
|
||||
// The bound of the child is the first index of its next sibling.
|
||||
let child_bound_index = first_index.increased(n_bytes.0 * (i + 1));
|
||||
AesCtrGenerator::from_block_cipher(
|
||||
block_cipher,
|
||||
child_first_index,
|
||||
child_bound_index,
|
||||
)
|
||||
}) as ChildrenClosure<BlockCipher>,
|
||||
);
|
||||
// The parent next index is the bound of the last child.
|
||||
let next_index = first_index.increased(n_bytes.0 * n_children.0);
|
||||
self.state = State::new(next_index);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub(crate) fn is_fork_in_bound(
|
||||
&self,
|
||||
n_child: ChildrenCount,
|
||||
child_bytes: BytesPerChild,
|
||||
) -> bool {
|
||||
let mut end = self.state.table_index();
|
||||
end.increase(n_child.0 * child_bytes.0);
|
||||
end <= self.last
|
||||
}
|
||||
}
|
||||
|
||||
impl<BlockCipher: AesBlockCipher> Iterator for AesCtrGenerator<BlockCipher> {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.state.table_index() >= self.last {
|
||||
None
|
||||
} else {
|
||||
match self.state.increment() {
|
||||
ShiftAction::OutputByte(BufferPointer(ptr)) => Some(self.buffer[ptr]),
|
||||
ShiftAction::RefreshBatchAndOutputByte(aes_index, BufferPointer(ptr)) => {
|
||||
self.buffer = self.block_cipher.generate_batch(aes_index);
|
||||
Some(self.buffer[ptr])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod aes_ctr_generic_test {
|
||||
#![allow(unused)] // to please clippy when tests are not activated
|
||||
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::index::{AesIndex, ByteIndex};
|
||||
use crate::generators::aes_ctr::BYTES_PER_AES_CALL;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
const REPEATS: usize = 1_000_000;
|
||||
|
||||
pub fn any_table_index() -> impl Iterator<Item = TableIndex> {
|
||||
std::iter::repeat_with(|| {
|
||||
TableIndex::new(
|
||||
AesIndex(thread_rng().gen()),
|
||||
ByteIndex(thread_rng().gen::<usize>() % BYTES_PER_AES_CALL),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn any_usize() -> impl Iterator<Item = usize> {
|
||||
std::iter::repeat_with(|| thread_rng().gen())
|
||||
}
|
||||
|
||||
pub fn any_children_count() -> impl Iterator<Item = ChildrenCount> {
|
||||
std::iter::repeat_with(|| ChildrenCount(thread_rng().gen::<usize>() % 2048 + 1))
|
||||
}
|
||||
|
||||
pub fn any_bytes_per_child() -> impl Iterator<Item = BytesPerChild> {
|
||||
std::iter::repeat_with(|| BytesPerChild(thread_rng().gen::<usize>() % 2048 + 1))
|
||||
}
|
||||
|
||||
pub fn any_key() -> impl Iterator<Item = AesKey> {
|
||||
std::iter::repeat_with(|| AesKey(thread_rng().gen()))
|
||||
}
|
||||
|
||||
/// Output a valid fork:
|
||||
/// a table index t,
|
||||
/// a number of children nc,
|
||||
/// a number of bytes per children nb
|
||||
/// and a positive integer i such that:
|
||||
/// increase(t, nc*nb+i) < MAX with MAX the largest table index.
|
||||
///
|
||||
/// Put differently, if we initialize a parent generator at t and fork it with (nc, nb), our
|
||||
/// parent generator current index gets shifted to an index, distant of at least i bytes of
|
||||
/// the max index.
|
||||
pub fn any_valid_fork(
|
||||
) -> impl Iterator<Item = (TableIndex, ChildrenCount, BytesPerChild, usize)> {
|
||||
any_table_index()
|
||||
.zip(any_children_count())
|
||||
.zip(any_bytes_per_child())
|
||||
.zip(any_usize())
|
||||
.map(|(((t, nc), nb), i)| (t, nc, nb, i))
|
||||
.filter(|(t, nc, nb, i)| {
|
||||
TableIndex::distance(&TableIndex::LAST, t).unwrap().0 > (nc.0 * nb.0 + i) as u128
|
||||
})
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the table index of the first child is the same as the table index of
|
||||
/// the parent before the fork.
|
||||
pub fn prop_fork_first_state_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
let first_child = forked_generator.try_fork(nc, nb).unwrap().next().unwrap();
|
||||
assert_eq!(original_generator.table_index(), first_child.table_index());
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the table index of the first byte outputted by the parent after the
|
||||
/// fork, is the bound of the last child of the fork.
|
||||
pub fn prop_fork_last_bound_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let mut parent_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let last_child = parent_generator.try_fork(nc, nb).unwrap().last().unwrap();
|
||||
assert_eq!(
|
||||
parent_generator.table_index().incremented(),
|
||||
last_child.get_bound()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the bound of the parent does not change.
|
||||
pub fn prop_fork_parent_bound_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
forked_generator.try_fork(nc, nb).unwrap().last().unwrap();
|
||||
assert_eq!(original_generator.get_bound(), forked_generator.get_bound());
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the parent table index is increased of the number of children
|
||||
/// multiplied by the number of bytes per child.
|
||||
pub fn prop_fork_parent_state_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
forked_generator.try_fork(nc, nb).unwrap().last().unwrap();
|
||||
assert_eq!(
|
||||
forked_generator.table_index(),
|
||||
// Decrement accounts for the fact that the table index stored is the previous one
|
||||
t.increased(nc.0 * nb.0).decremented()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the bytes outputted by the children in the fork order form the same
|
||||
/// sequence the parent would have had yielded no fork had happened.
|
||||
pub fn prop_fork<G: AesBlockCipher>() {
|
||||
for _ in 0..1000 {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let bytes_to_go = nc.0 * nb.0;
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
let initial_output: Vec<u8> = original_generator.take(bytes_to_go).collect();
|
||||
let forked_output: Vec<u8> = forked_generator
|
||||
.try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.flat_map(|child| child.collect::<Vec<_>>())
|
||||
.collect();
|
||||
assert_eq!(initial_output, forked_output);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, all children got a number of remaining bytes equals to the number of
|
||||
/// bytes per child given as fork input.
|
||||
pub fn prop_fork_children_remaining_bytes<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let mut generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
assert!(generator
|
||||
.try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.all(|c| c.remaining_bytes().0 == nb.0 as u128));
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the number of remaining bybtes of the parent is reduced by the number
|
||||
/// of children multiplied by the number of bytes per child.
|
||||
pub fn prop_fork_parent_remaining_bytes<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let bytes_to_go = nc.0 * nb.0;
|
||||
let mut generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let before_remaining_bytes = generator.remaining_bytes();
|
||||
let _ = generator.try_fork(nc, nb).unwrap();
|
||||
let after_remaining_bytes = generator.remaining_bytes();
|
||||
assert_eq!(
|
||||
before_remaining_bytes.0 - after_remaining_bytes.0,
|
||||
bytes_to_go as u128
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
389
concrete-csprng/src/generators/aes_ctr/index.rs
Normal file
389
concrete-csprng/src/generators/aes_ctr/index.rs
Normal file
@@ -0,0 +1,389 @@
|
||||
use crate::generators::aes_ctr::BYTES_PER_AES_CALL;
|
||||
use crate::generators::ByteCount;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// A structure representing an [aes index](#coarse-grained-pseudo-random-table-lookup).
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct AesIndex(pub u128);
|
||||
|
||||
/// A structure representing a [byte index](#fine-grained-pseudo-random-table-lookup).
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct ByteIndex(pub usize);
|
||||
|
||||
/// A structure representing a [table index](#fine-grained-pseudo-random-table-lookup)
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TableIndex {
|
||||
pub(crate) aes_index: AesIndex,
|
||||
pub(crate) byte_index: ByteIndex,
|
||||
}
|
||||
|
||||
impl TableIndex {
|
||||
/// The first table index.
|
||||
pub const FIRST: TableIndex = TableIndex {
|
||||
aes_index: AesIndex(0),
|
||||
byte_index: ByteIndex(0),
|
||||
};
|
||||
|
||||
/// The second table index.
|
||||
pub const SECOND: TableIndex = TableIndex {
|
||||
aes_index: AesIndex(0),
|
||||
byte_index: ByteIndex(1),
|
||||
};
|
||||
|
||||
/// The last table index.
|
||||
pub const LAST: TableIndex = TableIndex {
|
||||
aes_index: AesIndex(u128::MAX),
|
||||
byte_index: ByteIndex(BYTES_PER_AES_CALL - 1),
|
||||
};
|
||||
|
||||
/// Creates a table index from an aes index and a byte index.
|
||||
#[allow(unused)] // to please clippy when tests are not activated
|
||||
pub fn new(aes_index: AesIndex, byte_index: ByteIndex) -> Self {
|
||||
assert!(byte_index.0 < BYTES_PER_AES_CALL);
|
||||
TableIndex {
|
||||
aes_index,
|
||||
byte_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shifts the table index forward of `shift` bytes.
|
||||
pub fn increase(&mut self, shift: usize) {
|
||||
// Compute full shifts to avoid overflows
|
||||
let full_aes_shifts = shift / BYTES_PER_AES_CALL;
|
||||
let shift_remainder = shift % BYTES_PER_AES_CALL;
|
||||
|
||||
// Get the additional shift if any
|
||||
let new_byte_index = self.byte_index.0 + shift_remainder;
|
||||
let full_aes_shifts = full_aes_shifts + new_byte_index / BYTES_PER_AES_CALL;
|
||||
|
||||
// Store the reaminder in the byte index
|
||||
self.byte_index.0 = new_byte_index % BYTES_PER_AES_CALL;
|
||||
|
||||
self.aes_index.0 = self.aes_index.0.wrapping_add(full_aes_shifts as u128);
|
||||
}
|
||||
|
||||
/// Shifts the table index backward of `shift` bytes.
|
||||
pub fn decrease(&mut self, shift: usize) {
|
||||
let remainder = shift % BYTES_PER_AES_CALL;
|
||||
if remainder <= self.byte_index.0 {
|
||||
self.aes_index.0 = self
|
||||
.aes_index
|
||||
.0
|
||||
.wrapping_sub((shift / BYTES_PER_AES_CALL) as u128);
|
||||
self.byte_index.0 -= remainder;
|
||||
} else {
|
||||
self.aes_index.0 = self
|
||||
.aes_index
|
||||
.0
|
||||
.wrapping_sub((shift / BYTES_PER_AES_CALL) as u128 + 1);
|
||||
self.byte_index.0 += BYTES_PER_AES_CALL - remainder;
|
||||
}
|
||||
}
|
||||
|
||||
/// Shifts the table index forward of one byte.
|
||||
pub fn increment(&mut self) {
|
||||
self.increase(1)
|
||||
}
|
||||
|
||||
/// Shifts the table index backward of one byte.
|
||||
pub fn decrement(&mut self) {
|
||||
self.decrease(1)
|
||||
}
|
||||
|
||||
/// Returns the table index shifted forward by `shift` bytes.
|
||||
pub fn increased(mut self, shift: usize) -> Self {
|
||||
self.increase(shift);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the table index shifted backward by `shift` bytes.
|
||||
#[allow(unused)] // to please clippy when tests are not activated
|
||||
pub fn decreased(mut self, shift: usize) -> Self {
|
||||
self.decrease(shift);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the table index to the next byte.
|
||||
pub fn incremented(mut self) -> Self {
|
||||
self.increment();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the table index to the previous byte.
|
||||
pub fn decremented(mut self) -> Self {
|
||||
self.decrement();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the distance between two table indices in bytes.
|
||||
///
|
||||
/// Note:
|
||||
/// -----
|
||||
///
|
||||
/// This method assumes that the `larger` input is, well, larger than the `smaller` input. If
|
||||
/// this is not the case, the method returns `None`. Also, note that `ByteCount` uses the
|
||||
/// `u128` datatype to store the byte count. Unfortunately, the number of bytes between two
|
||||
/// table indices is in ⟦0;2¹³² -1⟧. When the distance is greater than 2¹²⁸ - 1, we saturate
|
||||
/// the count at 2¹²⁸ - 1.
|
||||
pub fn distance(larger: &Self, smaller: &Self) -> Option<ByteCount> {
|
||||
match std::cmp::Ord::cmp(larger, smaller) {
|
||||
Ordering::Less => None,
|
||||
Ordering::Equal => Some(ByteCount(0)),
|
||||
Ordering::Greater => {
|
||||
let mut result = larger.aes_index.0 - smaller.aes_index.0;
|
||||
result = result.saturating_mul(BYTES_PER_AES_CALL as u128);
|
||||
result = result.saturating_add(larger.byte_index.0 as u128);
|
||||
result = result.saturating_sub(smaller.byte_index.0 as u128);
|
||||
Some(ByteCount(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TableIndex {}
|
||||
|
||||
impl PartialEq<Self> for TableIndex {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
matches!(self.partial_cmp(other), Some(Ordering::Equal))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<Self> for TableIndex {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TableIndex {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match self.aes_index.cmp(&other.aes_index) {
|
||||
Ordering::Equal => self.byte_index.cmp(&other.byte_index),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
const REPEATS: usize = 1_000_000;
|
||||
|
||||
fn any_table_index() -> impl Iterator<Item = TableIndex> {
|
||||
std::iter::repeat_with(|| {
|
||||
TableIndex::new(
|
||||
AesIndex(thread_rng().gen()),
|
||||
ByteIndex(thread_rng().gen::<usize>() % BYTES_PER_AES_CALL),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn any_usize() -> impl Iterator<Item = usize> {
|
||||
std::iter::repeat_with(|| thread_rng().gen())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
/// Verifies that the constructor of `TableIndex` panics when the byte index is too large.
|
||||
fn test_table_index_new_panic() {
|
||||
TableIndex::new(AesIndex(12), ByteIndex(144));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that the `TableIndex` wraps nicely with predecessor
|
||||
fn test_table_index_predecessor_edge() {
|
||||
assert_eq!(TableIndex::FIRST.decremented(), TableIndex::LAST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that the `TableIndex` wraps nicely with successor
|
||||
fn test_table_index_successor_edge() {
|
||||
assert_eq!(TableIndex::LAST.incremented(), TableIndex::FIRST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check that the table index distance saturates nicely.
|
||||
fn prop_table_index_distance_saturates() {
|
||||
assert_eq!(
|
||||
TableIndex::distance(&TableIndex::LAST, &TableIndex::FIRST)
|
||||
.unwrap()
|
||||
.0,
|
||||
u128::MAX
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t,
|
||||
/// distance(t, t) = Some(0).
|
||||
fn prop_table_index_distance_zero() {
|
||||
for _ in 0..REPEATS {
|
||||
let t = any_table_index().next().unwrap();
|
||||
assert_eq!(TableIndex::distance(&t, &t), Some(ByteCount(0)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t1, t2 such that t1 < t2,
|
||||
/// distance(t1, t2) = None.
|
||||
fn prop_table_index_distance_wrong_order_none() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t1, t2) = any_table_index()
|
||||
.zip(any_table_index())
|
||||
.find(|(t1, t2)| t1 < t2)
|
||||
.unwrap();
|
||||
assert_eq!(TableIndex::distance(&t1, &t2), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t1, t2 such that t1 > t2,
|
||||
/// distance(t1, t2) = Some(v) where v is strictly positive.
|
||||
fn prop_table_index_distance_some_positive() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t1, t2) = any_table_index()
|
||||
.zip(any_table_index())
|
||||
.find(|(t1, t2)| t1 > t2)
|
||||
.unwrap();
|
||||
assert!(matches!(TableIndex::distance(&t1, &t2), Some(ByteCount(v)) if v > 0));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, positive i such that i < distance (MAX, t) with MAX the largest
|
||||
/// table index,
|
||||
/// distance(t.increased(i), t) = Some(i).
|
||||
fn prop_table_index_distance_increase() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, inc) = any_table_index()
|
||||
.zip(any_usize())
|
||||
.find(|(t, inc)| {
|
||||
(*inc as u128) < TableIndex::distance(&TableIndex::LAST, t).unwrap().0
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
TableIndex::distance(&t.increased(inc), &t).unwrap().0 as usize,
|
||||
inc
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, t =? t = true.
|
||||
fn prop_table_index_equality() {
|
||||
for _ in 0..REPEATS {
|
||||
let t = any_table_index().next().unwrap();
|
||||
assert_eq!(
|
||||
std::cmp::PartialOrd::partial_cmp(&t, &t),
|
||||
Some(std::cmp::Ordering::Equal)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, positive i such that i < distance (MAX, t) with MAX the largest
|
||||
/// table index,
|
||||
/// t.increased(i) >? t = true.
|
||||
fn prop_table_index_greater() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, inc) = any_table_index()
|
||||
.zip(any_usize())
|
||||
.find(|(t, inc)| {
|
||||
(*inc as u128) < TableIndex::distance(&TableIndex::LAST, t).unwrap().0
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
std::cmp::PartialOrd::partial_cmp(&t.increased(inc), &t),
|
||||
Some(std::cmp::Ordering::Greater),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, positive i such that i < distance (t, 0) with MAX the largest
|
||||
/// table index,
|
||||
/// t.decreased(i) <? t = true.
|
||||
fn prop_table_index_less() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, inc) = any_table_index()
|
||||
.zip(any_usize())
|
||||
.find(|(t, inc)| {
|
||||
(*inc as u128) < TableIndex::distance(t, &TableIndex::FIRST).unwrap().0
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
std::cmp::PartialOrd::partial_cmp(&t.decreased(inc), &t),
|
||||
Some(std::cmp::Ordering::Less)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t,
|
||||
/// successor(predecessor(t)) = t.
|
||||
fn prop_table_index_decrement_increment() {
|
||||
for _ in 0..REPEATS {
|
||||
let t = any_table_index().next().unwrap();
|
||||
assert_eq!(t.decremented().incremented(), t);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t,
|
||||
/// predecessor(successor(t)) = t.
|
||||
fn prop_table_index_increment_decrement() {
|
||||
for _ in 0..REPEATS {
|
||||
let t = any_table_index().next().unwrap();
|
||||
assert_eq!(t.incremented().decremented(), t);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, positive integer i,
|
||||
/// increase(decrease(t, i), i) = t.
|
||||
fn prop_table_index_increase_decrease() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, i) = any_table_index().zip(any_usize()).next().unwrap();
|
||||
assert_eq!(t.increased(i).decreased(i), t);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, positive integer i,
|
||||
/// decrease(increase(t, i), i) = t.
|
||||
fn prop_table_index_decrease_increase() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, i) = any_table_index().zip(any_usize()).next().unwrap();
|
||||
assert_eq!(t.decreased(i).increased(i), t);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check that a big increase does not overflow
|
||||
fn prop_table_increase_max_no_overflow() {
|
||||
let first = TableIndex::FIRST;
|
||||
// Increase so that ByteIndex is at 1usize
|
||||
let second = first.increased(1);
|
||||
|
||||
// Now increase by usize::MAX, as the underlying byte index stores a usize this may overflow
|
||||
// depending on implementation, ensure it does not overflow
|
||||
let big_increase = second.increased(usize::MAX);
|
||||
let total_full_aes_shifts = (1u128 + usize::MAX as u128) / BYTES_PER_AES_CALL as u128;
|
||||
|
||||
assert_eq!(
|
||||
big_increase,
|
||||
TableIndex::new(AesIndex(total_full_aes_shifts), ByteIndex(0))
|
||||
);
|
||||
}
|
||||
}
|
||||
223
concrete-csprng/src/generators/aes_ctr/mod.rs
Normal file
223
concrete-csprng/src/generators/aes_ctr/mod.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
//! A module implementing the random generator api with batched aes calls.
|
||||
//!
|
||||
//! This module provides a generic [`AesCtrGenerator`] structure which implements the
|
||||
//! [`super::RandomGenerator`] api using the AES block cipher in counter mode. That is, the
|
||||
//! generator holds a state (i.e. counter) which is incremented iteratively, to produce the stream
|
||||
//! of random values:
|
||||
//! ```ascii
|
||||
//! state=0 state=1 state=2
|
||||
//! ╔══↧══╗ ╔══↧══╗ ╔══↧══╗
|
||||
//! key ↦ AES ║ key ↦ AES ║ key ↦ AES ║ ...
|
||||
//! ╚══↧══╝ ╚══↧══╝ ╚══↧══╝
|
||||
//! output0 output1 output2
|
||||
//!
|
||||
//! t=0 t=1 t=2
|
||||
//! ```
|
||||
//!
|
||||
//! The [`AesCtrGenerator`] structure is generic over the AES block ciphers, which are
|
||||
//! represented by the [`AesBlockCipher`] trait. Consequently, implementers only need to implement
|
||||
//! the `AesBlockCipher` trait, to benefit from the whole api of the `AesCtrGenerator` structure.
|
||||
//!
|
||||
//! In the following section, we give details on the implementation of this generic generator.
|
||||
//!
|
||||
//! Coarse-grained pseudo-random lookup table
|
||||
//! =========================================
|
||||
//!
|
||||
//! To generate random values, we use the AES block cipher in counter mode. If we denote f the aes
|
||||
//! encryption function, we have:
|
||||
//! ```ascii
|
||||
//! f: ⟦0;2¹²⁸ -1⟧ X ⟦0;2¹²⁸ -1⟧ ↦ ⟦0;2¹²⁸ -1⟧
|
||||
//! f(secret_key, input) ↦ output
|
||||
//! ```
|
||||
|
||||
//! If we fix the secret key to a value k, we have a function fₖ from ⟦0;2¹²⁸ -1⟧ to ⟦0;2¹²⁸-1⟧,
|
||||
//! transforming the state of the counter into a pseudo random value. Essentially, this fₖ
|
||||
//! function can be considered as a the following lookup table, containing 2¹²⁸ pseudo-random
|
||||
//! values:
|
||||
//! ```ascii
|
||||
//! ╭──────────────┬──────────────┬─────┬──────────────╮
|
||||
//! │ 0 │ 1 │ │ 2¹²⁸ -1 │
|
||||
//! ├──────────────┼──────────────┼─────┼──────────────┤
|
||||
//! │ fₖ(0) │ fₖ(1) │ │ fₖ(2¹²⁸ -1) │
|
||||
//! ╔═══════↧══════╦═══════↧══════╦═════╦═══════↧══════╗
|
||||
//! ║┏━━━━━━━━━━━━┓║┏━━━━━━━━━━━━┓║ ║┏━━━━━━━━━━━━┓║
|
||||
//! ║┃ u128 ┃║┃ u128 ┃║ ... ║┃ u128 ┃║
|
||||
//! ║┗━━━━━━━━━━━━┛║┗━━━━━━━━━━━━┛║ ║┗━━━━━━━━━━━━┛║
|
||||
//! ╚══════════════╩══════════════╩═════╩══════════════╝
|
||||
//! ```
|
||||
//!
|
||||
//! An input to the fₖ function is called an _aes index_ (also called state or counter in the
|
||||
//! standards) of the pseudo-random table. The [`AesIndex`] structure defined in this module
|
||||
//! represents such an index in the code.
|
||||
//!
|
||||
//! Fine-grained pseudo-random table lookup
|
||||
//! =======================================
|
||||
//!
|
||||
//! Since we want to deliver the pseudo-random bytes one by one, we have to come with a finer
|
||||
//! grained indexing. Fortunately, each `u128` value outputted by fₖ can be seen as a table of 16
|
||||
//! `u8`:
|
||||
//! ```ascii
|
||||
//! ╭──────────────┬──────────────┬─────┬──────────────╮
|
||||
//! │ 0 │ 1 │ │ 2¹²⁸ -1 │
|
||||
//! ├──────────────┼──────────────┼─────┼──────────────┤
|
||||
//! │ fₖ(0) │ fₖ(1) │ │ fₖ(2¹²⁸ -1) │
|
||||
//! ╔═══════↧══════╦═══════↧══════╦═════╦═══════↧══════╗
|
||||
//! ║┏━━━━━━━━━━━━┓║┏━━━━━━━━━━━━┓║ ║┏━━━━━━━━━━━━┓║
|
||||
//! ║┃ u128 ┃║┃ u128 ┃║ ║┃ u128 ┃║
|
||||
//! ║┣━━┯━━┯━━━┯━━┫║┣━━┯━━┯━━━┯━━┫║ ... ║┣━━┯━━┯━━━┯━━┫║
|
||||
//! ║┃u8│u8│...│u8┃║┃u8│u8│...│u8┃║ ║┃u8│u8│...│u8┃║
|
||||
//! ║┗━━┷━━┷━━━┷━━┛║┗━━┷━━┷━━━┷━━┛║ ║┗━━┷━━┷━━━┷━━┛║
|
||||
//! ╚══════════════╩══════════════╩═════╩══════════════╝
|
||||
//! ```
|
||||
//!
|
||||
//! We introduce a second function to select a chunk of 8 bits:
|
||||
//! ```ascii
|
||||
//! g: ⟦0;2¹²⁸ -1⟧ X ⟦0;15⟧ ↦ ⟦0;2⁸ -1⟧
|
||||
//! g(big_int, index) ↦ byte
|
||||
//! ```
|
||||
//!
|
||||
//! If we fix the `u128` value to a value e, we have a function gₑ from ⟦0;15⟧ to ⟦0;2⁸ -1⟧
|
||||
//! transforming an index into a pseudo-random byte:
|
||||
//! ```ascii
|
||||
//! ┏━━━━━━━━┯━━━━━━━━┯━━━┯━━━━━━━━┓
|
||||
//! ┃ u8 │ u8 │...│ u8 ┃
|
||||
//! ┗━━━━━━━━┷━━━━━━━━┷━━━┷━━━━━━━━┛
|
||||
//! │ gₑ(0) │ gₑ(1) │ │ gₑ(15) │
|
||||
//! ╰────────┴─────-──┴───┴────────╯
|
||||
//! ```
|
||||
//!
|
||||
//! We call this input to the gₑ function, a _byte index_ of the pseudo-random table. The
|
||||
//! [`ByteIndex`] structure defined in this module represents such an index in the code.
|
||||
//!
|
||||
//! By using both the g and the fₖ functions, we can define a new function l which allows to index
|
||||
//! any byte of the pseudo-random table:
|
||||
//! ```ascii
|
||||
//! l: ⟦0;2¹²⁸ -1⟧ X ⟦0;15⟧ ↦ ⟦0;2⁸ -1⟧
|
||||
//! l(aes_index, byte_index) ↦ g(fₖ(aes_index), byte_index)
|
||||
//! ```
|
||||
//!
|
||||
//! In this sense, any member of ⟦0;2¹²⁸ -1⟧ X ⟦0;15⟧ uniquely defines a byte in this pseudo-random
|
||||
//! table:
|
||||
//! ```ascii
|
||||
//! e = fₖ(a)
|
||||
//! ╔══════════════╦═══════↧══════╦═════╦══════════════╗
|
||||
//! ║┏━━━━━━━━━━━━┓║┏━━━━━━━━━━━━┓║ ║┏━━━━━━━━━━━━┓║
|
||||
//! ║┃ u128 ┃║┃ u128 ┃║ ║┃ u128 ┃║
|
||||
//! ║┣━━┯━━┯━━━┯━━┫║┣━━┯━━┯━━━┯━━┫║ ... ║┣━━┯━━┯━━━┯━━┫║
|
||||
//! ║┃u8│u8│...│u8┃║┃u8│u8│...│u8┃║ ║┃u8│u8│...│u8┃║
|
||||
//! ║┗━━┷━━┷━━━┷━━┛║┗━━┷↥━┷━━━┷━━┛║ ║┗━━┷━━┷━━━┷━━┛║
|
||||
//! ║ ║│ gₑ(b) │║ ║ ║
|
||||
//! ║ ║╰───-────────╯║ ║ ║
|
||||
//! ╚══════════════╩══════════════╩═════╩══════════════╝
|
||||
//! ```
|
||||
//!
|
||||
//! We call this input to the l function, a _table index_ of the pseudo-random table. The
|
||||
//! [`TableIndex`] structure defined in this module represents such an index in the code.
|
||||
//!
|
||||
//! Prngs current table index
|
||||
//! =========================
|
||||
//!
|
||||
//! When created, a prng is given an initial _table index_, denoted (a₀, b₀), which identifies the
|
||||
//! first byte of the table to be outputted by the prng. Then, each time the prng is queried for a
|
||||
//! new value, the byte corresponding to the current _table index_ is returned, and the current
|
||||
//! _table index_ is incremented:
|
||||
//! ```ascii
|
||||
//! e = fₖ(a₀) e = fₖ(a₁)
|
||||
//! ╔═════↧═════╦═══════════╦═════╦═══════════╗ ╔═══════════╦═════↧═════╦═════╦═══════════╗
|
||||
//! ║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║ ... ║┏━┯━┯━━━┯━┓║ ║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║ ... ║┏━┯━┯━━━┯━┓║
|
||||
//! ║┃ │ │...│ ┃║┃ │ │...│ ┃║ ║┃ │ │...│ ┃║ ║┃ │ │...│ ┃║┃ │ │...│ ┃║ ║┃ │ │...│ ┃║
|
||||
//! ║┗━┷━┷━━━┷↥┛║┗━┷━┷━━━┷━┛║ ║┗━┷━┷━━━┷━┛║ → ║┗━┷━┷━━━┷━┛║┗↥┷━┷━━━┷━┛║ ║┗━┷━┷━━━┷━┛║
|
||||
//! ║│ gₑ(b₀) │║ ║ ║ ║ ║ ║│ gₑ(b₁) │║ ║ ║
|
||||
//! ║╰─────────╯║ ║ ║ ║ ║ ║╰─────────╯║ ║ ║
|
||||
//! ╚═══════════╩═══════════╩═════╩═══════════╝ ╚═══════════╩═══════════╩═════╩═══════════╝
|
||||
//! ```
|
||||
//!
|
||||
//! Prng bound
|
||||
//! ==========
|
||||
//!
|
||||
//! When created, a prng is also given a _bound_ (aₘ, bₘ) , that is a table index which it is not
|
||||
//! allowed to exceed:
|
||||
//! ```ascii
|
||||
//! e = fₖ(a₀)
|
||||
//! ╔═════↧═════╦═══════════╦═════╦═══════════╗
|
||||
//! ║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║ ... ║┏━┯━┯━━━┯━┓║
|
||||
//! ║┃ │ │...│ ┃║┃ │╳│...│╳┃║ ║┃╳│╳│...│╳┃║
|
||||
//! ║┗━┷━┷━━━┷↥┛║┗━┷━┷━━━┷━┛║ ║┗━┷━┷━━━┷━┛║ The current byte can be returned.
|
||||
//! ║│ gₑ(b₀) │║ ║ ║ ║
|
||||
//! ║╰─────────╯║ ║ ║ ║
|
||||
//! ╚═══════════╩═══════════╩═════╩═══════════╝
|
||||
//!
|
||||
//! e = fₖ(aₘ)
|
||||
//! ╔═══════════╦═════↧═════╦═════╦═══════════╗
|
||||
//! ║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║ ... ║┏━┯━┯━━━┯━┓║
|
||||
//! ║┃ │ │...│ ┃║┃ │╳│...│╳┃║ ║┃╳│╳│...│╳┃║ The table index reached the bound,
|
||||
//! ║┗━┷━┷━━━┷━┛║┗━┷↥┷━━━┷━┛║ ║┗━┷━┷━━━┷━┛║ the current byte can not be
|
||||
//! ║ ║│ gₑ(bₘ) │║ ║ ║ returned.
|
||||
//! ║ ║╰─────────╯║ ║ ║
|
||||
//! ╚═══════════╩═══════════╩═════╩═══════════╝
|
||||
//! ```
|
||||
//!
|
||||
//! Buffering
|
||||
//! =========
|
||||
//!
|
||||
//! Calling the aes function every time we need to output a single byte would be a huge waste of
|
||||
//! resources. In practice, we call aes 8 times in a row, for 8 successive values of aes index, and
|
||||
//! store the results in a buffer. For platforms which have a dedicated aes chip, this allows to
|
||||
//! fill the unit pipeline and reduces the amortized cost of the aes function.
|
||||
//!
|
||||
//! Together with the current table index of the prng, we also store a pointer p (initialized at
|
||||
//! p₀=b₀) to the current byte in the buffer. If we denote v the lookup function we have :
|
||||
//! ```ascii
|
||||
//! e = fₖ(a₀) Buffer(length=128)
|
||||
//! ╔═════╦═══════════╦═════↧═════╦═══════════╦═════╗ ┏━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓
|
||||
//! ║ ... ║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║ ... ║ ┃▓│▓│▓│▓│▓│▓│▓│▓│...│▓┃
|
||||
//! ║ ║┃ │ │...│ ┃║┃▓│▓│...│▓┃║┃▓│▓│...│▓┃║ ║ ┗━┷↥┷━┷━┷━┷━┷━┷━┷━━━┷━┛
|
||||
//! ║ ║┗━┷━┷━━━┷━┛║┗━┷↥┷━━━┷━┛║┗━┷━┷━━━┷━┛║ ║ │ v(p₀) │
|
||||
//! ║ ║ ║│ gₑ(b₀) │║ ║ ║ ╰─────────────────────╯
|
||||
//! ║ ║ ║╰─────────╯║ ║ ║
|
||||
//! ╚═════╩═══════════╩═══════════╩═══════════╩═════╝
|
||||
//! ```
|
||||
//!
|
||||
//! We call this input to the v function, a _buffer pointer_. The [`BufferPointer`] structure
|
||||
//! defined in this module represents such a pointer in the code.
|
||||
//!
|
||||
//! When the table index is incremented, the buffer pointer is incremented alongside:
|
||||
//! ```ascii
|
||||
//! e = fₖ(a) Buffer(length=128)
|
||||
//! ╔═════╦═══════════╦═════↧═════╦═══════════╦═════╗ ┏━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓
|
||||
//! ║ ... ║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║┏━┯━┯━━━┯━┓║ ... ║ ┃▓│▓│▓│▓│▓│▓│▓│▓│...│▓┃
|
||||
//! ║ ║┃ │ │...│ ┃║┃▓│▓│...│▓┃║┃▓│▓│...│▓┃║ ║ ┗━┷━┷↥┷━┷━┷━┷━┷━┷━━━┷━┛
|
||||
//! ║ ║┗━┷━┷━━━┷━┛║┗━┷━┷↥━━┷━┛║┗━┷━┷━━━┷━┛║ ║ │ v(p) │
|
||||
//! ║ ║ ║│ gₑ(b) │║ ║ ║ ╰─────────────────────╯
|
||||
//! ║ ║ ║╰─────────╯║ ║ ║
|
||||
//! ╚═════╩═══════════╩═══════════╩═══════════╩═════╝
|
||||
//! ```
|
||||
//!
|
||||
//! When the buffer pointer is incremented it is checked against the size of the buffer, and if
|
||||
//! necessary, a new batch of aes index values is generated.
|
||||
|
||||
pub const AES_CALLS_PER_BATCH: usize = 8;
|
||||
pub const BYTES_PER_AES_CALL: usize = 128 / 8;
|
||||
pub const BYTES_PER_BATCH: usize = BYTES_PER_AES_CALL * AES_CALLS_PER_BATCH;
|
||||
|
||||
/// A module containing structures to manage table indices.
|
||||
mod index;
|
||||
pub use index::*;
|
||||
|
||||
/// A module containing structures to manage table indices and buffer pointers together properly.
|
||||
mod states;
|
||||
pub use states::*;
|
||||
|
||||
/// A module containing an abstraction for aes block ciphers.
|
||||
mod block_cipher;
|
||||
pub use block_cipher::*;
|
||||
|
||||
/// A module containing a generic implementation of a random generator.
|
||||
mod generic;
|
||||
pub use generic::*;
|
||||
|
||||
/// A module extending `generic` to the `rayon` paradigm.
|
||||
#[cfg(feature = "parallel")]
|
||||
mod parallel;
|
||||
#[cfg(feature = "parallel")]
|
||||
pub use parallel::*;
|
||||
222
concrete-csprng/src/generators/aes_ctr/parallel.rs
Normal file
222
concrete-csprng/src/generators/aes_ctr/parallel.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use crate::generators::aes_ctr::{
|
||||
AesBlockCipher, AesCtrGenerator, ChildrenClosure, State, TableIndex,
|
||||
};
|
||||
use crate::generators::{BytesPerChild, ChildrenCount, ForkError};
|
||||
|
||||
/// A type alias for the parallel children iterator type.
|
||||
pub type ParallelChildrenIterator<BlockCipher> = rayon::iter::Map<
|
||||
rayon::iter::Zip<
|
||||
rayon::range::Iter<usize>,
|
||||
rayon::iter::RepeatN<(Box<BlockCipher>, TableIndex, BytesPerChild)>,
|
||||
>,
|
||||
fn((usize, (Box<BlockCipher>, TableIndex, BytesPerChild))) -> AesCtrGenerator<BlockCipher>,
|
||||
>;
|
||||
|
||||
impl<BlockCipher: AesBlockCipher> AesCtrGenerator<BlockCipher> {
|
||||
/// Tries to fork the current generator into `n_child` generators each able to output
|
||||
/// `child_bytes` random bytes as a parallel iterator.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This method necessitate the "multithread" feature.
|
||||
pub fn par_try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<ParallelChildrenIterator<BlockCipher>, ForkError>
|
||||
where
|
||||
BlockCipher: Send + Sync,
|
||||
{
|
||||
use rayon::prelude::*;
|
||||
|
||||
if n_children.0 == 0 {
|
||||
return Err(ForkError::ZeroChildrenCount);
|
||||
}
|
||||
if n_bytes.0 == 0 {
|
||||
return Err(ForkError::ZeroBytesPerChild);
|
||||
}
|
||||
if !self.is_fork_in_bound(n_children, n_bytes) {
|
||||
return Err(ForkError::ForkTooLarge);
|
||||
}
|
||||
|
||||
// The state currently stored in the parent generator points to the table index of the last
|
||||
// generated byte. The first index to be generated is the next one :
|
||||
let first_index = self.state.table_index().incremented();
|
||||
let output = (0..n_children.0)
|
||||
.into_par_iter()
|
||||
.zip(rayon::iter::repeatn(
|
||||
(self.block_cipher.clone(), first_index, n_bytes),
|
||||
n_children.0,
|
||||
))
|
||||
.map(
|
||||
// This map is a little weird because we need to cast the closure to a fn pointer
|
||||
// that matches the signature of `ChildrenIterator<BlockCipher>`. Unfortunately,
|
||||
// the compiler does not manage to coerce this one automatically.
|
||||
(|(i, (block_cipher, first_index, n_bytes))| {
|
||||
// The first index to be outputted by the child is the `first_index` shifted by
|
||||
// the proper amount of `child_bytes`.
|
||||
let child_first_index = first_index.increased(n_bytes.0 * i);
|
||||
// The bound of the child is the first index of its next sibling.
|
||||
let child_bound_index = first_index.increased(n_bytes.0 * (i + 1));
|
||||
AesCtrGenerator::from_block_cipher(
|
||||
block_cipher,
|
||||
child_first_index,
|
||||
child_bound_index,
|
||||
)
|
||||
}) as ChildrenClosure<BlockCipher>,
|
||||
);
|
||||
// The parent next index is the bound of the last child.
|
||||
let next_index = first_index.increased(n_bytes.0 * n_children.0);
|
||||
self.state = State::new(next_index);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod aes_ctr_parallel_generic_tests {
|
||||
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::aes_ctr_generic_test::{any_key, any_valid_fork};
|
||||
use rayon::prelude::*;
|
||||
|
||||
const REPEATS: usize = 1_000_000;
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the table index of the first child is the same as the table index of
|
||||
/// the parent before the fork.
|
||||
pub fn prop_fork_first_state_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
let first_child = forked_generator
|
||||
.par_try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.find_first(|_| true)
|
||||
.unwrap();
|
||||
assert_eq!(original_generator.table_index(), first_child.table_index());
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the table index of the first byte outputted by the parent after the
|
||||
/// fork, is the bound of the last child of the fork.
|
||||
pub fn prop_fork_last_bound_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let mut parent_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let last_child = parent_generator
|
||||
.par_try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.find_last(|_| true)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
parent_generator.table_index().incremented(),
|
||||
last_child.get_bound()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the bound of the parent does not change.
|
||||
pub fn prop_fork_parent_bound_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
forked_generator
|
||||
.par_try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.find_last(|_| true)
|
||||
.unwrap();
|
||||
assert_eq!(original_generator.get_bound(), forked_generator.get_bound());
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the parent table index is increased of the number of children
|
||||
/// multiplied by the number of bytes per child.
|
||||
pub fn prop_fork_parent_state_table_index<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
forked_generator
|
||||
.par_try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.find_last(|_| true)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
forked_generator.table_index(),
|
||||
// Decrement accounts for the fact that the table index stored is the previous one
|
||||
t.increased(nc.0 * nb.0).decremented()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the bytes outputted by the children in the fork order form the same
|
||||
/// sequence the parent would have had outputted no fork had happened.
|
||||
pub fn prop_fork<G: AesBlockCipher>() {
|
||||
for _ in 0..1000 {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let bytes_to_go = nc.0 * nb.0;
|
||||
let original_generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let mut forked_generator = original_generator.clone();
|
||||
let initial_output: Vec<u8> = original_generator.take(bytes_to_go).collect();
|
||||
let forked_output: Vec<u8> = forked_generator
|
||||
.par_try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.flat_map(|child| child.collect::<Vec<_>>())
|
||||
.collect();
|
||||
assert_eq!(initial_output, forked_output);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, all children got a number of remaining bytes equals to the number of
|
||||
/// bytes per child given as fork input.
|
||||
pub fn prop_fork_children_remaining_bytes<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let mut generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
assert!(generator
|
||||
.par_try_fork(nc, nb)
|
||||
.unwrap()
|
||||
.all(|c| c.remaining_bytes().0 == nb.0 as u128));
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the property:
|
||||
/// On a valid fork, the number of remaining bytes of the parent is reduced by the
|
||||
/// number of children multiplied by the number of bytes per child.
|
||||
pub fn prop_fork_parent_remaining_bytes<G: AesBlockCipher>() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, nc, nb, i) = any_valid_fork().next().unwrap();
|
||||
let k = any_key().next().unwrap();
|
||||
let bytes_to_go = nc.0 * nb.0;
|
||||
let mut generator =
|
||||
AesCtrGenerator::<G>::new(k, Some(t), Some(t.increased(nc.0 * nb.0 + i)));
|
||||
let before_remaining_bytes = generator.remaining_bytes();
|
||||
let _ = generator.par_try_fork(nc, nb).unwrap();
|
||||
let after_remaining_bytes = generator.remaining_bytes();
|
||||
assert_eq!(
|
||||
before_remaining_bytes.0 - after_remaining_bytes.0,
|
||||
bytes_to_go as u128
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
176
concrete-csprng/src/generators/aes_ctr/states.rs
Normal file
176
concrete-csprng/src/generators/aes_ctr/states.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use crate::generators::aes_ctr::index::{AesIndex, TableIndex};
|
||||
use crate::generators::aes_ctr::BYTES_PER_BATCH;
|
||||
|
||||
/// A pointer to the next byte to be outputted by the generator.
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct BufferPointer(pub usize);
|
||||
|
||||
/// A structure representing the current state of generator using batched aes-ctr approach.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct State {
|
||||
table_index: TableIndex,
|
||||
buffer_pointer: BufferPointer,
|
||||
}
|
||||
|
||||
/// A structure representing the action to be taken by the generator after shifting its state.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ShiftAction {
|
||||
/// Outputs the byte pointed to by the 0-th field.
|
||||
OutputByte(BufferPointer),
|
||||
/// Refresh the buffer starting from the 0-th field, and output the byte pointed to by the 0-th
|
||||
/// field.
|
||||
RefreshBatchAndOutputByte(AesIndex, BufferPointer),
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new state from the initial table index.
|
||||
///
|
||||
/// Note :
|
||||
/// ------
|
||||
///
|
||||
/// The `table_index` input, is the __first__ table index that will be outputted on the next
|
||||
/// call to `increment`. Put differently, the current table index of the newly created state
|
||||
/// is the predecessor of this one.
|
||||
pub fn new(table_index: TableIndex) -> Self {
|
||||
// We ensure that the table index is not the first one, to prevent wrapping on `decrement`,
|
||||
// and outputting `RefreshBatchAndOutputByte(AesIndex::MAX, ...)` on the first increment
|
||||
// (which would lead to loading a non continuous batch).
|
||||
assert_ne!(table_index, TableIndex::FIRST);
|
||||
State {
|
||||
// To ensure that the first outputted table index is the proper one, we decrement the
|
||||
// table index.
|
||||
table_index: table_index.decremented(),
|
||||
// To ensure that the first `ShiftAction` will be a `RefreshBatchAndOutputByte`, we set
|
||||
// the buffer to the last allowed value.
|
||||
buffer_pointer: BufferPointer(BYTES_PER_BATCH - 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shifts the state forward of `shift` bytes.
|
||||
pub fn increase(&mut self, shift: usize) -> ShiftAction {
|
||||
self.table_index.increase(shift);
|
||||
let total_batch_index = self.buffer_pointer.0 + shift;
|
||||
if total_batch_index > BYTES_PER_BATCH - 1 {
|
||||
self.buffer_pointer.0 = self.table_index.byte_index.0;
|
||||
ShiftAction::RefreshBatchAndOutputByte(self.table_index.aes_index, self.buffer_pointer)
|
||||
} else {
|
||||
self.buffer_pointer.0 = total_batch_index;
|
||||
ShiftAction::OutputByte(self.buffer_pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shifts the state forward of one byte.
|
||||
pub fn increment(&mut self) -> ShiftAction {
|
||||
self.increase(1)
|
||||
}
|
||||
|
||||
/// Returns the current table index.
|
||||
pub fn table_index(&self) -> TableIndex {
|
||||
self.table_index
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
State::new(TableIndex::FIRST)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::index::ByteIndex;
|
||||
use crate::generators::aes_ctr::BYTES_PER_AES_CALL;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
const REPEATS: usize = 1_000_000;
|
||||
|
||||
fn any_table_index() -> impl Iterator<Item = TableIndex> {
|
||||
std::iter::repeat_with(|| {
|
||||
TableIndex::new(
|
||||
AesIndex(thread_rng().gen()),
|
||||
ByteIndex(thread_rng().gen::<usize>() % BYTES_PER_AES_CALL),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn any_usize() -> impl Iterator<Item = usize> {
|
||||
std::iter::repeat_with(|| thread_rng().gen())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t,
|
||||
/// State::new(t).increment() = RefreshBatchAndOutputByte(t.aes_index, t.byte_index)
|
||||
fn prop_state_new_increment() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, mut s) = any_table_index()
|
||||
.map(|t| (t, State::new(t)))
|
||||
.next()
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
s.increment(),
|
||||
ShiftAction::RefreshBatchAndOutputByte(t_, BufferPointer(p_)) if t_ == t.aes_index && p_ == t.byte_index.0
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all states s, table indices t, positive integer i
|
||||
/// if s = State::new(t), then t.increased(i) = s.increased(i-1).table_index().
|
||||
fn prop_state_increase_table_index() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, mut s, i) = any_table_index()
|
||||
.zip(any_usize())
|
||||
.map(|(t, i)| (t, State::new(t), i))
|
||||
.next()
|
||||
.unwrap();
|
||||
s.increase(i);
|
||||
assert_eq!(s.table_index(), t.increased(i - 1))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, positive integer i such as t.byte_index + i < 127,
|
||||
/// if s = State::new(t), and s.increment() was executed, then
|
||||
/// s.increase(i) = OutputByte(t.byte_index + i).
|
||||
fn prop_state_increase_small() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, mut s, i) = any_table_index()
|
||||
.zip(any_usize())
|
||||
.map(|(t, i)| (t, State::new(t), i % BYTES_PER_BATCH))
|
||||
.find(|(t, _, i)| t.byte_index.0 + i < BYTES_PER_BATCH - 1)
|
||||
.unwrap();
|
||||
s.increment();
|
||||
assert!(matches!(
|
||||
s.increase(i),
|
||||
ShiftAction::OutputByte(BufferPointer(p_)) if p_ == t.byte_index.0 + i
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check the property:
|
||||
/// For all table indices t, positive integer i such as t.byte_index + i >= 127,
|
||||
/// if s = State::new(t), and s.increment() was executed, then
|
||||
/// s.increase(i) = RefreshBatchAndOutputByte(
|
||||
/// t.increased(i).aes_index,
|
||||
/// t.increased(i).byte_index).
|
||||
fn prop_state_increase_large() {
|
||||
for _ in 0..REPEATS {
|
||||
let (t, mut s, i) = any_table_index()
|
||||
.zip(any_usize())
|
||||
.map(|(t, i)| (t, State::new(t), i))
|
||||
.find(|(t, _, i)| t.byte_index.0 + i >= BYTES_PER_BATCH - 1)
|
||||
.unwrap();
|
||||
s.increment();
|
||||
assert!(matches!(
|
||||
s.increase(i),
|
||||
ShiftAction::RefreshBatchAndOutputByte(t_, BufferPointer(p_))
|
||||
if t_ == t.increased(i).aes_index && p_ == t.increased(i).byte_index.0
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
173
concrete-csprng/src/generators/implem/aarch64/block_cipher.rs
Normal file
173
concrete-csprng/src/generators/implem/aarch64/block_cipher.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use crate::generators::aes_ctr::{AesBlockCipher, AesIndex, AesKey, BYTES_PER_BATCH};
|
||||
use core::arch::aarch64::{
|
||||
uint8x16_t, vaeseq_u8, vaesmcq_u8, vdupq_n_u32, vdupq_n_u8, veorq_u8, vgetq_lane_u32,
|
||||
vreinterpretq_u32_u8, vreinterpretq_u8_u32,
|
||||
};
|
||||
use std::arch::is_aarch64_feature_detected;
|
||||
use std::mem::transmute;
|
||||
|
||||
const RCONS: [u32; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36];
|
||||
const NUM_WORDS_IN_KEY: usize = 4;
|
||||
const NUM_ROUNDS: usize = 10;
|
||||
const NUM_ROUND_KEYS: usize = NUM_ROUNDS + 1;
|
||||
|
||||
/// An aes block cipher implementation which uses `neon` and `aes` instructions.
|
||||
#[derive(Clone)]
|
||||
pub struct ArmAesBlockCipher {
|
||||
round_keys: [uint8x16_t; NUM_ROUND_KEYS],
|
||||
}
|
||||
|
||||
impl AesBlockCipher for ArmAesBlockCipher {
|
||||
fn new(key: AesKey) -> ArmAesBlockCipher {
|
||||
let aes_detected = is_aarch64_feature_detected!("aes");
|
||||
let neon_detected = is_aarch64_feature_detected!("neon");
|
||||
|
||||
if !(aes_detected && neon_detected) {
|
||||
panic!(
|
||||
"The ArmAesBlockCipher requires both aes and neon aarch64 CPU features.\n\
|
||||
aes feature available: {}\nneon feature available: {}\n.",
|
||||
aes_detected, neon_detected
|
||||
)
|
||||
}
|
||||
|
||||
let round_keys = unsafe { generate_round_keys(key) };
|
||||
ArmAesBlockCipher { round_keys }
|
||||
}
|
||||
|
||||
fn generate_batch(&mut self, AesIndex(aes_ctr): AesIndex) -> [u8; BYTES_PER_BATCH] {
|
||||
let mut output = [0u8; BYTES_PER_BATCH];
|
||||
// We want 128 bytes of output, the ctr gives 128 bit message (16 bytes)
|
||||
for (i, out) in output.chunks_exact_mut(16).enumerate() {
|
||||
let encrypted = unsafe {
|
||||
// Safe because we prevent the user from creating the Generator
|
||||
// on non-supported hardware
|
||||
encrypt(aes_ctr + (i as u128), &self.round_keys)
|
||||
};
|
||||
out.copy_from_slice(&encrypted.to_ne_bytes());
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/// Does the AES SubWord operation for the Key Expansion step
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// You must make sure the CPU's arch is`aarch64` and has
|
||||
/// `neon` and `aes` features.
|
||||
unsafe fn sub_word(word: u32) -> u32 {
|
||||
let data = vreinterpretq_u8_u32(vdupq_n_u32(word));
|
||||
let zero_key = vdupq_n_u8(0u8);
|
||||
let temp = vaeseq_u8(data, zero_key);
|
||||
// vaeseq_u8 does SubBytes(ShiftRow(XOR(data, key))
|
||||
// But because we used a zero aes key,the XOR did not alter data
|
||||
// We now have temp = SubBytes(ShiftRow(data))
|
||||
|
||||
// Since in AES ShiftRow operation, the first row is not shifted
|
||||
// We can just get that one to have our SubWord(word) result
|
||||
vgetq_lane_u32::<0>(vreinterpretq_u32_u8(temp))
|
||||
}
|
||||
|
||||
fn uint8x16_t_to_u128(input: uint8x16_t) -> u128 {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
fn u128_to_uint8x16_t(input: u128) -> uint8x16_t {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
unsafe fn generate_round_keys(key: AesKey) -> [uint8x16_t; NUM_ROUND_KEYS] {
|
||||
let mut round_keys: [uint8x16_t; NUM_ROUND_KEYS] = std::mem::zeroed();
|
||||
round_keys[0] = u128_to_uint8x16_t(key.0);
|
||||
|
||||
let words = std::slice::from_raw_parts_mut(
|
||||
round_keys.as_mut_ptr() as *mut u32,
|
||||
NUM_ROUND_KEYS * NUM_WORDS_IN_KEY,
|
||||
);
|
||||
|
||||
debug_assert_eq!(words.len(), 44);
|
||||
|
||||
// Skip the words of the first key, its already done
|
||||
for i in NUM_WORDS_IN_KEY..words.len() {
|
||||
if (i % NUM_WORDS_IN_KEY) == 0 {
|
||||
words[i] = words[i - NUM_WORDS_IN_KEY]
|
||||
^ sub_word(words[i - 1]).rotate_right(8)
|
||||
^ RCONS[(i / NUM_WORDS_IN_KEY) - 1];
|
||||
} else {
|
||||
words[i] = words[i - NUM_WORDS_IN_KEY] ^ words[i - 1];
|
||||
}
|
||||
// Note: there is also a special thing to do when
|
||||
// i mod SElf::NUM_WORDS_IN_KEY == 4 but it cannot happen on 128 bits keys
|
||||
}
|
||||
|
||||
round_keys
|
||||
}
|
||||
|
||||
/// Encrypts a 128-bit message
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// You must make sure the CPU's arch is`aarch64` and has
|
||||
/// `neon` and `aes` features.
|
||||
unsafe fn encrypt(message: u128, keys: &[uint8x16_t; NUM_ROUND_KEYS]) -> u128 {
|
||||
// Notes:
|
||||
// According the [ARM Manual](https://developer.arm.com/documentation/ddi0487/gb/):
|
||||
// `vaeseq_u8` is the following AES operations:
|
||||
// 1. AddRoundKey (XOR)
|
||||
// 2. ShiftRows
|
||||
// 3. SubBytes
|
||||
// `vaesmcq_u8` is MixColumns
|
||||
let mut data: uint8x16_t = u128_to_uint8x16_t(message);
|
||||
|
||||
for &key in keys.iter().take(NUM_ROUNDS - 1) {
|
||||
data = vaesmcq_u8(vaeseq_u8(data, key));
|
||||
}
|
||||
|
||||
data = vaeseq_u8(data, keys[NUM_ROUNDS - 1]);
|
||||
data = veorq_u8(data, keys[NUM_ROUND_KEYS - 1]);
|
||||
|
||||
uint8x16_t_to_u128(data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// Test vector for aes128, from the FIPS publication 197
|
||||
const CIPHER_KEY: u128 = u128::from_be(0x000102030405060708090a0b0c0d0e0f);
|
||||
const KEY_SCHEDULE: [u128; 11] = [
|
||||
u128::from_be(0x000102030405060708090a0b0c0d0e0f),
|
||||
u128::from_be(0xd6aa74fdd2af72fadaa678f1d6ab76fe),
|
||||
u128::from_be(0xb692cf0b643dbdf1be9bc5006830b3fe),
|
||||
u128::from_be(0xb6ff744ed2c2c9bf6c590cbf0469bf41),
|
||||
u128::from_be(0x47f7f7bc95353e03f96c32bcfd058dfd),
|
||||
u128::from_be(0x3caaa3e8a99f9deb50f3af57adf622aa),
|
||||
u128::from_be(0x5e390f7df7a69296a7553dc10aa31f6b),
|
||||
u128::from_be(0x14f9701ae35fe28c440adf4d4ea9c026),
|
||||
u128::from_be(0x47438735a41c65b9e016baf4aebf7ad2),
|
||||
u128::from_be(0x549932d1f08557681093ed9cbe2c974e),
|
||||
u128::from_be(0x13111d7fe3944a17f307a78b4d2b30c5),
|
||||
];
|
||||
const PLAINTEXT: u128 = u128::from_be(0x00112233445566778899aabbccddeeff);
|
||||
const CIPHERTEXT: u128 = u128::from_be(0x69c4e0d86a7b0430d8cdb78070b4c55a);
|
||||
|
||||
#[test]
|
||||
fn test_generate_key_schedule() {
|
||||
// Checks that the round keys are correctly generated from the sample key from FIPS
|
||||
let key = AesKey(CIPHER_KEY);
|
||||
let keys = unsafe { generate_round_keys(key) };
|
||||
for (expected, actual) in KEY_SCHEDULE.iter().zip(keys.iter()) {
|
||||
assert_eq!(*expected, uint8x16_t_to_u128(*actual));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_message() {
|
||||
// Checks that encrypting many plaintext at the same time gives the correct output.
|
||||
let message = PLAINTEXT;
|
||||
let key = AesKey(CIPHER_KEY);
|
||||
let keys = unsafe { generate_round_keys(key) };
|
||||
let ciphertext = unsafe { encrypt(message, &keys) };
|
||||
assert_eq!(CIPHERTEXT, ciphertext);
|
||||
}
|
||||
}
|
||||
110
concrete-csprng/src/generators/implem/aarch64/generator.rs
Normal file
110
concrete-csprng/src/generators/implem/aarch64/generator.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use crate::generators::aes_ctr::{AesCtrGenerator, AesKey, ChildrenIterator};
|
||||
use crate::generators::implem::aarch64::block_cipher::ArmAesBlockCipher;
|
||||
use crate::generators::{ByteCount, BytesPerChild, ChildrenCount, ForkError, RandomGenerator};
|
||||
use crate::seeders::Seed;
|
||||
|
||||
/// A random number generator using the `aesni` instructions.
|
||||
pub struct NeonAesRandomGenerator(pub(super) AesCtrGenerator<ArmAesBlockCipher>);
|
||||
|
||||
/// The children iterator used by [`NeonAesRandomGenerator`].
|
||||
///
|
||||
/// Outputs children generators one by one.
|
||||
pub struct ArmAesChildrenIterator(ChildrenIterator<ArmAesBlockCipher>);
|
||||
|
||||
impl Iterator for ArmAesChildrenIterator {
|
||||
type Item = NeonAesRandomGenerator;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(NeonAesRandomGenerator)
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomGenerator for NeonAesRandomGenerator {
|
||||
type ChildrenIter = ArmAesChildrenIterator;
|
||||
fn new(seed: Seed) -> Self {
|
||||
NeonAesRandomGenerator(AesCtrGenerator::new(AesKey(seed.0), None, None))
|
||||
}
|
||||
fn remaining_bytes(&self) -> ByteCount {
|
||||
self.0.remaining_bytes()
|
||||
}
|
||||
fn try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ChildrenIter, ForkError> {
|
||||
self.0
|
||||
.try_fork(n_children, n_bytes)
|
||||
.map(ArmAesChildrenIterator)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for NeonAesRandomGenerator {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::generators::aes_ctr::aes_ctr_generic_test;
|
||||
use crate::generators::implem::aarch64::block_cipher::ArmAesBlockCipher;
|
||||
use crate::generators::{generator_generic_test, NeonAesRandomGenerator};
|
||||
|
||||
#[test]
|
||||
fn prop_fork_first_state_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_first_state_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_last_bound_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_last_bound_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_bound_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_parent_bound_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_state_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_parent_state_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork() {
|
||||
aes_ctr_generic_test::prop_fork::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_children_remaining_bytes() {
|
||||
aes_ctr_generic_test::prop_fork_children_remaining_bytes::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_remaining_bytes() {
|
||||
aes_ctr_generic_test::prop_fork_parent_remaining_bytes::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roughly_uniform() {
|
||||
generator_generic_test::test_roughly_uniform::<NeonAesRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generator_determinism() {
|
||||
generator_generic_test::test_generator_determinism::<NeonAesRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fork() {
|
||||
generator_generic_test::test_fork_children::<NeonAesRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "expected test panic")]
|
||||
fn test_bounded_panic() {
|
||||
generator_generic_test::test_bounded_none_should_panic::<NeonAesRandomGenerator>();
|
||||
}
|
||||
}
|
||||
16
concrete-csprng/src/generators/implem/aarch64/mod.rs
Normal file
16
concrete-csprng/src/generators/implem/aarch64/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
//! A module implementing a random number generator, using the aarch64 `neon` and `aes`
|
||||
//! instructions.
|
||||
//!
|
||||
//! This module implements a cryptographically secure pseudorandom number generator
|
||||
//! (CS-PRNG), using a fast block cipher. The implementation is based on the
|
||||
//! [intel aesni white paper 323641-001 revision 3.0](https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf).
|
||||
|
||||
mod block_cipher;
|
||||
|
||||
mod generator;
|
||||
pub use generator::*;
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
mod parallel;
|
||||
#[cfg(feature = "parallel")]
|
||||
pub use parallel::*;
|
||||
95
concrete-csprng/src/generators/implem/aarch64/parallel.rs
Normal file
95
concrete-csprng/src/generators/implem/aarch64/parallel.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::{AesCtrGenerator, ParallelChildrenIterator};
|
||||
use crate::generators::implem::aarch64::block_cipher::ArmAesBlockCipher;
|
||||
use crate::generators::{BytesPerChild, ChildrenCount, ForkError, ParallelRandomGenerator};
|
||||
use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer};
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// The parallel children iterator used by [`NeonAesRandomGenerator`].
|
||||
///
|
||||
/// Outputs the children generators one by one.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct ParallelArmAesChildrenIterator(
|
||||
rayon::iter::Map<
|
||||
ParallelChildrenIterator<ArmAesBlockCipher>,
|
||||
fn(AesCtrGenerator<ArmAesBlockCipher>) -> NeonAesRandomGenerator,
|
||||
>,
|
||||
);
|
||||
|
||||
impl ParallelIterator for ParallelArmAesChildrenIterator {
|
||||
type Item = NeonAesRandomGenerator;
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.0.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexedParallelIterator for ParallelArmAesChildrenIterator {
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
|
||||
self.0.drive(consumer)
|
||||
}
|
||||
fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
|
||||
self.0.with_producer(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParallelRandomGenerator for NeonAesRandomGenerator {
|
||||
type ParChildrenIter = ParallelArmAesChildrenIterator;
|
||||
|
||||
fn par_try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ParChildrenIter, ForkError> {
|
||||
self.0
|
||||
.par_try_fork(n_children, n_bytes)
|
||||
.map(|iterator| ParallelArmAesChildrenIterator(iterator.map(NeonAesRandomGenerator)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod test {
|
||||
use crate::generators::aes_ctr::aes_ctr_parallel_generic_tests;
|
||||
use crate::generators::implem::aarch64::block_cipher::ArmAesBlockCipher;
|
||||
|
||||
#[test]
|
||||
fn prop_fork_first_state_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_first_state_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_last_bound_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_last_bound_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_bound_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_bound_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_state_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_state_table_index::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_ttt() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_children_remaining_bytes() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_children_remaining_bytes::<ArmAesBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_remaining_bytes() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_remaining_bytes::<ArmAesBlockCipher>();
|
||||
}
|
||||
}
|
||||
226
concrete-csprng/src/generators/implem/aesni/block_cipher.rs
Normal file
226
concrete-csprng/src/generators/implem/aesni/block_cipher.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
use crate::generators::aes_ctr::{AesBlockCipher, AesIndex, AesKey, BYTES_PER_BATCH};
|
||||
use std::arch::x86_64::{
|
||||
__m128i, _mm_aesenc_si128, _mm_aesenclast_si128, _mm_aeskeygenassist_si128, _mm_load_si128,
|
||||
_mm_shuffle_epi32, _mm_slli_si128, _mm_store_si128, _mm_xor_si128,
|
||||
};
|
||||
use std::mem::transmute;
|
||||
|
||||
/// An aes block cipher implementation which uses `aesni` instructions.
|
||||
#[derive(Clone)]
|
||||
pub struct AesniBlockCipher {
|
||||
// The set of round keys used for the aes encryption
|
||||
round_keys: [__m128i; 11],
|
||||
}
|
||||
|
||||
impl AesBlockCipher for AesniBlockCipher {
|
||||
fn new(key: AesKey) -> AesniBlockCipher {
|
||||
let aes_detected = is_x86_feature_detected!("aes");
|
||||
let sse2_detected = is_x86_feature_detected!("sse2");
|
||||
|
||||
if !(aes_detected && sse2_detected) {
|
||||
panic!(
|
||||
"The AesniBlockCipher requires both aes and sse2 x86 CPU features.\n\
|
||||
aes feature available: {}\nsse2 feature available: {}\n.",
|
||||
aes_detected, sse2_detected
|
||||
)
|
||||
}
|
||||
|
||||
let round_keys = generate_round_keys(key);
|
||||
AesniBlockCipher { round_keys }
|
||||
}
|
||||
|
||||
fn generate_batch(&mut self, AesIndex(aes_ctr): AesIndex) -> [u8; BYTES_PER_BATCH] {
|
||||
si128arr_to_u8arr(aes_encrypt_many(
|
||||
&u128_to_si128(aes_ctr),
|
||||
&u128_to_si128(aes_ctr + 1),
|
||||
&u128_to_si128(aes_ctr + 2),
|
||||
&u128_to_si128(aes_ctr + 3),
|
||||
&u128_to_si128(aes_ctr + 4),
|
||||
&u128_to_si128(aes_ctr + 5),
|
||||
&u128_to_si128(aes_ctr + 6),
|
||||
&u128_to_si128(aes_ctr + 7),
|
||||
&self.round_keys,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_round_keys(key: AesKey) -> [__m128i; 11] {
|
||||
let key = u128_to_si128(key.0);
|
||||
let mut keys: [__m128i; 11] = [u128_to_si128(0); 11];
|
||||
aes_128_key_expansion(key, &mut keys);
|
||||
keys
|
||||
}
|
||||
|
||||
// Uses aes to encrypt many values at once. This allows a substantial speedup (around 30%)
|
||||
// compared to the naive approach.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn aes_encrypt_many(
|
||||
message_1: &__m128i,
|
||||
message_2: &__m128i,
|
||||
message_3: &__m128i,
|
||||
message_4: &__m128i,
|
||||
message_5: &__m128i,
|
||||
message_6: &__m128i,
|
||||
message_7: &__m128i,
|
||||
message_8: &__m128i,
|
||||
keys: &[__m128i; 11],
|
||||
) -> [__m128i; 8] {
|
||||
unsafe {
|
||||
let message_1 = _mm_load_si128(message_1 as *const __m128i);
|
||||
let message_2 = _mm_load_si128(message_2 as *const __m128i);
|
||||
let message_3 = _mm_load_si128(message_3 as *const __m128i);
|
||||
let message_4 = _mm_load_si128(message_4 as *const __m128i);
|
||||
let message_5 = _mm_load_si128(message_5 as *const __m128i);
|
||||
let message_6 = _mm_load_si128(message_6 as *const __m128i);
|
||||
let message_7 = _mm_load_si128(message_7 as *const __m128i);
|
||||
let message_8 = _mm_load_si128(message_8 as *const __m128i);
|
||||
|
||||
let mut tmp_1 = _mm_xor_si128(message_1, keys[0]);
|
||||
let mut tmp_2 = _mm_xor_si128(message_2, keys[0]);
|
||||
let mut tmp_3 = _mm_xor_si128(message_3, keys[0]);
|
||||
let mut tmp_4 = _mm_xor_si128(message_4, keys[0]);
|
||||
let mut tmp_5 = _mm_xor_si128(message_5, keys[0]);
|
||||
let mut tmp_6 = _mm_xor_si128(message_6, keys[0]);
|
||||
let mut tmp_7 = _mm_xor_si128(message_7, keys[0]);
|
||||
let mut tmp_8 = _mm_xor_si128(message_8, keys[0]);
|
||||
|
||||
for key in keys.iter().take(10).skip(1) {
|
||||
tmp_1 = _mm_aesenc_si128(tmp_1, *key);
|
||||
tmp_2 = _mm_aesenc_si128(tmp_2, *key);
|
||||
tmp_3 = _mm_aesenc_si128(tmp_3, *key);
|
||||
tmp_4 = _mm_aesenc_si128(tmp_4, *key);
|
||||
tmp_5 = _mm_aesenc_si128(tmp_5, *key);
|
||||
tmp_6 = _mm_aesenc_si128(tmp_6, *key);
|
||||
tmp_7 = _mm_aesenc_si128(tmp_7, *key);
|
||||
tmp_8 = _mm_aesenc_si128(tmp_8, *key);
|
||||
}
|
||||
|
||||
tmp_1 = _mm_aesenclast_si128(tmp_1, keys[10]);
|
||||
tmp_2 = _mm_aesenclast_si128(tmp_2, keys[10]);
|
||||
tmp_3 = _mm_aesenclast_si128(tmp_3, keys[10]);
|
||||
tmp_4 = _mm_aesenclast_si128(tmp_4, keys[10]);
|
||||
tmp_5 = _mm_aesenclast_si128(tmp_5, keys[10]);
|
||||
tmp_6 = _mm_aesenclast_si128(tmp_6, keys[10]);
|
||||
tmp_7 = _mm_aesenclast_si128(tmp_7, keys[10]);
|
||||
tmp_8 = _mm_aesenclast_si128(tmp_8, keys[10]);
|
||||
|
||||
[tmp_1, tmp_2, tmp_3, tmp_4, tmp_5, tmp_6, tmp_7, tmp_8]
|
||||
}
|
||||
}
|
||||
|
||||
fn aes_128_assist(temp1: __m128i, temp2: __m128i) -> __m128i {
|
||||
let mut temp3: __m128i;
|
||||
let mut temp2 = temp2;
|
||||
let mut temp1 = temp1;
|
||||
unsafe {
|
||||
temp2 = _mm_shuffle_epi32(temp2, 0xff);
|
||||
temp3 = _mm_slli_si128(temp1, 0x4);
|
||||
temp1 = _mm_xor_si128(temp1, temp3);
|
||||
temp3 = _mm_slli_si128(temp3, 0x4);
|
||||
temp1 = _mm_xor_si128(temp1, temp3);
|
||||
temp3 = _mm_slli_si128(temp3, 0x4);
|
||||
temp1 = _mm_xor_si128(temp1, temp3);
|
||||
temp1 = _mm_xor_si128(temp1, temp2);
|
||||
}
|
||||
temp1
|
||||
}
|
||||
|
||||
fn aes_128_key_expansion(key: __m128i, keys: &mut [__m128i; 11]) {
|
||||
let (mut temp1, mut temp2): (__m128i, __m128i);
|
||||
temp1 = key;
|
||||
unsafe {
|
||||
_mm_store_si128(keys.as_mut_ptr(), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x01);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(1), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x02);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(2), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x04);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(3), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x08);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(4), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x10);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(5), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x20);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(6), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x40);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(7), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x80);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(8), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x1b);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(9), temp1);
|
||||
temp2 = _mm_aeskeygenassist_si128(temp1, 0x36);
|
||||
temp1 = aes_128_assist(temp1, temp2);
|
||||
_mm_store_si128(keys.as_mut_ptr().offset(10), temp1);
|
||||
}
|
||||
}
|
||||
|
||||
fn u128_to_si128(input: u128) -> __m128i {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
#[allow(unused)] // to please clippy when tests are not activated
|
||||
fn si128_to_u128(input: __m128i) -> u128 {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
fn si128arr_to_u8arr(input: [__m128i; 8]) -> [u8; BYTES_PER_BATCH] {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// Test vector for aes128, from the FIPS publication 197
|
||||
const CIPHER_KEY: u128 = u128::from_be(0x000102030405060708090a0b0c0d0e0f);
|
||||
const KEY_SCHEDULE: [u128; 11] = [
|
||||
u128::from_be(0x000102030405060708090a0b0c0d0e0f),
|
||||
u128::from_be(0xd6aa74fdd2af72fadaa678f1d6ab76fe),
|
||||
u128::from_be(0xb692cf0b643dbdf1be9bc5006830b3fe),
|
||||
u128::from_be(0xb6ff744ed2c2c9bf6c590cbf0469bf41),
|
||||
u128::from_be(0x47f7f7bc95353e03f96c32bcfd058dfd),
|
||||
u128::from_be(0x3caaa3e8a99f9deb50f3af57adf622aa),
|
||||
u128::from_be(0x5e390f7df7a69296a7553dc10aa31f6b),
|
||||
u128::from_be(0x14f9701ae35fe28c440adf4d4ea9c026),
|
||||
u128::from_be(0x47438735a41c65b9e016baf4aebf7ad2),
|
||||
u128::from_be(0x549932d1f08557681093ed9cbe2c974e),
|
||||
u128::from_be(0x13111d7fe3944a17f307a78b4d2b30c5),
|
||||
];
|
||||
const PLAINTEXT: u128 = u128::from_be(0x00112233445566778899aabbccddeeff);
|
||||
const CIPHERTEXT: u128 = u128::from_be(0x69c4e0d86a7b0430d8cdb78070b4c55a);
|
||||
|
||||
#[test]
|
||||
fn test_generate_key_schedule() {
|
||||
// Checks that the round keys are correctly generated from the sample key from FIPS
|
||||
let key = u128_to_si128(CIPHER_KEY);
|
||||
let mut keys: [__m128i; 11] = [u128_to_si128(0); 11];
|
||||
aes_128_key_expansion(key, &mut keys);
|
||||
for (expected, actual) in KEY_SCHEDULE.iter().zip(keys.iter()) {
|
||||
assert_eq!(*expected, si128_to_u128(*actual));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_many_messages() {
|
||||
// Checks that encrypting many plaintext at the same time gives the correct output.
|
||||
let message = u128_to_si128(PLAINTEXT);
|
||||
let key = u128_to_si128(CIPHER_KEY);
|
||||
let mut keys: [__m128i; 11] = [u128_to_si128(0); 11];
|
||||
aes_128_key_expansion(key, &mut keys);
|
||||
let ciphertexts = aes_encrypt_many(
|
||||
&message, &message, &message, &message, &message, &message, &message, &message, &keys,
|
||||
);
|
||||
for ct in &ciphertexts {
|
||||
assert_eq!(CIPHERTEXT, si128_to_u128(*ct));
|
||||
}
|
||||
}
|
||||
}
|
||||
110
concrete-csprng/src/generators/implem/aesni/generator.rs
Normal file
110
concrete-csprng/src/generators/implem/aesni/generator.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use crate::generators::aes_ctr::{AesCtrGenerator, AesKey, ChildrenIterator};
|
||||
use crate::generators::implem::aesni::block_cipher::AesniBlockCipher;
|
||||
use crate::generators::{ByteCount, BytesPerChild, ChildrenCount, ForkError, RandomGenerator};
|
||||
use crate::seeders::Seed;
|
||||
|
||||
/// A random number generator using the `aesni` instructions.
|
||||
pub struct AesniRandomGenerator(pub(super) AesCtrGenerator<AesniBlockCipher>);
|
||||
|
||||
/// The children iterator used by [`AesniRandomGenerator`].
|
||||
///
|
||||
/// Outputs children generators one by one.
|
||||
pub struct AesniChildrenIterator(ChildrenIterator<AesniBlockCipher>);
|
||||
|
||||
impl Iterator for AesniChildrenIterator {
|
||||
type Item = AesniRandomGenerator;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(AesniRandomGenerator)
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomGenerator for AesniRandomGenerator {
|
||||
type ChildrenIter = AesniChildrenIterator;
|
||||
fn new(seed: Seed) -> Self {
|
||||
AesniRandomGenerator(AesCtrGenerator::new(AesKey(seed.0), None, None))
|
||||
}
|
||||
fn remaining_bytes(&self) -> ByteCount {
|
||||
self.0.remaining_bytes()
|
||||
}
|
||||
fn try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ChildrenIter, ForkError> {
|
||||
self.0
|
||||
.try_fork(n_children, n_bytes)
|
||||
.map(AesniChildrenIterator)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for AesniRandomGenerator {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::generators::aes_ctr::aes_ctr_generic_test;
|
||||
use crate::generators::implem::aesni::block_cipher::AesniBlockCipher;
|
||||
use crate::generators::{generator_generic_test, AesniRandomGenerator};
|
||||
|
||||
#[test]
|
||||
fn prop_fork_first_state_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_first_state_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_last_bound_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_last_bound_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_bound_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_parent_bound_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_state_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_parent_state_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork() {
|
||||
aes_ctr_generic_test::prop_fork::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_children_remaining_bytes() {
|
||||
aes_ctr_generic_test::prop_fork_children_remaining_bytes::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_remaining_bytes() {
|
||||
aes_ctr_generic_test::prop_fork_parent_remaining_bytes::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roughly_uniform() {
|
||||
generator_generic_test::test_roughly_uniform::<AesniRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generator_determinism() {
|
||||
generator_generic_test::test_generator_determinism::<AesniRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fork() {
|
||||
generator_generic_test::test_fork_children::<AesniRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "expected test panic")]
|
||||
fn test_bounded_panic() {
|
||||
generator_generic_test::test_bounded_none_should_panic::<AesniRandomGenerator>();
|
||||
}
|
||||
}
|
||||
15
concrete-csprng/src/generators/implem/aesni/mod.rs
Normal file
15
concrete-csprng/src/generators/implem/aesni/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
//! A module implementing a random number generator, using the x86_64 `aesni` instructions.
|
||||
//!
|
||||
//! This module implements a cryptographically secure pseudorandom number generator
|
||||
//! (CS-PRNG), using a fast block cipher. The implementation is based on the
|
||||
//! [intel aesni white paper 323641-001 revision 3.0](https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf).
|
||||
|
||||
mod block_cipher;
|
||||
|
||||
mod generator;
|
||||
pub use generator::*;
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
mod parallel;
|
||||
#[cfg(feature = "parallel")]
|
||||
pub use parallel::*;
|
||||
95
concrete-csprng/src/generators/implem/aesni/parallel.rs
Normal file
95
concrete-csprng/src/generators/implem/aesni/parallel.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::{AesCtrGenerator, ParallelChildrenIterator};
|
||||
use crate::generators::implem::aesni::block_cipher::AesniBlockCipher;
|
||||
use crate::generators::{BytesPerChild, ChildrenCount, ForkError, ParallelRandomGenerator};
|
||||
use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer};
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// The parallel children iterator used by [`AesniRandomGenerator`].
|
||||
///
|
||||
/// Outputs the children generators one by one.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct ParallelAesniChildrenIterator(
|
||||
rayon::iter::Map<
|
||||
ParallelChildrenIterator<AesniBlockCipher>,
|
||||
fn(AesCtrGenerator<AesniBlockCipher>) -> AesniRandomGenerator,
|
||||
>,
|
||||
);
|
||||
|
||||
impl ParallelIterator for ParallelAesniChildrenIterator {
|
||||
type Item = AesniRandomGenerator;
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.0.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexedParallelIterator for ParallelAesniChildrenIterator {
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
|
||||
self.0.drive(consumer)
|
||||
}
|
||||
fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
|
||||
self.0.with_producer(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParallelRandomGenerator for AesniRandomGenerator {
|
||||
type ParChildrenIter = ParallelAesniChildrenIterator;
|
||||
|
||||
fn par_try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ParChildrenIter, ForkError> {
|
||||
self.0
|
||||
.par_try_fork(n_children, n_bytes)
|
||||
.map(|iterator| ParallelAesniChildrenIterator(iterator.map(AesniRandomGenerator)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod test {
|
||||
use crate::generators::aes_ctr::aes_ctr_parallel_generic_tests;
|
||||
use crate::generators::implem::aesni::block_cipher::AesniBlockCipher;
|
||||
|
||||
#[test]
|
||||
fn prop_fork_first_state_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_first_state_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_last_bound_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_last_bound_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_bound_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_bound_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_state_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_state_table_index::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_ttt() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_children_remaining_bytes() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_children_remaining_bytes::<AesniBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_remaining_bytes() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_remaining_bytes::<AesniBlockCipher>();
|
||||
}
|
||||
}
|
||||
14
concrete-csprng/src/generators/implem/mod.rs
Normal file
14
concrete-csprng/src/generators/implem/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[cfg(feature = "generator_x86_64_aesni")]
|
||||
mod aesni;
|
||||
#[cfg(feature = "generator_x86_64_aesni")]
|
||||
pub use aesni::*;
|
||||
|
||||
#[cfg(feature = "generator_aarch64_aes")]
|
||||
mod aarch64;
|
||||
#[cfg(feature = "generator_aarch64_aes")]
|
||||
pub use aarch64::*;
|
||||
|
||||
#[cfg(feature = "generator_fallback")]
|
||||
mod soft;
|
||||
#[cfg(feature = "generator_fallback")]
|
||||
pub use soft::*;
|
||||
114
concrete-csprng/src/generators/implem/soft/block_cipher.rs
Normal file
114
concrete-csprng/src/generators/implem/soft/block_cipher.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use crate::generators::aes_ctr::{
|
||||
AesBlockCipher, AesIndex, AesKey, AES_CALLS_PER_BATCH, BYTES_PER_AES_CALL, BYTES_PER_BATCH,
|
||||
};
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use aes::cipher::{BlockEncrypt, KeyInit};
|
||||
use aes::Aes128;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SoftwareBlockCipher {
|
||||
// Aes structure
|
||||
aes: Aes128,
|
||||
}
|
||||
|
||||
impl AesBlockCipher for SoftwareBlockCipher {
|
||||
fn new(key: AesKey) -> SoftwareBlockCipher {
|
||||
let key: [u8; BYTES_PER_AES_CALL] = key.0.to_ne_bytes();
|
||||
let key = GenericArray::clone_from_slice(&key[..]);
|
||||
let aes = Aes128::new(&key);
|
||||
SoftwareBlockCipher { aes }
|
||||
}
|
||||
|
||||
fn generate_batch(&mut self, AesIndex(aes_ctr): AesIndex) -> [u8; BYTES_PER_BATCH] {
|
||||
aes_encrypt_many(
|
||||
aes_ctr,
|
||||
aes_ctr + 1,
|
||||
aes_ctr + 2,
|
||||
aes_ctr + 3,
|
||||
aes_ctr + 4,
|
||||
aes_ctr + 5,
|
||||
aes_ctr + 6,
|
||||
aes_ctr + 7,
|
||||
&self.aes,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Uses aes to encrypt many values at once. This allows a substantial speedup (around 30%)
|
||||
// compared to the naive approach.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn aes_encrypt_many(
|
||||
message_1: u128,
|
||||
message_2: u128,
|
||||
message_3: u128,
|
||||
message_4: u128,
|
||||
message_5: u128,
|
||||
message_6: u128,
|
||||
message_7: u128,
|
||||
message_8: u128,
|
||||
cipher: &Aes128,
|
||||
) -> [u8; BYTES_PER_BATCH] {
|
||||
let mut b1 = GenericArray::clone_from_slice(&message_1.to_ne_bytes()[..]);
|
||||
let mut b2 = GenericArray::clone_from_slice(&message_2.to_ne_bytes()[..]);
|
||||
let mut b3 = GenericArray::clone_from_slice(&message_3.to_ne_bytes()[..]);
|
||||
let mut b4 = GenericArray::clone_from_slice(&message_4.to_ne_bytes()[..]);
|
||||
let mut b5 = GenericArray::clone_from_slice(&message_5.to_ne_bytes()[..]);
|
||||
let mut b6 = GenericArray::clone_from_slice(&message_6.to_ne_bytes()[..]);
|
||||
let mut b7 = GenericArray::clone_from_slice(&message_7.to_ne_bytes()[..]);
|
||||
let mut b8 = GenericArray::clone_from_slice(&message_8.to_ne_bytes()[..]);
|
||||
|
||||
cipher.encrypt_block(&mut b1);
|
||||
cipher.encrypt_block(&mut b2);
|
||||
cipher.encrypt_block(&mut b3);
|
||||
cipher.encrypt_block(&mut b4);
|
||||
cipher.encrypt_block(&mut b5);
|
||||
cipher.encrypt_block(&mut b6);
|
||||
cipher.encrypt_block(&mut b7);
|
||||
cipher.encrypt_block(&mut b8);
|
||||
|
||||
let output_array: [[u8; BYTES_PER_AES_CALL]; AES_CALLS_PER_BATCH] = [
|
||||
b1.into(),
|
||||
b2.into(),
|
||||
b3.into(),
|
||||
b4.into(),
|
||||
b5.into(),
|
||||
b6.into(),
|
||||
b7.into(),
|
||||
b8.into(),
|
||||
];
|
||||
|
||||
unsafe { *{ output_array.as_ptr() as *const [u8; BYTES_PER_BATCH] } }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::convert::TryInto;
|
||||
|
||||
// Test vector for aes128, from the FIPS publication 197
|
||||
const CIPHER_KEY: u128 = u128::from_be(0x000102030405060708090a0b0c0d0e0f);
|
||||
const PLAINTEXT: u128 = u128::from_be(0x00112233445566778899aabbccddeeff);
|
||||
const CIPHERTEXT: u128 = u128::from_be(0x69c4e0d86a7b0430d8cdb78070b4c55a);
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_many_messages() {
|
||||
// Checks that encrypting many plaintext at the same time gives the correct output.
|
||||
let key: [u8; BYTES_PER_AES_CALL] = CIPHER_KEY.to_ne_bytes();
|
||||
let aes = Aes128::new(&GenericArray::from(key));
|
||||
let ciphertexts = aes_encrypt_many(
|
||||
PLAINTEXT, PLAINTEXT, PLAINTEXT, PLAINTEXT, PLAINTEXT, PLAINTEXT, PLAINTEXT, PLAINTEXT,
|
||||
&aes,
|
||||
);
|
||||
let ciphertexts: [u8; BYTES_PER_BATCH] = ciphertexts[..].try_into().unwrap();
|
||||
for i in 0..8 {
|
||||
assert_eq!(
|
||||
u128::from_ne_bytes(
|
||||
ciphertexts[BYTES_PER_AES_CALL * i..BYTES_PER_AES_CALL * (i + 1)]
|
||||
.try_into()
|
||||
.unwrap()
|
||||
),
|
||||
CIPHERTEXT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
concrete-csprng/src/generators/implem/soft/generator.rs
Normal file
110
concrete-csprng/src/generators/implem/soft/generator.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use crate::generators::aes_ctr::{AesCtrGenerator, AesKey, ChildrenIterator};
|
||||
use crate::generators::implem::soft::block_cipher::SoftwareBlockCipher;
|
||||
use crate::generators::{ByteCount, BytesPerChild, ChildrenCount, ForkError, RandomGenerator};
|
||||
use crate::seeders::Seed;
|
||||
|
||||
/// A random number generator using a software implementation.
|
||||
pub struct SoftwareRandomGenerator(pub(super) AesCtrGenerator<SoftwareBlockCipher>);
|
||||
|
||||
/// The children iterator used by [`SoftwareRandomGenerator`].
|
||||
///
|
||||
/// Outputs children generators one by one.
|
||||
pub struct SoftwareChildrenIterator(ChildrenIterator<SoftwareBlockCipher>);
|
||||
|
||||
impl Iterator for SoftwareChildrenIterator {
|
||||
type Item = SoftwareRandomGenerator;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(SoftwareRandomGenerator)
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomGenerator for SoftwareRandomGenerator {
|
||||
type ChildrenIter = SoftwareChildrenIterator;
|
||||
fn new(seed: Seed) -> Self {
|
||||
SoftwareRandomGenerator(AesCtrGenerator::new(AesKey(seed.0), None, None))
|
||||
}
|
||||
fn remaining_bytes(&self) -> ByteCount {
|
||||
self.0.remaining_bytes()
|
||||
}
|
||||
fn try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ChildrenIter, ForkError> {
|
||||
self.0
|
||||
.try_fork(n_children, n_bytes)
|
||||
.map(SoftwareChildrenIterator)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SoftwareRandomGenerator {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::aes_ctr_generic_test;
|
||||
use crate::generators::generator_generic_test;
|
||||
|
||||
#[test]
|
||||
fn prop_fork_first_state_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_first_state_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_last_bound_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_last_bound_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_bound_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_parent_bound_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_state_table_index() {
|
||||
aes_ctr_generic_test::prop_fork_parent_state_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork() {
|
||||
aes_ctr_generic_test::prop_fork::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_children_remaining_bytes() {
|
||||
aes_ctr_generic_test::prop_fork_children_remaining_bytes::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_remaining_bytes() {
|
||||
aes_ctr_generic_test::prop_fork_parent_remaining_bytes::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roughly_uniform() {
|
||||
generator_generic_test::test_roughly_uniform::<SoftwareRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fork() {
|
||||
generator_generic_test::test_fork_children::<SoftwareRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generator_determinism() {
|
||||
generator_generic_test::test_generator_determinism::<SoftwareRandomGenerator>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "expected test panic")]
|
||||
fn test_bounded_panic() {
|
||||
generator_generic_test::test_bounded_none_should_panic::<SoftwareRandomGenerator>();
|
||||
}
|
||||
}
|
||||
11
concrete-csprng/src/generators/implem/soft/mod.rs
Normal file
11
concrete-csprng/src/generators/implem/soft/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! A module using a software fallback implementation of random number generator.
|
||||
|
||||
mod block_cipher;
|
||||
|
||||
mod generator;
|
||||
pub use generator::*;
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
mod parallel;
|
||||
#[cfg(feature = "parallel")]
|
||||
pub use parallel::*;
|
||||
94
concrete-csprng/src/generators/implem/soft/parallel.rs
Normal file
94
concrete-csprng/src/generators/implem/soft/parallel.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::{AesCtrGenerator, ParallelChildrenIterator};
|
||||
use crate::generators::implem::soft::block_cipher::SoftwareBlockCipher;
|
||||
use crate::generators::{BytesPerChild, ChildrenCount, ForkError, ParallelRandomGenerator};
|
||||
use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer};
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// The parallel children iterator used by [`SoftwareRandomGenerator`].
|
||||
///
|
||||
/// Outputs the children generators one by one.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct ParallelSoftwareChildrenIterator(
|
||||
rayon::iter::Map<
|
||||
ParallelChildrenIterator<SoftwareBlockCipher>,
|
||||
fn(AesCtrGenerator<SoftwareBlockCipher>) -> SoftwareRandomGenerator,
|
||||
>,
|
||||
);
|
||||
|
||||
impl ParallelIterator for ParallelSoftwareChildrenIterator {
|
||||
type Item = SoftwareRandomGenerator;
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.0.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexedParallelIterator for ParallelSoftwareChildrenIterator {
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
|
||||
self.0.drive(consumer)
|
||||
}
|
||||
fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
|
||||
self.0.with_producer(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParallelRandomGenerator for SoftwareRandomGenerator {
|
||||
type ParChildrenIter = ParallelSoftwareChildrenIterator;
|
||||
|
||||
fn par_try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ParChildrenIter, ForkError> {
|
||||
self.0
|
||||
.par_try_fork(n_children, n_bytes)
|
||||
.map(|iterator| ParallelSoftwareChildrenIterator(iterator.map(SoftwareRandomGenerator)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::generators::aes_ctr::aes_ctr_parallel_generic_tests;
|
||||
|
||||
#[test]
|
||||
fn prop_fork_first_state_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_first_state_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_last_bound_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_last_bound_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_bound_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_bound_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_state_table_index() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_state_table_index::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_children_remaining_bytes() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_children_remaining_bytes::<SoftwareBlockCipher>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prop_fork_parent_remaining_bytes() {
|
||||
aes_ctr_parallel_generic_tests::prop_fork_parent_remaining_bytes::<SoftwareBlockCipher>();
|
||||
}
|
||||
}
|
||||
235
concrete-csprng/src/generators/mod.rs
Normal file
235
concrete-csprng/src/generators/mod.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
//! A module containing random generators objects.
|
||||
//!
|
||||
//! See [crate-level](`crate`) explanations.
|
||||
use crate::seeders::Seed;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// The number of children created when a generator is forked.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ChildrenCount(pub usize);
|
||||
|
||||
/// The number of bytes each child can generate, when a generator is forked.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct BytesPerChild(pub usize);
|
||||
|
||||
/// A structure representing the number of bytes between two table indices.
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct ByteCount(pub u128);
|
||||
|
||||
/// An error occuring during a generator fork.
|
||||
#[derive(Debug)]
|
||||
pub enum ForkError {
|
||||
ForkTooLarge,
|
||||
ZeroChildrenCount,
|
||||
ZeroBytesPerChild,
|
||||
}
|
||||
|
||||
impl Display for ForkError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ForkError::ForkTooLarge => {
|
||||
write!(
|
||||
f,
|
||||
"The children generators would output bytes after the parent bound. "
|
||||
)
|
||||
}
|
||||
ForkError::ZeroChildrenCount => {
|
||||
write!(
|
||||
f,
|
||||
"The number of children in the fork must be greater than zero."
|
||||
)
|
||||
}
|
||||
ForkError::ZeroBytesPerChild => {
|
||||
write!(
|
||||
f,
|
||||
"The number of bytes per child must be greater than zero."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for ForkError {}
|
||||
|
||||
/// A trait for cryptographically secure pseudo-random generators.
|
||||
///
|
||||
/// See the [crate-level](#crate) documentation for details.
|
||||
pub trait RandomGenerator: Iterator<Item = u8> {
|
||||
/// The iterator over children generators, returned by `try_fork` in case of success.
|
||||
type ChildrenIter: Iterator<Item = Self>;
|
||||
|
||||
/// Creates a new generator from a seed.
|
||||
///
|
||||
/// This operation is usually costly to perform, as the aes round keys need to be generated from
|
||||
/// the seed.
|
||||
fn new(seed: Seed) -> Self;
|
||||
|
||||
/// Returns the number of bytes that can still be outputted by the generator before reaching its
|
||||
/// bound.
|
||||
///
|
||||
/// Note:
|
||||
/// -----
|
||||
///
|
||||
/// A fresh generator can generate 2¹³² bytes. Unfortunately, no rust integer type in is able
|
||||
/// to encode such a large number. Consequently [`ByteCount`] uses the largest integer type
|
||||
/// available to encode this value: the `u128` type. For this reason, this method does not
|
||||
/// effectively return the number of remaining bytes, but instead
|
||||
/// `min(2¹²⁸-1, remaining_bytes)`.
|
||||
fn remaining_bytes(&self) -> ByteCount;
|
||||
|
||||
/// Returns the next byte of the stream, if the generator did not yet reach its bound.
|
||||
fn next_byte(&mut self) -> Option<u8> {
|
||||
self.next()
|
||||
}
|
||||
|
||||
/// Tries to fork the generator into an iterator of `n_children` new generators, each able to
|
||||
/// output `n_bytes` bytes.
|
||||
///
|
||||
/// Note:
|
||||
/// -----
|
||||
///
|
||||
/// To be successful, the number of remaining bytes for the parent generator must be larger than
|
||||
/// `n_children*n_bytes`.
|
||||
fn try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ChildrenIter, ForkError>;
|
||||
}
|
||||
|
||||
/// A trait extending [`RandomGenerator`] to the parallel iterators of `rayon`.
|
||||
#[cfg(feature = "parallel")]
|
||||
pub trait ParallelRandomGenerator: RandomGenerator + Send {
|
||||
/// The iterator over children generators, returned by `par_try_fork` in case of success.
|
||||
type ParChildrenIter: rayon::prelude::IndexedParallelIterator<Item = Self>;
|
||||
|
||||
/// Tries to fork the generator into a parallel iterator of `n_children` new generators, each
|
||||
/// able to output `n_bytes` bytes.
|
||||
///
|
||||
/// Note:
|
||||
/// -----
|
||||
///
|
||||
/// To be successful, the number of remaining bytes for the parent generator must be larger than
|
||||
/// `n_children*n_bytes`.
|
||||
fn par_try_fork(
|
||||
&mut self,
|
||||
n_children: ChildrenCount,
|
||||
n_bytes: BytesPerChild,
|
||||
) -> Result<Self::ParChildrenIter, ForkError>;
|
||||
}
|
||||
|
||||
mod aes_ctr;
|
||||
|
||||
mod implem;
|
||||
pub use implem::*;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod generator_generic_test {
|
||||
#![allow(unused)] // to please clippy when tests are not activated
|
||||
use super::*;
|
||||
use rand::Rng;
|
||||
|
||||
const REPEATS: usize = 1_000;
|
||||
|
||||
fn any_seed() -> impl Iterator<Item = Seed> {
|
||||
std::iter::repeat_with(|| Seed(rand::thread_rng().gen()))
|
||||
}
|
||||
|
||||
fn some_children_count() -> impl Iterator<Item = ChildrenCount> {
|
||||
std::iter::repeat_with(|| ChildrenCount(rand::thread_rng().gen::<usize>() % 16 + 1))
|
||||
}
|
||||
|
||||
fn some_bytes_per_child() -> impl Iterator<Item = BytesPerChild> {
|
||||
std::iter::repeat_with(|| BytesPerChild(rand::thread_rng().gen::<usize>() % 128 + 1))
|
||||
}
|
||||
|
||||
/// Checks that the PRNG roughly generates uniform numbers.
|
||||
///
|
||||
/// To do that, we perform an histogram of the occurences of each byte value, over a fixed
|
||||
/// number of samples and check that the empirical probabilities of the bins are close to
|
||||
/// the theoretical probabilities.
|
||||
pub fn test_roughly_uniform<G: RandomGenerator>() {
|
||||
// Number of bins to use for the histogram.
|
||||
const N_BINS: usize = u8::MAX as usize + 1;
|
||||
// Number of samples to use for the histogram.
|
||||
let n_samples = 10_000_000_usize;
|
||||
// Theoretical probability of a each bins.
|
||||
let expected_prob: f64 = 1. / N_BINS as f64;
|
||||
// Absolute error allowed on the empirical probabilities.
|
||||
// This value was tuned to make the test pass on an arguably correct state of
|
||||
// implementation. 10^-4 precision is arguably pretty fine for this rough test, but it would
|
||||
// be interesting to improve this test.
|
||||
let precision = 10f64.powi(-3);
|
||||
|
||||
for _ in 0..REPEATS {
|
||||
// We instantiate a new generator.
|
||||
let seed = any_seed().next().unwrap();
|
||||
let mut generator = G::new(seed);
|
||||
// We create a new histogram
|
||||
let mut counts = [0usize; N_BINS];
|
||||
// We fill the histogram.
|
||||
for _ in 0..n_samples {
|
||||
counts[generator.next_byte().unwrap() as usize] += 1;
|
||||
}
|
||||
// We check that the empirical probabilities are close enough to the theoretical one.
|
||||
counts
|
||||
.iter()
|
||||
.map(|a| (*a as f64) / (n_samples as f64))
|
||||
.for_each(|a| assert!((a - expected_prob).abs() < precision))
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that given a state and a key, the PRNG is determinist.
|
||||
pub fn test_generator_determinism<G: RandomGenerator>() {
|
||||
for _ in 0..REPEATS {
|
||||
let seed = any_seed().next().unwrap();
|
||||
let mut first_generator = G::new(seed);
|
||||
let mut second_generator = G::new(seed);
|
||||
for _ in 0..1024 {
|
||||
assert_eq!(first_generator.next(), second_generator.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that forks returns a bounded child, and that the proper number of bytes can be
|
||||
/// generated.
|
||||
pub fn test_fork_children<G: RandomGenerator>() {
|
||||
for _ in 0..REPEATS {
|
||||
let ((seed, n_children), n_bytes) = any_seed()
|
||||
.zip(some_children_count())
|
||||
.zip(some_bytes_per_child())
|
||||
.next()
|
||||
.unwrap();
|
||||
let mut gen = G::new(seed);
|
||||
let mut bounded = gen.try_fork(n_children, n_bytes).unwrap().next().unwrap();
|
||||
assert_eq!(bounded.remaining_bytes(), ByteCount(n_bytes.0 as u128));
|
||||
for _ in 0..n_bytes.0 {
|
||||
bounded.next().unwrap();
|
||||
}
|
||||
|
||||
// Assert we are at the bound
|
||||
assert!(bounded.next().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that a bounded prng returns none when exceeding the allowed number of bytes.
|
||||
///
|
||||
/// To properly check for panic use `#[should_panic(expected = "expected test panic")]` as an
|
||||
/// attribute on the test function.
|
||||
pub fn test_bounded_none_should_panic<G: RandomGenerator>() {
|
||||
let ((seed, n_children), n_bytes) = any_seed()
|
||||
.zip(some_children_count())
|
||||
.zip(some_bytes_per_child())
|
||||
.next()
|
||||
.unwrap();
|
||||
let mut gen = G::new(seed);
|
||||
let mut bounded = gen.try_fork(n_children, n_bytes).unwrap().next().unwrap();
|
||||
assert_eq!(bounded.remaining_bytes(), ByteCount(n_bytes.0 as u128));
|
||||
for _ in 0..n_bytes.0 {
|
||||
assert!(bounded.next().is_some());
|
||||
}
|
||||
|
||||
// One call too many, should panic
|
||||
bounded.next().ok_or("expected test panic").unwrap();
|
||||
}
|
||||
}
|
||||
114
concrete-csprng/src/lib.rs
Normal file
114
concrete-csprng/src/lib.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
//! Cryptographically secure pseudo random number generator.
|
||||
//!
|
||||
//! Welcome to the `concrete-csprng` documentation.
|
||||
//!
|
||||
//! This crate provides a fast cryptographically secure pseudo-random number generator, suited to
|
||||
//! work in a multithreaded setting.
|
||||
//!
|
||||
//! Random Generators
|
||||
//! =================
|
||||
//!
|
||||
//! The central abstraction of this crate is the [`RandomGenerator`](generators::RandomGenerator)
|
||||
//! trait, which is implemented by different types, each supporting a different platform. In
|
||||
//! essence, a type implementing [`RandomGenerator`](generators::RandomGenerator) is a type that
|
||||
//! outputs a new pseudo-random byte at each call to
|
||||
//! [`next_byte`](generators::RandomGenerator::next_byte). Such a generator `g` can be seen as
|
||||
//! enclosing a growing index into an imaginary array of pseudo-random bytes:
|
||||
//! ```ascii
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M-1 │
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │
|
||||
//! ┃ │ │ │ │ │ │ │ │ │ │...│ ┃ │
|
||||
//! ┗↥┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━━━┷━┛ │
|
||||
//! g │
|
||||
//! │
|
||||
//! g.next_byte() │
|
||||
//! │
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M-1 │
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │
|
||||
//! ┃╳│ │ │ │ │ │ │ │ │ │...│ ┃ │
|
||||
//! ┗━┷↥┷━┷━┷━┷━┷━┷━┷━┷━┷━━━┷━┛ │
|
||||
//! g │
|
||||
//! │
|
||||
//! g.next_byte() │ legend:
|
||||
//! │ -------
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M-1 │ ↥ : next byte to be outputted by g
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │ │ │: byte not yet outputted by g
|
||||
//! ┃╳│╳│ │ │ │ │ │ │ │ │...│ ┃ │ │╳│: byte already outputted by g
|
||||
//! ┗━┷━┷↥┷━┷━┷━┷━┷━┷━┷━┷━━━┷━┛ │
|
||||
//! g 🭭
|
||||
//! ```
|
||||
//!
|
||||
//! While being large, this imaginary array is still bounded to M = 2¹³² bytes. Consequently, a
|
||||
//! generator is always bounded to a maximal index. That is, there is always a max amount of
|
||||
//! elements of this array that can be outputted by the generator. By default, generators created
|
||||
//! via [`new`](generators::RandomGenerator::new) are always bounded to M-1.
|
||||
//!
|
||||
//! Tree partition of the pseudo-random stream
|
||||
//! ==========================================
|
||||
//!
|
||||
//! One particularity of this implementation is that you can use the
|
||||
//! [`try_fork`](generators::RandomGenerator::try_fork) method to create an arbitrary partition tree
|
||||
//! of a region of this array. Indeed, calling `try_fork(nc, nb)` outputs `nc` new generators, each
|
||||
//! able to output `nb` bytes. The `try_fork` method ensures that the states and bounds of the
|
||||
//! parent and children generators are set so as to prevent the same substream to be outputted
|
||||
//! twice:
|
||||
//! ```ascii
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M │
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │
|
||||
//! ┃P│P│P│P│P│P│P│P│P│P│...│P┃ │
|
||||
//! ┗↥┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━━━┷━┛ │
|
||||
//! p │
|
||||
//! │
|
||||
//! (a,b) = p.fork(2,4) │
|
||||
//! │
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M │
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │
|
||||
//! ┃A│A│A│A│B│B│B│B│P│P│...│P┃ │
|
||||
//! ┗↥┷━┷━┷━┷↥┷━┷━┷━┷↥┷━┷━━━┷━┛ │
|
||||
//! a b p │
|
||||
//! │ legend:
|
||||
//! (c,d) = b.fork(2, 1) │ -------
|
||||
//! │ ↥ : next byte to be outputted by p
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M │ │P│: byte to be outputted by p
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │ │╳│: byte already outputted
|
||||
//! ┃A│A│A│A│C│D│B│B│P│P│...│P┃ │
|
||||
//! ┗↥┷━┷━┷━┷↥┷↥┷↥┷━┷↥┷━┷━━━┷━┛ │
|
||||
//! a c d b p 🭭
|
||||
//! ```
|
||||
//!
|
||||
//! This makes it possible to consume the stream at different places. This is particularly useful in
|
||||
//! a multithreaded setting, in which we want to use the same generator from different independent
|
||||
//! threads:
|
||||
//!
|
||||
//! ```ascii
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M │
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │
|
||||
//! ┃A│A│A│A│C│D│B│B│P│P│...│P┃ │
|
||||
//! ┗↥┷━┷━┷━┷↥┷↥┷↥┷━┷↥┷━┷━━━┷━┛ │
|
||||
//! a c d b p │
|
||||
//! │
|
||||
//! a.next_byte() │
|
||||
//! │
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M │
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │
|
||||
//! ┃╳│A│A│A│C│D│B│B│P│P│...│P┃ │
|
||||
//! ┗━┷↥┷━┷━┷↥┷↥┷↥┷━┷↥┷━┷━━━┷━┛ │
|
||||
//! a c d b p │
|
||||
//! │ legend:
|
||||
//! b.next_byte() │ -------
|
||||
//! │ ↥ : next byte to be outputted by p
|
||||
//! 0 1 2 3 4 5 6 7 8 9 M │ │P│: byte to be outputted by p
|
||||
//! ┏━┯━┯━┯━┯━┯━┯━┯━┯━┯━┯━━━┯━┓ │ │╳│: byte already outputted
|
||||
//! ┃╳│A│A│A│C│D│╳│B│P│P│...│P┃ │
|
||||
//! ┗━┷↥┷━┷━┷↥┷↥┷━┷↥┷↥┷━┷━━━┷━┛ │
|
||||
//! a c d b p 🭭
|
||||
//! ```
|
||||
//!
|
||||
//! Implementation
|
||||
//! ==============
|
||||
//!
|
||||
//! The implementation is based on the AES blockcipher used in counter (CTR) mode, as presented
|
||||
//! in the ISO/IEC 18033-4 document.
|
||||
pub mod generators;
|
||||
pub mod seeders;
|
||||
58
concrete-csprng/src/main.rs
Normal file
58
concrete-csprng/src/main.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! This program uses the concrete csprng to generate an infinite stream of random bytes on
|
||||
//! the program stdout. For testing purpose.
|
||||
#[cfg(feature = "generator_x86_64_aesni")]
|
||||
use concrete_csprng::generators::AesniRandomGenerator as ActivatedRandomGenerator;
|
||||
#[cfg(feature = "generator_aarch64_aes")]
|
||||
use concrete_csprng::generators::NeonAesRandomGenerator as ActivatedRandomGenerator;
|
||||
#[cfg(all(
|
||||
not(feature = "generator_x86_64_aesni"),
|
||||
not(feature = "generator_aarch64_aes"),
|
||||
feature = "generator_fallback"
|
||||
))]
|
||||
use concrete_csprng::generators::SoftwareRandomGenerator as ActivatedRandomGenerator;
|
||||
|
||||
use concrete_csprng::generators::RandomGenerator;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use concrete_csprng::seeders::AppleSecureEnclaveSeeder as ActivatedSeeder;
|
||||
#[cfg(all(not(target_os = "macos"), feature = "seeder_x86_64_rdseed"))]
|
||||
use concrete_csprng::seeders::RdseedSeeder as ActivatedSeeder;
|
||||
#[cfg(all(
|
||||
not(target_os = "macos"),
|
||||
not(feature = "seeder_x86_64_rdseed"),
|
||||
feature = "seeder_unix"
|
||||
))]
|
||||
use concrete_csprng::seeders::UnixSeeder as ActivatedSeeder;
|
||||
|
||||
use concrete_csprng::seeders::Seeder;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::stdout;
|
||||
|
||||
pub fn main() {
|
||||
// Ugly hack to be able to use UnixSeeder
|
||||
#[cfg(all(
|
||||
not(target_os = "macos"),
|
||||
not(feature = "seeder_x86_64_rdseed"),
|
||||
feature = "seeder_unix"
|
||||
))]
|
||||
let new_seeder = || ActivatedSeeder::new(0);
|
||||
#[cfg(not(all(
|
||||
not(target_os = "macos"),
|
||||
not(feature = "seeder_x86_64_rdseed"),
|
||||
feature = "seeder_unix"
|
||||
)))]
|
||||
let new_seeder = || ActivatedSeeder;
|
||||
|
||||
let mut seeder = new_seeder();
|
||||
let mut generator = ActivatedRandomGenerator::new(seeder.seed());
|
||||
let mut stdout = stdout();
|
||||
let mut buffer = [0u8; 16];
|
||||
loop {
|
||||
buffer
|
||||
.iter_mut()
|
||||
.zip(&mut generator)
|
||||
.for_each(|(b, g)| *b = g);
|
||||
stdout.write_all(&buffer).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use crate::seeders::{Seed, Seeder};
|
||||
use libc;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// There is no `rseed` equivalent in the ARM specification until `ARMv8.5-A`.
|
||||
/// However it seems that these instructions are not exposed in `core::arch::aarch64`.
|
||||
///
|
||||
/// Our primary interest for supporting aarch64 targets is AppleSilicon support
|
||||
/// which for the M1 macs available, they are based on the `ARMv8.4-A` set.
|
||||
///
|
||||
/// So we fall back to using a function from Apple's API which
|
||||
/// uses the [Secure Enclave] to generate cryptographically secure random bytes.
|
||||
///
|
||||
/// [Secure Enclave]: https://support.apple.com/fr-fr/guide/security/sec59b0b31ff/web
|
||||
mod secure_enclave {
|
||||
pub enum __SecRandom {}
|
||||
pub type SecRandomRef = *const __SecRandom;
|
||||
use libc::{c_int, c_void};
|
||||
|
||||
#[link(name = "Security", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static kSecRandomDefault: SecRandomRef;
|
||||
|
||||
pub fn SecRandomCopyBytes(rnd: SecRandomRef, count: usize, bytes: *mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
pub fn generate_random_bytes(bytes: &mut [u8]) -> std::io::Result<()> {
|
||||
// As per Apple's documentation:
|
||||
// - https://developer.apple.com/documentation/security/randomization_services?language=objc
|
||||
// - https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc
|
||||
//
|
||||
// The `SecRandomCopyBytes` "Generate cryptographically secure random numbers"
|
||||
unsafe {
|
||||
let res = SecRandomCopyBytes(
|
||||
kSecRandomDefault,
|
||||
bytes.len(),
|
||||
bytes.as_mut_ptr() as *mut c_void,
|
||||
);
|
||||
if res != 0 {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A seeder which uses the `SecRandomCopyBytes` function from Apple's `Security` framework.
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc>
|
||||
pub struct AppleSecureEnclaveSeeder;
|
||||
|
||||
impl Seeder for AppleSecureEnclaveSeeder {
|
||||
fn seed(&mut self) -> Seed {
|
||||
// 16 bytes == 128 bits
|
||||
let mut bytes = [0u8; 16];
|
||||
secure_enclave::generate_random_bytes(&mut bytes)
|
||||
.expect("Failure while using Apple secure enclave: {err:?}");
|
||||
|
||||
Seed(u128::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
fn is_available() -> bool {
|
||||
let os_version_sysctl_name = match std::ffi::CString::new("kern.osproductversion") {
|
||||
Ok(c_str) => c_str,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// Big enough buffer to get a version output as an ASCII string
|
||||
const OUTPUT_BUFFER_SIZE: usize = 64;
|
||||
let mut output_buffer_size = OUTPUT_BUFFER_SIZE;
|
||||
let mut output_buffer = [0u8; OUTPUT_BUFFER_SIZE];
|
||||
let res = unsafe {
|
||||
libc::sysctlbyname(
|
||||
os_version_sysctl_name.as_ptr() as *const _ as *const _,
|
||||
&mut output_buffer as *mut _ as *mut _,
|
||||
&mut output_buffer_size as *mut _ as *mut _,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if res != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let result_c_str =
|
||||
match std::ffi::CStr::from_bytes_with_nul(&output_buffer[..output_buffer_size]) {
|
||||
Ok(c_str) => c_str,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let result_string = match result_c_str.to_str() {
|
||||
Ok(str) => str,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// Normally we get a major version and minor version
|
||||
let split_string: Vec<&str> = result_string.split('.').collect();
|
||||
|
||||
let mut major = -1;
|
||||
let mut minor = -1;
|
||||
|
||||
// Major part of the version string
|
||||
if !split_string.is_empty() {
|
||||
major = match split_string[0].parse() {
|
||||
Ok(major_from_str) => major_from_str,
|
||||
_ => return false,
|
||||
};
|
||||
}
|
||||
|
||||
// SecRandomCopyBytes is available starting with mac OS 10.7
|
||||
// https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc
|
||||
// This match pattern is recommended by clippy, so we oblige here
|
||||
match major.cmp(&10) {
|
||||
Ordering::Greater => true,
|
||||
Ordering::Equal => {
|
||||
// Minor part of the version string
|
||||
if split_string.len() >= 2 {
|
||||
minor = match split_string[1].parse() {
|
||||
Ok(minor_from_str) => minor_from_str,
|
||||
_ => return false,
|
||||
};
|
||||
}
|
||||
minor >= 7
|
||||
}
|
||||
Ordering::Less => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::seeders::generic_tests::check_seeder_fixed_sequences_different;
|
||||
|
||||
#[test]
|
||||
fn check_bounded_sequence_difference() {
|
||||
check_seeder_fixed_sequences_different(|_| AppleSecureEnclaveSeeder);
|
||||
}
|
||||
}
|
||||
14
concrete-csprng/src/seeders/implem/mod.rs
Normal file
14
concrete-csprng/src/seeders/implem/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod apple_secure_enclave_seeder;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use apple_secure_enclave_seeder::AppleSecureEnclaveSeeder;
|
||||
|
||||
#[cfg(feature = "seeder_x86_64_rdseed")]
|
||||
mod rdseed;
|
||||
#[cfg(feature = "seeder_x86_64_rdseed")]
|
||||
pub use rdseed::RdseedSeeder;
|
||||
|
||||
#[cfg(feature = "seeder_unix")]
|
||||
mod unix;
|
||||
#[cfg(feature = "seeder_unix")]
|
||||
pub use unix::UnixSeeder;
|
||||
50
concrete-csprng/src/seeders/implem/rdseed.rs
Normal file
50
concrete-csprng/src/seeders/implem/rdseed.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::seeders::{Seed, Seeder};
|
||||
|
||||
/// A seeder which uses the `rdseed` x86_64 instruction.
|
||||
///
|
||||
/// The `rdseed` instruction allows to deliver seeds from a hardware source of entropy see
|
||||
/// <https://www.felixcloutier.com/x86/rdseed> .
|
||||
pub struct RdseedSeeder;
|
||||
|
||||
impl Seeder for RdseedSeeder {
|
||||
fn seed(&mut self) -> Seed {
|
||||
Seed(rdseed_random_m128())
|
||||
}
|
||||
|
||||
fn is_available() -> bool {
|
||||
is_x86_feature_detected!("rdseed")
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a random 128 bits value from rdseed
|
||||
fn rdseed_random_m128() -> u128 {
|
||||
let mut rand1: u64 = 0;
|
||||
let mut rand2: u64 = 0;
|
||||
let mut output_bytes = [0u8; 16];
|
||||
unsafe {
|
||||
loop {
|
||||
if core::arch::x86_64::_rdseed64_step(&mut rand1) == 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
loop {
|
||||
if core::arch::x86_64::_rdseed64_step(&mut rand2) == 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
output_bytes[0..8].copy_from_slice(&rand1.to_ne_bytes());
|
||||
output_bytes[8..16].copy_from_slice(&rand2.to_ne_bytes());
|
||||
u128::from_ne_bytes(output_bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::seeders::generic_tests::check_seeder_fixed_sequences_different;
|
||||
|
||||
#[test]
|
||||
fn check_bounded_sequence_difference() {
|
||||
check_seeder_fixed_sequences_different(|_| RdseedSeeder);
|
||||
}
|
||||
}
|
||||
72
concrete-csprng/src/seeders/implem/unix.rs
Normal file
72
concrete-csprng/src/seeders/implem/unix.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use crate::seeders::{Seed, Seeder};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
/// A seeder which uses the `/dev/random` source on unix-like systems.
|
||||
pub struct UnixSeeder {
|
||||
counter: u128,
|
||||
secret: u128,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl UnixSeeder {
|
||||
/// Creates a new seeder from a user defined secret.
|
||||
///
|
||||
/// Important:
|
||||
/// ----------
|
||||
///
|
||||
/// This secret is used to ensure the quality of the seed in scenarios where `/dev/random` may
|
||||
/// be compromised.
|
||||
///
|
||||
/// The attack hypotheses are as follow:
|
||||
/// - `/dev/random` output can be predicted by a process running on the machine by just
|
||||
/// observing various states of the machine
|
||||
/// - The attacker cannot read data from the process where `concrete-csprng` is running
|
||||
///
|
||||
/// Using a secret in `concrete-csprng` allows to generate values that the attacker cannot
|
||||
/// predict, making this seeder secure on systems were `/dev/random` outputs can be
|
||||
/// predicted.
|
||||
pub fn new(secret: u128) -> UnixSeeder {
|
||||
let file = std::fs::File::open("/dev/random").expect("Failed to open /dev/random .");
|
||||
let counter = std::time::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.expect("Failed to initialize unix seeder.")
|
||||
.as_nanos();
|
||||
UnixSeeder {
|
||||
secret,
|
||||
counter,
|
||||
file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Seeder for UnixSeeder {
|
||||
fn seed(&mut self) -> Seed {
|
||||
let output = self.secret ^ self.counter ^ dev_random(&mut self.file);
|
||||
self.counter = self.counter.wrapping_add(1);
|
||||
Seed(output)
|
||||
}
|
||||
|
||||
fn is_available() -> bool {
|
||||
cfg!(target_family = "unix")
|
||||
}
|
||||
}
|
||||
|
||||
fn dev_random(random: &mut File) -> u128 {
|
||||
let mut buf = [0u8; 16];
|
||||
random
|
||||
.read_exact(&mut buf[..])
|
||||
.expect("Failed to read from /dev/random .");
|
||||
u128::from_ne_bytes(buf)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::seeders::generic_tests::check_seeder_fixed_sequences_different;
|
||||
|
||||
#[test]
|
||||
fn check_bounded_sequence_difference() {
|
||||
check_seeder_fixed_sequences_different(UnixSeeder::new);
|
||||
}
|
||||
}
|
||||
47
concrete-csprng/src/seeders/mod.rs
Normal file
47
concrete-csprng/src/seeders/mod.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
//! A module containing seeders objects.
|
||||
//!
|
||||
//! When initializing a generator, one needs to provide a [`Seed`], which is then used as key to the
|
||||
//! AES blockcipher. As a consequence, the quality of the outputs of the generator is directly
|
||||
//! conditioned by the quality of this seed. This module proposes different mechanisms to deliver
|
||||
//! seeds that can accomodate varying scenarios.
|
||||
|
||||
/// A seed value, used to initialize a generator.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Seed(pub u128);
|
||||
|
||||
/// A trait representing a seeding strategy.
|
||||
pub trait Seeder {
|
||||
/// Generates a new seed.
|
||||
fn seed(&mut self) -> Seed;
|
||||
|
||||
/// Check whether the seeder can be used on the current machine. This function may check if some
|
||||
/// required CPU features are available or if some OS features are availble for example.
|
||||
fn is_available() -> bool
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
mod implem;
|
||||
pub use implem::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod generic_tests {
|
||||
use crate::seeders::Seeder;
|
||||
|
||||
/// Naively verifies that two fixed-size sequences generated by repeatedly calling the seeder
|
||||
/// are different.
|
||||
#[allow(unused)] // to please clippy when tests are not activated
|
||||
pub fn check_seeder_fixed_sequences_different<S: Seeder, F: Fn(u128) -> S>(
|
||||
construct_seeder: F,
|
||||
) {
|
||||
const SEQUENCE_SIZE: usize = 500;
|
||||
const REPEATS: usize = 10_000;
|
||||
for i in 0..REPEATS {
|
||||
let mut seeder = construct_seeder(i as u128);
|
||||
let orig_seed = seeder.seed();
|
||||
for _ in 0..SEQUENCE_SIZE {
|
||||
assert_ne!(seeder.seed(), orig_seed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user