snarkvm_utilities/
rand.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use rand::{
17    Rng,
18    SeedableRng,
19    distributions::{Distribution, Standard},
20    rngs::StdRng,
21};
22use rand_xorshift::XorShiftRng;
23
24/// A trait for a uniform random number generator.
25pub trait Uniform: Sized {
26    /// Samples a random value from a uniform distribution.
27    fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self;
28}
29
30impl<T> Uniform for T
31where
32    Standard: Distribution<T>,
33{
34    #[inline]
35    fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self {
36        rng.sample(Standard)
37    }
38}
39
40/// A fast RNG used **solely** for testing and benchmarking, **not** for any real world purposes.
41pub struct TestRng {
42    seed: u64,
43    rng: XorShiftRng,
44    calls: usize,
45}
46
47impl Default for TestRng {
48    fn default() -> Self {
49        // Obtain the initial seed using entropy provided by the OS.
50        let seed = StdRng::from_entropy().r#gen();
51
52        // Use it as the basis for the underlying Rng.
53        Self::fixed(seed)
54    }
55}
56
57impl TestRng {
58    pub fn fixed(seed: u64) -> Self {
59        // Print the seed, so it's displayed if any of the tests using `test_rng` fails.
60        println!("\nInitializing 'TestRng' with seed '{seed}'\n");
61
62        // Use the seed to initialize a fast, non-cryptographic Rng.
63        Self::from_seed(seed)
64    }
65
66    // This is the preferred method to use once the main instance of TestRng had already
67    // been initialized in a test or benchmark and an auxiliary one is desired without
68    // spamming the stdout.
69    pub fn from_seed(seed: u64) -> Self {
70        Self { seed, rng: XorShiftRng::seed_from_u64(seed), calls: 0 }
71    }
72
73    /// Returns a randomly-sampled `String`, given the maximum size in bytes and an RNG.
74    ///
75    /// Some of the snarkVM internal tests involve the random generation of strings,
76    /// which are parsed and tested against the original ones. However, since the string parser
77    /// rejects certain characters, if those characters are randomly generated, the tests fail.
78    ///
79    /// To prevent these failures, as we randomly generate the characters,
80    /// we ensure that they are not among the ones rejected by the parser;
81    /// if they are, we adjust them to be allowed characters.
82    ///
83    /// Note that the randomness of the characters is strictly for **testing** purposes;
84    /// also note that the disallowed characters are a small fraction of the total set of characters,
85    /// and thus the adjustments rarely occur.
86    pub fn next_string(&mut self, max_bytes: u32, is_fixed_size: bool) -> String {
87        /// Adjust an unsafe character.
88        ///
89        /// As our parser rejects certain potentially unsafe characters (see `Sanitizer::parse_safe_char`),
90        /// we need to avoid generating them randomly. This function acts as an adjusting filter:
91        /// it changes an unsafe character to `'0'` (other choices are possible), and leaves other
92        /// characters unchanged.
93        fn adjust_unsafe_char(ch: char) -> char {
94            let code = ch as u32;
95            if code < 9
96                || code == 11
97                || code == 12
98                || (14..=31).contains(&code)
99                || code == 127
100                || (0x202a..=0x202e).contains(&code)
101                || (0x2066..=0x2069).contains(&code)
102            {
103                '0'
104            } else {
105                ch
106            }
107        }
108
109        /// Adjust a backslash and a double quote.
110        ///
111        /// Aside from the characters rejected through the function [adjust_unsafe_char],
112        /// the syntax of strings allows backslash and double quotes only in certain circumstances:
113        /// backslash is used to introduce an escape, and there are constraints on what can occur
114        /// after a backslash; double quotes is only used in escaped form just after a backslash.
115        ///
116        /// If we randomly sample characters, we may end up generating backslashes with
117        /// malformed escape syntax, or double quotes not preceded by backslash. Thus,
118        /// we also adjust backslashes and double quotes as we randomly sample characters.
119        ///
120        /// Note that, this way, we do not test the parsing of any escape sequences;
121        /// to do that, we would need to reify the possible elements of strings,
122        /// namely characters and escapes, and randomly generate such elements.
123        fn adjust_backslash_and_doublequote(ch: char) -> char {
124            if ch == '\\' || ch == '\"' { '0' } else { ch }
125        }
126
127        let range = match is_fixed_size {
128            true => 0..max_bytes,
129            false => 0..self.gen_range(0..max_bytes),
130        };
131
132        range.map(|_| self.r#gen::<char>()).map(adjust_unsafe_char).map(adjust_backslash_and_doublequote).collect()
133    }
134}
135
136impl rand::RngCore for TestRng {
137    fn next_u32(&mut self) -> u32 {
138        self.calls += 1;
139        self.rng.next_u32()
140    }
141
142    fn next_u64(&mut self) -> u64 {
143        self.calls += 1;
144        self.rng.next_u64()
145    }
146
147    fn fill_bytes(&mut self, dest: &mut [u8]) {
148        self.calls += 1;
149        self.rng.fill_bytes(dest)
150    }
151
152    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
153        self.calls += 1;
154        self.rng.try_fill_bytes(dest)
155    }
156}
157
158impl rand::CryptoRng for TestRng {}
159
160impl Drop for TestRng {
161    fn drop(&mut self) {
162        println!("Called TestRng with seed {} {} times", self.seed, self.calls);
163    }
164}