Skip to content

Commit 6cfdc5b

Browse files
committed
random: convert XoRoShiRo128PlusPlus into full RNG
Convert XoRoShiRo128PlusPlus into a full RandomMixin-based RNG class, providing all utility functionality that FastRandomContext has. In doing so, it is renamed to InsecureRandomContext, highlighting its non-cryptographic nature. To do this, a fillrand fallback is added to RandomMixin (where it is used by InsecureRandomContext), but FastRandomContext still uses its own fillrand.
1 parent 8cc2f45 commit 6cfdc5b

File tree

9 files changed

+68
-102
lines changed

9 files changed

+68
-102
lines changed

src/random.h

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ template<typename T>
147147
concept RandomNumberGenerator = requires(T& rng, Span<std::byte> s) {
148148
// A random number generator must provide rand64().
149149
{ rng.rand64() } noexcept -> std::same_as<uint64_t>;
150-
// A random number generator must provide randfill(Span<std::byte>).
151-
{ rng.fillrand(s) } noexcept;
152150
// A random number generator must derive from RandomMixin, which adds other rand* functions.
153151
requires std::derived_from<std::remove_reference_t<T>, RandomMixin<std::remove_reference_t<T>>>;
154152
};
@@ -261,6 +259,25 @@ class RandomMixin
261259
}
262260
}
263261

262+
/** Fill a Span with random bytes. */
263+
void fillrand(Span<std::byte> span) noexcept
264+
{
265+
while (span.size() >= 8) {
266+
uint64_t gen = Impl().rand64();
267+
WriteLE64(UCharCast(span.data()), gen);
268+
span = span.subspan(8);
269+
}
270+
if (span.size() >= 4) {
271+
uint32_t gen = Impl().rand32();
272+
WriteLE32(UCharCast(span.data()), gen);
273+
span = span.subspan(4);
274+
}
275+
while (span.size()) {
276+
span[0] = std::byte(Impl().template randbits<8>());
277+
span = span.subspan(1);
278+
}
279+
}
280+
264281
/** Generate random bytes. */
265282
template <BasicByte B = unsigned char>
266283
std::vector<B> randbytes(size_t len) noexcept
@@ -345,19 +362,19 @@ class FastRandomContext : public RandomMixin<FastRandomContext>
345362
return ReadLE64(UCharCast(buf.data()));
346363
}
347364

348-
/** Fill a byte Span with random bytes. */
365+
/** Fill a byte Span with random bytes. This overrides the RandomMixin version. */
349366
void fillrand(Span<std::byte> output) noexcept;
350367
};
351368

352369
/** xoroshiro128++ PRNG. Extremely fast, not appropriate for cryptographic purposes.
353370
*
354-
* Memory footprint is 128bit, period is 2^128 - 1.
371+
* Memory footprint is very small, period is 2^128 - 1.
355372
* This class is not thread-safe.
356373
*
357374
* Reference implementation available at https://prng.di.unimi.it/xoroshiro128plusplus.c
358375
* See https://prng.di.unimi.it/
359376
*/
360-
class XoRoShiRo128PlusPlus
377+
class InsecureRandomContext : public RandomMixin<InsecureRandomContext>
361378
{
362379
uint64_t m_s0;
363380
uint64_t m_s1;
@@ -371,21 +388,19 @@ class XoRoShiRo128PlusPlus
371388
}
372389

373390
public:
374-
using result_type = uint64_t;
375-
376-
constexpr explicit XoRoShiRo128PlusPlus(uint64_t seedval) noexcept
391+
constexpr explicit InsecureRandomContext(uint64_t seedval) noexcept
377392
: m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {}
378393

379394
// no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams
380395
// with exactly the same results.
381-
XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete;
382-
XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete;
396+
InsecureRandomContext(const InsecureRandomContext&) = delete;
397+
InsecureRandomContext& operator=(const InsecureRandomContext&) = delete;
383398

384399
// allow moves
385-
XoRoShiRo128PlusPlus(XoRoShiRo128PlusPlus&&) = default;
386-
XoRoShiRo128PlusPlus& operator=(XoRoShiRo128PlusPlus&&) = default;
400+
InsecureRandomContext(InsecureRandomContext&&) = default;
401+
InsecureRandomContext& operator=(InsecureRandomContext&&) = default;
387402

