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 {}