snarkvm_utilities/
rand.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Copyright 2024 Aleo Network Foundation
// This file is part of the snarkVM library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use rand::{
    Rng,
    SeedableRng,
    distributions::{Distribution, Standard},
    rngs::StdRng,
};
use rand_xorshift::XorShiftRng;

/// A trait for a uniform random number generator.
pub trait Uniform: Sized {
    /// Samples a random value from a uniform distribution.
    fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self;
}

impl<T> Uniform for T
where
    Standard: Distribution<T>,
{
    #[inline]
    fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self {
        rng.sample(Standard)
    }
}

/// A fast RNG used **solely** for testing and benchmarking, **not** for any real world purposes.
pub struct TestRng(XorShiftRng);

impl Default for TestRng {
    fn default() -> Self {
        // Obtain the initial seed using entropy provided by the OS.
        let seed = StdRng::from_entropy().gen();

        // Use it as the basis for the underlying Rng.
        Self::fixed(seed)
    }
}

impl TestRng {
    pub fn fixed(seed: u64) -> Self {
        // Print the seed, so it's displayed if any of the tests using `test_rng` fails.
        println!("\nInitializing 'TestRng' with seed '{seed}'\n");

        // Use the seed to initialize a fast, non-cryptographic Rng.
        Self::from_seed(seed)
    }

    // This is the preferred method to use once the main instance of TestRng had already
    // been initialized in a test or benchmark and an auxiliary one is desired without
    // spamming the stdout.
    pub fn from_seed(seed: u64) -> Self {
        Self(XorShiftRng::seed_from_u64(seed))
    }

    /// Returns a randomly-sampled `String`, given the maximum size in bytes and an RNG.
    ///
    /// Some of the snarkVM internal tests involve the random generation of strings,
    /// which are parsed and tested against the original ones. However, since the string parser
    /// rejects certain characters, if those characters are randomly generated, the tests fail.
    ///
    /// To prevent these failures, as we randomly generate the characters,
    /// we ensure that they are not among the ones rejected by the parser;
    /// if they are, we adjust them to be allowed characters.
    ///
    /// Note that the randomness of the characters is strictly for **testing** purposes;
    /// also note that the disallowed characters are a small fraction of the total set of characters,
    /// and thus the adjustments rarely occur.
    pub fn next_string(&mut self, max_bytes: u32, is_fixed_size: bool) -> String {
        /// Adjust an unsafe character.
        ///
        /// As our parser rejects certain potentially unsafe characters (see `Sanitizer::parse_safe_char`),
        /// we need to avoid generating them randomly. This function acts as an adjusting filter:
        /// it changes an unsafe character to `'0'` (other choices are possible), and leaves other
        /// characters unchanged.
        fn adjust_unsafe_char(ch: char) -> char {
            let code = ch as u32;
            if code < 9
                || code == 11
                || code == 12
                || (14..=31).contains(&code)
                || code == 127
                || (0x202a..=0x202e).contains(&code)
                || (0x2066..=0x2069).contains(&code)
            {
                '0'
            } else {
                ch
            }
        }

        /// Adjust a backslash and a double quote.
        ///
        /// Aside from the characters rejected through the function [adjust_unsafe_char],
        /// the syntax of strings allows backslash and double quotes only in certain circumstances:
        /// backslash is used to introduce an escape, and there are constraints on what can occur
        /// after a backslash; double quotes is only used in escaped form just after a backslash.
        ///
        /// If we randomly sample characters, we may end up generating backslashes with
        /// malformed escape syntax, or double quotes not preceded by backslash. Thus,
        /// we also adjust backslashes and double quotes as we randomly sample characters.
        ///
        /// Note that, this way, we do not test the parsing of any escape sequences;
        /// to do that, we would need to reify the possible elements of strings,
        /// namely characters and escapes, and randomly generate such elements.
        fn adjust_backslash_and_doublequote(ch: char) -> char {
            if ch == '\\' || ch == '\"' { '0' } else { ch }
        }

        let range = match is_fixed_size {
            true => 0..max_bytes,
            false => 0..self.gen_range(0..max_bytes),
        };

        range.map(|_| self.gen::<char>()).map(adjust_unsafe_char).map(adjust_backslash_and_doublequote).collect()
    }
}

impl rand::RngCore for TestRng {
    fn next_u32(&mut self) -> u32 {
        self.0.next_u32()
    }

    fn next_u64(&mut self) -> u64 {
        self.0.next_u64()
    }

    fn fill_bytes(&mut self, dest: &mut [u8]) {
        self.0.fill_bytes(dest)
    }

    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
        self.0.try_fill_bytes(dest)
    }
}

impl rand::CryptoRng for TestRng {}