14
14
#include < bit>
15
15
#include < cassert>
16
16
#include < chrono>
17
+ #include < concepts>
17
18
#include < cstdint>
18
19
#include < limits>
20
+ #include < type_traits>
19
21
#include < vector>
20
22
21
23
/* *
@@ -135,50 +137,68 @@ void RandAddPeriodic() noexcept;
135
137
*/
136
138
void RandAddEvent (const uint32_t event_info) noexcept ;
137
139
138
- /* *
139
- * Fast randomness source. This is seeded once with secure random data, but
140
- * is completely deterministic and does not gather more entropy after that.
140
+ // Forward declaration of RandomMixin, used in RandomNumberGenerator concept.
141
+ template <typename T>
142
+ class RandomMixin ;
143
+
144
+ /* * A concept for RandomMixin-based random number generators. */
145
+ template <typename T>
146
+ concept RandomNumberGenerator = requires (T& rng, Span<std::byte> s) {
147
+ // A random number generator must provide rand64().
148
+ { rng.rand64 () } noexcept -> std::same_as<uint64_t >;
149
+ // A random number generator must provide randfill(Span<std::byte>).
150
+ { rng.fillrand (s) } noexcept ;
151
+ // A random number generator must derive from RandomMixin, which adds other rand* functions.
152
+ requires std::derived_from<std::remove_reference_t <T>, RandomMixin<std::remove_reference_t <T>>>;
153
+ };
154
+
155
+ /* * Mixin class that provides helper randomness functions.
141
156
*
142
- * This class is not thread-safe.
157
+ * Intended to be used through CRTP: https://en.cppreference.com/w/cpp/language/crtp.
158
+ * An RNG class FunkyRNG would derive publicly from RandomMixin<FunkyRNG>. This permits
159
+ * RandomMixin from accessing the derived class's rand64() function, while also allowing
160
+ * the derived class to provide more.
161
+ *
162
+ * The derived class must satisfy the RandomNumberGenerator concept.
143
163
*/
144
- class FastRandomContext
164
+ template <typename T>
165
+ class RandomMixin
145
166
{
146
167
private:
147
- bool requires_seed;
148
- ChaCha20 rng;
149
-
150
168
uint64_t bitbuf;
151
- int bitbuf_size;
169
+ int bitbuf_size{ 0 } ;
152
170
153
- void RandomSeed () noexcept ;
171
+ /* * Access the underlying generator.
172
+ *
173
+ * This also enforces the RandomNumberGenerator concept. We cannot declare that in the template
174
+ * (no template<RandomNumberGenerator T>) because the type isn't fully instantiated yet there.
175
+ */
176
+ RandomNumberGenerator auto & Impl () noexcept { return static_cast <T&>(*this ); }
154
177
155
178
void FillBitBuffer () noexcept
156
179
{
157
- bitbuf = rand64 ();
180
+ bitbuf = Impl (). rand64 ();
158
181
bitbuf_size = 64 ;
159
182
}
160
183
161
184
public:
162
- explicit FastRandomContext ( bool fDeterministic = false ) noexcept ;
185
+ RandomMixin () noexcept = default ;
163
186
164
- /* * Initialize with explicit seed (only for testing) */
165
- explicit FastRandomContext (const uint256& seed) noexcept ;
187
+ // Do not permit copying an RNG.
188
+ RandomMixin (const RandomMixin&) = delete ;
189
+ RandomMixin& operator =(const RandomMixin&) = delete ;
166
190
167
- // Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded).
168
- FastRandomContext (const FastRandomContext&) = delete ;
169
- FastRandomContext (FastRandomContext&&) = delete ;
170
- FastRandomContext& operator =(const FastRandomContext&) = delete ;
171
-
172
- /* * Move a FastRandomContext. If the original one is used again, it will be reseeded. */
173
- FastRandomContext& operator =(FastRandomContext&& from) noexcept ;
191
+ RandomMixin (RandomMixin&& other) noexcept : bitbuf(other.bitbuf), bitbuf_size(other.bitbuf_size)
192
+ {
193
+ other.bitbuf_size = 0 ;
194
+ }
174
195
175
- /* * Generate a random 64-bit integer. */
176
- uint64_t rand64 () noexcept
196
+ RandomMixin& operator =(RandomMixin&& other) noexcept
177
197
{
178
- if (requires_seed) RandomSeed () ;
179
- std::array<std::byte, 8 > buf ;
180
- rng. Keystream (buf) ;
181
- return ReadLE64 ( UCharCast (buf. data ())) ;
198
+ bitbuf = other. bitbuf ;
199
+ bitbuf_size = other. bitbuf_size ;
200
+ other. bitbuf_size = 0 ;
201
+ return * this ;
182
202
}
183
203
184
204
/* * Generate a random (bits)-bit integer. */
@@ -187,7 +207,7 @@ class FastRandomContext
187
207
if (bits == 0 ) {
188
208
return 0 ;
189
209
} else if (bits > 32 ) {
190
- return rand64 () >> (64 - bits);
210
+ return Impl (). rand64 () >> (64 - bits);
191
211
} else {
192
212
if (bitbuf_size < bits) FillBitBuffer ();
193
213
uint64_t ret = bitbuf & (~uint64_t {0 } >> (64 - bits));
@@ -206,7 +226,7 @@ class FastRandomContext
206
226
--range;
207
227
int bits = std::bit_width (range);
208
228
while (true ) {
209
- uint64_t ret = randbits (bits);
229
+ uint64_t ret = Impl (). randbits (bits);
210
230
if (ret <= range) return ret;
211
231
}
212
232
}
@@ -216,49 +236,87 @@ class FastRandomContext
216
236
std::vector<B> randbytes (size_t len) noexcept
217
237
{
218
238
std::vector<B> ret (len);
219
- fillrand (MakeWritableByteSpan (ret));
239
+ Impl (). fillrand (MakeWritableByteSpan (ret));
220
240
return ret;
221
241
}
222
242
223
- /* * Fill a byte Span with random bytes. */
224
- void fillrand (Span<std::byte> output) noexcept ;
225
-
226
243
/* * Generate a random 32-bit integer. */
227
- uint32_t rand32 () noexcept { return randbits (32 ); }
244
+ uint32_t rand32 () noexcept { return Impl (). randbits (32 ); }
228
245
229
246
/* * generate a random uint256. */
230
247
uint256 rand256 () noexcept
231
248
{
232
249
uint256 ret;
233
- fillrand (MakeWritableByteSpan (ret));
250
+ Impl (). fillrand (MakeWritableByteSpan (ret));
234
251
return ret;
235
252
}
236
253
237
254
/* * Generate a random boolean. */
238
- bool randbool () noexcept { return randbits (1 ); }
255
+ bool randbool () noexcept { return Impl (). randbits (1 ); }
239
256
240
257
/* * Return the time point advanced by a uniform random duration. */
241
258
template <typename Tp>
242
259
Tp rand_uniform_delay (const Tp& time, typename Tp::duration range) noexcept
243
260
{
244
- return time + rand_uniform_duration<Tp>(range);
261
+ return time + Impl (). template rand_uniform_duration <Tp>(range);
245
262
}
246
263
247
264
/* * Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */
248
265
template <typename Chrono>
249
266
typename Chrono::duration rand_uniform_duration (typename Chrono::duration range) noexcept
250
267
{
251
268
using Dur = typename Chrono::duration;
252
- return range.count () > 0 ? /* interval [0..range) */ Dur{randrange (range.count ())} :
253
- range.count () < 0 ? /* interval (range..0] */ -Dur{randrange (-range.count ())} :
269
+ return range.count () > 0 ? /* interval [0..range) */ Dur{Impl (). randrange (range.count ())} :
270
+ range.count () < 0 ? /* interval (range..0] */ -Dur{Impl (). randrange (-range.count ())} :
254
271
/* interval [0..0] */ Dur{0 };
255
272
};
256
273
257
274
// Compatibility with the UniformRandomBitGenerator concept
258
275
typedef uint64_t result_type;
259
276
static constexpr uint64_t min () noexcept { return 0 ; }
260
277
static constexpr uint64_t max () noexcept { return std::numeric_limits<uint64_t >::max (); }
261
- inline uint64_t operator ()() noexcept { return rand64 (); }
278
+ inline uint64_t operator ()() noexcept { return Impl ().rand64 (); }
279
+ };
280
+
281
+ /* *
282
+ * Fast randomness source. This is seeded once with secure random data, but
283
+ * is completely deterministic and does not gather more entropy after that.
284
+ *
285
+ * This class is not thread-safe.
286
+ */
287
+ class FastRandomContext : public RandomMixin <FastRandomContext>
288
+ {
289
+ private:
290
+ bool requires_seed;
291
+ ChaCha20 rng;
292
+
293
+ void RandomSeed () noexcept ;
294
+
295
+ public:
296
+ explicit FastRandomContext (bool fDeterministic = false ) noexcept ;
297
+
298
+ /* * Initialize with explicit seed (only for testing) */
299
+ explicit FastRandomContext (const uint256& seed) noexcept ;
300
+
301
+ // Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded).
302
+ FastRandomContext (const FastRandomContext&) = delete ;
303
+ FastRandomContext (FastRandomContext&&) = delete ;
304
+ FastRandomContext& operator =(const FastRandomContext&) = delete ;
305
+
306
+ /* * Move a FastRandomContext. If the original one is used again, it will be reseeded. */
307
+ FastRandomContext& operator =(FastRandomContext&& from) noexcept ;
308
+
309
+ /* * Generate a random 64-bit integer. */
310
+ uint64_t rand64 () noexcept
311
+ {
312
+ if (requires_seed) RandomSeed ();
313
+ std::array<std::byte, 8 > buf;
314
+ rng.Keystream (buf);
315
+ return ReadLE64 (UCharCast (buf.data ()));
316
+ }
317
+
318
+ /* * Fill a byte Span with random bytes. */
319
+ void fillrand (Span<std::byte> output) noexcept ;
262
320
};
263
321
264
322
/* * More efficient than using std::shuffle on a FastRandomContext.
@@ -271,7 +329,7 @@ class FastRandomContext
271
329
* debug mode detects and panics on. This is a known issue, see
272
330
* https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle
273
331
*/
274
- template <typename I, typename R>
332
+ template <typename I, RandomNumberGenerator R>
275
333
void Shuffle (I first, I last, R&& rng)
276
334
{
277
335
while (first != last) {
0 commit comments