rapidhash/inner/state/seedable_state.rs
1use core::hash::BuildHasher;
2use core::fmt::Formatter;
3use crate::inner::RapidHasher;
4use crate::inner::seeding::secrets::GlobalSecrets;
5
6/// A [`std::hash::BuildHasher`] that initializes a [`RapidHasher`] with a user-provided seed and
7/// secrets.
8///
9/// `SeedableState` should rarely be used as providing DoS resistance requires a randomized seed and
10/// secrets. Users should instead prefer either:
11/// * [`crate::inner::GlobalState`], which uses a global random seed and secrets initialized once at
12/// program start.
13/// * [`crate::inner::RandomState`], which uses a random seed per instance and global random secrets.
14///
15/// The lifetime `'s` is for the reference to the secrets. When using [`SeedableState::random`] or
16/// [`SeedableState::fixed`] secrets, this lifetime will be `'static`.
17///
18/// # Example
19/// ```
20/// use std::collections::HashMap;
21/// use std::hash::Hasher;
22///
23/// use rapidhash::quality::SeedableState;
24///
25/// let mut map = HashMap::with_hasher(SeedableState::default());
26/// map.insert(42, "the answer");
27/// ```
28#[derive(Copy, Clone, Eq, PartialEq)]
29pub struct SeedableState<'s, const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool = false, const PROTECTED: bool = false> {
30 seed: u64,
31 secrets: &'s [u64; 7],
32}
33
34impl<'s, const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> Default for SeedableState<'s, AVALANCHE, SPONGE, COMPACT, PROTECTED> {
35 /// Create a new [SeedableState] with a random seed. See [SeedableState::random] for more details.
36 #[inline]
37 fn default() -> Self {
38 Self::random()
39 }
40}
41
42impl<'s, const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> SeedableState<'s, AVALANCHE, SPONGE, COMPACT, PROTECTED> {
43 /// Create a new seedable state with a custom seed and automatically generated secrets.
44 ///
45 /// The seed will be pre-mixed to improve entropy. The global secrets are randomly generated
46 /// once at program start, and then will be re-used for all subsequent calls to this function.
47 ///
48 /// # Example
49 /// ```
50 /// use core::hash::BuildHasher;
51 /// use rapidhash::quality::SeedableState;
52 ///
53 /// let state = SeedableState::new(0);
54 ///
55 /// let hash: u64 = state.hash_one(b"hello");
56 /// println!("hash: {hash}");
57 /// ```
58 pub fn new(seed: u64) -> Self {
59 Self {
60 seed: crate::inner::seed::rapidhash_seed(seed),
61 secrets: GlobalSecrets::new().get(),
62 }
63 }
64
65 /// Create a new seedable state with a random seed.
66 ///
67 /// This is slower than using [`crate::inner::RandomState`], please use that instead.
68 #[inline]
69 pub fn random() -> Self {
70 Self {
71 seed: crate::inner::seeding::seed::get_seed(),
72 secrets: GlobalSecrets::new().get(),
73 }
74 }
75
76 /// Create a new seedable state with the default seed and secrets.
77 ///
78 /// Using the default secrets does not offer HashDoS resistance, but they will be fixed between
79 /// different runs of the program.
80 ///
81 /// Please note that `fast::RapidHasher` and `quality::RapidHasher` are **not guaranteed** to
82 /// produce the same hash outputs between different crate versions, compiler versions, or
83 /// platforms.
84 ///
85 /// Also see [`GlobalState`] for a faster zero-sized alternative that uses global secrets that
86 /// are fixed only for the lifetime of the program.
87 #[inline]
88 pub fn fixed() -> Self {
89 Self {
90 seed: crate::inner::seed::rapidhash_seed(crate::inner::seed::DEFAULT_SEED),
91 secrets: &crate::inner::seed::DEFAULT_SECRETS,
92 }
93 }
94
95 /// Create a new seedable state with a custom seed and secrets.
96 ///
97 /// ## Warning
98 /// This constructor uses the provided `seed` and `secrets` as the initial state
99 /// **without any pre-mixing or validation**. Supplying low-entropy or structured
100 /// values (e.g., `0`, all-zero arrays, counters, timestamps) can produce
101 /// degenerate hashing (high collision rates or identical outputs).
102 ///
103 /// ### Requirements
104 /// - `seed` and `secrets` **must not** be zero; avoid any all-zero/near-zero state.
105 /// - Generate `seed` and `secrets` with a **cryptographically secure PRNG** and
106 /// treat them as **independent** for each hasher instance.
107 /// - Do not derive successive seeds from predictable data (time, PID/TID, memory
108 /// addresses) or by simple incrementation.
109 ///
110 /// ### Recommendation
111 /// If you cannot pre-mix the seed yourself, use [`SeedableState::new`] instead.
112 ///
113 /// ### Example (secure generation)
114 /// ```rust
115 /// use core::hash::BuildHasher;
116 /// use rapidhash::quality::SeedableState;
117 ///
118 /// // randomly generate secrets
119 /// let seed: u64 = rand::random();
120 /// let secrets: [u64; 7] = rand::random();
121 ///
122 /// // create the state
123 /// let state = SeedableState::custom(seed, &secrets);
124 ///
125 /// // hash using the state
126 /// let hash: u64 = state.hash_one(b"hello");
127 /// println!("hash: {hash}");
128 /// ```
129 #[inline]
130 pub fn custom(seed: u64, secrets: &'s [u64; 7]) -> Self {
131 Self {
132 seed,
133 secrets,
134 }
135 }
136
137 /// Deprecated and renamed to [`SeedableState::custom`].
138 #[deprecated(since = "4.1.0", note = "Use custom() or new() instead.")]
139 #[inline]
140 pub fn with_seed(seed: u64, secrets: &'s [u64; 7]) -> Self {
141 Self::custom(seed, secrets)
142 }
143}
144
145impl<'s, const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> BuildHasher for SeedableState<'s, AVALANCHE, SPONGE, COMPACT, PROTECTED> {
146 type Hasher = RapidHasher<'s, AVALANCHE, SPONGE, COMPACT, PROTECTED>;
147
148 #[inline(always)]
149 fn build_hasher(&self) -> Self::Hasher {
150 RapidHasher::new_precomputed_seed(self.seed, self.secrets)
151 }
152}
153
154impl<'s, const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> core::fmt::Debug for SeedableState<'s, AVALANCHE, SPONGE, COMPACT, PROTECTED> {
155 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
156 f.debug_struct("SeedableState").finish_non_exhaustive()
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use core::hash::BuildHasher;
163
164 type SeedableState<'s> = super::SeedableState<'s, false, true, false, false>;
165
166 #[test]
167 fn test_random_init() {
168 assert_eq!(core::mem::size_of::<SeedableState>(), 16);
169
170 let state1 = SeedableState::random();
171 let state2 = SeedableState::random();
172
173 let finish1a = state1.hash_one(b"hello");
174 let finish1b = state1.hash_one(b"hello");
175 let finish2a = state2.hash_one(b"hello");
176
177 assert_eq!(finish1a, finish1b);
178 assert_ne!(finish1a, finish2a);
179 }
180
181 #[test]
182 fn test_fixed_init() {
183 assert_eq!(core::mem::size_of::<SeedableState>(), 16);
184
185 let state1 = SeedableState::fixed();
186 let state2 = SeedableState::fixed();
187
188 let finish1a = state1.hash_one(b"hello");
189 let finish1b = state1.hash_one(b"hello");
190 let finish2a = state2.hash_one(b"hello");
191
192 assert_eq!(finish1a, finish1b);
193 assert_eq!(finish1a, finish2a);
194 }
195
196 #[test]
197 fn test_debug() {
198 extern crate alloc;
199 let state = SeedableState::random();
200 let debug_str = alloc::format!("{:?}", state);
201 assert_eq!(debug_str, "SeedableState { .. }");
202 }
203}