diff --git a/Makefile b/Makefile index 5c0085668..179bb5420 100644 --- a/Makefile +++ b/Makefile @@ -174,8 +174,8 @@ install_zizmor: install_rs_build_toolchain ( echo "Unable to install zizmor, unknown error." && exit 1 ) .PHONY: install_cargo_cross # Install custom tfhe-rs lints -install_cargo_cross: - cargo install cross +install_cargo_cross: install_rs_build_toolchain + cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cross .PHONY: setup_venv # Setup Python virtualenv for wasm tests setup_venv: diff --git a/tfhe-csprng/src/generators/aes_ctr/block_cipher.rs b/tfhe-csprng/src/generators/aes_ctr/block_cipher.rs index a9095a697..ca27f2e4d 100644 --- a/tfhe-csprng/src/generators/aes_ctr/block_cipher.rs +++ b/tfhe-csprng/src/generators/aes_ctr/block_cipher.rs @@ -1,4 +1,4 @@ -use crate::generators::aes_ctr::{AES_CALLS_PER_BATCH, BYTES_PER_BATCH}; +use crate::generators::aes_ctr::{AES_CALLS_PER_BATCH, BYTES_PER_AES_CALL, BYTES_PER_BATCH}; /// Represents a key used in the AES block cipher. #[derive(Clone, Copy)] @@ -16,4 +16,6 @@ pub trait AesBlockCipher: Clone + Send + Sync { fn new(key: AesKey) -> Self; /// Generates the batch corresponding to the given index. fn generate_batch(&mut self, data: [u128; AES_CALLS_PER_BATCH]) -> [u8; BYTES_PER_BATCH]; + /// Generate next bytes + fn generate_next(&mut self, data: u128) -> [u8; BYTES_PER_AES_CALL]; } diff --git a/tfhe-csprng/src/generators/aes_ctr/generic.rs b/tfhe-csprng/src/generators/aes_ctr/generic.rs index 15149523f..5cdf63ed0 100644 --- a/tfhe-csprng/src/generators/aes_ctr/generic.rs +++ b/tfhe-csprng/src/generators/aes_ctr/generic.rs @@ -3,6 +3,7 @@ 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}; +use crate::seeders::SeedKind; // 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 @@ -82,6 +83,26 @@ impl AesCtrGenerator { } } + pub(crate) fn from_seed(seed: impl Into) -> Self { + match seed.into() { + SeedKind::Ctr(seed) => Self::new(AesKey(seed.0), None, None), + SeedKind::Xof(seed) => { + let (key, init_index) = super::xof_init(seed); + let last_index = TableIndex::LAST.decremented(); + let state = State::with_offset(TableIndex::SECOND, init_index); + let block_cipher = Box::new(BlockCipher::new(key)); + let buffer = [0u8; BYTES_PER_BATCH]; + + Self { + block_cipher, + state, + last: last_index, + buffer, + } + } + } + } + /// Returns the table index related to the previous random byte. pub fn table_index(&self) -> TableIndex { self.state.table_index() diff --git a/tfhe-csprng/src/generators/aes_ctr/mod.rs b/tfhe-csprng/src/generators/aes_ctr/mod.rs index fb20825d9..b8b8cdaee 100644 --- a/tfhe-csprng/src/generators/aes_ctr/mod.rs +++ b/tfhe-csprng/src/generators/aes_ctr/mod.rs @@ -202,6 +202,7 @@ 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. @@ -218,5 +219,28 @@ pub use generic::*; /// A module extending `generic` to the `rayon` paradigm. #[cfg(feature = "parallel")] mod parallel; + +use crate::seeders::XofSeed; #[cfg(feature = "parallel")] pub use parallel::*; + +pub(crate) fn xof_init(seed: XofSeed) -> (AesKey, AesIndex) { + let init_key = AesKey(0); + let mut aes = crate::generators::default::DefaultBlockCipher::new(init_key); + + let blocks = seed + .iter_u128_blocks() + .chain(std::iter::once(seed.bit_len().to_le())); + + let mut prev_c = 0; + let mut c = 0; + for mi in blocks { + prev_c = c; + c = u128::from_ne_bytes(aes.generate_next(prev_c ^ mi)); + } + + let init = AesIndex(prev_c.to_le()); + let key = AesKey(c); + + (key, init) +} diff --git a/tfhe-csprng/src/generators/aes_ctr/states.rs b/tfhe-csprng/src/generators/aes_ctr/states.rs index 2af399cb7..4dfd92279 100644 --- a/tfhe-csprng/src/generators/aes_ctr/states.rs +++ b/tfhe-csprng/src/generators/aes_ctr/states.rs @@ -10,6 +10,7 @@ pub struct BufferPointer(pub usize); pub struct State { table_index: TableIndex, buffer_pointer: BufferPointer, + offset: AesIndex, } /// A structure representing the action to be taken by the generator after shifting its state. @@ -25,24 +26,42 @@ pub enum ShiftAction { impl State { /// Creates a new state from the initial table index. /// - /// Note : + /// Note: /// ------ /// - /// The `table_index` input, is the __first__ table index that will be outputted on the next + /// 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 { + Self::with_offset(table_index, AesIndex(0)) + } + + /// Creates a new state from the initial table index and offset + /// + /// The `offset` AesIndex will be applied to all AES encryption. + /// AES(Key, counter + offset). + /// This is to be used when one wants to start the AES + /// counter at a specific value but still output all the (2^128-1) values + /// + /// 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 with_offset(table_index: TableIndex, offset: AesIndex) -> 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). + // (which would lead to loading a non-continuous batch). assert_ne!(table_index, TableIndex::FIRST); - State { + Self { // 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), + offset, } } @@ -52,7 +71,8 @@ impl State { 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) + let index = AesIndex(self.table_index.aes_index.0.wrapping_add(self.offset.0)); + ShiftAction::RefreshBatchAndOutputByte(index, self.buffer_pointer) } else { self.buffer_pointer.0 = total_batch_index; ShiftAction::OutputByte(self.buffer_pointer) diff --git a/tfhe-csprng/src/generators/default.rs b/tfhe-csprng/src/generators/default.rs index 4914b37ef..5a06a14c3 100644 --- a/tfhe-csprng/src/generators/default.rs +++ b/tfhe-csprng/src/generators/default.rs @@ -7,3 +7,13 @@ pub type DefaultRandomGenerator = super::NeonAesRandomGenerator; not(any(target_arch = "x86_64", target_arch = "aarch64")) ))] pub type DefaultRandomGenerator = super::SoftwareRandomGenerator; + +#[cfg(all(target_arch = "x86_64", not(feature = "software-prng")))] +pub type DefaultBlockCipher = super::implem::AesniBlockCipher; +#[cfg(all(target_arch = "aarch64", not(feature = "software-prng")))] +pub type DefaultBlockCipher = super::implem::ArmAesBlockCipher; +#[cfg(any( + feature = "software-prng", + not(any(target_arch = "x86_64", target_arch = "aarch64")) +))] +pub type DefaultBlockCipher = super::SoftwareBlockCipher; diff --git a/tfhe-csprng/src/generators/implem/aarch64/block_cipher.rs b/tfhe-csprng/src/generators/implem/aarch64/block_cipher.rs index da8a105f0..5fde06145 100644 --- a/tfhe-csprng/src/generators/implem/aarch64/block_cipher.rs +++ b/tfhe-csprng/src/generators/implem/aarch64/block_cipher.rs @@ -55,6 +55,10 @@ impl AesBlockCipher for ArmAesBlockCipher { // SAFETY: we checked for aes and neon availability in `Self::new` unsafe { implementation(self, data) } } + + fn generate_next(&mut self, data: u128) -> [u8; BYTES_PER_AES_CALL] { + unsafe { encrypt(data, &self.round_keys) }.to_ne_bytes() + } } /// Does the AES SubWord operation for the Key Expansion step diff --git a/tfhe-csprng/src/generators/implem/aarch64/generator.rs b/tfhe-csprng/src/generators/implem/aarch64/generator.rs index 0a84efe98..7d2d0962a 100644 --- a/tfhe-csprng/src/generators/implem/aarch64/generator.rs +++ b/tfhe-csprng/src/generators/implem/aarch64/generator.rs @@ -1,7 +1,7 @@ -use crate::generators::aes_ctr::{AesCtrGenerator, AesKey, ChildrenIterator}; +use crate::generators::aes_ctr::{AesCtrGenerator, ChildrenIterator}; use crate::generators::implem::aarch64::block_cipher::ArmAesBlockCipher; use crate::generators::{ByteCount, BytesPerChild, ChildrenCount, ForkError, RandomGenerator}; -use crate::seeders::Seed; +use crate::seeders::SeedKind; /// A random number generator using the arm `neon` instructions. pub struct NeonAesRandomGenerator(pub(super) AesCtrGenerator); @@ -21,8 +21,8 @@ impl Iterator for ArmAesChildrenIterator { impl RandomGenerator for NeonAesRandomGenerator { type ChildrenIter = ArmAesChildrenIterator; - fn new(seed: Seed) -> Self { - NeonAesRandomGenerator(AesCtrGenerator::new(AesKey(seed.0), None, None)) + fn new(seed: impl Into) -> Self { + NeonAesRandomGenerator(AesCtrGenerator::from_seed(seed)) } fn remaining_bytes(&self) -> ByteCount { self.0.remaining_bytes() @@ -112,4 +112,9 @@ mod test { fn test_vector() { generator_generic_test::test_vectors::(); } + + #[test] + fn test_vector_xof_seed() { + generator_generic_test::test_vectors_xof_seed::(); + } } diff --git a/tfhe-csprng/src/generators/implem/aarch64/mod.rs b/tfhe-csprng/src/generators/implem/aarch64/mod.rs index 27ffba245..b8a8beba2 100644 --- a/tfhe-csprng/src/generators/implem/aarch64/mod.rs +++ b/tfhe-csprng/src/generators/implem/aarch64/mod.rs @@ -6,6 +6,7 @@ //! [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; +pub use block_cipher::ArmAesBlockCipher; mod generator; pub use generator::*; diff --git a/tfhe-csprng/src/generators/implem/aesni/block_cipher.rs b/tfhe-csprng/src/generators/implem/aesni/block_cipher.rs index de287785a..3a3fcdce9 100644 --- a/tfhe-csprng/src/generators/implem/aesni/block_cipher.rs +++ b/tfhe-csprng/src/generators/implem/aesni/block_cipher.rs @@ -1,4 +1,6 @@ -use crate::generators::aes_ctr::{AesBlockCipher, AesKey, AES_CALLS_PER_BATCH, BYTES_PER_BATCH}; +use crate::generators::aes_ctr::{ + AesBlockCipher, AesKey, AES_CALLS_PER_BATCH, BYTES_PER_AES_CALL, BYTES_PER_BATCH, +}; use std::arch::x86_64::{ __m128i, _mm_aesenc_si128, _mm_aesenclast_si128, _mm_aeskeygenassist_si128, _mm_shuffle_epi32, _mm_slli_si128, _mm_store_si128, _mm_xor_si128, @@ -51,6 +53,10 @@ impl AesBlockCipher for AesniBlockCipher { // SAFETY: we checked for aes and sse2 availability in `Self::new` unsafe { implementation(self, data) } } + + fn generate_next(&mut self, data: u128) -> [u8; BYTES_PER_AES_CALL] { + unsafe { transmute(aes_encrypt_one(u128_to_si128(data), &self.round_keys)) } + } } #[target_feature(enable = "sse2,aes")] @@ -61,6 +67,19 @@ unsafe fn generate_round_keys(key: AesKey) -> [__m128i; 11] { keys } +#[inline(always)] +fn aes_encrypt_one(message: __m128i, keys: &[__m128i; 11]) -> __m128i { + unsafe { + let mut tmp_1 = _mm_xor_si128(message, keys[0]); + + for key in keys.iter().take(10).skip(1) { + tmp_1 = _mm_aesenc_si128(tmp_1, *key); + } + + _mm_aesenclast_si128(tmp_1, keys[10]) + } +} + // 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)] @@ -228,4 +247,14 @@ mod test { assert_eq!(CIPHERTEXT, si128_to_u128(*ct)); } } + + #[test] + fn test_encrypt_one_message() { + 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 ciphertext = aes_encrypt_one(message, &keys); + assert_eq!(CIPHERTEXT, si128_to_u128(ciphertext)); + } } diff --git a/tfhe-csprng/src/generators/implem/aesni/generator.rs b/tfhe-csprng/src/generators/implem/aesni/generator.rs index 073e2169a..4fed52816 100644 --- a/tfhe-csprng/src/generators/implem/aesni/generator.rs +++ b/tfhe-csprng/src/generators/implem/aesni/generator.rs @@ -1,7 +1,7 @@ -use crate::generators::aes_ctr::{AesCtrGenerator, AesKey, ChildrenIterator}; +use crate::generators::aes_ctr::{AesCtrGenerator, ChildrenIterator}; use crate::generators::implem::aesni::block_cipher::AesniBlockCipher; use crate::generators::{ByteCount, BytesPerChild, ChildrenCount, ForkError, RandomGenerator}; -use crate::seeders::Seed; +use crate::seeders::SeedKind; /// A random number generator using the `aesni` instructions. pub struct AesniRandomGenerator(pub(super) AesCtrGenerator); @@ -21,8 +21,8 @@ impl Iterator for AesniChildrenIterator { impl RandomGenerator for AesniRandomGenerator { type ChildrenIter = AesniChildrenIterator; - fn new(seed: Seed) -> Self { - AesniRandomGenerator(AesCtrGenerator::new(AesKey(seed.0), None, None)) + fn new(seed: impl Into) -> Self { + AesniRandomGenerator(AesCtrGenerator::from_seed(seed)) } fn remaining_bytes(&self) -> ByteCount { self.0.remaining_bytes() @@ -112,4 +112,9 @@ mod test { fn test_vector() { generator_generic_test::test_vectors::(); } + + #[test] + fn test_vector_xof_seed() { + generator_generic_test::test_vectors_xof_seed::(); + } } diff --git a/tfhe-csprng/src/generators/implem/aesni/mod.rs b/tfhe-csprng/src/generators/implem/aesni/mod.rs index c169cbb98..0bf4abe1b 100644 --- a/tfhe-csprng/src/generators/implem/aesni/mod.rs +++ b/tfhe-csprng/src/generators/implem/aesni/mod.rs @@ -5,6 +5,7 @@ //! [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; +pub use block_cipher::AesniBlockCipher; mod generator; pub use generator::*; diff --git a/tfhe-csprng/src/generators/implem/soft/block_cipher.rs b/tfhe-csprng/src/generators/implem/soft/block_cipher.rs index f10b1781c..1337904cd 100644 --- a/tfhe-csprng/src/generators/implem/soft/block_cipher.rs +++ b/tfhe-csprng/src/generators/implem/soft/block_cipher.rs @@ -24,6 +24,18 @@ impl AesBlockCipher for SoftwareBlockCipher { data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], &self.aes, ) } + + fn generate_next(&mut self, data: u128) -> [u8; BYTES_PER_AES_CALL] { + aes_encrypt_one(data, &self.aes) + } +} + +fn aes_encrypt_one(message: u128, cipher: &Aes128) -> [u8; BYTES_PER_AES_CALL] { + let mut b1 = GenericArray::clone_from_slice(&message.to_ne_bytes()[..]); + + cipher.encrypt_block(&mut b1); + + b1.into() } // Uses aes to encrypt many values at once. This allows a substantial speedup (around 30%) @@ -102,4 +114,12 @@ mod test { ); } } + + #[test] + fn test_encrypt_one_message() { + let key: [u8; BYTES_PER_AES_CALL] = CIPHER_KEY.to_ne_bytes(); + let aes = Aes128::new(&GenericArray::from(key)); + let ciphertext = aes_encrypt_one(PLAINTEXT, &aes); + assert_eq!(u128::from_ne_bytes(ciphertext), CIPHERTEXT); + } } diff --git a/tfhe-csprng/src/generators/implem/soft/generator.rs b/tfhe-csprng/src/generators/implem/soft/generator.rs index 3568d6a2e..4d8bb013a 100644 --- a/tfhe-csprng/src/generators/implem/soft/generator.rs +++ b/tfhe-csprng/src/generators/implem/soft/generator.rs @@ -1,7 +1,7 @@ -use crate::generators::aes_ctr::{AesCtrGenerator, AesKey, ChildrenIterator}; +use crate::generators::aes_ctr::{AesCtrGenerator, ChildrenIterator}; use crate::generators::implem::soft::block_cipher::SoftwareBlockCipher; use crate::generators::{ByteCount, BytesPerChild, ChildrenCount, ForkError, RandomGenerator}; -use crate::seeders::Seed; +use crate::seeders::SeedKind; /// A random number generator using a software implementation. pub struct SoftwareRandomGenerator(pub(super) AesCtrGenerator); @@ -21,8 +21,8 @@ impl Iterator for SoftwareChildrenIterator { impl RandomGenerator for SoftwareRandomGenerator { type ChildrenIter = SoftwareChildrenIterator; - fn new(seed: Seed) -> Self { - SoftwareRandomGenerator(AesCtrGenerator::new(AesKey(seed.0), None, None)) + fn new(seed: impl Into) -> Self { + SoftwareRandomGenerator(AesCtrGenerator::from_seed(seed)) } fn remaining_bytes(&self) -> ByteCount { self.0.remaining_bytes() @@ -120,4 +120,9 @@ mod test { fn test_vector() { generator_generic_test::test_vectors::(); } + + #[test] + fn test_vector_xof_seed() { + generator_generic_test::test_vectors_xof_seed::(); + } } diff --git a/tfhe-csprng/src/generators/implem/soft/mod.rs b/tfhe-csprng/src/generators/implem/soft/mod.rs index 602ba02fc..996d42c2a 100644 --- a/tfhe-csprng/src/generators/implem/soft/mod.rs +++ b/tfhe-csprng/src/generators/implem/soft/mod.rs @@ -1,6 +1,7 @@ //! A module using a software fallback implementation of random number generator. mod block_cipher; +pub use block_cipher::SoftwareBlockCipher; mod generator; pub use generator::*; diff --git a/tfhe-csprng/src/generators/mod.rs b/tfhe-csprng/src/generators/mod.rs index b656f9a7e..7317a5331 100644 --- a/tfhe-csprng/src/generators/mod.rs +++ b/tfhe-csprng/src/generators/mod.rs @@ -1,7 +1,7 @@ //! A module containing random generators objects. //! //! See [crate-level](`crate`) explanations. -use crate::seeders::Seed; +use crate::seeders::SeedKind; use std::error::Error; use std::fmt::{Display, Formatter}; @@ -62,7 +62,7 @@ pub trait RandomGenerator: Iterator { /// /// This operation is usually costly to perform, as the aes round keys need to be generated from /// the seed. - fn new(seed: Seed) -> Self; + fn new(seed: impl Into) -> Self; /// Returns the number of bytes that can still be outputted by the generator before reaching its /// bound. @@ -131,6 +131,7 @@ pub use default::DefaultRandomGenerator; #[allow(unused)] // to please clippy when tests are not activated pub mod generator_generic_test { use super::*; + use crate::seeders::{Seed, XofSeed}; use rand::Rng; const REPEATS: usize = 1_000; @@ -266,4 +267,35 @@ pub mod generator_generic_test { let bytes = rng.take(N_BYTES).collect::>(); assert_eq!(bytes, EXPECTED_BYTE); } + + pub fn test_vectors_xof_seed() { + // Number of random bytes to generate, + // this should be 2 batch worth of aes calls (where a batch is 8 aes) + const N_BYTES: usize = 16 * 2 * 8; + + const EXPECTED_BYTE: [u8; N_BYTES] = [ + 134, 231, 117, 200, 60, 174, 158, 95, 80, 64, 236, 147, 204, 196, 251, 198, 110, 155, + 74, 69, 162, 251, 224, 46, 46, 83, 209, 224, 89, 108, 68, 240, 37, 16, 109, 194, 92, 3, + 164, 21, 167, 224, 205, 31, 90, 178, 59, 150, 142, 238, 113, 144, 181, 118, 160, 72, + 187, 38, 29, 61, 189, 229, 66, 22, 4, 38, 210, 63, 232, 182, 115, 49, 96, 6, 120, 226, + 40, 51, 144, 59, 136, 224, 252, 195, 50, 250, 134, 45, 149, 220, 32, 27, 35, 225, 190, + 73, 161, 182, 250, 149, 153, 131, 220, 143, 181, 152, 187, 25, 62, 197, 24, 10, 142, + 57, 172, 15, 17, 244, 242, 232, 51, 50, 244, 85, 58, 69, 28, 113, 151, 143, 138, 166, + 198, 16, 210, 46, 234, 138, 32, 124, 98, 167, 141, 251, 60, 13, 158, 106, 29, 86, 63, + 73, 42, 138, 174, 195, 192, 72, 122, 74, 54, 134, 107, 144, 241, 12, 33, 70, 27, 116, + 154, 123, 1, 252, 141, 73, 79, 30, 162, 43, 57, 8, 99, 62, 222, 117, 232, 147, 81, 189, + 54, 17, 233, 33, 41, 132, 155, 246, 185, 189, 17, 77, 32, 107, 134, 61, 174, 64, 174, + 80, 229, 239, 243, 143, 152, 249, 254, 125, 42, 0, 170, 253, 34, 57, 100, 82, 244, 9, + 101, 126, 138, 218, 215, 55, 58, 177, 154, 5, 28, 113, 89, 123, 129, 254, 212, 191, + 162, 44, 120, 67, 241, 157, 31, 162, 113, 91, + ]; + + let seed_bytes: [u8; 16] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let seed = u128::from_ne_bytes(seed_bytes); + let xof_seed = XofSeed::new_u128(seed, [b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h']); + + let mut rng = G::new(xof_seed); + let bytes = rng.take(N_BYTES).collect::>(); + assert_eq!(bytes, EXPECTED_BYTE); + } } diff --git a/tfhe-csprng/src/seeders/mod.rs b/tfhe-csprng/src/seeders/mod.rs index 54ae2b2c9..4d137f578 100644 --- a/tfhe-csprng/src/seeders/mod.rs +++ b/tfhe-csprng/src/seeders/mod.rs @@ -9,6 +9,90 @@ #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Seed(pub u128); +/// A Seed as described in the [NIST document] +/// +/// This seed contains 2 information: +/// * The domain separator bytes (ASCII string) +/// * The seed bytes +/// +/// [NIST document]: https://eprint.iacr.org/2025/699 +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct XofSeed { + // We store the domain separator concatenated with the seed bytes (str||seed) + // as it makes it easier to create the iterator of u128 blocks + data: Vec, +} + +impl XofSeed { + const DOMAIN_SEP_LEN: usize = 8; + + // Creates a new seed of 128 bits + pub fn new_u128(seed: u128, domain_separator: [u8; Self::DOMAIN_SEP_LEN]) -> Self { + let mut data = vec![0u8; size_of::() + domain_separator.len()]; + data[..Self::DOMAIN_SEP_LEN].copy_from_slice(domain_separator.as_slice()); + data[Self::DOMAIN_SEP_LEN..].copy_from_slice(seed.to_ne_bytes().as_slice()); + + Self { data } + } + + pub fn new(mut seed: Vec, domain_separator: [u8; Self::DOMAIN_SEP_LEN]) -> Self { + seed.resize(domain_separator.len() + seed.len(), 0); + seed.rotate_right(domain_separator.len()); + seed[..Self::DOMAIN_SEP_LEN].copy_from_slice(domain_separator.as_slice()); + Self { data: seed } + } + + /// Returns the seed part + pub fn seed(&self) -> &[u8] { + &self.data[Self::DOMAIN_SEP_LEN..] + } + + /// Returns the domain separator + pub fn domain_separator(&self) -> [u8; Self::DOMAIN_SEP_LEN] { + let mut sep = [0u8; Self::DOMAIN_SEP_LEN]; + sep.copy_from_slice(&self.data[..Self::DOMAIN_SEP_LEN]); + sep + } + + /// Total len (seed bytes + domain separator) in bits + pub fn bit_len(&self) -> u128 { + (self.data.len()) as u128 * 8 + } + + /// Returns an iterator that iterates over the concatenated seed||domain_separator + /// as blocks of u128 bits + pub(crate) fn iter_u128_blocks(&self) -> impl Iterator + '_ { + self.data.chunks(size_of::()).map(move |chunk| { + let mut buf = [0u8; size_of::()]; + buf[..chunk.len()].copy_from_slice(chunk); + u128::from_ne_bytes(buf) + }) + } +} + +pub enum SeedKind { + /// Initializes the Aes-Ctr with a counter starting at 0 + /// and uses the seed as the Aes key. + Ctr(Seed), + /// Seed that initialized the Aes-Ctr following the NIST document (see [XofSeed]). + /// + /// An Aes-Key and starting counter will be derived from the XofSeed, to + /// then initialize the Aes-Ctr random generator + Xof(XofSeed), +} + +impl From for SeedKind { + fn from(value: Seed) -> Self { + Self::Ctr(value) + } +} + +impl From for SeedKind { + fn from(value: XofSeed) -> Self { + Self::Xof(value) + } +} + /// A trait representing a seeding strategy. pub trait Seeder { /// Generates a new seed. @@ -29,7 +113,7 @@ pub use implem::*; #[cfg(test)] mod generic_tests { - use crate::seeders::Seeder; + use crate::seeders::{Seeder, XofSeed}; /// Naively verifies that two fixed-size sequences generated by repeatedly calling the seeder /// are different. @@ -47,4 +131,31 @@ mod generic_tests { } } } + + #[test] + fn test_xof_seed_getters() { + let bits = u128::from_ne_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + let dsep = [b't', b'f', b'h', b'e', b'k', b's', b'p', b's']; + let seed = XofSeed::new_u128(bits, dsep); + + let s = u128::from_ne_bytes(seed.seed().try_into().unwrap()); + assert_eq!(s, bits); + assert_eq!(seed.domain_separator(), dsep); + assert_eq!(seed.bit_len(), 192); + + let collected_u128s = seed.iter_u128_blocks().collect::>(); + assert_eq!( + collected_u128s, + vec![ + u128::from_ne_bytes([ + b't', b'f', b'h', b'e', b'k', b's', b'p', b's', 1, 2, 3, 4, 5, 6, 7, 8 + ]), + u128::from_ne_bytes([9, 10, 11, 12, 13, 14, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0]), + ] + ); + + // To make sure both constructors yield the same results + let seed2 = XofSeed::new(bits.to_ne_bytes().to_vec(), dsep); + assert_eq!(seed.data, seed2.data); + } }