snarkvm_utilities/
rand.rs

1// Copyright 2024 Aleo Network Foundation
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(XorShiftRng);
42
43impl Default for TestRng {
44    fn default() -> Self {
45        // Obtain the initial seed using entropy provided by the OS.
46        let seed = StdRng::from_entropy().gen();
47
48        // Use it as the basis for the underlying Rng.
49        Self::fixed(seed)
50    }
51}
52
53impl TestRng {
54    pub fn fixed(seed: u64) -> Self {
55        // Print the seed, so it's displayed if any of the tests using `test_rng` fails.
56        println!("\nInitializing 'TestRng' with seed '{seed}'\n");
57
58        // Use the seed to initialize a fast, non-cryptographic Rng.
59        Self::from_seed(seed)
60    }
61
62    // This is the preferred method to use once the main instance of TestRng had already
63    // been initialized in a test or benchmark and an auxiliary one is desired without
64    // spamming the stdout.
65    pub fn from_seed(seed: u64) -> Self {
66        Self(XorShiftRng::seed_from_u64(seed))
67    }
68
69    /// Returns a randomly-sampled `String`, given the maximum size in bytes and an RNG.
70    ///
71    /// Some of the snarkVM internal tests involve the random generation of strings,
72    /// which are parsed and tested against the original ones. However, since the string parser
73    /// rejects certain characters, if those characters are randomly generated, the tests fail.
74    ///
75    /// To prevent these failures, as we randomly generate the characters,
76    /// we ensure that they are not among the ones rejected by the parser;
77    /// if they are, we adjust them to be allowed characters.
78    ///
79    /// Note that the randomness of the characters is strictly for **testing** purposes;
80    /// also note that the disallowed characters are a small fraction of the total set of characters,
81    /// and thus the adjustments rarely occur.
82    pub fn next_string(&mut self, max_bytes: u32, is_fixed_size: bool) -> String {
83        /// Adjust an unsafe character.
84        ///
85        /// As our parser rejects certain potentially unsafe characters (see `Sanitizer::parse_safe_char`),
86        /// we need to avoid generating them randomly. This function acts as an adjusting filter:
87        /// it changes an unsafe character to `'0'` (other choices are possible), and leaves other
88        /// characters unchanged.
89        fn adjust_unsafe_char(ch: char) -> char {
90            let code = ch as u32;
91            if code < 9
92                || code == 11
93                || code == 12
94                || (14..=31).contains(&code)
95                || code == 127
96                || (0x202a..=0x202e).contains(&code)
97                || (0x2066..=0x2069).contains(&code)
98            {
99                '0'
100            } else {
101                ch
102            }
103        }
104
105        /// Adjust a backslash and a double quote.
106        ///
107        /// Aside from the characters rejected through the function [adjust_unsafe_char],
108        /// the syntax of strings allows backslash and double quotes only in certain circumstances:
109        /// backslash is used to introduce an escape, and there are constraints on what can occur
110        /// after a backslash; double quotes is only used in escaped form just after a backslash.
111        ///
112        /// If we randomly sample characters, we may end up generating backslashes with
113        /// malformed escape syntax, or double quotes not preceded by backslash. Thus,
114        /// we also adjust backslashes and double quotes as we randomly sample characters.
115        ///
116        /// Note that, this way, we do not test the parsing of any escape sequences;
117        /// to do that, we would need to reify the possible elements of strings,
118        /// namely characters and escapes, and randomly generate such elements.
119        fn adjust_backslash_and_doublequote(ch: char) -> char {
120            if ch == '\\' || ch == '\"' { '0' } else { ch }
121        }
122
123        let range = match is_fixed_size {
124            true => 0..max_bytes,
125            false => 0..self.gen_range(0..max_bytes),
126        };
127
128        range.map(|_| self.gen::<char>()).map(adjust_unsafe_char).map(adjust_backslash_and_doublequote).collect()
129    }
130}
131
132impl rand::RngCore for TestRng {
133    fn next_u32(&mut self) -> u32 {
134        self.0.next_u32()
135    }
136
137    fn next_u64(&mut self) -> u64 {
138        self.0.next_u64()
139    }
140
141    fn fill_bytes(&mut self, dest: &mut [u8]) {
142        self.0.fill_bytes(dest)
143    }
144
145    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
146        self.0.try_fill_bytes(dest)
147    }
148}
149
150impl rand::CryptoRng for TestRng {}