388-
constexpr result_type operator()() noexcept
403+
constexpr uint64_t rand64() noexcept
389404
{
390405
uint64_t s0 = m_s0, s1 = m_s1;
391406
const uint64_t result = std::rotl(s0 + s1, 17) + s0;
@@ -394,10 +409,6 @@ class XoRoShiRo128PlusPlus
394409
m_s1 = std::rotl(s1, 28);
395410
return result;
396411
}
397-
398-
static constexpr result_type min() noexcept { return std::numeric_limits<result_type>::min(); }
399-
static constexpr result_type max() noexcept { return std::numeric_limits<result_type>::max(); }
400-
static constexpr double entropy() noexcept { return 0.0; }
401412
};
402413

403414
/** More efficient than using std::shuffle on a FastRandomContext.

src/test/fuzz/bip324.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize)
5656
// (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid
5757
// reading the actual data for those from the fuzzer input (which would need large amounts of
5858
// data).
59-
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>());
59+
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
6060

6161
// Compare session IDs and garbage terminators.
6262
assert(initiator.GetSessionID() == responder.GetSessionID());
@@ -79,10 +79,8 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize)
7979
unsigned length_bits = 2 * ((mode >> 5) & 7);
8080
unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1);
8181
// Generate aad and content.
82-
std::vector<std::byte> aad(aad_length);
83-
for (auto& val : aad) val = std::byte{(uint8_t)rng()};
84-
std::vector<std::byte> contents(length);
85-
for (auto& val : contents) val = std::byte{(uint8_t)rng()};
82+
auto aad = rng.randbytes<std::byte>(aad_length);
83+
auto contents = rng.randbytes<std::byte>(length);
8684

8785
// Pick sides.
8886
auto& sender{from_init ? initiator : responder};

src/test/fuzz/bitset.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ void TestType(FuzzBufferType buffer)
2929
* bitsets and their simulations do not matter for the purpose of detecting edge cases, thus
3030
* these are taken from a deterministically-seeded RNG instead. To provide some level of
3131
* variation however, pick the seed based on the buffer size and size of the chosen bitset. */
32-
XoRoShiRo128PlusPlus rng(buffer.size() + 0x10000 * S::Size());
32+
InsecureRandomContext rng(buffer.size() + 0x10000 * S::Size());
3333

3434
using Sim = std::bitset<S::Size()>;
3535
// Up to 4 real BitSets (initially 2).
@@ -124,17 +124,17 @@ void TestType(FuzzBufferType buffer)
124124
sim[dest].reset();
125125
real[dest] = S{};
126126
for (unsigned i = 0; i < S::Size(); ++i) {
127-
if (rng() & 1) {
127+
if (rng.randbool()) {
128128
sim[dest][i] = true;
129129
real[dest].Set(i);
130130
}
131131
}
132132
break;
133133
} else if (dest < sim.size() && command-- == 0) {
134134
/* Assign initializer list. */
135-
unsigned r1 = rng() % S::Size();
136-
unsigned r2 = rng() % S::Size();
137-
unsigned r3 = rng() % S::Size();
135+
unsigned r1 = rng.randrange(S::Size());
136+
unsigned r2 = rng.randrange(S::Size());
137+
unsigned r3 = rng.randrange(S::Size());
138138
compare_fn(dest);
139139
sim[dest].reset();
140140
real[dest] = {r1, r2, r3};
@@ -166,8 +166,8 @@ void TestType(FuzzBufferType buffer)
166166
break;
167167
} else if (sim.size() < 4 && command-- == 0) {
168168
/* Construct with initializer list. */
169-
unsigned r1 = rng() % S::Size();
170-
unsigned r2 = rng() % S::Size();
169+
unsigned r1 = rng.randrange(S::Size());
170+
unsigned r2 = rng.randrange(S::Size());
171171
sim.emplace_back();
172172
sim.back().set(r1);
173173
sim.back().set(r2);

src/test/fuzz/crypto_chacha20.cpp

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ namespace
5353
once for a large block at once, and then the same data in chunks, comparing
5454
the outcome.
5555
56-
If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt().
56+
If UseCrypt, seeded InsecureRandomContext output is used as input to Crypt().
5757
If not, Keystream() is used directly, or sequences of 0x00 are encrypted.
5858
*/
5959
template<bool UseCrypt>
@@ -78,25 +78,11 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
7878
data1.resize(total_bytes);
7979
data2.resize(total_bytes);
8080

81-
// If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based
81+
// If using Crypt(), initialize data1 and data2 with the same InsecureRandomContext based
8282
// stream.
8383
if constexpr (UseCrypt) {
84-
uint64_t seed = provider.ConsumeIntegral<uint64_t>();
85-
XoRoShiRo128PlusPlus rng(seed);
86-
uint64_t bytes = 0;
87-
while (bytes < (total_bytes & ~uint64_t{7})) {
88-
uint64_t val = rng();
89-
WriteLE64(UCharCast(data1.data() + bytes), val);
90-
WriteLE64(UCharCast(data2.data() + bytes), val);
91-
bytes += 8;
92-
}
93-
if (bytes < total_bytes) {
94-
std::byte valbytes[8];
95-
uint64_t val = rng();
96-
WriteLE64(UCharCast(valbytes), val);
97-
std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
98-
std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
99-
}
84+
InsecureRandomContext(provider.ConsumeIntegral<uint64_t>()).fillrand(data1);
85+
std::copy(data1.begin(), data1.end(), data2.begin());
10086
}
10187

10288
// Whether UseCrypt is used or not, the two byte arrays must match.

src/test/fuzz/p2p_transport_serialization.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serial
103103

104104
namespace {
105105

106-
template<typename R>
106+
template<RandomNumberGenerator R>
107107
void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDataProvider& provider)
108108
{
109109
// Simulation test with two Transport objects, which send messages to each other, with
@@ -164,8 +164,7 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
164164
// Determine size of message to send (limited to 75 kB for performance reasons).
165165
size_t size = provider.ConsumeIntegralInRange<uint32_t>(0, 75000);
166166
// Get payload of message from RNG.
167-
msg.data.resize(size);
168-
for (auto& v : msg.data) v = uint8_t(rng());
167+
msg.data = rng.randbytes(size);
169168
// Return.
170169
return msg;
171170
};
@@ -336,7 +335,7 @@ std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept
336335
return std::make_unique<V1Transport>(nodeid);
337336
}
338337

