Skip to main content

forest/utils/rand/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use rand::{CryptoRng, Rng, RngCore, SeedableRng as _};
5
6/// A wrapper of [`uuid::Builder::from_random_bytes`] that uses [`forest_rng`] internally
7pub fn new_uuid_v4() -> uuid::Uuid {
8    let mut random_bytes = uuid::Bytes::default();
9    forest_rng().fill(&mut random_bytes);
10    uuid::Builder::from_random_bytes(random_bytes).into_uuid()
11}
12
13/// A random, well-formed [`cid::Cid`] (v1 / DAG-CBOR / Blake2b-256), for tests
14/// that need distinct CIDs without caring about their contents.
15#[cfg(test)]
16pub(crate) fn random_cid() -> cid::Cid {
17    use crate::utils::multihash::prelude::*;
18    let mut digest = [0u8; 32];
19    forest_rng().fill(&mut digest);
20    cid::Cid::new_v1(
21        fvm_ipld_encoding::DAG_CBOR,
22        MultihashCode::Blake2b256.digest(&digest),
23    )
24}
25
26/// A wrapper of [`rand::thread_rng`] that can be overridden by reproducible seeded
27/// [`rand_chacha::ChaChaRng`] via `FOREST_TEST_RNG_FIXED_SEED` environment variable.
28/// This is required for reproducible test cases for normally non-deterministic methods.
29pub fn forest_rng() -> impl Rng + CryptoRng {
30    forest_rng_internal(ForestRngMode::ThreadRng)
31}
32
33/// A wrapper of [`rand::rngs::OsRng`] that can be overridden by reproducible seeded
34/// [`rand_chacha::ChaChaRng`] via `FOREST_TEST_RNG_FIXED_SEED` environment variable.
35/// This is required for reproducible test cases for normally non-deterministic methods.
36pub fn forest_os_rng() -> impl Rng + CryptoRng {
37    forest_rng_internal(ForestRngMode::OsRng)
38}
39
40pub const FIXED_RNG_SEED_ENV: &str = "FOREST_TEST_RNG_FIXED_SEED";
41
42enum ForestRngMode {
43    ThreadRng,
44    OsRng,
45}
46
47fn forest_rng_internal(mode: ForestRngMode) -> impl Rng + CryptoRng {
48    const ENV: &str = FIXED_RNG_SEED_ENV;
49    if let Ok(v) = std::env::var(ENV) {
50        if let Ok(seed) = v.parse() {
51            #[cfg(not(test))]
52            tracing::warn!("[security] using test RNG with fixed seed {seed} set by {ENV}");
53            return Either::Left(rand_chacha::ChaChaRng::seed_from_u64(seed));
54        } else {
55            tracing::warn!("invalid u64 seed set by {ENV}: {v}. Falling back to the default RNG.");
56        }
57    }
58    match mode {
59        #[allow(clippy::disallowed_methods)]
60        ForestRngMode::ThreadRng => Either::Right(Either::Left(rand::thread_rng())),
61        #[allow(clippy::disallowed_types)]
62        ForestRngMode::OsRng => Either::Right(Either::Right(rand::rngs::OsRng)),
63    }
64}
65
66enum Either<A, B> {
67    Left(A),
68    Right(B),
69}
70
71impl<A, B> RngCore for Either<A, B>
72where
73    A: RngCore,
74    B: RngCore,
75{
76    fn next_u32(&mut self) -> u32 {
77        match self {
78            Self::Left(i) => i.next_u32(),
79            Self::Right(i) => i.next_u32(),
80        }
81    }
82
83    fn next_u64(&mut self) -> u64 {
84        match self {
85            Self::Left(i) => i.next_u64(),
86            Self::Right(i) => i.next_u64(),
87        }
88    }
89
90    fn fill_bytes(&mut self, dst: &mut [u8]) {
91        match self {
92            Self::Left(i) => i.fill_bytes(dst),
93            Self::Right(i) => i.fill_bytes(dst),
94        }
95    }
96
97    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
98        match self {
99            Self::Left(i) => i.try_fill_bytes(dest),
100            Self::Right(i) => i.try_fill_bytes(dest),
101        }
102    }
103}
104
105impl<A, B> CryptoRng for Either<A, B>
106where
107    A: RngCore,
108    B: RngCore,
109{
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use serial_test::serial;
116
117    #[test]
118    #[serial]
119    fn test_fixed_seed_env() {
120        unsafe { std::env::set_var(FIXED_RNG_SEED_ENV, "0") };
121
122        let mut a = [0; 1024];
123        let mut b = [0; 1024];
124
125        forest_rng().fill(&mut a);
126        forest_rng().fill(&mut b);
127        assert_eq!(a, b);
128
129        forest_os_rng().fill(&mut a);
130        forest_os_rng().fill(&mut b);
131        assert_eq!(a, b);
132
133        unsafe { std::env::remove_var(FIXED_RNG_SEED_ENV) };
134    }
135
136    #[test]
137    #[serial]
138    fn test_thread_rng() {
139        unsafe { std::env::remove_var(FIXED_RNG_SEED_ENV) };
140        let mut a = [0; 1024];
141        forest_rng().fill(&mut a);
142        let mut b = [0; 1024];
143        forest_rng().fill(&mut b);
144        assert_ne!(a, b);
145    }
146
147    #[test]
148    #[serial]
149    fn test_os_rng() {
150        unsafe { std::env::remove_var(FIXED_RNG_SEED_ENV) };
151        let mut a = [0; 1024];
152        forest_os_rng().fill(&mut a);
153        let mut b = [0; 1024];
154        forest_os_rng().fill(&mut b);
155        assert_ne!(a, b);
156    }
157}