From 493a2e024e845e623e202e3eefe1cc2010e9b514 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jun 2024 07:41:42 -0400 Subject: [PATCH 01/23] random: write rand256() in function of fillrand() --- src/random.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 239d5bc6fe9..091f6310292 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -661,9 +661,8 @@ void FastRandomContext::RandomSeed() uint256 FastRandomContext::rand256() noexcept { - if (requires_seed) RandomSeed(); uint256 ret; - rng.Keystream(MakeWritableByteSpan(ret)); + fillrand(MakeWritableByteSpan(ret)); return ret; } From b3b382dde202ad508baf553817c5b38fdd2d4a0c Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jun 2024 07:46:47 -0400 Subject: [PATCH 02/23] random: move rand256() and randbytes() to .h file --- src/random.cpp | 17 ----------------- src/random.h | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 091f6310292..d6068e958fb 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -659,23 +659,6 @@ void FastRandomContext::RandomSeed() requires_seed = false; } -uint256 FastRandomContext::rand256() noexcept -{ - uint256 ret; - fillrand(MakeWritableByteSpan(ret)); - return ret; -} - -template -std::vector FastRandomContext::randbytes(size_t len) -{ - std::vector ret(len); - fillrand(MakeWritableByteSpan(ret)); - return ret; -} -template std::vector FastRandomContext::randbytes(size_t); -template std::vector FastRandomContext::randbytes(size_t); - void FastRandomContext::fillrand(Span output) { if (requires_seed) RandomSeed(); diff --git a/src/random.h b/src/random.h index f7c20ee4b03..ab9686c5b94 100644 --- a/src/random.h +++ b/src/random.h @@ -213,7 +213,12 @@ public: /** Generate random bytes. */ template - std::vector randbytes(size_t len); + std::vector randbytes(size_t len) + { + std::vector ret(len); + fillrand(MakeWritableByteSpan(ret)); + return ret; + } /** Fill a byte Span with random bytes. */ void fillrand(Span output); @@ -222,7 +227,12 @@ public: uint32_t rand32() noexcept { return randbits(32); } /** generate a random uint256. */ - uint256 rand256() noexcept; + uint256 rand256() noexcept + { + uint256 ret; + fillrand(MakeWritableByteSpan(ret)); + return ret; + } /** Generate a random boolean. */ bool randbool() noexcept { return randbits(1); } From 27cefc7fd6a6a159779f572f4c3a06170f955ed8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jun 2024 07:57:47 -0400 Subject: [PATCH 03/23] random: add a few noexcepts to FastRandomContext --- src/random.cpp | 4 ++-- src/random.h | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index d6068e958fb..89651f70712 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -652,14 +652,14 @@ uint256 GetRandHash() noexcept return hash; } -void FastRandomContext::RandomSeed() +void FastRandomContext::RandomSeed() noexcept { uint256 seed = GetRandHash(); rng.SetKey(MakeByteSpan(seed)); requires_seed = false; } -void FastRandomContext::fillrand(Span output) +void FastRandomContext::fillrand(Span output) noexcept { if (requires_seed) RandomSeed(); rng.Keystream(output); diff --git a/src/random.h b/src/random.h index ab9686c5b94..b4c42a42bba 100644 --- a/src/random.h +++ b/src/random.h @@ -150,9 +150,9 @@ private: uint64_t bitbuf; int bitbuf_size; - void RandomSeed(); + void RandomSeed() noexcept; - void FillBitBuffer() + void FillBitBuffer() noexcept { bitbuf = rand64(); bitbuf_size = 64; @@ -213,7 +213,7 @@ public: /** Generate random bytes. */ template - std::vector randbytes(size_t len) + std::vector randbytes(size_t len) noexcept { std::vector ret(len); fillrand(MakeWritableByteSpan(ret)); @@ -221,7 +221,7 @@ public: } /** Fill a byte Span with random bytes. */ - void fillrand(Span output); + void fillrand(Span output) noexcept; /** Generate a random 32-bit integer. */ uint32_t rand32() noexcept { return randbits(32); } @@ -239,7 +239,7 @@ public: /** Return the time point advanced by a uniform random duration. */ template - Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) + Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) noexcept { return time + rand_uniform_duration(range); } @@ -256,8 +256,8 @@ public: // Compatibility with the UniformRandomBitGenerator concept typedef uint64_t result_type; - static constexpr uint64_t min() { return 0; } - static constexpr uint64_t max() { return std::numeric_limits::max(); } + static constexpr uint64_t min() noexcept { return 0; } + static constexpr uint64_t max() noexcept { return std::numeric_limits::max(); } inline uint64_t operator()() noexcept { return rand64(); } }; From 40dd86fc3b60d7a67a9720a84a685f16e3f05b06 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jun 2024 08:05:27 -0400 Subject: [PATCH 04/23] random: use BasicByte concept in randbytes --- src/random.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/random.h b/src/random.h index b4c42a42bba..c885f183b60 100644 --- a/src/random.h +++ b/src/random.h @@ -212,7 +212,7 @@ public: } /** Generate random bytes. */ - template + template std::vector randbytes(size_t len) noexcept { std::vector ret(len); From 9b14d3d2da05f74ffb6a2ac20b7d9efefbe29634 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 08:58:56 -0400 Subject: [PATCH 05/23] random: refactor: move rand* utilities to RandomMixin Rather than make all the useful types of randomness be exclusive to FastRandomContext, move it to a separate RandomMixin class where it can be reused by other RNGs. A Curiously Recurring Template Pattern (CRTP) is used for this, to provide the ability for individual RNG classes to override one or more randomness functions, without needing the runtime-cost of virtual classes. Specifically, RNGs are expected to only provide fillrand and rand64, while all others are derived from those: - randbits - randrange - randbytes - rand32 - rand256 - randbool - rand_uniform_delay - rand_uniform_duration - min(), max(), operator()(), to comply with C++ URBG concept. --- src/random.cpp | 8 +- src/random.h | 232 ++++++++++++++++++++++++++++++------------------- 2 files changed, 148 insertions(+), 92 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 89651f70712..bb19d70d922 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -665,7 +665,7 @@ void FastRandomContext::fillrand(Span output) noexcept rng.Keystream(output); } -FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), rng(MakeByteSpan(seed)), bitbuf_size(0) {} +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), rng(MakeByteSpan(seed)) {} bool Random_SanityCheck() { @@ -715,7 +715,7 @@ bool Random_SanityCheck() static constexpr std::array ZERO_KEY{}; -FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), rng(ZERO_KEY), bitbuf_size(0) +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), rng(ZERO_KEY) { // Note that despite always initializing with ZERO_KEY, requires_seed is set to true if not // fDeterministic. That means the rng will be reinitialized with a secure random key upon first @@ -726,10 +726,8 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce { requires_seed = from.requires_seed; rng = from.rng; - bitbuf = from.bitbuf; - bitbuf_size = from.bitbuf_size; from.requires_seed = true; - from.bitbuf_size = 0; + static_cast&>(*this) = std::move(from); return *this; } diff --git a/src/random.h b/src/random.h index c885f183b60..9d16dce6782 100644 --- a/src/random.h +++ b/src/random.h @@ -14,8 +14,10 @@ #include #include #include +#include #include #include +#include #include /** @@ -135,29 +137,161 @@ void RandAddPeriodic() noexcept; */ void RandAddEvent(const uint32_t event_info) noexcept; +// Forward declaration of RandomMixin, used in RandomNumberGenerator concept. +template +class RandomMixin; + +/** A concept for RandomMixin-based random number generators. */ +template +concept RandomNumberGenerator = requires(T& rng, Span s) { + // A random number generator must provide rand64(). + { rng.rand64() } noexcept -> std::same_as; + // A random number generator must provide randfill(Span). + { rng.fillrand(s) } noexcept; + // A random number generator must derive from RandomMixin, which adds other rand* functions. + requires std::derived_from, RandomMixin>>; +}; + +/** Mixin class that provides helper randomness functions. + * + * Intended to be used through CRTP: https://en.cppreference.com/w/cpp/language/crtp. + * An RNG class FunkyRNG would derive publicly from RandomMixin. This permits + * RandomMixin from accessing the derived class's rand64() function, while also allowing + * the derived class to provide more. + * + * The derived class must satisfy the RandomNumberGenerator concept. + */ +template +class RandomMixin +{ +private: + uint64_t bitbuf; + int bitbuf_size{0}; + + /** Access the underlying generator. + * + * This also enforces the RandomNumberGenerator concept. We cannot declare that in the template + * (no template) because the type isn't fully instantiated yet there. + */ + RandomNumberGenerator auto& Impl() noexcept { return static_cast(*this); } + + void FillBitBuffer() noexcept + { + bitbuf = Impl().rand64(); + bitbuf_size = 64; + } + +public: + RandomMixin() noexcept = default; + + // Do not permit copying an RNG. + RandomMixin(const RandomMixin&) = delete; + RandomMixin& operator=(const RandomMixin&) = delete; + + RandomMixin(RandomMixin&& other) noexcept : bitbuf(other.bitbuf), bitbuf_size(other.bitbuf_size) + { + other.bitbuf_size = 0; + } + + RandomMixin& operator=(RandomMixin&& other) noexcept + { + bitbuf = other.bitbuf; + bitbuf_size = other.bitbuf_size; + other.bitbuf_size = 0; + return *this; + } + + /** Generate a random (bits)-bit integer. */ + uint64_t randbits(int bits) noexcept + { + if (bits == 0) { + return 0; + } else if (bits > 32) { + return Impl().rand64() >> (64 - bits); + } else { + if (bitbuf_size < bits) FillBitBuffer(); + uint64_t ret = bitbuf & (~uint64_t{0} >> (64 - bits)); + bitbuf >>= bits; + bitbuf_size -= bits; + return ret; + } + } + + /** Generate a random integer in the range [0..range). + * Precondition: range > 0. + */ + uint64_t randrange(uint64_t range) noexcept + { + assert(range); + --range; + int bits = std::bit_width(range); + while (true) { + uint64_t ret = Impl().randbits(bits); + if (ret <= range) return ret; + } + } + + /** Generate random bytes. */ + template + std::vector randbytes(size_t len) noexcept + { + std::vector ret(len); + Impl().fillrand(MakeWritableByteSpan(ret)); + return ret; + } + + /** Generate a random 32-bit integer. */ + uint32_t rand32() noexcept { return Impl().randbits(32); } + + /** generate a random uint256. */ + uint256 rand256() noexcept + { + uint256 ret; + Impl().fillrand(MakeWritableByteSpan(ret)); + return ret; + } + + /** Generate a random boolean. */ + bool randbool() noexcept { return Impl().randbits(1); } + + /** Return the time point advanced by a uniform random duration. */ + template + Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) noexcept + { + return time + Impl().template rand_uniform_duration(range); + } + + /** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */ + template + typename Chrono::duration rand_uniform_duration(typename Chrono::duration range) noexcept + { + using Dur = typename Chrono::duration; + return range.count() > 0 ? /* interval [0..range) */ Dur{Impl().randrange(range.count())} : + range.count() < 0 ? /* interval (range..0] */ -Dur{Impl().randrange(-range.count())} : + /* interval [0..0] */ Dur{0}; + }; + + // Compatibility with the UniformRandomBitGenerator concept + typedef uint64_t result_type; + static constexpr uint64_t min() noexcept { return 0; } + static constexpr uint64_t max() noexcept { return std::numeric_limits::max(); } + inline uint64_t operator()() noexcept { return Impl().rand64(); } +}; + /** * Fast randomness source. This is seeded once with secure random data, but * is completely deterministic and does not gather more entropy after that. * * This class is not thread-safe. */ -class FastRandomContext +class FastRandomContext : public RandomMixin { private: bool requires_seed; ChaCha20 rng; - uint64_t bitbuf; - int bitbuf_size; - void RandomSeed() noexcept; - void FillBitBuffer() noexcept - { - bitbuf = rand64(); - bitbuf_size = 64; - } - public: explicit FastRandomContext(bool fDeterministic = false) noexcept; @@ -181,84 +315,8 @@ public: return ReadLE64(UCharCast(buf.data())); } - /** Generate a random (bits)-bit integer. */ - uint64_t randbits(int bits) noexcept - { - if (bits == 0) { - return 0; - } else if (bits > 32) { - return rand64() >> (64 - bits); - } else { - if (bitbuf_size < bits) FillBitBuffer(); - uint64_t ret = bitbuf & (~uint64_t{0} >> (64 - bits)); - bitbuf >>= bits; - bitbuf_size -= bits; - return ret; - } - } - - /** Generate a random integer in the range [0..range). - * Precondition: range > 0. - */ - uint64_t randrange(uint64_t range) noexcept - { - assert(range); - --range; - int bits = std::bit_width(range); - while (true) { - uint64_t ret = randbits(bits); - if (ret <= range) return ret; - } - } - - /** Generate random bytes. */ - template - std::vector randbytes(size_t len) noexcept - { - std::vector ret(len); - fillrand(MakeWritableByteSpan(ret)); - return ret; - } - /** Fill a byte Span with random bytes. */ void fillrand(Span output) noexcept; - - /** Generate a random 32-bit integer. */ - uint32_t rand32() noexcept { return randbits(32); } - - /** generate a random uint256. */ - uint256 rand256() noexcept - { - uint256 ret; - fillrand(MakeWritableByteSpan(ret)); - return ret; - } - - /** Generate a random boolean. */ - bool randbool() noexcept { return randbits(1); } - - /** Return the time point advanced by a uniform random duration. */ - template - Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) noexcept - { - return time + rand_uniform_duration(range); - } - - /** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */ - template - typename Chrono::duration rand_uniform_duration(typename Chrono::duration range) noexcept - { - using Dur = typename Chrono::duration; - return range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} : - range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} : - /* interval [0..0] */ Dur{0}; - }; - - // Compatibility with the UniformRandomBitGenerator concept - typedef uint64_t result_type; - static constexpr uint64_t min() noexcept { return 0; } - static constexpr uint64_t max() noexcept { return std::numeric_limits::max(); } - inline uint64_t operator()() noexcept { return rand64(); } }; /** More efficient than using std::shuffle on a FastRandomContext. @@ -271,7 +329,7 @@ public: * debug mode detects and panics on. This is a known issue, see * https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle */ -template +template void Shuffle(I first, I last, R&& rng) { while (first != last) { From 21ce9d8658fed0d3e4552e8b02a6902cb31c572e Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 10:16:30 -0400 Subject: [PATCH 06/23] random: Improve RandomMixin::randbits The previous randbits code would, when requesting more randomness than available in its random bits buffer, discard the remaining entropy and generate new. Benchmarks show that it's usually better to first consume the existing randomness and only then generate new ones. This adds some complexity to randbits, but it doesn't weigh up against the reduced need to generate more randomness. --- src/random.h | 36 ++++++++++++--------- src/test/random_tests.cpp | 52 +++++++++++++++++++++++++++++-- test/sanitizer_suppressions/ubsan | 1 + 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/random.h b/src/random.h index 9d16dce6782..efcdae5142b 100644 --- a/src/random.h +++ b/src/random.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -165,7 +166,7 @@ template class RandomMixin { private: - uint64_t bitbuf; + uint64_t bitbuf{0}; int bitbuf_size{0}; /** Access the underlying generator. @@ -175,12 +176,6 @@ private: */ RandomNumberGenerator auto& Impl() noexcept { return static_cast(*this); } - void FillBitBuffer() noexcept - { - bitbuf = Impl().rand64(); - bitbuf_size = 64; - } - public: RandomMixin() noexcept = default; @@ -190,6 +185,7 @@ public: RandomMixin(RandomMixin&& other) noexcept : bitbuf(other.bitbuf), bitbuf_size(other.bitbuf_size) { + other.bitbuf = 0; other.bitbuf_size = 0; } @@ -197,6 +193,7 @@ public: { bitbuf = other.bitbuf; bitbuf_size = other.bitbuf_size; + other.bitbuf = 0; other.bitbuf_size = 0; return *this; } @@ -204,17 +201,26 @@ public: /** Generate a random (bits)-bit integer. */ uint64_t randbits(int bits) noexcept { - if (bits == 0) { - return 0; - } else if (bits > 32) { - return Impl().rand64() >> (64 - bits); - } else { - if (bitbuf_size < bits) FillBitBuffer(); - uint64_t ret = bitbuf & (~uint64_t{0} >> (64 - bits)); + Assume(bits <= 64); + // Requests for the full 64 bits are passed through. + if (bits == 64) return Impl().rand64(); + uint64_t ret; + if (bits <= bitbuf_size) { + // If there is enough entropy left in bitbuf, return its bottom bits bits. + ret = bitbuf; bitbuf >>= bits; bitbuf_size -= bits; - return ret; + } else { + // If not, return all of bitbuf, supplemented with the (bits - bitbuf_size) bottom + // bits of a newly generated 64-bit number on top. The remainder of that generated + // number becomes the new bitbuf. + uint64_t gen = Impl().rand64(); + ret = (gen << bitbuf_size) | bitbuf; + bitbuf = gen >> (bits - bitbuf_size); + bitbuf_size = 64 + bitbuf_size - bits; } + // Return the bottom bits bits of ret. + return ret & ((uint64_t{1} << bits) - 1); } /** Generate a random integer in the range [0..range). diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 43d887b5c95..53c29f31cab 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -39,9 +39,9 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) BOOST_CHECK_EQUAL(7, ctx.rand_uniform_delay(time_point, 9s).time_since_epoch().count()); BOOST_CHECK_EQUAL(-6, ctx.rand_uniform_delay(time_point, -9s).time_since_epoch().count()); BOOST_CHECK_EQUAL(1, ctx.rand_uniform_delay(time_point, 0s).time_since_epoch().count()); - BOOST_CHECK_EQUAL(1467825113502396065, ctx.rand_uniform_delay(time_point, 9223372036854775807s).time_since_epoch().count()); - BOOST_CHECK_EQUAL(-970181367944767837, ctx.rand_uniform_delay(time_point, -9223372036854775807s).time_since_epoch().count()); - BOOST_CHECK_EQUAL(24761, ctx.rand_uniform_delay(time_point, 9h).time_since_epoch().count()); + BOOST_CHECK_EQUAL(4652286523065884857, ctx.rand_uniform_delay(time_point, 9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(-8813961240025683129, ctx.rand_uniform_delay(time_point, -9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(26443, ctx.rand_uniform_delay(time_point, 9h).time_since_epoch().count()); } BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); @@ -103,6 +103,52 @@ BOOST_AUTO_TEST_CASE(fastrandom_randbits) } } +/** Verify that RandomMixin::randbits returns 0 and 1 for every requested bit. */ +BOOST_AUTO_TEST_CASE(randbits_test) +{ + FastRandomContext ctx_lens; //!< RNG for producing the lengths requested from ctx_test. + FastRandomContext ctx_test; //!< The RNG being tested. + int ctx_test_bitsleft{0}; //!< (Assumed value of) ctx_test::bitbuf_len + + // Run the entire test 5 times. + for (int i = 0; i < 5; ++i) { + // count (first) how often it has occurred, and (second) how often it was true: + // - for every bit position, in every requested bits count (0 + 1 + 2 + ... + 64 = 2080) + // - for every value of ctx_test_bitsleft (0..63 = 64) + std::vector> seen(2080 * 64); + while (true) { + // Loop 1000 times, just to not continuously check std::all_of. + for (int j = 0; j < 1000; ++j) { + // Decide on a number of bits to request (0 through 64, inclusive; don't use randbits/randrange). + int bits = ctx_lens.rand64() % 65; + // Generate that many bits. + uint64_t gen = ctx_test.randbits(bits); + // Make sure the result is in range. + if (bits < 64) BOOST_CHECK_EQUAL(gen >> bits, 0); + // Mark all the seen bits in the output. + for (int bit = 0; bit < bits; ++bit) { + int idx = bit + (bits * (bits - 1)) / 2 + 2080 * ctx_test_bitsleft; + seen[idx].first += 1; + seen[idx].second += (gen >> bit) & 1; + } + // Update ctx_test_bitself. + if (bits > ctx_test_bitsleft) { + ctx_test_bitsleft = ctx_test_bitsleft + 64 - bits; + } else { + ctx_test_bitsleft -= bits; + } + } + // Loop until every bit position/combination is seen 242 times. + if (std::all_of(seen.begin(), seen.end(), [](const auto& x) { return x.first >= 242; })) break; + } + // Check that each bit appears within 7.78 standard deviations of 50% + // (each will fail with P < 1/(2080 * 64 * 10^9)). + for (const auto& val : seen) { + assert(fabs(val.first * 0.5 - val.second) < sqrt(val.first * 0.25) * 7.78); + } + } +} + /** Does-it-compile test for compatibility with standard library RNG interface. */ BOOST_AUTO_TEST_CASE(stdrandom_test) { diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 9818d73fdf1..be9c7fb300a 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -76,3 +76,4 @@ shift-base:crypto/ shift-base:streams.h shift-base:FormatHDKeypath shift-base:xoroshiro128plusplus.h +shift-base:RandomMixin<*>::randbits From ddb7d26cfd96c1f626def4755e0e1b5aaac94d3e Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 12:38:14 -0400 Subject: [PATCH 07/23] random: add RandomMixin::randbits with compile-known bits In many cases, it is known at compile time how many bits are requested from randbits. Provide a variant of randbits that accepts this number as a template, to make sure the compiler can make use of this knowledge. This is used immediately in rand32() and randbool(), and a few further call sites. --- src/addrman.cpp | 2 +- src/random.cpp | 2 +- src/random.h | 28 ++++++++++++++++++++++++++-- src/test/crypto_tests.cpp | 6 +++--- src/test/random_tests.cpp | 22 ++++++++++++++++++++-- test/sanitizer_suppressions/ubsan | 1 + 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/addrman.cpp b/src/addrman.cpp index d0b820ee651..054a9bee32d 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -776,7 +776,7 @@ std::pair AddrManImpl::Select_(bool new_only, std::option const AddrInfo& info{it_found->second}; // With probability GetChance() * chance_factor, return the entry. - if (insecure_rand.randbits(30) < chance_factor * info.GetChance() * (1 << 30)) { + if (insecure_rand.randbits<30>() < chance_factor * info.GetChance() * (1 << 30)) { LogPrint(BCLog::ADDRMAN, "Selected %s from %s\n", info.ToStringAddrPort(), search_tried ? "tried" : "new"); return {info, info.m_last_try}; } diff --git a/src/random.cpp b/src/random.cpp index bb19d70d922..10ad4e2558a 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -741,6 +741,6 @@ void RandomInit() std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval) { - double unscaled = -std::log1p(GetRand(uint64_t{1} << 48) * -0.0000000000000035527136788 /* -1/2^48 */); + double unscaled = -std::log1p(FastRandomContext().randbits<48>() * -0.0000000000000035527136788 /* -1/2^48 */); return now + std::chrono::duration_cast(unscaled * average_interval + 0.5us); } diff --git a/src/random.h b/src/random.h index efcdae5142b..007853bc364 100644 --- a/src/random.h +++ b/src/random.h @@ -223,6 +223,30 @@ public: return ret & ((uint64_t{1} << bits) - 1); } + /** Same as above, but with compile-time fixed bits count. */ + template + uint64_t randbits() noexcept + { + static_assert(Bits >= 0 && Bits <= 64); + if constexpr (Bits == 64) { + return Impl().rand64(); + } else { + uint64_t ret; + if (Bits <= bitbuf_size) { + ret = bitbuf; + bitbuf >>= Bits; + bitbuf_size -= Bits; + } else { + uint64_t gen = Impl().rand64(); + ret = (gen << bitbuf_size) | bitbuf; + bitbuf = gen >> (Bits - bitbuf_size); + bitbuf_size = 64 + bitbuf_size - Bits; + } + constexpr uint64_t MASK = (uint64_t{1} << Bits) - 1; + return ret & MASK; + } + } + /** Generate a random integer in the range [0..range). * Precondition: range > 0. */ @@ -247,7 +271,7 @@ public: } /** Generate a random 32-bit integer. */ - uint32_t rand32() noexcept { return Impl().randbits(32); } + uint32_t rand32() noexcept { return Impl().template randbits<32>(); } /** generate a random uint256. */ uint256 rand256() noexcept @@ -258,7 +282,7 @@ public: } /** Generate a random boolean. */ - bool randbool() noexcept { return Impl().randbits(1); } + bool randbool() noexcept { return Impl().template randbits<1>(); } /** Return the time point advanced by a uniform random duration. */ template diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 46acc6fc9f5..d78957e35a4 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -1195,7 +1195,7 @@ BOOST_AUTO_TEST_CASE(muhash_tests) uint256 res; int table[4]; for (int i = 0; i < 4; ++i) { - table[i] = g_insecure_rand_ctx.randbits(3); + table[i] = g_insecure_rand_ctx.randbits<3>(); } for (int order = 0; order < 4; ++order) { MuHash3072 acc; @@ -1215,8 +1215,8 @@ BOOST_AUTO_TEST_CASE(muhash_tests) } } - MuHash3072 x = FromInt(g_insecure_rand_ctx.randbits(4)); // x=X - MuHash3072 y = FromInt(g_insecure_rand_ctx.randbits(4)); // x=X, y=Y + MuHash3072 x = FromInt(g_insecure_rand_ctx.randbits<4>()); // x=X + MuHash3072 y = FromInt(g_insecure_rand_ctx.randbits<4>()); // x=X, y=Y MuHash3072 z; // x=X, y=Y, z=1 z *= x; // x=X, y=Y, z=X z *= y; // x=X, y=Y, z=X*Y diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 53c29f31cab..2617cc4a2a5 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE(fastrandom_randbits) BOOST_AUTO_TEST_CASE(randbits_test) { FastRandomContext ctx_lens; //!< RNG for producing the lengths requested from ctx_test. - FastRandomContext ctx_test; //!< The RNG being tested. + FastRandomContext ctx_test1(true), ctx_test2(true); //!< The RNGs being tested. int ctx_test_bitsleft{0}; //!< (Assumed value of) ctx_test::bitbuf_len // Run the entire test 5 times. @@ -122,7 +122,25 @@ BOOST_AUTO_TEST_CASE(randbits_test) // Decide on a number of bits to request (0 through 64, inclusive; don't use randbits/randrange). int bits = ctx_lens.rand64() % 65; // Generate that many bits. - uint64_t gen = ctx_test.randbits(bits); + uint64_t gen = ctx_test1.randbits(bits); + // For certain bits counts, also test randbits and compare. + uint64_t gen2; + if (bits == 0) { + gen2 = ctx_test2.randbits<0>(); + } else if (bits == 1) { + gen2 = ctx_test2.randbits<1>(); + } else if (bits == 7) { + gen2 = ctx_test2.randbits<7>(); + } else if (bits == 32) { + gen2 = ctx_test2.randbits<32>(); + } else if (bits == 51) { + gen2 = ctx_test2.randbits<51>(); + } else if (bits == 64) { + gen2 = ctx_test2.randbits<64>(); + } else { + gen2 = ctx_test2.randbits(bits); + } + BOOST_CHECK_EQUAL(gen, gen2); // Make sure the result is in range. if (bits < 64) BOOST_CHECK_EQUAL(gen >> bits, 0); // Mark all the seen bits in the output. diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index be9c7fb300a..d949aabf846 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -77,3 +77,4 @@ shift-base:streams.h shift-base:FormatHDKeypath shift-base:xoroshiro128plusplus.h shift-base:RandomMixin<*>::randbits +shift-base:RandomMixin<*>::randbits<*> From 8924f5120f66269c04633167def01f82c74ea730 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 14:29:28 -0400 Subject: [PATCH 08/23] random: modernize XoRoShiRo128PlusPlus a bit Make use of C++20 functions in XoRoShiRo128PlusPlus. --- src/test/util/xoroshiro128plusplus.h | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/test/util/xoroshiro128plusplus.h b/src/test/util/xoroshiro128plusplus.h index ac9f59b3f5a..1f579d8280c 100644 --- a/src/test/util/xoroshiro128plusplus.h +++ b/src/test/util/xoroshiro128plusplus.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H #define BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H +#include #include #include @@ -21,26 +22,19 @@ class XoRoShiRo128PlusPlus uint64_t m_s0; uint64_t m_s1; - [[nodiscard]] constexpr static uint64_t rotl(uint64_t x, int n) - { - return (x << n) | (x >> (64 - n)); - } - [[nodiscard]] constexpr static uint64_t SplitMix64(uint64_t& seedval) noexcept { - uint64_t z = (seedval += UINT64_C(0x9e3779b97f4a7c15)); - z = (z ^ (z >> 30U)) * UINT64_C(0xbf58476d1ce4e5b9); - z = (z ^ (z >> 27U)) * UINT64_C(0x94d049bb133111eb); - return z ^ (z >> 31U); + uint64_t z = (seedval += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); } public: using result_type = uint64_t; constexpr explicit XoRoShiRo128PlusPlus(uint64_t seedval) noexcept - : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) - { - } + : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {} // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams // with exactly the same results. If you need a copy, call copy(). @@ -51,15 +45,13 @@ public: XoRoShiRo128PlusPlus(XoRoShiRo128PlusPlus&&) = default; XoRoShiRo128PlusPlus& operator=(XoRoShiRo128PlusPlus&&) = default; - ~XoRoShiRo128PlusPlus() = default; - constexpr result_type operator()() noexcept { uint64_t s0 = m_s0, s1 = m_s1; - const uint64_t result = rotl(s0 + s1, 17) + s0; + const uint64_t result = std::rotl(s0 + s1, 17) + s0; s1 ^= s0; - m_s0 = rotl(s0, 49) ^ s1 ^ (s1 << 21); - m_s1 = rotl(s1, 28); + m_s0 = std::rotl(s0, 49) ^ s1 ^ (s1 << 21); + m_s1 = std::rotl(s1, 28); return result; } From 8f5ac0d0b608bdf396d8f2d758a792f869c2cd2a Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jun 2024 08:42:53 -0400 Subject: [PATCH 09/23] xoroshiro128plusplus: drop comment about nonexisting copy() --- src/test/util/xoroshiro128plusplus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/util/xoroshiro128plusplus.h b/src/test/util/xoroshiro128plusplus.h index 1f579d8280c..93ab473b37d 100644 --- a/src/test/util/xoroshiro128plusplus.h +++ b/src/test/util/xoroshiro128plusplus.h @@ -37,7 +37,7 @@ public: : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {} // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams - // with exactly the same results. If you need a copy, call copy(). + // with exactly the same results. XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete; XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete; From 8cc2f45065fc1864f879248d1e1444588e27076b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 14:50:20 -0400 Subject: [PATCH 10/23] random: move XoRoShiRo128PlusPlus into random module This is preparation for making it more generally accessible. --- src/Makefile.test.include | 3 +- src/Makefile.test_util.include | 3 +- src/random.h | 51 +++++++++++++++ src/test/fuzz/bip324.cpp | 2 +- src/test/fuzz/bitset.cpp | 2 +- src/test/fuzz/crypto_chacha20.cpp | 2 +- src/test/fuzz/p2p_transport_serialization.cpp | 1 - src/test/fuzz/poolresource.cpp | 2 +- src/test/fuzz/vecdeque.cpp | 2 +- src/test/random_tests.cpp | 17 +++++ src/test/util/xoroshiro128plusplus.h | 63 ------------------- src/test/xoroshiro128plusplus_tests.cpp | 29 --------- test/sanitizer_suppressions/ubsan | 5 +- 13 files changed, 78 insertions(+), 104 deletions(-) delete mode 100644 src/test/util/xoroshiro128plusplus.h delete mode 100644 src/test/xoroshiro128plusplus_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 633d0776f56..8651eea9424 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -173,8 +173,7 @@ BITCOIN_TESTS =\ test/validation_flush_tests.cpp \ test/validation_tests.cpp \ test/validationinterface_tests.cpp \ - test/versionbits_tests.cpp \ - test/xoroshiro128plusplus_tests.cpp + test/versionbits_tests.cpp if ENABLE_WALLET BITCOIN_TESTS += \ diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 6a1fd712bd1..960eb078c8a 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -23,8 +23,7 @@ TEST_UTIL_H = \ test/util/str.h \ test/util/transaction_utils.h \ test/util/txmempool.h \ - test/util/validation.h \ - test/util/xoroshiro128plusplus.h + test/util/validation.h if ENABLE_WALLET TEST_UTIL_H += wallet/test/util.h diff --git a/src/random.h b/src/random.h index 007853bc364..e8708671704 100644 --- a/src/random.h +++ b/src/random.h @@ -349,6 +349,57 @@ public: void fillrand(Span output) noexcept; }; +/** xoroshiro128++ PRNG. Extremely fast, not appropriate for cryptographic purposes. + * + * Memory footprint is 128bit, period is 2^128 - 1. + * This class is not thread-safe. + * + * Reference implementation available at https://prng.di.unimi.it/xoroshiro128plusplus.c + * See https://prng.di.unimi.it/ + */ +class XoRoShiRo128PlusPlus +{ + uint64_t m_s0; + uint64_t m_s1; + + [[nodiscard]] constexpr static uint64_t SplitMix64(uint64_t& seedval) noexcept + { + uint64_t z = (seedval += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + +public: + using result_type = uint64_t; + + constexpr explicit XoRoShiRo128PlusPlus(uint64_t seedval) noexcept + : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {} + + // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams + // with exactly the same results. + XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete; + XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete; + + // allow moves + XoRoShiRo128PlusPlus(XoRoShiRo128PlusPlus&&) = default; + XoRoShiRo128PlusPlus& operator=(XoRoShiRo128PlusPlus&&) = default; + + constexpr result_type operator()() noexcept + { + uint64_t s0 = m_s0, s1 = m_s1; + const uint64_t result = std::rotl(s0 + s1, 17) + s0; + s1 ^= s0; + m_s0 = std::rotl(s0, 49) ^ s1 ^ (s1 << 21); + m_s1 = std::rotl(s1, 28); + return result; + } + + static constexpr result_type min() noexcept { return std::numeric_limits::min(); } + static constexpr result_type max() noexcept { return std::numeric_limits::max(); } + static constexpr double entropy() noexcept { return 0.0; } +}; + /** More efficient than using std::shuffle on a FastRandomContext. * * This is more efficient as std::shuffle will consume entropy in groups of diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp index 8210e75cee2..06199168198 100644 --- a/src/test/fuzz/bip324.cpp +++ b/src/test/fuzz/bip324.cpp @@ -4,11 +4,11 @@ #include #include +#include #include #include #include #include -#include #include #include diff --git a/src/test/fuzz/bitset.cpp b/src/test/fuzz/bitset.cpp index 76843377292..cc3ed30f625 100644 --- a/src/test/fuzz/bitset.cpp +++ b/src/test/fuzz/bitset.cpp @@ -2,9 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include -#include #include #include diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index 50c77bf699b..ff3d463ca51 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -3,10 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include -#include #include #include diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index 767238d103f..5a26fb86ce1 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/src/test/fuzz/poolresource.cpp b/src/test/fuzz/poolresource.cpp index f764d9f8dbe..a3e48994c97 100644 --- a/src/test/fuzz/poolresource.cpp +++ b/src/test/fuzz/poolresource.cpp @@ -2,13 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include #include #include #include #include -#include #include #include diff --git a/src/test/fuzz/vecdeque.cpp b/src/test/fuzz/vecdeque.cpp index 1d9a98931fb..d94e6d895a0 100644 --- a/src/test/fuzz/vecdeque.cpp +++ b/src/test/fuzz/vecdeque.cpp @@ -2,9 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include -#include #include #include diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 2617cc4a2a5..09425c7cd7e 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -219,4 +219,21 @@ BOOST_AUTO_TEST_CASE(shuffle_stat_test) BOOST_CHECK_EQUAL(sum, 12000U); } +BOOST_AUTO_TEST_CASE(xoroshiro128plusplus_reference_values) +{ + // numbers generated from reference implementation + XoRoShiRo128PlusPlus rng(0); + BOOST_TEST(0x6f68e1e7e2646ee1 == rng()); + BOOST_TEST(0xbf971b7f454094ad == rng()); + BOOST_TEST(0x48f2de556f30de38 == rng()); + BOOST_TEST(0x6ea7c59f89bbfc75 == rng()); + + // seed with a random number + rng = XoRoShiRo128PlusPlus(0x1a26f3fa8546b47a); + BOOST_TEST(0xc8dc5e08d844ac7d == rng()); + BOOST_TEST(0x5b5f1f6d499dad1b == rng()); + BOOST_TEST(0xbeb0031f93313d6f == rng()); + BOOST_TEST(0xbfbcf4f43a264497 == rng()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/xoroshiro128plusplus.h b/src/test/util/xoroshiro128plusplus.h deleted file mode 100644 index 93ab473b37d..00000000000 --- a/src/test/util/xoroshiro128plusplus.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2022 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H -#define BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H - -#include -#include -#include - -/** xoroshiro128++ PRNG. Extremely fast, not appropriate for cryptographic purposes. - * - * Memory footprint is 128bit, period is 2^128 - 1. - * This class is not thread-safe. - * - * Reference implementation available at https://prng.di.unimi.it/xoroshiro128plusplus.c - * See https://prng.di.unimi.it/ - */ -class XoRoShiRo128PlusPlus -{ - uint64_t m_s0; - uint64_t m_s1; - - [[nodiscard]] constexpr static uint64_t SplitMix64(uint64_t& seedval) noexcept - { - uint64_t z = (seedval += 0x9e3779b97f4a7c15); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); - } - -public: - using result_type = uint64_t; - - constexpr explicit XoRoShiRo128PlusPlus(uint64_t seedval) noexcept - : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {} - - // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams - // with exactly the same results. - XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete; - XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete; - - // allow moves - XoRoShiRo128PlusPlus(XoRoShiRo128PlusPlus&&) = default; - XoRoShiRo128PlusPlus& operator=(XoRoShiRo128PlusPlus&&) = default; - - constexpr result_type operator()() noexcept - { - uint64_t s0 = m_s0, s1 = m_s1; - const uint64_t result = std::rotl(s0 + s1, 17) + s0; - s1 ^= s0; - m_s0 = std::rotl(s0, 49) ^ s1 ^ (s1 << 21); - m_s1 = std::rotl(s1, 28); - return result; - } - - static constexpr result_type min() noexcept { return std::numeric_limits::min(); } - static constexpr result_type max() noexcept { return std::numeric_limits::max(); } - static constexpr double entropy() noexcept { return 0.0; } -}; - -#endif // BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H diff --git a/src/test/xoroshiro128plusplus_tests.cpp b/src/test/xoroshiro128plusplus_tests.cpp deleted file mode 100644 index ea1b3e355f6..00000000000 --- a/src/test/xoroshiro128plusplus_tests.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2022 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include - -#include - -BOOST_FIXTURE_TEST_SUITE(xoroshiro128plusplus_tests, BasicTestingSetup) - -BOOST_AUTO_TEST_CASE(reference_values) -{ - // numbers generated from reference implementation - XoRoShiRo128PlusPlus rng(0); - BOOST_TEST(0x6f68e1e7e2646ee1 == rng()); - BOOST_TEST(0xbf971b7f454094ad == rng()); - BOOST_TEST(0x48f2de556f30de38 == rng()); - BOOST_TEST(0x6ea7c59f89bbfc75 == rng()); - - // seed with a random number - rng = XoRoShiRo128PlusPlus(0x1a26f3fa8546b47a); - BOOST_TEST(0xc8dc5e08d844ac7d == rng()); - BOOST_TEST(0x5b5f1f6d499dad1b == rng()); - BOOST_TEST(0xbeb0031f93313d6f == rng()); - BOOST_TEST(0xbfbcf4f43a264497 == rng()); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index d949aabf846..7d8b0fdfd03 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -57,7 +57,8 @@ unsigned-integer-overflow:CBlockPolicyEstimator::processBlockTx unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal unsigned-integer-overflow:prevector.h unsigned-integer-overflow:EvalScript -unsigned-integer-overflow:xoroshiro128plusplus.h +unsigned-integer-overflow:XoRoShiRo128PlusPlus::operator() +unsigned-integer-overflow:XoRoShiRo128PlusPlus::SplitMix64 unsigned-integer-overflow:bitset_detail::PopCount implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx implicit-integer-sign-change:SetStdinEcho @@ -75,6 +76,6 @@ shift-base:arith_uint256.cpp shift-base:crypto/ shift-base:streams.h shift-base:FormatHDKeypath -shift-base:xoroshiro128plusplus.h +shift-base:XoRoShiRo128PlusPlus::operator() shift-base:RandomMixin<*>::randbits shift-base:RandomMixin<*>::randbits<*> From 6cfdc5b104caf9952393f9dac2a36539d964077f Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 15:16:20 -0400 Subject: [PATCH 11/23] 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. --- src/random.h | 45 ++++++++++++------- src/test/fuzz/bip324.cpp | 8 ++-- src/test/fuzz/bitset.cpp | 14 +++--- src/test/fuzz/crypto_chacha20.cpp | 22 ++------- src/test/fuzz/p2p_transport_serialization.cpp | 16 +++---- src/test/fuzz/poolresource.cpp | 35 ++------------- src/test/fuzz/vecdeque.cpp | 20 ++++----- src/test/random_tests.cpp | 4 +- test/sanitizer_suppressions/ubsan | 6 +-- 9 files changed, 68 insertions(+), 102 deletions(-) diff --git a/src/random.h b/src/random.h index e8708671704..cdf2f3a66d2 100644 --- a/src/random.h +++ b/src/random.h @@ -147,8 +147,6 @@ template concept RandomNumberGenerator = requires(T& rng, Span s) { // A random number generator must provide rand64(). { rng.rand64() } noexcept -> std::same_as; - // A random number generator must provide randfill(Span). - { rng.fillrand(s) } noexcept; // A random number generator must derive from RandomMixin, which adds other rand* functions. requires std::derived_from, RandomMixin>>; }; @@ -261,6 +259,25 @@ public: } } + /** Fill a Span with random bytes. */ + void fillrand(Span span) noexcept + { + while (span.size() >= 8) { + uint64_t gen = Impl().rand64(); + WriteLE64(UCharCast(span.data()), gen); + span = span.subspan(8); + } + if (span.size() >= 4) { + uint32_t gen = Impl().rand32(); + WriteLE32(UCharCast(span.data()), gen); + span = span.subspan(4); + } + while (span.size()) { + span[0] = std::byte(Impl().template randbits<8>()); + span = span.subspan(1); + } + } + /** Generate random bytes. */ template std::vector randbytes(size_t len) noexcept @@ -345,19 +362,19 @@ public: return ReadLE64(UCharCast(buf.data())); } - /** Fill a byte Span with random bytes. */ + /** Fill a byte Span with random bytes. This overrides the RandomMixin version. */ void fillrand(Span output) noexcept; }; /** xoroshiro128++ PRNG. Extremely fast, not appropriate for cryptographic purposes. * - * Memory footprint is 128bit, period is 2^128 - 1. + * Memory footprint is very small, period is 2^128 - 1. * This class is not thread-safe. * * Reference implementation available at https://prng.di.unimi.it/xoroshiro128plusplus.c * See https://prng.di.unimi.it/ */ -class XoRoShiRo128PlusPlus +class InsecureRandomContext : public RandomMixin { uint64_t m_s0; uint64_t m_s1; @@ -371,21 +388,19 @@ class XoRoShiRo128PlusPlus } public: - using result_type = uint64_t; - - constexpr explicit XoRoShiRo128PlusPlus(uint64_t seedval) noexcept + constexpr explicit InsecureRandomContext(uint64_t seedval) noexcept : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {} // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams // with exactly the same results. - XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete; - XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete; + InsecureRandomContext(const InsecureRandomContext&) = delete; + InsecureRandomContext& operator=(const InsecureRandomContext&) = delete; // allow moves - XoRoShiRo128PlusPlus(XoRoShiRo128PlusPlus&&) = default; - XoRoShiRo128PlusPlus& operator=(XoRoShiRo128PlusPlus&&) = default; + InsecureRandomContext(InsecureRandomContext&&) = default; + InsecureRandomContext& operator=(InsecureRandomContext&&) = default; - constexpr result_type operator()() noexcept + constexpr uint64_t rand64() noexcept { uint64_t s0 = m_s0, s1 = m_s1; const uint64_t result = std::rotl(s0 + s1, 17) + s0; @@ -394,10 +409,6 @@ public: m_s1 = std::rotl(s1, 28); return result; } - - static constexpr result_type min() noexcept { return std::numeric_limits::min(); } - static constexpr result_type max() noexcept { return std::numeric_limits::max(); } - static constexpr double entropy() noexcept { return 0.0; } }; /** More efficient than using std::shuffle on a FastRandomContext. diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp index 06199168198..9892e7a81ce 100644 --- a/src/test/fuzz/bip324.cpp +++ b/src/test/fuzz/bip324.cpp @@ -56,7 +56,7 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) // (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid // reading the actual data for those from the fuzzer input (which would need large amounts of // data). - XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral()); + InsecureRandomContext rng(provider.ConsumeIntegral()); // Compare session IDs and garbage terminators. assert(initiator.GetSessionID() == responder.GetSessionID()); @@ -79,10 +79,8 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) unsigned length_bits = 2 * ((mode >> 5) & 7); unsigned length = provider.ConsumeIntegralInRange(0, (1 << length_bits) - 1); // Generate aad and content. - std::vector aad(aad_length); - for (auto& val : aad) val = std::byte{(uint8_t)rng()}; - std::vector contents(length); - for (auto& val : contents) val = std::byte{(uint8_t)rng()}; + auto aad = rng.randbytes(aad_length); + auto contents = rng.randbytes(length); // Pick sides. auto& sender{from_init ? initiator : responder}; diff --git a/src/test/fuzz/bitset.cpp b/src/test/fuzz/bitset.cpp index cc3ed30f625..ce6be0499cf 100644 --- a/src/test/fuzz/bitset.cpp +++ b/src/test/fuzz/bitset.cpp @@ -29,7 +29,7 @@ void TestType(FuzzBufferType buffer) * bitsets and their simulations do not matter for the purpose of detecting edge cases, thus * these are taken from a deterministically-seeded RNG instead. To provide some level of * variation however, pick the seed based on the buffer size and size of the chosen bitset. */ - XoRoShiRo128PlusPlus rng(buffer.size() + 0x10000 * S::Size()); + InsecureRandomContext rng(buffer.size() + 0x10000 * S::Size()); using Sim = std::bitset; // Up to 4 real BitSets (initially 2). @@ -124,7 +124,7 @@ void TestType(FuzzBufferType buffer) sim[dest].reset(); real[dest] = S{}; for (unsigned i = 0; i < S::Size(); ++i) { - if (rng() & 1) { + if (rng.randbool()) { sim[dest][i] = true; real[dest].Set(i); } @@ -132,9 +132,9 @@ void TestType(FuzzBufferType buffer) break; } else if (dest < sim.size() && command-- == 0) { /* Assign initializer list. */ - unsigned r1 = rng() % S::Size(); - unsigned r2 = rng() % S::Size(); - unsigned r3 = rng() % S::Size(); + unsigned r1 = rng.randrange(S::Size()); + unsigned r2 = rng.randrange(S::Size()); + unsigned r3 = rng.randrange(S::Size()); compare_fn(dest); sim[dest].reset(); real[dest] = {r1, r2, r3}; @@ -166,8 +166,8 @@ void TestType(FuzzBufferType buffer) break; } else if (sim.size() < 4 && command-- == 0) { /* Construct with initializer list. */ - unsigned r1 = rng() % S::Size(); - unsigned r2 = rng() % S::Size(); + unsigned r1 = rng.randrange(S::Size()); + unsigned r2 = rng.randrange(S::Size()); sim.emplace_back(); sim.back().set(r1); sim.back().set(r2); diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index ff3d463ca51..d115a2b7e16 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -53,7 +53,7 @@ namespace once for a large block at once, and then the same data in chunks, comparing the outcome. - If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt(). + If UseCrypt, seeded InsecureRandomContext output is used as input to Crypt(). If not, Keystream() is used directly, or sequences of 0x00 are encrypted. */ template @@ -78,25 +78,11 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) data1.resize(total_bytes); data2.resize(total_bytes); - // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based + // If using Crypt(), initialize data1 and data2 with the same InsecureRandomContext based // stream. if constexpr (UseCrypt) { - uint64_t seed = provider.ConsumeIntegral(); - XoRoShiRo128PlusPlus rng(seed); - uint64_t bytes = 0; - while (bytes < (total_bytes & ~uint64_t{7})) { - uint64_t val = rng(); - WriteLE64(UCharCast(data1.data() + bytes), val); - WriteLE64(UCharCast(data2.data() + bytes), val); - bytes += 8; - } - if (bytes < total_bytes) { - std::byte valbytes[8]; - uint64_t val = rng(); - WriteLE64(UCharCast(valbytes), val); - std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes); - std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes); - } + InsecureRandomContext(provider.ConsumeIntegral()).fillrand(data1); + std::copy(data1.begin(), data1.end(), data2.begin()); } // Whether UseCrypt is used or not, the two byte arrays must match. diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index 5a26fb86ce1..93f77b6e5be 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -103,7 +103,7 @@ FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serial namespace { -template +template void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDataProvider& provider) { // 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 // Determine size of message to send (limited to 75 kB for performance reasons). size_t size = provider.ConsumeIntegralInRange(0, 75000); // Get payload of message from RNG. - msg.data.resize(size); - for (auto& v : msg.data) v = uint8_t(rng()); + msg.data = rng.randbytes(size); // Return. return msg; }; @@ -336,7 +335,7 @@ std::unique_ptr MakeV1Transport(NodeId nodeid) noexcept return std::make_unique(nodeid); } -template +template std::unique_ptr MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider) { // Retrieve key @@ -352,8 +351,7 @@ std::unique_ptr MakeV2Transport(NodeId nodeid, bool initiator, RNG& r } else { // If it's longer, generate it from the RNG. This avoids having large amounts of // (hopefully) irrelevant data needing to be stored in the fuzzer data. - garb.resize(garb_len); - for (auto& v : garb) v = uint8_t(rng()); + garb = rng.randbytes(garb_len); } // Retrieve entropy auto ent = provider.ConsumeBytes(32); @@ -377,7 +375,7 @@ FUZZ_TARGET(p2p_transport_bidirectional, .init = initialize_p2p_transport_serial { // Test with two V1 transports talking to each other. FuzzedDataProvider provider{buffer.data(), buffer.size()}; - XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral()); + InsecureRandomContext rng(provider.ConsumeIntegral()); auto t1 = MakeV1Transport(NodeId{0}); auto t2 = MakeV1Transport(NodeId{1}); if (!t1 || !t2) return; @@ -388,7 +386,7 @@ FUZZ_TARGET(p2p_transport_bidirectional_v2, .init = initialize_p2p_transport_ser { // Test with two V2 transports talking to each other. FuzzedDataProvider provider{buffer.data(), buffer.size()}; - XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral()); + InsecureRandomContext rng(provider.ConsumeIntegral()); auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider); auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider); if (!t1 || !t2) return; @@ -399,7 +397,7 @@ FUZZ_TARGET(p2p_transport_bidirectional_v1v2, .init = initialize_p2p_transport_s { // Test with a V1 initiator talking to a V2 responder. FuzzedDataProvider provider{buffer.data(), buffer.size()}; - XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral()); + InsecureRandomContext rng(provider.ConsumeIntegral()); auto t1 = MakeV1Transport(NodeId{0}); auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider); if (!t1 || !t2) return; diff --git a/src/test/fuzz/poolresource.cpp b/src/test/fuzz/poolresource.cpp index a3e48994c97..28bf7175c08 100644 --- a/src/test/fuzz/poolresource.cpp +++ b/src/test/fuzz/poolresource.cpp @@ -71,41 +71,14 @@ public: void RandomContentFill(Entry& entry) { - XoRoShiRo128PlusPlus rng(entry.seed); - auto ptr = entry.span.data(); - auto size = entry.span.size(); - - while (size >= 8) { - auto r = rng(); - std::memcpy(ptr, &r, 8); - size -= 8; - ptr += 8; - } - if (size > 0) { - auto r = rng(); - std::memcpy(ptr, &r, size); - } + InsecureRandomContext(entry.seed).fillrand(entry.span); } void RandomContentCheck(const Entry& entry) { - XoRoShiRo128PlusPlus rng(entry.seed); - auto ptr = entry.span.data(); - auto size = entry.span.size(); - - std::byte buf[8]; - while (size >= 8) { - auto r = rng(); - std::memcpy(buf, &r, 8); - assert(std::memcmp(buf, ptr, 8) == 0); - size -= 8; - ptr += 8; - } - if (size > 0) { - auto r = rng(); - std::memcpy(buf, &r, size); - assert(std::memcmp(buf, ptr, size) == 0); - } + std::vector expect(entry.span.size()); + InsecureRandomContext(entry.seed).fillrand(expect); + assert(entry.span == expect); } void Deallocate(const Entry& entry) diff --git a/src/test/fuzz/vecdeque.cpp b/src/test/fuzz/vecdeque.cpp index d94e6d895a0..3bb858ee8ae 100644 --- a/src/test/fuzz/vecdeque.cpp +++ b/src/test/fuzz/vecdeque.cpp @@ -28,7 +28,7 @@ void TestType(Span buffer, uint64_t rng_tweak) { FuzzedDataProvider provider(buffer.data(), buffer.size()); // Local RNG, only used for the seeds to initialize T objects with. - XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral() ^ rng_tweak); + InsecureRandomContext rng(provider.ConsumeIntegral() ^ rng_tweak); // Real circular buffers. std::vector> real; @@ -175,7 +175,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_full && command-- == 0) { /* push_back() (copying) */ - tmp = T(rng()); + tmp = T(rng.rand64()); size_t old_size = real[idx].size(); size_t old_cap = real[idx].capacity(); real[idx].push_back(*tmp); @@ -191,7 +191,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_full && command-- == 0) { /* push_back() (moving) */ - tmp = T(rng()); + tmp = T(rng.rand64()); size_t old_size = real[idx].size(); size_t old_cap = real[idx].capacity(); sim[idx].push_back(*tmp); @@ -207,7 +207,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_full && command-- == 0) { /* emplace_back() */ - uint64_t seed{rng()}; + uint64_t seed{rng.rand64()}; size_t old_size = real[idx].size(); size_t old_cap = real[idx].capacity(); sim[idx].emplace_back(seed); @@ -223,7 +223,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_full && command-- == 0) { /* push_front() (copying) */ - tmp = T(rng()); + tmp = T(rng.rand64()); size_t old_size = real[idx].size(); size_t old_cap = real[idx].capacity(); real[idx].push_front(*tmp); @@ -239,7 +239,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_full && command-- == 0) { /* push_front() (moving) */ - tmp = T(rng()); + tmp = T(rng.rand64()); size_t old_size = real[idx].size(); size_t old_cap = real[idx].capacity(); sim[idx].push_front(*tmp); @@ -255,7 +255,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_full && command-- == 0) { /* emplace_front() */ - uint64_t seed{rng()}; + uint64_t seed{rng.rand64()}; size_t old_size = real[idx].size(); size_t old_cap = real[idx].capacity(); sim[idx].emplace_front(seed); @@ -271,7 +271,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_empty && command-- == 0) { /* front() [modifying] */ - tmp = T(rng()); + tmp = T(rng.rand64()); size_t old_size = real[idx].size(); assert(sim[idx].front() == real[idx].front()); sim[idx].front() = *tmp; @@ -281,7 +281,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_empty && command-- == 0) { /* back() [modifying] */ - tmp = T(rng()); + tmp = T(rng.rand64()); size_t old_size = real[idx].size(); assert(sim[idx].back() == real[idx].back()); sim[idx].back() = *tmp; @@ -291,7 +291,7 @@ void TestType(Span buffer, uint64_t rng_tweak) } if (existing_buffer_non_empty && command-- == 0) { /* operator[] [modifying] */ - tmp = T(rng()); + tmp = T(rng.rand64()); size_t pos = provider.ConsumeIntegralInRange(0, sim[idx].size() - 1); size_t old_size = real[idx].size(); assert(sim[idx][pos] == real[idx][pos]); diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 09425c7cd7e..8392a2bc5d1 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -222,14 +222,14 @@ BOOST_AUTO_TEST_CASE(shuffle_stat_test) BOOST_AUTO_TEST_CASE(xoroshiro128plusplus_reference_values) { // numbers generated from reference implementation - XoRoShiRo128PlusPlus rng(0); + InsecureRandomContext rng(0); BOOST_TEST(0x6f68e1e7e2646ee1 == rng()); BOOST_TEST(0xbf971b7f454094ad == rng()); BOOST_TEST(0x48f2de556f30de38 == rng()); BOOST_TEST(0x6ea7c59f89bbfc75 == rng()); // seed with a random number - rng = XoRoShiRo128PlusPlus(0x1a26f3fa8546b47a); + rng = InsecureRandomContext(0x1a26f3fa8546b47a); BOOST_TEST(0xc8dc5e08d844ac7d == rng()); BOOST_TEST(0x5b5f1f6d499dad1b == rng()); BOOST_TEST(0xbeb0031f93313d6f == rng()); diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 7d8b0fdfd03..94303f237c5 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -57,8 +57,8 @@ unsigned-integer-overflow:CBlockPolicyEstimator::processBlockTx unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal unsigned-integer-overflow:prevector.h unsigned-integer-overflow:EvalScript -unsigned-integer-overflow:XoRoShiRo128PlusPlus::operator() -unsigned-integer-overflow:XoRoShiRo128PlusPlus::SplitMix64 +unsigned-integer-overflow:InsecureRandomContext::rand64 +unsigned-integer-overflow:InsecureRandomContext::SplitMix64 unsigned-integer-overflow:bitset_detail::PopCount implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx implicit-integer-sign-change:SetStdinEcho @@ -76,6 +76,6 @@ shift-base:arith_uint256.cpp shift-base:crypto/ shift-base:streams.h shift-base:FormatHDKeypath -shift-base:XoRoShiRo128PlusPlus::operator() +shift-base:InsecureRandomContext::rand64 shift-base:RandomMixin<*>::randbits shift-base:RandomMixin<*>::randbits<*> From 810cdf6b4e12a1fdace7998d75b4daf8b67d7028 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 19:49:42 -0400 Subject: [PATCH 12/23] tests: overhaul deterministic test randomness The existing code provides two randomness mechanisms for test purposes: - g_insecure_rand_ctx (with its wrappers InsecureRand*), which during tests is initialized using either zeros (SeedRand::ZEROS), or using environment-provided randomness (SeedRand::SEED). - g_mock_deterministic_tests, which controls some (but not all) of the normal randomness output if set, but then makes it extremely predictable (identical output repeatedly). Replace this with a single mechanism, which retains the SeedRand modes to control all randomness. There is a new internal deterministic PRNG inside the random module, which is used in GetRandBytes() when in test mode, and which is also used to initialize g_insecure_rand_ctx. This means that during tests, all random numbers are made deterministic. There is one exception, GetStrongRandBytes(), which even in test mode still uses the normal PRNG state. This probably opens the door to removing a lot of the ad-hoc "deterministic" mode functions littered through the codebase (by simply running relevant tests in SeedRand::ZEROS mode), but this isn't done yet. --- src/random.cpp | 65 +++++++++++++++++++++++++++------- src/random.h | 7 ++++ src/test/bloom_tests.cpp | 8 ++--- src/test/coins_tests.cpp | 5 +-- src/test/cuckoocache_tests.cpp | 10 +++--- src/test/fuzz/fuzz.cpp | 1 + src/test/prevector_tests.cpp | 2 +- src/test/random_tests.cpp | 54 ++++++++++++++++++++-------- src/test/streams_tests.cpp | 2 +- src/test/util/random.cpp | 31 +++++++++------- src/test/util/random.h | 20 ++--------- src/test/util/setup_common.cpp | 2 +- src/test/util_tests.cpp | 2 +- 13 files changed, 133 insertions(+), 76 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 10ad4e2558a..95e4806aa43 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #ifdef WIN32 @@ -417,6 +418,10 @@ class RNGState { uint64_t m_counter GUARDED_BY(m_mutex) = 0; bool m_strongly_seeded GUARDED_BY(m_mutex) = false; + /** If not nullopt, the output of this RNGState is redirected and drawn from here + * (unless always_use_real_rng is passed to MixExtract). */ + std::optional m_deterministic_prng GUARDED_BY(m_mutex); + Mutex m_events_mutex; CSHA256 m_events_hasher GUARDED_BY(m_events_mutex); @@ -457,11 +462,21 @@ public: m_events_hasher.Write(events_hash, 32); } + /** Make the output of MixExtract (unless always_use_real_rng) deterministic, with specified seed. */ + void MakeDeterministic(const uint256& seed) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) + { + LOCK(m_mutex); + m_deterministic_prng.emplace(MakeByteSpan(seed)); + } + /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. * * If this function has never been called with strong_seed = true, false is returned. + * + * If always_use_real_rng is false, and MakeDeterministic has been called before, output + * from the deterministic PRNG instead. */ - bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed, bool always_use_real_rng) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { assert(num <= 32); unsigned char buf[64]; @@ -479,6 +494,13 @@ public: hasher.Finalize(buf); // Store the last 32 bytes of the hash output as new RNG state. memcpy(m_state, buf + 32, 32); + // Handle requests for deterministic randomness. + if (!always_use_real_rng && m_deterministic_prng.has_value()) [[unlikely]] { + // Overwrite the beginning of buf, which will be used for output. + m_deterministic_prng->Keystream(AsWritableBytes(Span{buf, num})); + // Do not require strong seeding for deterministic output. + ret = true; + } } // If desired, copy (up to) the first 32 bytes of the hash output as output. if (num) { @@ -552,8 +574,9 @@ static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept static void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration dur) noexcept { // Generate 32 bytes of entropy from the RNG, and a copy of the entropy already in hasher. + // Never use the deterministic PRNG for this, as the result is only used internally. unsigned char strengthen_seed[32]; - rng.MixExtract(strengthen_seed, sizeof(strengthen_seed), CSHA512(hasher), false); + rng.MixExtract(strengthen_seed, sizeof(strengthen_seed), CSHA512(hasher), false, /*always_use_real_rng=*/true); // Strengthen the seed, and feed it into hasher. Strengthen(strengthen_seed, dur, hasher); } @@ -604,7 +627,7 @@ enum class RNGLevel { PERIODIC, //!< Called by RandAddPeriodic() }; -static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept +static void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_use_real_rng) noexcept { // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available). RNGState& rng = GetRNGState(); @@ -625,24 +648,40 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept } // Combine with and update state - if (!rng.MixExtract(out, num, std::move(hasher), false)) { + if (!rng.MixExtract(out, num, std::move(hasher), false, always_use_real_rng)) { // On the first invocation, also seed with SeedStartup(). CSHA512 startup_hasher; SeedStartup(startup_hasher, rng); - rng.MixExtract(out, num, std::move(startup_hasher), true); + rng.MixExtract(out, num, std::move(startup_hasher), true, always_use_real_rng); } } -void GetRandBytes(Span bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST); } -void GetStrongRandBytes(Span bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::SLOW); } -void RandAddPeriodic() noexcept { ProcRand(nullptr, 0, RNGLevel::PERIODIC); } -void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } +/** Internal function to set g_determinstic_rng. Only accessed from tests. */ +void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept +{ + GetRNGState().MakeDeterministic(seed); +} -bool g_mock_deterministic_tests{false}; +void GetRandBytes(Span bytes) noexcept +{ + ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST, /*always_use_real_rng=*/false); +} + +void GetStrongRandBytes(Span bytes) noexcept +{ + ProcRand(bytes.data(), bytes.size(), RNGLevel::SLOW, /*always_use_real_rng=*/true); +} + +void RandAddPeriodic() noexcept +{ + ProcRand(nullptr, 0, RNGLevel::PERIODIC, /*always_use_real_rng=*/false); +} + +void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } uint64_t GetRandInternal(uint64_t nMax) noexcept { - return FastRandomContext(g_mock_deterministic_tests).randrange(nMax); + return FastRandomContext().randrange(nMax); } uint256 GetRandHash() noexcept @@ -708,7 +747,7 @@ bool Random_SanityCheck() CSHA512 to_add; to_add.Write((const unsigned char*)&start, sizeof(start)); to_add.Write((const unsigned char*)&stop, sizeof(stop)); - GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false); + GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false, /*always_use_real_rng=*/true); return true; } @@ -734,7 +773,7 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce void RandomInit() { // Invoke RNG code to trigger initialization (if not already performed) - ProcRand(nullptr, 0, RNGLevel::FAST); + ProcRand(nullptr, 0, RNGLevel::FAST, /*always_use_real_rng=*/true); ReportHardwareRand(); } diff --git a/src/random.h b/src/random.h index cdf2f3a66d2..fb83eebbf2f 100644 --- a/src/random.h +++ b/src/random.h @@ -63,6 +63,12 @@ * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and * (up to) the first 32 bytes of H are produced as output, while the last 32 bytes * become the new RNG state. + * + * During tests, the RNG can be put into a special deterministic mode, in which the output + * of all RNG functions, with the exception of GetStrongRandBytes(), is replaced with the + * output of a deterministic RNG. This deterministic RNG does not gather entropy, and is + * unaffected by RandAddPeriodic() or RandAddEvent(). It produces pseudorandom data that + * only depends on the seed it was initialized with, possibly until it is reinitialized. */ /** @@ -340,6 +346,7 @@ private: void RandomSeed() noexcept; public: + /** Construct a FastRandomContext with GetRandHash()-based entropy (or zero key if fDeterministic). */ explicit FastRandomContext(bool fDeterministic = false) noexcept; /** Initialize with explicit seed (only for testing) */ diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp index cbf85277a86..6699afdbfab 100644 --- a/src/test/bloom_tests.cpp +++ b/src/test/bloom_tests.cpp @@ -463,8 +463,7 @@ static std::vector RandomData() BOOST_AUTO_TEST_CASE(rolling_bloom) { - SeedInsecureRand(SeedRand::ZEROS); - g_mock_deterministic_tests = true; + SeedRandomForTest(SeedRand::ZEROS); // last-100-entry, 1% false positive: CRollingBloomFilter rb1(100, 0.01); @@ -491,7 +490,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom) ++nHits; } // Expect about 100 hits - BOOST_CHECK_EQUAL(nHits, 75U); + BOOST_CHECK_EQUAL(nHits, 71U); BOOST_CHECK(rb1.contains(data[DATASIZE-1])); rb1.reset(); @@ -519,7 +518,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom) ++nHits; } // Expect about 5 false positives - BOOST_CHECK_EQUAL(nHits, 6U); + BOOST_CHECK_EQUAL(nHits, 3U); // last-1000-entry, 0.01% false positive: CRollingBloomFilter rb2(1000, 0.001); @@ -530,7 +529,6 @@ BOOST_AUTO_TEST_CASE(rolling_bloom) for (int i = 0; i < DATASIZE; i++) { BOOST_CHECK(rb2.contains(data[i])); } - g_mock_deterministic_tests = false; } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index b6d3e7d5676..a992e2fa033 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -307,8 +307,7 @@ UtxoData::iterator FindRandomFrom(const std::set &utxoSet) { // has the expected effect (the other duplicate is overwritten at all cache levels) BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) { - SeedInsecureRand(SeedRand::ZEROS); - g_mock_deterministic_tests = true; + SeedRandomForTest(SeedRand::ZEROS); bool spent_a_duplicate_coinbase = false; // A simple map to track what we expect the cache stack to represent. @@ -496,8 +495,6 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // Verify coverage. BOOST_CHECK(spent_a_duplicate_coinbase); - - g_mock_deterministic_tests = false; } BOOST_AUTO_TEST_CASE(ccoins_serialization) diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index eafbcf5681d..906fbb4afa5 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -37,7 +37,7 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests); */ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) { - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); CuckooCache::cache cc{}; size_t megabytes = 4; cc.setup_bytes(megabytes << 20); @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) template static double test_cache(size_t megabytes, double load) { - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); std::vector hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -126,7 +126,7 @@ template static void test_cache_erase(size_t megabytes) { double load = 1; - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); std::vector hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -189,7 +189,7 @@ template static void test_cache_erase_parallel(size_t megabytes) { double load = 1; - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); std::vector hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -293,7 +293,7 @@ static void test_cache_generations() // iterations with non-deterministic values, so it isn't "overfit" to the // specific entropy in FastRandomContext(true) and implementation of the // cache. - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); // block_activity models a chunk of network activity. n_insert elements are // added to the cache. The first and last n/4 are stored for removal later diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index c1c9945a04a..115cf2fc997 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index 1559011fcd1..9abdd84c5a4 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -210,7 +210,7 @@ public: } prevector_tester() { - SeedInsecureRand(); + SeedRandomForTest(); rand_seed = InsecureRand256(); rand_cache = FastRandomContext(rand_seed); } diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 8392a2bc5d1..89546166b4b 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -20,19 +20,30 @@ BOOST_AUTO_TEST_CASE(osrandom_tests) BOOST_CHECK(Random_SanityCheck()); } -BOOST_AUTO_TEST_CASE(fastrandom_tests) +BOOST_AUTO_TEST_CASE(fastrandom_tests_deterministic) { // Check that deterministic FastRandomContexts are deterministic - g_mock_deterministic_tests = true; - FastRandomContext ctx1(true); - FastRandomContext ctx2(true); + SeedRandomForTest(SeedRand::ZEROS); + FastRandomContext ctx1{true}; + FastRandomContext ctx2{true}; - for (int i = 10; i > 0; --i) { - BOOST_CHECK_EQUAL(GetRand(), uint64_t{10393729187455219830U}); - BOOST_CHECK_EQUAL(GetRand(), int{769702006}); - BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654); - BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374); + { + BOOST_CHECK_EQUAL(GetRand(), uint64_t{9330418229102544152u}); + BOOST_CHECK_EQUAL(GetRand(), int{618925161}); + BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 1271170921); + BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2803534); + + BOOST_CHECK_EQUAL(GetRand(), uint64_t{10170981140880778086u}); + BOOST_CHECK_EQUAL(GetRand(), int{1689082725}); + BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2464643716); + BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2312205); + + BOOST_CHECK_EQUAL(GetRand(), uint64_t{5689404004456455543u}); + BOOST_CHECK_EQUAL(GetRand(), int{785839937}); + BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 93558804); + BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 507022); } + { constexpr SteadySeconds time_point{1s}; FastRandomContext ctx{true}; @@ -65,15 +76,28 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) // Check with time-point type BOOST_CHECK_EQUAL(2782, ctx.rand_uniform_duration(9h).count()); } +} +BOOST_AUTO_TEST_CASE(fastrandom_tests_nondeterministic) +{ // Check that a nondeterministic ones are not - g_mock_deterministic_tests = false; - for (int i = 10; i > 0; --i) { - BOOST_CHECK(GetRand() != uint64_t{10393729187455219830U}); - BOOST_CHECK(GetRand() != int{769702006}); - BOOST_CHECK(GetRandMicros(std::chrono::hours{1}) != std::chrono::microseconds{2917185654}); - BOOST_CHECK(GetRandMillis(std::chrono::hours{1}) != std::chrono::milliseconds{2144374}); + { + BOOST_CHECK(GetRand() != uint64_t{9330418229102544152u}); + BOOST_CHECK(GetRand() != int{618925161}); + BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 1271170921); + BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2803534); + + BOOST_CHECK(GetRand() != uint64_t{10170981140880778086u}); + BOOST_CHECK(GetRand() != int{1689082725}); + BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 2464643716); + BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2312205); + + BOOST_CHECK(GetRand() != uint64_t{5689404004456455543u}); + BOOST_CHECK(GetRand() != int{785839937}); + BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 93558804); + BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 507022); } + { FastRandomContext ctx3, ctx4; BOOST_CHECK(ctx3.rand64() != ctx4.rand64()); // extremely unlikely to be equal diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index e666e117582..eed932b6d29 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_skip) BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) { // Make this test deterministic. - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); fs::path streams_test_filename = m_args.GetDataDirBase() / "streams_test_tmp"; for (int rep = 0; rep < 50; ++rep) { diff --git a/src/test/util/random.cpp b/src/test/util/random.cpp index 4c87ab8df84..aa8c16e8377 100644 --- a/src/test/util/random.cpp +++ b/src/test/util/random.cpp @@ -13,21 +13,26 @@ FastRandomContext g_insecure_rand_ctx; -/** Return the unsigned from the environment var if available, otherwise 0 */ -static uint256 GetUintFromEnv(const std::string& env_name) -{ - const char* num = std::getenv(env_name.c_str()); - if (!num) return {}; - return uint256S(num); -} +extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept; -void Seed(FastRandomContext& ctx) +void SeedRandomForTest(SeedRand seedtype) { - // Should be enough to get the seed once for the process - static uint256 seed{}; static const std::string RANDOM_CTX_SEED{"RANDOM_CTX_SEED"}; - if (seed.IsNull()) seed = GetUintFromEnv(RANDOM_CTX_SEED); - if (seed.IsNull()) seed = GetRandHash(); + + // Do this once, on the first call, regardless of seedtype, because once + // MakeRandDeterministicDANGEROUS is called, the output of GetRandHash is + // no longer truly random. It should be enough to get the seed once for the + // process. + static const uint256 ctx_seed = []() { + // If RANDOM_CTX_SEED is set, use that as seed. + const char* num = std::getenv(RANDOM_CTX_SEED.c_str()); + if (num) return uint256S(num); + // Otherwise use a (truly) random value. + return GetRandHash(); + }(); + + const uint256& seed{seedtype == SeedRand::SEED ? ctx_seed : uint256::ZERO}; LogPrintf("%s: Setting random seed for current tests to %s=%s\n", __func__, RANDOM_CTX_SEED, seed.GetHex()); - ctx = FastRandomContext(seed); + MakeRandDeterministicDANGEROUS(seed); + g_insecure_rand_ctx = FastRandomContext(GetRandHash()); } diff --git a/src/test/util/random.h b/src/test/util/random.h index 18ab425e481..09a475f8b3d 100644 --- a/src/test/util/random.h +++ b/src/test/util/random.h @@ -19,27 +19,13 @@ */ extern FastRandomContext g_insecure_rand_ctx; -/** - * Flag to make GetRand in random.h return the same number - */ -extern bool g_mock_deterministic_tests; - enum class SeedRand { ZEROS, //!< Seed with a compile time constant of zeros - SEED, //!< Call the Seed() helper + SEED, //!< Use (and report) random seed from environment, or a (truly) random one. }; -/** Seed the given random ctx or use the seed passed in via an environment var */ -void Seed(FastRandomContext& ctx); - -static inline void SeedInsecureRand(SeedRand seed = SeedRand::SEED) -{ - if (seed == SeedRand::ZEROS) { - g_insecure_rand_ctx = FastRandomContext(/*fDeterministic=*/true); - } else { - Seed(g_insecure_rand_ctx); - } -} +/** Seed the RNG for testing. This affects all randomness, except GetStrongRandBytes(). */ +void SeedRandomForTest(SeedRand seed = SeedRand::SEED); static inline uint32_t InsecureRand32() { diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index cc7b2d65463..283e19971c0 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -178,7 +178,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root)); SelectParams(chainType); - SeedInsecureRand(); + SeedRandomForTest(); if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN); InitLogging(*m_node.args); AppInitParameterInteraction(*m_node.args); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index a371753adf0..9f452d5f8f2 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -459,7 +459,7 @@ BOOST_AUTO_TEST_CASE(util_IsHexNumber) BOOST_AUTO_TEST_CASE(util_seed_insecure_rand) { - SeedInsecureRand(SeedRand::ZEROS); + SeedRandomForTest(SeedRand::ZEROS); for (int mod=2;mod<11;mod++) { int mask = 1; From e2d1f84858485650ff743753ffa5c679f210a992 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 10 Mar 2024 23:38:31 -0400 Subject: [PATCH 13/23] random: make GetRand() support entire range (incl. max) The existing code uses GetRand(nMax), with a default value for nMax, where nMax is the range of values (not the maximum!) that the output is allowed to take. This will always miss the last possible value (e.g. GetRand() will never return 0xffffffff). Fix this, by moving the functionality largely in RandomMixin, and also adding a separate RandomMixin::rand function, which returns a value in the entire (non-negative) range of an integer. --- src/random.cpp | 5 --- src/random.h | 78 +++++++++++++++++++++++++------------------ src/test/util/net.cpp | 2 +- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 95e4806aa43..5067bf259c3 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -679,11 +679,6 @@ void RandAddPeriodic() noexcept void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } -uint64_t GetRandInternal(uint64_t nMax) noexcept -{ - return FastRandomContext().randrange(nMax); -} - uint256 GetRandHash() noexcept { uint256 hash; diff --git a/src/random.h b/src/random.h index fb83eebbf2f..afbae0cec3b 100644 --- a/src/random.h +++ b/src/random.h @@ -80,31 +80,6 @@ * Thread-safe. */ void GetRandBytes(Span bytes) noexcept; -/** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */ -uint64_t GetRandInternal(uint64_t nMax) noexcept; -/** Generate a uniform random integer of type T in the range [0..nMax) - * nMax defaults to std::numeric_limits::max() - * Precondition: nMax > 0, T is an integral type, no larger than uint64_t - */ -template -T GetRand(T nMax=std::numeric_limits::max()) noexcept { - static_assert(std::is_integral(), "T must be integral"); - static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), "GetRand only supports up to uint64_t"); - return T(GetRandInternal(nMax)); -} -/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ -template -D GetRandomDuration(typename std::common_type::type max) noexcept -// Having the compiler infer the template argument from the function argument -// is dangerous, because the desired return value generally has a different -// type than the function argument. So std::common_type is used to force the -// call site to specify the type of the return value. -{ - assert(max.count() > 0); - return D{GetRand(max.count())}; -}; -constexpr auto GetRandMicros = GetRandomDuration; -constexpr auto GetRandMillis = GetRandomDuration; /** * Return a timestamp in the future sampled from an exponential distribution @@ -251,17 +226,17 @@ public: } } - /** Generate a random integer in the range [0..range). - * Precondition: range > 0. - */ - uint64_t randrange(uint64_t range) noexcept + /** Generate a random integer in the range [0..range), with range > 0. */ + template + I randrange(I range) noexcept { - assert(range); - --range; - int bits = std::bit_width(range); + static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); + Assume(range > 0); + uint64_t maxval = range - 1U; + int bits = std::bit_width(maxval); while (true) { uint64_t ret = Impl().randbits(bits); - if (ret <= range) return ret; + if (ret <= maxval) return ret; } } @@ -284,6 +259,16 @@ public: } } + /** Generate a random integer in its entire (non-negative) range. */ + template + I rand() noexcept + { + static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); + static constexpr auto BITS = std::bit_width(uint64_t(std::numeric_limits::max())); + static_assert(std::numeric_limits::max() == std::numeric_limits::max() >> (64 - BITS)); + return I(Impl().template randbits()); + } + /** Generate random bytes. */ template std::vector randbytes(size_t len) noexcept @@ -441,6 +426,33 @@ void Shuffle(I first, I last, R&& rng) } } +/** Generate a uniform random integer of type T in the range [0..nMax) + * Precondition: nMax > 0, T is an integral type, no larger than uint64_t + */ +template +T GetRand(T nMax) noexcept { + return T(FastRandomContext().randrange(nMax)); +} + +/** Generate a uniform random integer of type T in its entire non-negative range. */ +template +T GetRand() noexcept { + return T(FastRandomContext().rand()); +} + +/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ +template +D GetRandomDuration(typename std::common_type::type max) noexcept +// Having the compiler infer the template argument from the function argument +// is dangerous, because the desired return value generally has a different +// type than the function argument. So std::common_type is used to force the +// call site to specify the type of the return value. +{ + return D{GetRand(max.count())}; +}; +constexpr auto GetRandMicros = GetRandomDuration; +constexpr auto GetRandMillis = GetRandomDuration; + /* Number of random bytes returned by GetOSRand. * When changing this constant make sure to change all call sites, and make * sure that the underlying OS APIs for all platforms support the number. diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp index 9257a4964ac..9b38d85d582 100644 --- a/src/test/util/net.cpp +++ b/src/test/util/net.cpp @@ -126,7 +126,7 @@ std::vector GetRandomNodeEvictionCandidates(int n_candida /*fRelevantServices=*/random_context.randbool(), /*m_relay_txs=*/random_context.randbool(), /*fBloomFilter=*/random_context.randbool(), - /*nKeyedNetGroup=*/random_context.randrange(100), + /*nKeyedNetGroup=*/random_context.randrange(100u), /*prefer_evict=*/random_context.randbool(), /*m_is_local=*/random_context.randbool(), /*m_network=*/ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())], From ddc184d999d7e1a87efaf6bcb222186f0dcd87ec Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 27 Jun 2024 11:40:00 -0400 Subject: [PATCH 14/23] random: get rid of GetRand by inlining --- src/addrdb.cpp | 2 +- src/common/bloom.cpp | 2 +- src/headerssync.cpp | 2 +- src/init.cpp | 5 +++-- src/net_processing.cpp | 8 ++++---- src/netaddress.h | 4 ++-- src/node/txreconciliation.cpp | 2 +- src/random.h | 16 +--------------- src/test/random_tests.cpp | 24 ++++++++++++------------ src/txmempool.cpp | 2 +- src/txrequest.cpp | 4 ++-- src/util/bytevectorhash.cpp | 4 ++-- src/util/hasher.cpp | 12 ++++++++---- src/validation.cpp | 2 +- 14 files changed, 40 insertions(+), 49 deletions(-) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 4d34c24ba95..e9838d72229 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -53,7 +53,7 @@ template bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data) { // Generate random temporary filename - const uint16_t randv{GetRand()}; + const uint16_t randv{FastRandomContext().rand()}; std::string tmpfn = strprintf("%s.%04x", prefix, randv); // open temp output file diff --git a/src/common/bloom.cpp b/src/common/bloom.cpp index ca6af90b760..076ee406354 100644 --- a/src/common/bloom.cpp +++ b/src/common/bloom.cpp @@ -239,7 +239,7 @@ bool CRollingBloomFilter::contains(Span vKey) const void CRollingBloomFilter::reset() { - nTweak = GetRand(); + nTweak = FastRandomContext().rand(); nEntriesThisGeneration = 0; nGeneration = 1; std::fill(data.begin(), data.end(), 0); diff --git a/src/headerssync.cpp b/src/headerssync.cpp index e14de004f54..b41fe07754e 100644 --- a/src/headerssync.cpp +++ b/src/headerssync.cpp @@ -25,7 +25,7 @@ static_assert(sizeof(CompressedHeader) == 48); HeadersSyncState::HeadersSyncState(NodeId id, const Consensus::Params& consensus_params, const CBlockIndex* chain_start, const arith_uint256& minimum_required_work) : - m_commit_offset(GetRand(HEADER_COMMITMENT_PERIOD)), + m_commit_offset(FastRandomContext().randrange(HEADER_COMMITMENT_PERIOD)), m_id(id), m_consensus_params(consensus_params), m_chain_start(chain_start), m_minimum_required_work(minimum_required_work), diff --git a/src/init.cpp b/src/init.cpp index 44c256d203c..0022aeb039f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1273,11 +1273,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) node.addrman = std::move(*addrman); } + FastRandomContext rng; assert(!node.banman); node.banman = std::make_unique(args.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); - node.connman = std::make_unique(GetRand(), - GetRand(), + node.connman = std::make_unique(rng.rand64(), + rng.rand64(), *node.addrman, *node.netgroupman, chainparams, args.GetBoolArg("-networkactive", true)); assert(!node.fee_estimator); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 3be44795877..d674811ba73 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2124,7 +2124,7 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr &blo */ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr& pblock) { - auto pcmpctblock = std::make_shared(*pblock, GetRand()); + auto pcmpctblock = std::make_shared(*pblock, FastRandomContext().rand64()); LOCK(cs_main); @@ -2522,7 +2522,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& if (a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, *a_recent_compact_block); } else { - CBlockHeaderAndShortTxIDs cmpctblock{*pblock, GetRand()}; + CBlockHeaderAndShortTxIDs cmpctblock{*pblock, FastRandomContext().rand64()}; MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, cmpctblock); } } else { @@ -5617,7 +5617,7 @@ void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::mic if (pingSend) { uint64_t nonce; do { - nonce = GetRand(); + nonce = FastRandomContext().rand64(); } while (nonce == 0); peer.m_ping_queued = false; peer.m_ping_start = now; @@ -5984,7 +5984,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) CBlock block; const bool ret{m_chainman.m_blockman.ReadBlockFromDisk(block, *pBestIndex)}; assert(ret); - CBlockHeaderAndShortTxIDs cmpctblock{block, GetRand()}; + CBlockHeaderAndShortTxIDs cmpctblock{block, m_rng.rand64()}; MakeAndPushMessage(*pto, NetMsgType::CMPCTBLOCK, cmpctblock); } state.pindexBestHeaderSent = pBestIndex; diff --git a/src/netaddress.h b/src/netaddress.h index 52fecada1c9..24f5c3fb962 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -567,8 +567,8 @@ class CServiceHash { public: CServiceHash() - : m_salt_k0{GetRand()}, - m_salt_k1{GetRand()} + : m_salt_k0{FastRandomContext().rand64()}, + m_salt_k1{FastRandomContext().rand64()} { } diff --git a/src/node/txreconciliation.cpp b/src/node/txreconciliation.cpp index d62046daaaa..e6e19c5756b 100644 --- a/src/node/txreconciliation.cpp +++ b/src/node/txreconciliation.cpp @@ -85,7 +85,7 @@ public: LOCK(m_txreconciliation_mutex); LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Pre-register peer=%d\n", peer_id); - const uint64_t local_salt{GetRand(UINT64_MAX)}; + const uint64_t local_salt{FastRandomContext().rand64()}; // We do this exactly once per peer (which are unique by NodeId, see GetNewNodeId) so it's // safe to assume we don't have this record yet. diff --git a/src/random.h b/src/random.h index afbae0cec3b..1573e49ef7b 100644 --- a/src/random.h +++ b/src/random.h @@ -426,20 +426,6 @@ void Shuffle(I first, I last, R&& rng) } } -/** Generate a uniform random integer of type T in the range [0..nMax) - * Precondition: nMax > 0, T is an integral type, no larger than uint64_t - */ -template -T GetRand(T nMax) noexcept { - return T(FastRandomContext().randrange(nMax)); -} - -/** Generate a uniform random integer of type T in its entire non-negative range. */ -template -T GetRand() noexcept { - return T(FastRandomContext().rand()); -} - /** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ template D GetRandomDuration(typename std::common_type::type max) noexcept @@ -448,7 +434,7 @@ D GetRandomDuration(typename std::common_type::type max) noexcept // type than the function argument. So std::common_type is used to force the // call site to specify the type of the return value. { - return D{GetRand(max.count())}; + return D{FastRandomContext().randrange(max.count())}; }; constexpr auto GetRandMicros = GetRandomDuration; constexpr auto GetRandMillis = GetRandomDuration; diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 89546166b4b..6932e186d56 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -28,18 +28,18 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests_deterministic) FastRandomContext ctx2{true}; { - BOOST_CHECK_EQUAL(GetRand(), uint64_t{9330418229102544152u}); - BOOST_CHECK_EQUAL(GetRand(), int{618925161}); + BOOST_CHECK_EQUAL(FastRandomContext().rand(), uint64_t{9330418229102544152u}); + BOOST_CHECK_EQUAL(FastRandomContext().rand(), int{618925161}); BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 1271170921); BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2803534); - BOOST_CHECK_EQUAL(GetRand(), uint64_t{10170981140880778086u}); - BOOST_CHECK_EQUAL(GetRand(), int{1689082725}); + BOOST_CHECK_EQUAL(FastRandomContext().rand(), uint64_t{10170981140880778086u}); + BOOST_CHECK_EQUAL(FastRandomContext().rand(), int{1689082725}); BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2464643716); BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2312205); - BOOST_CHECK_EQUAL(GetRand(), uint64_t{5689404004456455543u}); - BOOST_CHECK_EQUAL(GetRand(), int{785839937}); + BOOST_CHECK_EQUAL(FastRandomContext().rand(), uint64_t{5689404004456455543u}); + BOOST_CHECK_EQUAL(FastRandomContext().rand(), int{785839937}); BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 93558804); BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 507022); } @@ -82,18 +82,18 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests_nondeterministic) { // Check that a nondeterministic ones are not { - BOOST_CHECK(GetRand() != uint64_t{9330418229102544152u}); - BOOST_CHECK(GetRand() != int{618925161}); + BOOST_CHECK(FastRandomContext().rand() != uint64_t{9330418229102544152u}); + BOOST_CHECK(FastRandomContext().rand() != int{618925161}); BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 1271170921); BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2803534); - BOOST_CHECK(GetRand() != uint64_t{10170981140880778086u}); - BOOST_CHECK(GetRand() != int{1689082725}); + BOOST_CHECK(FastRandomContext().rand() != uint64_t{10170981140880778086u}); + BOOST_CHECK(FastRandomContext().rand() != int{1689082725}); BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 2464643716); BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2312205); - BOOST_CHECK(GetRand() != uint64_t{5689404004456455543u}); - BOOST_CHECK(GetRand() != int{785839937}); + BOOST_CHECK(FastRandomContext().rand() != uint64_t{5689404004456455543u}); + BOOST_CHECK(FastRandomContext().rand() != int{785839937}); BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 93558804); BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 507022); } diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 10674c07aca..f56da08e5f2 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -657,7 +657,7 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei { if (m_opts.check_ratio == 0) return; - if (GetRand(m_opts.check_ratio) >= 1) return; + if (FastRandomContext().randrange(m_opts.check_ratio) >= 1) return; AssertLockHeld(::cs_main); LOCK(cs); diff --git a/src/txrequest.cpp b/src/txrequest.cpp index ce5fbd9a7f9..6338ccb118b 100644 --- a/src/txrequest.cpp +++ b/src/txrequest.cpp @@ -113,8 +113,8 @@ class PriorityComputer { const uint64_t m_k0, m_k1; public: explicit PriorityComputer(bool deterministic) : - m_k0{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)}, - m_k1{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)} {} + m_k0{deterministic ? 0 : FastRandomContext().rand64()}, + m_k1{deterministic ? 0 : FastRandomContext().rand64()} {} Priority operator()(const uint256& txhash, NodeId peer, bool preferred) const { diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp index 92f1dbd5d80..79e4a21fe9b 100644 --- a/src/util/bytevectorhash.cpp +++ b/src/util/bytevectorhash.cpp @@ -9,8 +9,8 @@ #include ByteVectorHash::ByteVectorHash() : - m_k0(GetRand()), - m_k1(GetRand()) + m_k0(FastRandomContext().rand64()), + m_k1(FastRandomContext().rand64()) { } diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index f5717257865..3109ba02a8d 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -7,14 +7,18 @@ #include #include -SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand()), k1(GetRand()) {} +SaltedTxidHasher::SaltedTxidHasher() : + k0{FastRandomContext().rand64()}, + k1{FastRandomContext().rand64()} {} SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) : - k0(deterministic ? 0x8e819f2607a18de6 : GetRand()), - k1(deterministic ? 0xf4020d2e3983b0eb : GetRand()) + k0{deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64()}, + k1{deterministic ? 0xf4020d2e3983b0eb : FastRandomContext().rand64()} {} -SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand()), m_k1(GetRand()) {} +SaltedSipHasher::SaltedSipHasher() : + m_k0{FastRandomContext().rand64()}, + m_k1{FastRandomContext().rand64()} {} size_t SaltedSipHasher::operator()(const Span& script) const { diff --git a/src/validation.cpp b/src/validation.cpp index 3e9ba08bb16..7955a41f4f0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5193,7 +5193,7 @@ bool ChainstateManager::ShouldCheckBlockIndex() const { // Assert to verify Flatten() has been called. if (!*Assert(m_options.check_block_index)) return false; - if (GetRand(*m_options.check_block_index) >= 1) return false; + if (FastRandomContext().randrange(*m_options.check_block_index) >= 1) return false; return true; } From 82de1b80d95fc9447e64c098dcadb6b8a2f1f2ee Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 9 Jun 2024 08:13:01 -0400 Subject: [PATCH 15/23] net: use GetRandMicros for cache expiration This matches the data type of m_cache_entry_expiration. --- src/net.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net.cpp b/src/net.cpp index 990c58ee3d8..ac665400228 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3475,7 +3475,7 @@ std::vector CConnman::GetAddresses(CNode& requestor, size_t max_addres // nodes to be "terrible" (see IsTerrible()) if the timestamps are older than 30 days, // max. 24 hours of "penalty" due to cache shouldn't make any meaningful difference // in terms of the freshness of the response. - cache_entry.m_cache_entry_expiration = current_time + std::chrono::hours(21) + GetRandMillis(std::chrono::hours(6)); + cache_entry.m_cache_entry_expiration = current_time + std::chrono::hours(21) + GetRandMicros(std::chrono::hours(6)); } return cache_entry.m_addrs_response_cache; } From 4eaa239dc3e189369d59144b524cb2808cbef8c3 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 11 Mar 2024 10:17:20 -0400 Subject: [PATCH 16/23] random: convert GetRand{Micros,Millis} into randrange There are only a few call sites of these throughout the codebase, so move the functionality into FastRandomContext, and rewrite all call sites. This requires the callers to explicit construct FastRandomContext objects, which do add to the verbosity, but also make potentially apparent locations where the code can be improved by reusing a FastRandomContext object (see further commit). --- src/net.cpp | 3 ++- src/net_processing.cpp | 6 +++--- src/random.h | 33 +++++++++++++++++++-------------- src/test/random_tests.cpp | 24 ++++++++++++------------ 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index ac665400228..9e969718b7d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3475,7 +3475,8 @@ std::vector CConnman::GetAddresses(CNode& requestor, size_t max_addres // nodes to be "terrible" (see IsTerrible()) if the timestamps are older than 30 days, // max. 24 hours of "penalty" due to cache shouldn't make any meaningful difference // in terms of the freshness of the response. - cache_entry.m_cache_entry_expiration = current_time + std::chrono::hours(21) + GetRandMicros(std::chrono::hours(6)); + cache_entry.m_cache_entry_expiration = current_time + + 21h + FastRandomContext().randrange(6h); } return cache_entry.m_addrs_response_cache; } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index d674811ba73..ce3ff71df15 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1698,7 +1698,7 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler) // Schedule next run for 10-15 minutes in the future. // We add randomness on every cycle to avoid the possibility of P2P fingerprinting. - const std::chrono::milliseconds delta = 10min + GetRandMillis(5min); + const auto delta = 10min + FastRandomContext().randrange(5min); scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); } @@ -2050,7 +2050,7 @@ void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler) scheduler.scheduleEvery([this] { this->CheckForStaleTipAndEvictPeers(); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL}); // schedule next run for 10-15 minutes in the future - const std::chrono::milliseconds delta = 10min + GetRandMillis(5min); + const auto delta = 10min + FastRandomContext().randrange(5min); scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); } @@ -5753,7 +5753,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < peer.m_next_send_feefilter && (currentFilter < 3 * peer.m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_fee_filter_sent / 3)) { - peer.m_next_send_feefilter = current_time + GetRandomDuration(MAX_FEEFILTER_CHANGE_DELAY); + peer.m_next_send_feefilter = current_time + FastRandomContext().randrange(MAX_FEEFILTER_CHANGE_DELAY); } } diff --git a/src/random.h b/src/random.h index 1573e49ef7b..b9cba1d6029 100644 --- a/src/random.h +++ b/src/random.h @@ -132,6 +132,13 @@ concept RandomNumberGenerator = requires(T& rng, Span s) { requires std::derived_from, RandomMixin>>; }; +/** A concept for C++ std::chrono durations. */ +template +concept StdChronoDuration = requires { + [](std::type_identity>){}( + std::type_identity()); +}; + /** Mixin class that provides helper randomness functions. * * Intended to be used through CRTP: https://en.cppreference.com/w/cpp/language/crtp. @@ -300,7 +307,7 @@ public: } /** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */ - template + template requires StdChronoDuration typename Chrono::duration rand_uniform_duration(typename Chrono::duration range) noexcept { using Dur = typename Chrono::duration; @@ -309,6 +316,17 @@ public: /* interval [0..0] */ Dur{0}; }; + /** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ + template + Dur randrange(typename std::common_type_t range) noexcept + // Having the compiler infer the template argument from the function argument + // is dangerous, because the desired return value generally has a different + // type than the function argument. So std::common_type is used to force the + // call site to specify the type of the return value. + { + return Dur{Impl().randrange(range.count())}; + } + // Compatibility with the UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() noexcept { return 0; } @@ -426,19 +444,6 @@ void Shuffle(I first, I last, R&& rng) } } -/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ -template -D GetRandomDuration(typename std::common_type::type max) noexcept -// Having the compiler infer the template argument from the function argument -// is dangerous, because the desired return value generally has a different -// type than the function argument. So std::common_type is used to force the -// call site to specify the type of the return value. -{ - return D{FastRandomContext().randrange(max.count())}; -}; -constexpr auto GetRandMicros = GetRandomDuration; -constexpr auto GetRandMillis = GetRandomDuration; - /* Number of random bytes returned by GetOSRand. * When changing this constant make sure to change all call sites, and make * sure that the underlying OS APIs for all platforms support the number. diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 6932e186d56..b7479d310cc 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -30,18 +30,18 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests_deterministic) { BOOST_CHECK_EQUAL(FastRandomContext().rand(), uint64_t{9330418229102544152u}); BOOST_CHECK_EQUAL(FastRandomContext().rand(), int{618925161}); - BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 1271170921); - BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2803534); + BOOST_CHECK_EQUAL(FastRandomContext().randrange(1h).count(), 1271170921); + BOOST_CHECK_EQUAL(FastRandomContext().randrange(1h).count(), 2803534); BOOST_CHECK_EQUAL(FastRandomContext().rand(), uint64_t{10170981140880778086u}); BOOST_CHECK_EQUAL(FastRandomContext().rand(), int{1689082725}); - BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2464643716); - BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2312205); + BOOST_CHECK_EQUAL(FastRandomContext().randrange(1h).count(), 2464643716); + BOOST_CHECK_EQUAL(FastRandomContext().randrange(1h).count(), 2312205); BOOST_CHECK_EQUAL(FastRandomContext().rand(), uint64_t{5689404004456455543u}); BOOST_CHECK_EQUAL(FastRandomContext().rand(), int{785839937}); - BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 93558804); - BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 507022); + BOOST_CHECK_EQUAL(FastRandomContext().randrange(1h).count(), 93558804); + BOOST_CHECK_EQUAL(FastRandomContext().randrange(1h).count(), 507022); } { @@ -84,18 +84,18 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests_nondeterministic) { BOOST_CHECK(FastRandomContext().rand() != uint64_t{9330418229102544152u}); BOOST_CHECK(FastRandomContext().rand() != int{618925161}); - BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 1271170921); - BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2803534); + BOOST_CHECK(FastRandomContext().randrange(1h).count() != 1271170921); + BOOST_CHECK(FastRandomContext().randrange(1h).count() != 2803534); BOOST_CHECK(FastRandomContext().rand() != uint64_t{10170981140880778086u}); BOOST_CHECK(FastRandomContext().rand() != int{1689082725}); - BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 2464643716); - BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2312205); + BOOST_CHECK(FastRandomContext().randrange(1h).count() != 2464643716); + BOOST_CHECK(FastRandomContext().randrange(1h).count() != 2312205); BOOST_CHECK(FastRandomContext().rand() != uint64_t{5689404004456455543u}); BOOST_CHECK(FastRandomContext().rand() != int{785839937}); - BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 93558804); - BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 507022); + BOOST_CHECK(FastRandomContext().randrange(1h).count() != 93558804); + BOOST_CHECK(FastRandomContext().randrange(1h).count() != 507022); } { From cfb0dfe2cf0b46f3ea9e62992ade989860f086c8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 11 Mar 2024 10:44:35 -0400 Subject: [PATCH 17/23] random: convert GetExponentialRand into rand_exp_duration --- src/net.cpp | 12 ++++++------ src/net_processing.cpp | 10 +++++----- src/random.cpp | 5 ++--- src/random.h | 31 ++++++++++++++++++++----------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 9e969718b7d..034e290dc5a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2481,9 +2481,9 @@ void CConnman::ThreadOpenConnections(const std::vector connect) auto start = GetTime(); // Minimum time before next feeler connection (in microseconds). - auto next_feeler = GetExponentialRand(start, FEELER_INTERVAL); - auto next_extra_block_relay = GetExponentialRand(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); - auto next_extra_network_peer{GetExponentialRand(start, EXTRA_NETWORK_PEER_INTERVAL)}; + auto next_feeler = start + FastRandomContext().rand_exp_duration(FEELER_INTERVAL); + auto next_extra_block_relay = start + FastRandomContext().rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + auto next_extra_network_peer{start + FastRandomContext().rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)}; const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED); bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS); const bool use_seednodes{gArgs.IsArgSet("-seednode")}; @@ -2642,10 +2642,10 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // Because we can promote these connections to block-relay-only // connections, they do not get their own ConnectionType enum // (similar to how we deal with extra outbound peers). - next_extra_block_relay = GetExponentialRand(now, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + next_extra_block_relay = now + FastRandomContext().rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); conn_type = ConnectionType::BLOCK_RELAY; } else if (now > next_feeler) { - next_feeler = GetExponentialRand(now, FEELER_INTERVAL); + next_feeler = now + FastRandomContext().rand_exp_duration(FEELER_INTERVAL); conn_type = ConnectionType::FEELER; fFeeler = true; } else if (nOutboundFullRelay == m_max_outbound_full_relay && @@ -2658,7 +2658,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // This is not attempted if the user changed -maxconnections to a value // so low that less than MAX_OUTBOUND_FULL_RELAY_CONNECTIONS are made, // to prevent interactions with otherwise protected outbound peers. - next_extra_network_peer = GetExponentialRand(now, EXTRA_NETWORK_PEER_INTERVAL); + next_extra_network_peer = now + FastRandomContext().rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL); } else { // skip to next iteration of while loop continue; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index ce3ff71df15..dad81997839 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1244,7 +1244,7 @@ std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::micros // If this function were called from multiple threads simultaneously // it would possible that both update the next send variable, and return a different result to their caller. // This is not possible in practice as only the net processing thread invokes this function. - m_next_inv_to_inbounds = GetExponentialRand(now, average_interval); + m_next_inv_to_inbounds = now + FastRandomContext().rand_exp_duration(average_interval); } return m_next_inv_to_inbounds; } @@ -5654,13 +5654,13 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros CAddress local_addr{*local_service, peer.m_our_services, Now()}; PushAddress(peer, local_addr); } - peer.m_next_local_addr_send = GetExponentialRand(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); + peer.m_next_local_addr_send = current_time + FastRandomContext().rand_exp_duration(AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); } // We sent an `addr` message to this peer recently. Nothing more to do. if (current_time <= peer.m_next_addr_send) return; - peer.m_next_addr_send = GetExponentialRand(current_time, AVG_ADDRESS_BROADCAST_INTERVAL); + peer.m_next_addr_send = current_time + FastRandomContext().rand_exp_duration(AVG_ADDRESS_BROADCAST_INTERVAL); if (!Assume(peer.m_addrs_to_send.size() <= MAX_ADDR_TO_SEND)) { // Should be impossible since we always check size before adding to @@ -5747,7 +5747,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi MakeAndPushMessage(pto, NetMsgType::FEEFILTER, filterToSend); peer.m_fee_filter_sent = filterToSend; } - peer.m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); + peer.m_next_send_feefilter = current_time + FastRandomContext().rand_exp_duration(AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. @@ -6059,7 +6059,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pto->IsInboundConn()) { tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); } else { - tx_relay->m_next_inv_send_time = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); + tx_relay->m_next_inv_send_time = current_time + FastRandomContext().rand_exp_duration(OUTBOUND_INVENTORY_BROADCAST_INTERVAL); } } diff --git a/src/random.cpp b/src/random.cpp index 5067bf259c3..530dfff1ed7 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -773,8 +773,7 @@ void RandomInit() ReportHardwareRand(); } -std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval) +double MakeExponentiallyDistributed(uint64_t uniform) noexcept { - double unscaled = -std::log1p(FastRandomContext().randbits<48>() * -0.0000000000000035527136788 /* -1/2^48 */); - return now + std::chrono::duration_cast(unscaled * average_interval + 0.5us); + return -std::log1p((uniform >> 16) * -0.0000000000000035527136788 /* -1/2^48 */); } diff --git a/src/random.h b/src/random.h index b9cba1d6029..821c84fce3a 100644 --- a/src/random.h +++ b/src/random.h @@ -81,17 +81,6 @@ */ void GetRandBytes(Span bytes) noexcept; -/** - * Return a timestamp in the future sampled from an exponential distribution - * (https://en.wikipedia.org/wiki/Exponential_distribution). This distribution - * is memoryless and should be used for repeated network events (e.g. sending a - * certain type of message) to minimize leaking information to observers. - * - * The probability of an event occurring before time x is 1 - e^-(x/a) where a - * is the average interval between events. - * */ -std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval); - uint256 GetRandHash() noexcept; /** @@ -139,6 +128,9 @@ concept StdChronoDuration = requires { std::type_identity()); }; +/** Given a uniformly random uint64_t, return an exponentially distributed double with mean 1. */ +double MakeExponentiallyDistributed(uint64_t uniform) noexcept; + /** Mixin class that provides helper randomness functions. * * Intended to be used through CRTP: https://en.cppreference.com/w/cpp/language/crtp. @@ -327,6 +319,23 @@ public: return Dur{Impl().randrange(range.count())}; } + /** + * Return a duration sampled from an exponential distribution + * (https://en.wikipedia.org/wiki/Exponential_distribution). Successive events + * whose intervals are distributed according to this form a memoryless Poisson + * process. This should be used for repeated network events (e.g. sending a + * certain type of message) to minimize leaking information to observers. + * + * The probability of an event occurring before time x is 1 - e^-(x/a) where a + * is the average interval between events. + * */ + std::chrono::microseconds rand_exp_duration(std::chrono::microseconds mean) noexcept + { + using namespace std::chrono_literals; + auto unscaled = MakeExponentiallyDistributed(Impl().rand64()); + return std::chrono::duration_cast(unscaled * mean + 0.5us); + } + // Compatibility with the UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() noexcept { return 0; } From d5fcbe966bc501db8bf6a3809633f0b82e6ae547 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 18 Jun 2024 12:39:56 -0400 Subject: [PATCH 18/23] random: improve precision of MakeExponentiallyDistributed --- src/random.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/random.cpp b/src/random.cpp index 530dfff1ed7..cb5c127e0da 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -775,5 +775,14 @@ void RandomInit() double MakeExponentiallyDistributed(uint64_t uniform) noexcept { - return -std::log1p((uniform >> 16) * -0.0000000000000035527136788 /* -1/2^48 */); + // To convert uniform into an exponentially-distributed double, we use two steps: + // - Convert uniform into a uniformly-distributed double in range [0, 1), use the expression + // ((uniform >> 11) * 0x1.0p-53), as described in https://prng.di.unimi.it/ under + // "Generating uniform doubles in the unit interval". Call this value x. + // - Given an x in uniformly distributed in [0, 1), we find an exponentially distributed value + // by applying the quantile function to it. For the exponential distribution with mean 1 this + // is F(x) = -log(1 - x). + // + // Combining the two, and using log1p(x) = log(1 + x), we obtain the following: + return -std::log1p((uniform >> 11) * -0x1.0p-53); } From 8e31cf9c9b5e9fdd01e8b220c08a3ccde5cf584c Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 18 Jun 2024 11:23:10 -0400 Subject: [PATCH 19/23] net, net_processing: use existing RNG objects more PeerManagerImpl, as well as several net functions, already have existing FastRandomContext objects. Reuse them instead of constructing new ones. --- src/net.cpp | 12 ++++++------ src/net_processing.cpp | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 034e290dc5a..1a251d6f95a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2481,9 +2481,9 @@ void CConnman::ThreadOpenConnections(const std::vector connect) auto start = GetTime(); // Minimum time before next feeler connection (in microseconds). - auto next_feeler = start + FastRandomContext().rand_exp_duration(FEELER_INTERVAL); - auto next_extra_block_relay = start + FastRandomContext().rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); - auto next_extra_network_peer{start + FastRandomContext().rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)}; + auto next_feeler = start + rng.rand_exp_duration(FEELER_INTERVAL); + auto next_extra_block_relay = start + rng.rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + auto next_extra_network_peer{start + rng.rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)}; const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED); bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS); const bool use_seednodes{gArgs.IsArgSet("-seednode")}; @@ -2642,10 +2642,10 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // Because we can promote these connections to block-relay-only // connections, they do not get their own ConnectionType enum // (similar to how we deal with extra outbound peers). - next_extra_block_relay = now + FastRandomContext().rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + next_extra_block_relay = now + rng.rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); conn_type = ConnectionType::BLOCK_RELAY; } else if (now > next_feeler) { - next_feeler = now + FastRandomContext().rand_exp_duration(FEELER_INTERVAL); + next_feeler = now + rng.rand_exp_duration(FEELER_INTERVAL); conn_type = ConnectionType::FEELER; fFeeler = true; } else if (nOutboundFullRelay == m_max_outbound_full_relay && @@ -2658,7 +2658,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // This is not attempted if the user changed -maxconnections to a value // so low that less than MAX_OUTBOUND_FULL_RELAY_CONNECTIONS are made, // to prevent interactions with otherwise protected outbound peers. - next_extra_network_peer = now + FastRandomContext().rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL); + next_extra_network_peer = now + rng.rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL); } else { // skip to next iteration of while loop continue; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index dad81997839..8137e17c980 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -936,7 +936,7 @@ private: * accurately determine when we received the transaction (and potentially * determine the transaction's origin). */ std::chrono::microseconds NextInvToInbounds(std::chrono::microseconds now, - std::chrono::seconds average_interval); + std::chrono::seconds average_interval) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); // All of the following cache a recent block, and are protected by m_most_recent_block_mutex @@ -1244,7 +1244,7 @@ std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::micros // If this function were called from multiple threads simultaneously // it would possible that both update the next send variable, and return a different result to their caller. // This is not possible in practice as only the net processing thread invokes this function. - m_next_inv_to_inbounds = now + FastRandomContext().rand_exp_duration(average_interval); + m_next_inv_to_inbounds = now + m_rng.rand_exp_duration(average_interval); } return m_next_inv_to_inbounds; } @@ -5654,13 +5654,13 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros CAddress local_addr{*local_service, peer.m_our_services, Now()}; PushAddress(peer, local_addr); } - peer.m_next_local_addr_send = current_time + FastRandomContext().rand_exp_duration(AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); + peer.m_next_local_addr_send = current_time + m_rng.rand_exp_duration(AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); } // We sent an `addr` message to this peer recently. Nothing more to do. if (current_time <= peer.m_next_addr_send) return; - peer.m_next_addr_send = current_time + FastRandomContext().rand_exp_duration(AVG_ADDRESS_BROADCAST_INTERVAL); + peer.m_next_addr_send = current_time + m_rng.rand_exp_duration(AVG_ADDRESS_BROADCAST_INTERVAL); if (!Assume(peer.m_addrs_to_send.size() <= MAX_ADDR_TO_SEND)) { // Should be impossible since we always check size before adding to @@ -5747,13 +5747,13 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi MakeAndPushMessage(pto, NetMsgType::FEEFILTER, filterToSend); peer.m_fee_filter_sent = filterToSend; } - peer.m_next_send_feefilter = current_time + FastRandomContext().rand_exp_duration(AVG_FEEFILTER_BROADCAST_INTERVAL); + peer.m_next_send_feefilter = current_time + m_rng.rand_exp_duration(AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < peer.m_next_send_feefilter && (currentFilter < 3 * peer.m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_fee_filter_sent / 3)) { - peer.m_next_send_feefilter = current_time + FastRandomContext().randrange(MAX_FEEFILTER_CHANGE_DELAY); + peer.m_next_send_feefilter = current_time + m_rng.randrange(MAX_FEEFILTER_CHANGE_DELAY); } } @@ -6059,7 +6059,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pto->IsInboundConn()) { tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); } else { - tx_relay->m_next_inv_send_time = current_time + FastRandomContext().rand_exp_duration(OUTBOUND_INVENTORY_BROADCAST_INTERVAL); + tx_relay->m_next_inv_send_time = current_time + m_rng.rand_exp_duration(OUTBOUND_INVENTORY_BROADCAST_INTERVAL); } } From 2c91330dd68064e402e8eceea3df9474bb7afd48 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 11 Mar 2024 14:10:44 -0400 Subject: [PATCH 20/23] random: cleanup order, comments, static --- src/random.cpp | 79 ++++++++++++----------- src/random.h | 111 ++++++++++++++++++++------------- src/test/cuckoocache_tests.cpp | 2 +- 3 files changed, 109 insertions(+), 83 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index cb5c127e0da..21a08c7fd39 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -45,13 +45,23 @@ #include #endif -[[noreturn]] static void RandFailure() +namespace { + +/* Number of random bytes returned by GetOSRand. + * When changing this constant make sure to change all call sites, and make + * sure that the underlying OS APIs for all platforms support the number. + * (many cap out at 256 bytes). + */ +static const int NUM_OS_RANDOM_BYTES = 32; + + +[[noreturn]] void RandFailure() { LogPrintf("Failed to read randomness, aborting\n"); std::abort(); } -static inline int64_t GetPerformanceCounter() noexcept +inline int64_t GetPerformanceCounter() noexcept { // Read the hardware time stamp counter when available. // See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information. @@ -72,10 +82,10 @@ static inline int64_t GetPerformanceCounter() noexcept } #ifdef HAVE_GETCPUID -static bool g_rdrand_supported = false; -static bool g_rdseed_supported = false; -static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; -static constexpr uint32_t CPUID_F7_EBX_RDSEED = 0x00040000; +bool g_rdrand_supported = false; +bool g_rdseed_supported = false; +constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; +constexpr uint32_t CPUID_F7_EBX_RDSEED = 0x00040000; #ifdef bit_RDRND static_assert(CPUID_F1_ECX_RDRAND == bit_RDRND, "Unexpected value for bit_RDRND"); #endif @@ -83,7 +93,7 @@ static_assert(CPUID_F1_ECX_RDRAND == bit_RDRND, "Unexpected value for bit_RDRND" static_assert(CPUID_F7_EBX_RDSEED == bit_RDSEED, "Unexpected value for bit_RDSEED"); #endif -static void InitHardwareRand() +void InitHardwareRand() { uint32_t eax, ebx, ecx, edx; GetCPUID(1, 0, eax, ebx, ecx, edx); @@ -96,7 +106,7 @@ static void InitHardwareRand() } } -static void ReportHardwareRand() +void ReportHardwareRand() { // This must be done in a separate function, as InitHardwareRand() may be indirectly called // from global constructors, before logging is initialized. @@ -112,7 +122,7 @@ static void ReportHardwareRand() * * Must only be called when RdRand is supported. */ -static uint64_t GetRdRand() noexcept +uint64_t GetRdRand() noexcept { // RdRand may very rarely fail. Invoke it up to 10 times in a loop to reduce this risk. #ifdef __i386__ @@ -147,7 +157,7 @@ static uint64_t GetRdRand() noexcept * * Must only be called when RdSeed is supported. */ -static uint64_t GetRdSeed() noexcept +uint64_t GetRdSeed() noexcept { // RdSeed may fail when the HW RNG is overloaded. Loop indefinitely until enough entropy is gathered, // but pause after every failure. @@ -181,16 +191,16 @@ static uint64_t GetRdSeed() noexcept #elif defined(__aarch64__) && defined(HWCAP2_RNG) -static bool g_rndr_supported = false; +bool g_rndr_supported = false; -static void InitHardwareRand() +void InitHardwareRand() { if (getauxval(AT_HWCAP2) & HWCAP2_RNG) { g_rndr_supported = true; } } -static void ReportHardwareRand() +void ReportHardwareRand() { // This must be done in a separate function, as InitHardwareRand() may be indirectly called // from global constructors, before logging is initialized. @@ -203,7 +213,7 @@ static void ReportHardwareRand() * * Must only be called when RNDR is supported. */ -static uint64_t GetRNDR() noexcept +uint64_t GetRNDR() noexcept { uint8_t ok; uint64_t r1; @@ -221,7 +231,7 @@ static uint64_t GetRNDR() noexcept * * Must only be called when RNDRRS is supported. */ -static uint64_t GetRNDRRS() noexcept +uint64_t GetRNDRRS() noexcept { uint8_t ok; uint64_t r1; @@ -241,12 +251,12 @@ static uint64_t GetRNDRRS() noexcept * Slower sources should probably be invoked separately, and/or only from * RandAddPeriodic (which is called once a minute). */ -static void InitHardwareRand() {} -static void ReportHardwareRand() {} +void InitHardwareRand() {} +void ReportHardwareRand() {} #endif /** Add 64 bits of entropy gathered from hardware to hasher. Do nothing if not supported. */ -static void SeedHardwareFast(CSHA512& hasher) noexcept { +void SeedHardwareFast(CSHA512& hasher) noexcept { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) if (g_rdrand_supported) { uint64_t out = GetRdRand(); @@ -263,7 +273,7 @@ static void SeedHardwareFast(CSHA512& hasher) noexcept { } /** Add 256 bits of entropy gathered from hardware to hasher. Do nothing if not supported. */ -static void SeedHardwareSlow(CSHA512& hasher) noexcept { +void SeedHardwareSlow(CSHA512& hasher) noexcept { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) // When we want 256 bits of entropy, prefer RdSeed over RdRand, as it's // guaranteed to produce independent randomness on every call. @@ -296,7 +306,7 @@ static void SeedHardwareSlow(CSHA512& hasher) noexcept { } /** Use repeated SHA512 to strengthen the randomness in seed32, and feed into hasher. */ -static void Strengthen(const unsigned char (&seed)[32], SteadyClock::duration dur, CSHA512& hasher) noexcept +void Strengthen(const unsigned char (&seed)[32], SteadyClock::duration dur, CSHA512& hasher) noexcept { CSHA512 inner_hasher; inner_hasher.Write(seed, sizeof(seed)); @@ -327,7 +337,7 @@ static void Strengthen(const unsigned char (&seed)[32], SteadyClock::duration du /** Fallback: get 32 bytes of system entropy from /dev/urandom. The most * compatible way to get cryptographic randomness on UNIX-ish platforms. */ -[[maybe_unused]] static void GetDevURandom(unsigned char *ent32) +[[maybe_unused]] void GetDevURandom(unsigned char *ent32) { int f = open("/dev/urandom", O_RDONLY); if (f == -1) { @@ -402,8 +412,6 @@ void GetOSRand(unsigned char *ent32) #endif } -namespace { - class RNGState { Mutex m_mutex; /* The RNG state consists of 256 bits of entropy, taken from the output of @@ -521,20 +529,19 @@ RNGState& GetRNGState() noexcept static std::vector> g_rng(1); return g_rng[0]; } -} /* A note on the use of noexcept in the seeding functions below: * * None of the RNG code should ever throw any exception. */ -static void SeedTimestamp(CSHA512& hasher) noexcept +void SeedTimestamp(CSHA512& hasher) noexcept { int64_t perfcounter = GetPerformanceCounter(); hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); } -static void SeedFast(CSHA512& hasher) noexcept +void SeedFast(CSHA512& hasher) noexcept { unsigned char buffer[32]; @@ -549,7 +556,7 @@ static void SeedFast(CSHA512& hasher) noexcept SeedTimestamp(hasher); } -static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept +void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept { unsigned char buffer[32]; @@ -571,7 +578,7 @@ static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept } /** Extract entropy from rng, strengthen it, and feed it into hasher. */ -static void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration dur) noexcept +void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration dur) noexcept { // Generate 32 bytes of entropy from the RNG, and a copy of the entropy already in hasher. // Never use the deterministic PRNG for this, as the result is only used internally. @@ -581,7 +588,7 @@ static void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration Strengthen(strengthen_seed, dur, hasher); } -static void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept +void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept { // Everything that the 'fast' seeder includes SeedFast(hasher); @@ -601,7 +608,7 @@ static void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept SeedStrengthen(hasher, rng, 10ms); } -static void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept +void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept { // Gather 256 bits of hardware randomness, if available SeedHardwareSlow(hasher); @@ -627,7 +634,7 @@ enum class RNGLevel { PERIODIC, //!< Called by RandAddPeriodic() }; -static void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_use_real_rng) noexcept +void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_use_real_rng) noexcept { // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available). RNGState& rng = GetRNGState(); @@ -656,6 +663,9 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_us } } +} // namespace + + /** Internal function to set g_determinstic_rng. Only accessed from tests. */ void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept { @@ -679,13 +689,6 @@ void RandAddPeriodic() noexcept void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } -uint256 GetRandHash() noexcept -{ - uint256 hash; - GetRandBytes(hash); - return hash; -} - void FastRandomContext::RandomSeed() noexcept { uint256 seed = GetRandHash(); diff --git a/src/random.h b/src/random.h index 821c84fce3a..ea517d2d2e2 100644 --- a/src/random.h +++ b/src/random.h @@ -28,8 +28,8 @@ * The following (classes of) functions interact with that state by mixing in new * entropy, and optionally extracting random output from it: * - * - The GetRand*() class of functions, as well as construction of FastRandomContext objects, - * perform 'fast' seeding, consisting of mixing in: + * - GetRandBytes, GetRandHash, GetRandDur, as well as construction of FastRandomContext + * objects, perform 'fast' seeding, consisting of mixing in: * - A stack pointer (indirectly committing to calling thread and call stack) * - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise) * - 64 bits from the hardware RNG (rdrand) when available. @@ -38,7 +38,7 @@ * FastRandomContext on the other hand does not protect against this once created, but * is even faster (and acceptable to use inside tight loops). * - * - The GetStrongRand*() class of function perform 'slow' seeding, including everything + * - The GetStrongRandBytes() function performs 'slow' seeding, including everything * that fast seeding includes, but additionally: * - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if * this entropy source fails. @@ -53,12 +53,12 @@ * - Strengthen the entropy for 10 ms using repeated SHA512. * This is run once every minute. * - * On first use of the RNG (regardless of what function is called first), all entropy - * sources used in the 'slow' seeder are included, but also: - * - 256 bits from the hardware RNG (rdseed or rdrand) when available. - * - Dynamic environment data (performance monitoring, ...) - * - Static environment data - * - Strengthen the entropy for 100 ms using repeated SHA512. + * - On first use of the RNG (regardless of what function is called first), all entropy + * sources used in the 'slow' seeder are included, but also: + * - 256 bits from the hardware RNG (rdseed or rdrand) when available. + * - Dynamic environment data (performance monitoring, ...) + * - Static environment data + * - Strengthen the entropy for 100 ms using repeated SHA512. * * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and * (up to) the first 32 bytes of H are produced as output, while the last 32 bytes @@ -71,27 +71,16 @@ * only depends on the seed it was initialized with, possibly until it is reinitialized. */ -/** - * Generate random data via the internal PRNG. - * - * These functions are designed to be fast (sub microsecond), but do not necessarily - * meaningfully add entropy to the PRNG state. - * - * Thread-safe. - */ -void GetRandBytes(Span bytes) noexcept; -uint256 GetRandHash() noexcept; +/* ============================= INITIALIZATION AND ADDING ENTROPY ============================= */ /** - * Gather entropy from various sources, feed it into the internal PRNG, and - * generate random data using it. + * Initialize global RNG state and log any CPU features that are used. * - * This function will cause failure whenever the OS RNG fails. - * - * Thread-safe. + * Calling this function is optional. RNG state will be initialized when first + * needed if it is not called. */ -void GetStrongRandBytes(Span bytes) noexcept; +void RandomInit(); /** * Gather entropy from various expensive sources, and feed them to the PRNG state. @@ -108,6 +97,47 @@ void RandAddPeriodic() noexcept; */ void RandAddEvent(const uint32_t event_info) noexcept; + +/* =========================== BASE RANDOMNESS GENERATION FUNCTIONS =========================== + * + * All produced randomness is eventually generated by one of these functions. + */ + +/** + * Generate random data via the internal PRNG. + * + * These functions are designed to be fast (sub microsecond), but do not necessarily + * meaningfully add entropy to the PRNG state. + * + * In test mode (see SeedRandomForTest in src/test/util/random.h), the normal PRNG state is + * bypassed, and a deterministic, seeded, PRNG is used instead. + * + * Thread-safe. + */ +void GetRandBytes(Span bytes) noexcept; + +/** + * Gather entropy from various sources, feed it into the internal PRNG, and + * generate random data using it. + * + * This function will cause failure whenever the OS RNG fails. + * + * The normal PRNG is never bypassed here, even in test mode. + * + * Thread-safe. + */ +void GetStrongRandBytes(Span bytes) noexcept; + + +/* ============================= RANDOM NUMBER GENERATION CLASSES ============================= + * + * In this section, 3 classes are defined: + * - RandomMixin: a base class that adds functionality to all RNG classes. + * - FastRandomContext: a cryptographic RNG (seeded through GetRandBytes in its default + * constructor). + * - InsecureRandomContext: a non-cryptographic, very fast, RNG. + */ + // Forward declaration of RandomMixin, used in RandomNumberGenerator concept. template class RandomMixin; @@ -430,6 +460,17 @@ public: } }; + +/* ==================== CONVENIENCE FUNCTIONS FOR COMMONLY USED RANDOMNESS ==================== */ + +/** Generate a random uint256. */ +inline uint256 GetRandHash() noexcept +{ + uint256 hash; + GetRandBytes(hash); + return hash; +} + /** More efficient than using std::shuffle on a FastRandomContext. * * This is more efficient as std::shuffle will consume entropy in groups of @@ -453,29 +494,11 @@ void Shuffle(I first, I last, R&& rng) } } -/* Number of random bytes returned by GetOSRand. - * When changing this constant make sure to change all call sites, and make - * sure that the underlying OS APIs for all platforms support the number. - * (many cap out at 256 bytes). - */ -static const int NUM_OS_RANDOM_BYTES = 32; - -/** Get 32 bytes of system entropy. Do not use this in application code: use - * GetStrongRandBytes instead. - */ -void GetOSRand(unsigned char* ent32); +/* ============================= MISCELLANEOUS TEST-ONLY FUNCTIONS ============================= */ /** Check that OS randomness is available and returning the requested number * of bytes. */ bool Random_SanityCheck(); -/** - * Initialize global RNG state and log any CPU features that are used. - * - * Calling this function is optional. RNG state will be initialized when first - * needed if it is not called. - */ -void RandomInit(); - #endif // BITCOIN_RANDOM_H diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index 906fbb4afa5..fc22daeb57b 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -33,7 +33,7 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests); /* Test that no values not inserted into the cache are read out of it. * - * There are no repeats in the first 200000 insecure_GetRandHash calls + * There are no repeats in the first 200000 InsecureRand256() calls */ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) { From 97e16f57042cab07e5e73f6bed19feec2006e4f7 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 15 Mar 2024 14:32:20 -0400 Subject: [PATCH 21/23] tests: make fuzz tests (mostly) deterministic with fixed seed --- src/test/fuzz/fuzz.cpp | 6 ++++++ src/test/util/setup_common.cpp | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index 115cf2fc997..80652d5dd1a 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -102,6 +102,12 @@ void ResetCoverageCounters() {} void initialize() { + // By default, make the RNG deterministic with a fixed seed. This will affect all + // randomness during the fuzz test, except: + // - GetStrongRandBytes(), which is used for the creation of private key material. + // - Creating a BasicTestingSetup or derived class will switch to a random seed. + SeedRandomForTest(SeedRand::ZEROS); + // Terminate immediately if a fuzzing harness ever tries to create a socket. // Individual tests can override this by pointing CreateSock to a mocked alternative. CreateSock = [](int, int, int) -> std::unique_ptr { std::terminate(); }; diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 283e19971c0..52981bd2dca 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -145,6 +145,10 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto } } + // Use randomly chosen seed for deterministic PRNG, so that (by default) test + // data directories use a random name that doesn't overlap with other tests. + SeedRandomForTest(SeedRand::SEED); + if (!m_node.args->IsArgSet("-testdatadir")) { // By default, the data directory has a random name const auto rand_str{g_insecure_rand_ctx_temp_path.rand256().ToString()}; @@ -178,7 +182,6 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root)); SelectParams(chainType); - SeedRandomForTest(); if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN); InitLogging(*m_node.args); AppInitParameterInteraction(*m_node.args); From 2ae392d561ecfdf81855e6df6b9ad3d8843cdfa2 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jun 2024 08:19:35 -0400 Subject: [PATCH 22/23] random: use LogError for init failure --- src/random.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/random.cpp b/src/random.cpp index 21a08c7fd39..4f930fdaa32 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -57,7 +57,7 @@ static const int NUM_OS_RANDOM_BYTES = 32; [[noreturn]] void RandFailure() { - LogPrintf("Failed to read randomness, aborting\n"); + LogError("Failed to read randomness, aborting\n"); std::abort(); } From ce8094246ee95232e9d84f7e37f3c0a43ef587ce Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 31 May 2024 10:39:23 -0400 Subject: [PATCH 23/23] random: replace construct/assign with explicit Reseed() --- src/random.cpp | 16 +++++------ src/random.h | 53 ++++++++++++++---------------------- src/test/fuzz/addrman.cpp | 2 +- src/test/orphanage_tests.cpp | 2 +- src/test/prevector_tests.cpp | 2 +- src/test/random_tests.cpp | 2 +- src/test/util/random.cpp | 2 +- 7 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 4f930fdaa32..7cb6098d540 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -704,6 +704,13 @@ void FastRandomContext::fillrand(Span output) noexcept FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), rng(MakeByteSpan(seed)) {} +void FastRandomContext::Reseed(const uint256& seed) noexcept +{ + FlushCache(); + requires_seed = false; + rng = {MakeByteSpan(seed)}; +} + bool Random_SanityCheck() { uint64_t start = GetPerformanceCounter(); @@ -759,15 +766,6 @@ FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_se // use. } -FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept -{ - requires_seed = from.requires_seed; - rng = from.rng; - from.requires_seed = true; - static_cast&>(*this) = std::move(from); - return *this; -} - void RandomInit() { // Invoke RNG code to trigger initialization (if not already performed) diff --git a/src/random.h b/src/random.h index ea517d2d2e2..8a6ef13d5ef 100644 --- a/src/random.h +++ b/src/random.h @@ -184,27 +184,21 @@ private: */ RandomNumberGenerator auto& Impl() noexcept { return static_cast(*this); } -public: - RandomMixin() noexcept = default; +protected: + constexpr void FlushCache() noexcept + { + bitbuf = 0; + bitbuf_size = 0; + } - // Do not permit copying an RNG. +public: + constexpr RandomMixin() noexcept = default; + + // Do not permit copying or moving an RNG. RandomMixin(const RandomMixin&) = delete; RandomMixin& operator=(const RandomMixin&) = delete; - - RandomMixin(RandomMixin&& other) noexcept : bitbuf(other.bitbuf), bitbuf_size(other.bitbuf_size) - { - other.bitbuf = 0; - other.bitbuf_size = 0; - } - - RandomMixin& operator=(RandomMixin&& other) noexcept - { - bitbuf = other.bitbuf; - bitbuf_size = other.bitbuf_size; - other.bitbuf = 0; - other.bitbuf_size = 0; - return *this; - } + RandomMixin(RandomMixin&&) = delete; + RandomMixin& operator=(RandomMixin&&) = delete; /** Generate a random (bits)-bit integer. */ uint64_t randbits(int bits) noexcept @@ -394,13 +388,8 @@ public: /** Initialize with explicit seed (only for testing) */ explicit FastRandomContext(const uint256& seed) noexcept; - // Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded). - FastRandomContext(const FastRandomContext&) = delete; - FastRandomContext(FastRandomContext&&) = delete; - FastRandomContext& operator=(const FastRandomContext&) = delete; - - /** Move a FastRandomContext. If the original one is used again, it will be reseeded. */ - FastRandomContext& operator=(FastRandomContext&& from) noexcept; + /** Reseed with explicit seed (only for testing). */ + void Reseed(const uint256& seed) noexcept; /** Generate a random 64-bit integer. */ uint64_t rand64() noexcept @@ -440,14 +429,12 @@ public: constexpr explicit InsecureRandomContext(uint64_t seedval) noexcept : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {} - // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams - // with exactly the same results. - InsecureRandomContext(const InsecureRandomContext&) = delete; - InsecureRandomContext& operator=(const InsecureRandomContext&) = delete; - - // allow moves - InsecureRandomContext(InsecureRandomContext&&) = default; - InsecureRandomContext& operator=(InsecureRandomContext&&) = default; + constexpr void Reseed(uint64_t seedval) noexcept + { + FlushCache(); + m_s0 = SplitMix64(seedval); + m_s1 = SplitMix64(seedval); + } constexpr uint64_t rand64() noexcept { diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 8a54cc656d3..dbec2bc858e 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -124,7 +124,7 @@ public: explicit AddrManDeterministic(const NetGroupManager& netgroupman, FuzzedDataProvider& fuzzed_data_provider) : AddrMan(netgroupman, /*deterministic=*/true, GetCheckRatio()) { - WITH_LOCK(m_impl->cs, m_impl->insecure_rand = FastRandomContext{ConsumeUInt256(fuzzed_data_provider)}); + WITH_LOCK(m_impl->cs, m_impl->insecure_rand.Reseed(ConsumeUInt256(fuzzed_data_provider))); } /** diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp index 450bf6a4fc7..3459aa9f0eb 100644 --- a/src/test/orphanage_tests.cpp +++ b/src/test/orphanage_tests.cpp @@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) // ecdsa_signature_parse_der_lax are executed during this test. // Specifically branches that run only when an ECDSA // signature's R and S values have leading zeros. - g_insecure_rand_ctx = FastRandomContext{uint256{33}}; + g_insecure_rand_ctx.Reseed(uint256{33}); TxOrphanageTest orphanage; CKey key; diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index 9abdd84c5a4..1ac7abf492e 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -212,7 +212,7 @@ public: prevector_tester() { SeedRandomForTest(); rand_seed = InsecureRand256(); - rand_cache = FastRandomContext(rand_seed); + rand_cache.Reseed(rand_seed); } }; diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index b7479d310cc..9fa7135b771 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(xoroshiro128plusplus_reference_values) BOOST_TEST(0x6ea7c59f89bbfc75 == rng()); // seed with a random number - rng = InsecureRandomContext(0x1a26f3fa8546b47a); + rng.Reseed(0x1a26f3fa8546b47a); BOOST_TEST(0xc8dc5e08d844ac7d == rng()); BOOST_TEST(0x5b5f1f6d499dad1b == rng()); BOOST_TEST(0xbeb0031f93313d6f == rng()); diff --git a/src/test/util/random.cpp b/src/test/util/random.cpp index aa8c16e8377..47d03055e28 100644 --- a/src/test/util/random.cpp +++ b/src/test/util/random.cpp @@ -34,5 +34,5 @@ void SeedRandomForTest(SeedRand seedtype) const uint256& seed{seedtype == SeedRand::SEED ? ctx_seed : uint256::ZERO}; LogPrintf("%s: Setting random seed for current tests to %s=%s\n", __func__, RANDOM_CTX_SEED, seed.GetHex()); MakeRandDeterministicDANGEROUS(seed); - g_insecure_rand_ctx = FastRandomContext(GetRandHash()); + g_insecure_rand_ctx.Reseed(GetRandHash()); }