339-
template<typename RNG>
338+
template<RandomNumberGenerator RNG>
340339
std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider)
341340
{
342341
// Retrieve key
@@ -352,8 +351,7 @@ std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& r
352351
} else {
353352
// If it's longer, generate it from the RNG. This avoids having large amounts of
354353
// (hopefully) irrelevant data needing to be stored in the fuzzer data.
355-
garb.resize(garb_len);
356-
for (auto& v : garb) v = uint8_t(rng());
354+
garb = rng.randbytes(garb_len);
357355
}
358356
// Retrieve entropy
359357
auto ent = provider.ConsumeBytes<std::byte>(32);
@@ -377,7 +375,7 @@ FUZZ_TARGET(p2p_transport_bidirectional, .init = initialize_p2p_transport_serial
377375
{
378376
// Test with two V1 transports talking to each other.
379377
FuzzedDataProvider provider{buffer.data(), buffer.size()};
380-
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>());
378+
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
381379
auto t1 = MakeV1Transport(NodeId{0});
382380
auto t2 = MakeV1Transport(NodeId{1});
383381
if (!t1 || !t2) return;
@@ -388,7 +386,7 @@ FUZZ_TARGET(p2p_transport_bidirectional_v2, .init = initialize_p2p_transport_ser
388386
{
389387
// Test with two V2 transports talking to each other.
390388
FuzzedDataProvider provider{buffer.data(), buffer.size()};
391-
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>());
389+
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
392390
auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider);
393391
auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
394392
if (!t1 || !t2) return;
@@ -399,7 +397,7 @@ FUZZ_TARGET(p2p_transport_bidirectional_v1v2, .init = initialize_p2p_transport_s
399397
{
400398
// Test with a V1 initiator talking to a V2 responder.
401399
FuzzedDataProvider provider{buffer.data(), buffer.size()};
402-
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>());
400+
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
403401
auto t1 = MakeV1Transport(NodeId{0});
404402
auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
405403
if (!t1 || !t2) return;

src/test/fuzz/poolresource.cpp

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -71,41 +71,14 @@ class PoolResourceFuzzer
7171

7272
void RandomContentFill(Entry& entry)
7373
{
74-
XoRoShiRo128PlusPlus rng(entry.seed);
75-
auto ptr = entry.span.data();
76-
auto size = entry.span.size();
77-
78-
while (size >= 8) {
79-
auto r = rng();
80-
std::memcpy(ptr, &r, 8);
81-
size -= 8;
82-
ptr += 8;
83-
}
84-
if (size > 0) {
85-
auto r = rng();
86-
std::memcpy(ptr, &r, size);
87-
}
74+
InsecureRandomContext(entry.seed).fillrand(entry.span);
8875
}
8976

9077
void RandomContentCheck(const Entry& entry)
9178
{
92-
XoRoShiRo128PlusPlus rng(entry.seed);
93-
auto ptr = entry.span.data();
94-
auto size = entry.span.size();
95-
96-
std::byte buf[8];
97-
while (size >= 8) {
98-
auto r = rng();
99-
std::memcpy(buf, &r, 8);
100-
assert(std::memcmp(buf, ptr, 8) == 0);
101-
size -= 8;
102-
ptr += 8;
103-
}
104-
if (size > 0) {
105-
auto r = rng();
106-
std::memcpy(buf, &r, size);
107-
assert(std::memcmp(buf, ptr, size) == 0);
108-
}
79+
std::vector<std::byte> expect(entry.span.size());
80+
InsecureRandomContext(entry.seed).fillrand(expect);
81+
assert(entry.span == expect);
10982
}
11083

11184
void Deallocate(const Entry& entry)

src/test/fuzz/vecdeque.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
2828
{
2929
FuzzedDataProvider provider(buffer.data(), buffer.size());
3030
// Local RNG, only used for the seeds to initialize T objects with.
31-
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>() ^ rng_tweak);
31+
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>() ^ rng_tweak);
3232

3333
// Real circular buffers.
3434
std::vector<VecDeque<T>> real;
@@ -175,7 +175,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
175175
}
176176
if (existing_buffer_non_full && command-- == 0) {
177177
/* push_back() (copying) */
178-
tmp = T(rng());
178+
tmp = T(rng.rand64());
179179
size_t old_size = real[idx].size();
180180
size_t old_cap = real[idx].capacity();
181181
real[idx].push_back(*tmp);
@@ -191,7 +191,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
191191
}
192192
if (existing_buffer_non_full && command-- == 0) {
193193
/* push_back() (moving) */
194-
tmp = T(rng());
194+
tmp = T(rng.rand64());
195195
size_t old_size = real[idx].size();
196196
size_t old_cap = real[idx].capacity();
197197
sim[idx].push_back(*tmp);
@@ -207,7 +207,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
207207
}
208208
if (existing_buffer_non_full && command-- == 0) {
209209
/* emplace_back() */
210-
uint64_t seed{rng()};
210+
uint64_t seed{rng.rand64()};
211211
size_t old_size = real[idx].size();
212212
size_t old_cap = real[idx].capacity();
213213
sim[idx].emplace_back(seed);
@@ -223,7 +223,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
223223
}
224224
if (existing_buffer_non_full && command-- == 0) {
225225
/* push_front() (copying) */
226-
tmp = T(rng());
226+
tmp = T(rng.rand64());
227227
size_t old_size = real[idx].size();
228228
size_t old_cap = real[idx].capacity();
229229
real[idx].push_front(*tmp);
@@ -239,7 +239,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
239239
}
240240
if (existing_buffer_non_full && command-- == 0) {
241241
/* push_front() (moving) */
242-
tmp = T(rng());
242+
tmp = T(rng.rand64());
243243
size_t old_size = real[idx].size();
244244
size_t old_cap = real[idx].capacity();
245245
sim[idx].push_front(*tmp);
@@ -255,7 +255,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
255255
}
256256
if (existing_buffer_non_full && command-- == 0) {
257257
/* emplace_front() */
258-
uint64_t seed{rng()};
258+
uint64_t seed{rng.rand64()};
259259
size_t old_size = real[idx].size();
260260
size_t old_cap = real[idx].capacity();
261261
sim[idx].emplace_front(seed);
@@ -271,7 +271,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
271271
}
272272
if (existing_buffer_non_empty && command-- == 0) {
273273
/* front() [modifying] */
274-
tmp = T(rng());
274+
tmp = T(rng.rand64());
275275
size_t old_size = real[idx].size();
276276
assert(sim[idx].front() == real[idx].front());
277277
sim[idx].front() = *tmp;
@@ -281,7 +281,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
281281
}
282282
if (existing_buffer_non_empty && command-- == 0) {
283283
/* back() [modifying] */
284-
tmp = T(rng());
284+
tmp = T(rng.rand64());
285285
size_t old_size = real[idx].size();
286286
assert(sim[idx].back() == real[idx].back());
287287
sim[idx].back() = *tmp;
@@ -291,7 +291,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
291291
}
292292
if (existing_buffer_non_empty && command-- == 0) {
293293
/* operator[] [modifying] */
294-
tmp = T(rng());
294+
tmp = T(rng.rand64());
295295
size_t pos = provider.ConsumeIntegralInRange<size_t>(0, sim[idx].size() - 1);
296296
size_t old_size = real[idx].size();
297297
assert(sim[idx][pos] == real[idx][pos]);

0 commit comments

Comments
 (0)