Skip to main content

rapidhash/inner/state/
random_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::RandomState`] compatible hasher that initializes a [`RapidHasher`] with a random
7/// seed and random global secrets.
8///
9/// This is designed to provide some HashDoS resistance by using a random seed per hashmap, and
10/// a global random set of secrets.
11///
12/// # Performance
13///
14/// Enabling the `std` feature will make `RandomState` slightly faster to initialize by avoiding
15/// a global atomic load/store and using thread-locals instead.
16///
17/// Note that `GlobalState` will be even faster to initialize than `RandomState`, as it does not
18/// generate a new seed for every instantiation. `GlobalState` also randomizes the seed and secrets
19/// on the first instantiation, so it can still provide minimal DoS resistance, but then re-uses
20/// that same randomized seed and secrets for subsequent instantiations. If you are creating many
21/// instances of `RandomState` in a tight loop, you may want to consider using `GlobalState`
22/// instead.
23///
24/// # Portability
25///
26/// On most target platforms, the secrets are randomly initialized once and cached globally for the
27/// lifetime of the program. With the `getrandom_04` feature this uses OS/platform entropy directly;
28/// with `std` it uses the standard library's secure RNG; with neither it falls back to ASLR-based
29/// entropy alone. A fresh seed is generated for each new instance of `RandomState` from a
30/// thread-local or global counter mixed with ASLR entropy.
31///
32/// Some targets have no ambient entropy or ASLR at all, and seeding on them is otherwise fully
33/// deterministic between boots. Enable the `getrandom_04` feature to get true randomisation for
34/// HashDoS resistance on these targets:
35///
36/// - **wasm32 with WASI** (`wasm32-wasip1`/`wasm32-wasip2`): enabling rapidhash's `getrandom_04`
37///   feature is sufficient; entropy comes from the WASI host.
38/// - **wasm32 in the browser or node** (`wasm32-unknown-unknown`): enable rapidhash's `getrandom_04`
39///   feature, add `getrandom = { version = "0.4", features = ["wasm_js"] }` to the top-level
40///   binary's dependencies, and build with `RUSTFLAGS='--cfg getrandom_backend="wasm_js"'`.
41///   Without the backend flag the build fails with instructions, rather than silently
42///   falling back to deterministic seeding.
43/// - **Embedded and other `no_std` targets with a hardware RNG**: enable rapidhash's `getrandom_04`
44///   feature and register a [custom backend](https://docs.rs/getrandom/0.4/getrandom/#custom-backend)
45///   that reads from the platform's RNG.
46///
47/// Older or exotic platforms that getrandom does not support can also use the custom backend
48/// mechanism to supply their own entropy source.
49///
50/// On targets without atomic pointer support (e.g. `thumbv6m-none-eabi`), the global secrets
51/// cannot be randomized and fall back to the default secrets. With the `getrandom_04` feature each
52/// `RandomState` still draws a fresh random per-map seed directly from the entropy source,
53/// retaining minimal HashDoS resistance; without it these platforms have none. If stronger
54/// support for these platforms is important to your application, please raise a GitHub issue.
55///
56/// # Example
57/// ```rust
58/// use std::collections::HashMap;
59/// use std::hash::Hasher;
60///
61/// use rapidhash::quality::RandomState;
62///
63/// let mut map = HashMap::with_hasher(RandomState::default());
64/// map.insert(42, "the answer");
65/// ```
66#[derive(Copy, Clone, Eq, PartialEq)]
67pub struct RandomState<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> {
68    seed: u64,
69
70    /// The global secrets is a zero-sized type to keep HashMap<K, V, RandomState> small.
71    secrets: GlobalSecrets,
72}
73
74impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
75    /// Create a new random state with a fresh per-instance seed and the global secrets.
76    ///
77    /// A new seed is generated for every `RandomState` instance. With the `std` feature this uses a
78    /// fast thread-local counter mixed with stack-pointer (ASLR) entropy, where each thread's
79    /// counter is initialized from the process-wide random seed and a global thread counter;
80    /// without `std` it uses a global atomic counter initialized from the process-wide random
81    /// seed. On targets with neither, each instance draws its seed from getrandom when the
82    /// `getrandom_04` feature is enabled, or falls back to ASLR alone.
83    ///
84    /// The secrets are randomized once and then cached globally for the lifetime of the program.
85    /// The one-time randomization is derived from OS/platform entropy with the `getrandom_04`
86    /// feature, or the standard library's secure RNG with the `std` feature (the same source
87    /// `std` uses to seed its own hashers); with neither it falls back to mixing ASLR and other
88    /// weaker sources of entropy.
89    ///
90    /// On platforms that do not support atomic pointers, the secrets will be the default rapidhash
91    /// secrets, which are not randomized. Therefore, **targets without atomic pointer support only
92    /// have minimal HashDoS resistance when the `getrandom_04` feature provides random seeds**.
93    #[inline]
94    pub fn new() -> Self {
95        Self {
96            seed: crate::inner::seeding::seed::get_seed(),
97            secrets: GlobalSecrets::new(),
98        }
99    }
100}
101
102/// Warning that `RandomState` only randomizes the seed on platforms that support atomic pointers.
103impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> Default for RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
104    #[inline]
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool>  BuildHasher for RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
111    type Hasher = RapidHasher<'static, AVALANCHE, SPONGE, COMPACT, PROTECTED>;
112
113    #[inline(always)]
114    fn build_hasher(&self) -> Self::Hasher {
115        RapidHasher::new_precomputed_seed(self.seed, self.secrets.get())
116    }
117}
118
119impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> core::fmt::Debug for RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
120    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
121        f.debug_struct("RandomState").finish_non_exhaustive()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use core::hash::BuildHasher;
128
129    type RandomState = super::RandomState<false, true, false, false>;
130
131    #[test]
132    fn test_random_state() {
133        let state1 = RandomState::new();
134        let state2 = RandomState::new();
135
136        let finish1a = state1.hash_one(b"hello");
137        let finish1b = state1.hash_one(b"hello");
138        let finish2a = state2.hash_one(b"hello");
139
140        assert_eq!(finish1a, finish1b);
141        assert_ne!(finish1a, finish2a);
142    }
143
144    #[test]
145    fn test_debug() {
146        extern crate alloc;
147        let state = RandomState::new();
148        let debug_str = alloc::format!("{:?}", state);
149        assert_eq!(debug_str, "RandomState { .. }");
150    }
151}