1414#include < bit>
1515#include < cassert>
1616#include < chrono>
17+ #include < concepts>
1718#include < cstdint>
1819#include < limits>
20+ #include < type_traits>
1921#include < vector>
2022
2123/* *
@@ -135,50 +137,68 @@ void RandAddPeriodic() noexcept;
135137 */
136138void RandAddEvent (const uint32_t event_info) noexcept ;
137139
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.
141156 *
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.
143163 */
144- class FastRandomContext
164+ template <typename T>
165+ class RandomMixin
145166{
146167private:
147- bool requires_seed;
148- ChaCha20 rng;
149-
150168 uint64_t bitbuf;
151- int bitbuf_size;
169+ int bitbuf_size{ 0 } ;
152170
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 ); }
154177
155178 void FillBitBuffer () noexcept
156179 {
157- bitbuf = rand64 ();
180+ bitbuf = Impl (). rand64 ();
158181 bitbuf_size = 64 ;
159182 }
160183
161184public:
162- explicit FastRandomContext ( bool fDeterministic = false ) noexcept ;
185+ RandomMixin () noexcept = default ;
163186
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 ;
166190
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+ }
174195
175- /* * Generate a random 64-bit integer. */
176- uint64_t rand64 () noexcept
196+ RandomMixin& operator =(RandomMixin&& other) noexcept
177197 {
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 ;
182202 }
183203
184204 /* * Generate a random (bits)-bit integer. */
@@ -187,7 +207,7 @@ class FastRandomContext
187207 if (bits == 0 ) {
188208 return 0 ;
189209 } else if (bits > 32 ) {
190- return rand64 () >> (64 - bits);
210+ return Impl (). rand64 () >> (64 - bits);
191211 } else {
192212 if (bitbuf_size < bits) FillBitBuffer ();
193213 uint64_t ret = bitbuf & (~uint64_t {0 } >> (64 - bits));
@@ -206,7 +226,7 @@ class FastRandomContext
206226 --range;
207227 int bits = std::bit_width (range);
208228 while (true ) {
209- uint64_t ret = randbits (bits);
229+ uint64_t ret = Impl (). randbits (bits);
210230 if (ret <= range) return ret;
211231 }
212232 }
@@ -216,49 +236,87 @@ class FastRandomContext
216236 std::vector<B> randbytes (size_t len) noexcept
217237 {
218238 std::vector<B> ret (len);
219- fillrand (MakeWritableByteSpan (ret));
239+ Impl (). fillrand (MakeWritableByteSpan (ret));
220240 return ret;
221241 }
222242
223- /* * Fill a byte Span with random bytes. */
224- void fillrand (Span<std::byte> output) noexcept ;
225-
226243 /* * Generate a random 32-bit integer. */
227- uint32_t rand32 () noexcept { return randbits (32 ); }
244+ uint32_t rand32 () noexcept { return Impl (). randbits (32 ); }
228245
229246 /* * generate a random uint256. */
230247 uint256 rand256 () noexcept
231248 {
232249 uint256 ret;
233- fillrand (MakeWritableByteSpan (ret));
250+ Impl (). fillrand (MakeWritableByteSpan (ret));
234251 return ret;
235252 }
236253
237254 /* * Generate a random boolean. */
238- bool randbool () noexcept { return randbits (1 ); }
255+ bool randbool () noexcept { return Impl (). randbits (1 ); }
239256
240257 /* * Return the time point advanced by a uniform random duration. */
241258 template <typename Tp>
242259 Tp rand_uniform_delay (const Tp& time, typename Tp::duration range) noexcept
243260 {
244- return time + rand_uniform_duration<Tp>(range);
261+ return time + Impl (). template rand_uniform_duration <Tp>(range);
245262 }
246263
247264 /* * Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */
248265 template <typename Chrono>
249266 typename Chrono::duration rand_uniform_duration (typename Chrono::duration range) noexcept
250267 {
251268 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 ())} :
254271 /* interval [0..0] */ Dur{0 };
255272 };
256273
257274 // Compatibility with the UniformRandomBitGenerator concept
258275 typedef uint64_t result_type;
259276 static constexpr uint64_t min () noexcept { return 0 ; }
260277 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 ;
262320};
263321
264322/* * More efficient than using std::shuffle on a FastRandomContext.
@@ -271,7 +329,7 @@ class FastRandomContext
271329 * debug mode detects and panics on. This is a known issue, see
272330 * https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle
273331 */
274- template <typename I, typename R>
332+ template <typename I, RandomNumberGenerator R>
275333void Shuffle (I first, I last, R&& rng)
276334{
277335 while (first != last) {
0 commit comments