Skip to content

Commit b4ea500

Browse files
Add a pseudorandom generator for move tests (#7554)
Closing #7544 Co-authored-by: Sam Blackshear <sam.blackshear@gmail.com>
1 parent 15320d1 commit b4ea500

File tree

2 files changed

+413
-0
lines changed

2 files changed

+413
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) Mysten Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#[test_only]
5+
module sui::test_random {
6+
use std::hash;
7+
use std::vector;
8+
9+
// Internally, the pseudorandom generator uses a hash chain over Sha3-256
10+
// which has an output length of 32 bytes.
11+
const DIGEST_LENGTH: u64 = 32;
12+
13+
/// This represents a seeded pseudorandom generator. Note that the generated
14+
/// values are not safe to use for cryptographic purposes.
15+
struct Random has store, drop {
16+
state: vector<u8>,
17+
}
18+
19+
/// Update the state of the generator and return a vector holding `DIGEST_LENGTH`
20+
/// random bytes.
21+
fun next_digest(random: &mut Random): vector<u8> {
22+
random.state = hash::sha3_256(random.state);
23+
random.state
24+
}
25+
26+
/// Create a new pseudorandom generator with the given seed.
27+
public fun new(seed: vector<u8>): Random {
28+
Random { state: seed }
29+
}
30+
31+
/// Use the given pseudorandom generator to generate a vector with l random bytes.
32+
public fun next_bytes(random: &mut Random, l: u64): vector<u8> {
33+
// We need ceil(l / DIGEST_LENGTH) digests to fill the array
34+
let quotient = l / DIGEST_LENGTH;
35+
let remainder = l - quotient * DIGEST_LENGTH;
36+
37+
let (i, output) = (0, vector[]);
38+
while (i < quotient) {
39+
vector::append(&mut output, next_digest(random));
40+
i = i + 1;
41+
};
42+
43+
// If quotient is not exact, fill the remaining bytes
44+
if (remainder > 0) {
45+
let (i, digest) = (0, next_digest(random));
46+
while (i < remainder) {
47+
vector::push_back(&mut output, *vector::borrow(&mut digest, i));
48+
i = i + 1;
49+
};
50+
};
51+
52+
output
53+
}
54+
55+
/// Use the given pseudorandom generator to generate a random `u256` integer.
56+
public fun next_u256(random: &mut Random): u256 {
57+
let bytes = next_digest(random);
58+
let (value, i) = (0u256, 0u8);
59+
while (i < 32) {
60+
let byte = (vector::pop_back(&mut bytes) as u256);
61+
value = value + (byte << 8*i);
62+
i = i + 1;
63+
};
64+
value
65+
}
66+
67+
/// Use the given pseudo-random generator and a non-zero `upper_bound` to generate a
68+
/// random `u256` integer in the range [0, ..., upper_bound - 1]. Note that if the upper
69+
/// bound is not a power of two, the distribution will not be completely uniform.
70+
public fun next_u256_in_range(random: &mut Random, upper_bound: u256): u256 {
71+
assert!(upper_bound > 0, 0);
72+
next_u256(random) % upper_bound
73+
}
74+
75+
/// Use the given pseudorandom generator to generate a random `u128` integer.
76+
public fun next_u128(random: &mut Random): u128 {
77+
(next_u256_in_range(random, 1 << 128) as u128)
78+
}
79+
80+
/// Use the given pseudo-random generator and a non-zero `upper_bound` to generate a
81+
/// random `u128` integer in the range [0, ..., upper_bound - 1]. Note that if the upper
82+
/// bound is not a power of two, the distribution will not be completely uniform.
83+
public fun next_u128_in_range(random: &mut Random, upper_bound: u128): u128 {
84+
assert!(upper_bound > 0, 0);
85+
next_u128(random) % upper_bound
86+
}
87+
88+
/// Use the given pseudorandom generator to generate a random `u64` integer.
89+
public fun next_u64(random: &mut Random): u64 {
90+
(next_u256_in_range(random, 1 << 64) as u64)
91+
}
92+
93+
/// Use the given pseudo-random generator and a non-zero `upper_bound` to generate a
94+
/// random `u64` integer in the range [0, ..., upper_bound - 1]. Note that if the upper
95+
/// bound is not a power of two, the distribution will not be completely uniform.
96+
public fun next_u64_in_range(random: &mut Random, upper_bound: u64): u64 {
97+
assert!(upper_bound > 0, 0);
98+
next_u64(random) % upper_bound
99+
}
100+
101+
/// Use the given pseudorandom generator to generate a random `u32`.
102+
public fun next_u32(random: &mut Random): u32 {
103+
(next_u256_in_range(random, 1 << 32) as u32)
104+
}
105+
106+
/// Use the given pseudo-random generator and a non-zero `upper_bound` to generate a
107+
/// random `u32` integer in the range [0, ..., upper_bound - 1]. Note that if the upper
108+
/// bound is not a power of two, the distribution will not be completely uniform.
109+
public fun next_u32_in_range(random: &mut Random, upper_bound: u32): u32 {
110+
assert!(upper_bound > 0, 0);
111+
next_u32(random) % upper_bound
112+
}
113+
114+
/// Use the given pseudorandom generator to generate a random `u16`.
115+
public fun next_u16(random: &mut Random): u16 {
116+
(next_u256_in_range(random, 1 << 16) as u16)
117+
}
118+
119+
/// Use the given pseudo-random generator and a non-zero `upper_bound` to generate a
120+
/// random `u16` integer in the range [0, ..., upper_bound - 1]. Note that if the upper
121+
/// bound is not a power of two, the distribution will not be completely uniform.
122+
public fun next_u16_in_range(random: &mut Random, upper_bound: u16): u16 {
123+
assert!(upper_bound > 0, 0);
124+
next_u16(random) % upper_bound
125+
}
126+
127+
/// Use the given pseudorandom generator to generate a random `u8`.
128+
public fun next_u8(random: &mut Random): u8 {
129+
vector::pop_back(&mut next_digest(random))
130+
}
131+
132+
/// Use the given pseudo-random generator and a non-zero `upper_bound` to generate a
133+
/// random `u8` integer in the range [0, ..., upper_bound - 1]. Note that if the upper
134+
/// bound is not a power of two, the distribution will not be completely uniform.
135+
public fun next_u8_in_range(random: &mut Random, upper_bound: u8): u8 {
136+
assert!(upper_bound > 0, 0);
137+
next_u8(random) % upper_bound
138+
}
139+
140+
/// Use the given pseudorandom generator to generate a random `bool`.
141+
public fun next_bool(random: &mut Random): bool {
142+
next_u8(random) % 2 == 1
143+
}
144+
}

0 commit comments

Comments
 (0)