Skip to main content

tfhe_csprng/seeders/
mod.rs

1//! A module containing seeders objects.
2//!
3//! When initializing a generator, one needs to provide a [`Seed`], which is then used as key to the
4//! AES blockcipher. As a consequence, the quality of the outputs of the generator is directly
5//! conditioned by the quality of this seed. This module proposes different mechanisms to deliver
6//! seeds that can accommodate varying scenarios.
7
8/// A seed value, used to initialize a generator.
9#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, Versionize)]
10#[versionize(SeedVersions)]
11pub struct Seed(pub u128);
12
13/// A Seed as described in the [Threshold (Fully) Homomorphic Encryption]
14///
15/// This seed contains 2 information:
16/// * The domain separator bytes (ASCII string)
17/// * The seed bytes
18///
19/// [Threshold (Fully) Homomorphic Encryption]: https://eprint.iacr.org/2025/699
20#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, Versionize)]
21#[versionize(XofSeedVersions)]
22pub struct XofSeed {
23    // We store the domain separator concatenated with the seed bytes (str||seed)
24    // as it makes it easier to create the iterator of u128 blocks
25    data: Vec<u8>,
26}
27
28impl XofSeed {
29    pub const DOMAIN_SEP_LEN: usize = 8;
30
31    // Creates a new seed of 128 bits
32    pub fn new_u128(seed: u128, domain_separator: [u8; Self::DOMAIN_SEP_LEN]) -> Self {
33        let mut data = vec![0u8; size_of::<u128>() + domain_separator.len()];
34        data[..Self::DOMAIN_SEP_LEN].copy_from_slice(domain_separator.as_slice());
35        data[Self::DOMAIN_SEP_LEN..].copy_from_slice(seed.to_le_bytes().as_slice());
36
37        Self { data }
38    }
39
40    pub fn new(mut seed: Vec<u8>, domain_separator: [u8; Self::DOMAIN_SEP_LEN]) -> Self {
41        seed.resize(domain_separator.len() + seed.len(), 0);
42        seed.rotate_right(domain_separator.len());
43        seed[..Self::DOMAIN_SEP_LEN].copy_from_slice(domain_separator.as_slice());
44        Self { data: seed }
45    }
46
47    /// Returns the seed part
48    pub fn seed(&self) -> &[u8] {
49        &self.data[Self::DOMAIN_SEP_LEN..]
50    }
51
52    /// Returns the domain separator
53    pub fn domain_separator(&self) -> [u8; Self::DOMAIN_SEP_LEN] {
54        let mut sep = [0u8; Self::DOMAIN_SEP_LEN];
55        sep.copy_from_slice(&self.data[..Self::DOMAIN_SEP_LEN]);
56        sep
57    }
58
59    /// Total len (seed bytes + domain separator) in bits
60    pub fn bit_len(&self) -> u128 {
61        (self.data.len()) as u128 * 8
62    }
63
64    /// Returns an iterator that iterates over the concatenated seed||domain_separator
65    /// as blocks of u128 bits
66    pub(crate) fn iter_u128_blocks(&self) -> impl Iterator<Item = u128> + '_ {
67        self.data.chunks(size_of::<u128>()).map(move |chunk| {
68            let mut buf = [0u8; size_of::<u128>()];
69            buf[..chunk.len()].copy_from_slice(chunk);
70            u128::from_ne_bytes(buf)
71        })
72    }
73
74    /// Creates a new XofSeed from raw bytes.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the provided data is smaller than the domain separator length
79    pub fn from_bytes(data: Vec<u8>) -> Self {
80        assert!(
81            data.len() >= Self::DOMAIN_SEP_LEN,
82            "XofSeed must be at least {} bytes long (got {})",
83            Self::DOMAIN_SEP_LEN,
84            data.len()
85        );
86        Self { data }
87    }
88
89    pub fn bytes(&self) -> &Vec<u8> {
90        &self.data
91    }
92
93    pub fn into_bytes(self) -> Vec<u8> {
94        self.data
95    }
96}
97
98#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Versionize)]
99#[versionize(SeedKindVersions)]
100pub enum SeedKind {
101    /// Initializes the Aes-Ctr with a counter starting at 0
102    /// and uses the seed as the Aes key.
103    Ctr(Seed),
104    /// Seed that initialized the Aes-Ctr following the Threshold (Fully) Homomorphic Encryption
105    /// document (see [XofSeed]).
106    ///
107    /// An Aes-Key and starting counter will be derived from the XofSeed, to
108    /// then initialize the Aes-Ctr random generator
109    Xof(XofSeed),
110}
111
112impl From<Seed> for SeedKind {
113    fn from(value: Seed) -> Self {
114        Self::Ctr(value)
115    }
116}
117
118impl From<XofSeed> for SeedKind {
119    fn from(value: XofSeed) -> Self {
120        Self::Xof(value)
121    }
122}
123
124/// A trait representing a seeding strategy.
125pub trait Seeder {
126    /// Generates a new seed.
127    fn seed(&mut self) -> Seed;
128
129    /// Check whether the seeder can be used on the current machine. This function may check if some
130    /// required CPU features are available or if some OS features are available for example.
131    fn is_available() -> bool
132    where
133        Self: Sized;
134}
135
136pub mod backward_compatibility;
137mod implem;
138// This import statement can be empty if seeder features are disabled, rustc's behavior changed to
139// warn of empty modules, we know this can happen, so allow it.
140#[allow(unused_imports)]
141pub use implem::*;
142use tfhe_versionable::Versionize;
143
144use crate::seeders::backward_compatibility::{SeedKindVersions, SeedVersions, XofSeedVersions};
145
146#[cfg(test)]
147mod generic_tests {
148    use crate::seeders::{Seeder, XofSeed};
149
150    /// Naively verifies that two fixed-size sequences generated by repeatedly calling the seeder
151    /// are different.
152    #[allow(unused)] // to please clippy when tests are not activated
153    pub fn check_seeder_fixed_sequences_different<S: Seeder, F: Fn(u128) -> S>(
154        construct_seeder: F,
155    ) {
156        const SEQUENCE_SIZE: usize = 500;
157        const REPEATS: usize = 10_000;
158        for i in 0..REPEATS {
159            let mut seeder = construct_seeder(i as u128);
160            let orig_seed = seeder.seed();
161            for _ in 0..SEQUENCE_SIZE {
162                assert_ne!(seeder.seed(), orig_seed);
163            }
164        }
165    }
166
167    #[test]
168    fn test_xof_seed_getters() {
169        let seed_bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
170        let bits = u128::from_le_bytes(seed_bytes);
171        let dsep = *b"tfheksps";
172        let seed = XofSeed::new_u128(bits, dsep);
173
174        let s = u128::from_le_bytes(seed.seed().try_into().unwrap());
175        assert_eq!(s, bits);
176        assert_eq!(seed.domain_separator(), dsep);
177        assert_eq!(seed.bit_len(), 192);
178
179        let collected_u128s = seed.iter_u128_blocks().collect::<Vec<_>>();
180        // Those u128 are used in AES computations and are just a way to handle a [u8; 16] so those
181        // are ok to check in ne_bytes
182        assert_eq!(
183            collected_u128s,
184            vec![
185                u128::from_ne_bytes([
186                    b't', b'f', b'h', b'e', b'k', b's', b'p', b's', 1, 2, 3, 4, 5, 6, 7, 8
187                ]),
188                u128::from_ne_bytes([9, 10, 11, 12, 13, 14, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0]),
189            ]
190        );
191
192        // To make sure both constructors yield the same results
193        let seed2 = XofSeed::new(seed_bytes.to_vec(), dsep);
194        assert_eq!(seed.data, seed2.data);
195    }
196}