mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-05-29 05:11:44 +02:00
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.
This commit is contained in:
parent
9b14d3d2da
commit
21ce9d8658
36
src/random.h
36
src/random.h
@ -10,6 +10,7 @@
|
|||||||
#include <crypto/common.h>
|
#include <crypto/common.h>
|
||||||
#include <span.h>
|
#include <span.h>
|
||||||
#include <uint256.h>
|
#include <uint256.h>
|
||||||
|
#include <util/check.h>
|
||||||
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@ -165,7 +166,7 @@ template<typename T>
|
|||||||
class RandomMixin
|
class RandomMixin
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
uint64_t bitbuf;
|
uint64_t bitbuf{0};
|
||||||
int bitbuf_size{0};
|
int bitbuf_size{0};
|
||||||
|
|
||||||
/** Access the underlying generator.
|
/** Access the underlying generator.
|
||||||
@ -175,12 +176,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
RandomNumberGenerator auto& Impl() noexcept { return static_cast<T&>(*this); }
|
RandomNumberGenerator auto& Impl() noexcept { return static_cast<T&>(*this); }
|
||||||
|
|
||||||
void FillBitBuffer() noexcept
|
|
||||||
{
|
|
||||||
bitbuf = Impl().rand64();
|
|
||||||
bitbuf_size = 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RandomMixin() noexcept = default;
|
RandomMixin() noexcept = default;
|
||||||
|
|
||||||
@ -190,6 +185,7 @@ public:
|
|||||||
|
|
||||||
RandomMixin(RandomMixin&& other) noexcept : bitbuf(other.bitbuf), bitbuf_size(other.bitbuf_size)
|
RandomMixin(RandomMixin&& other) noexcept : bitbuf(other.bitbuf), bitbuf_size(other.bitbuf_size)
|
||||||
{
|
{
|
||||||
|
other.bitbuf = 0;
|
||||||
other.bitbuf_size = 0;
|
other.bitbuf_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +193,7 @@ public:
|
|||||||
{
|
{
|
||||||
bitbuf = other.bitbuf;
|
bitbuf = other.bitbuf;
|
||||||
bitbuf_size = other.bitbuf_size;
|
bitbuf_size = other.bitbuf_size;
|
||||||
|
other.bitbuf = 0;
|
||||||
other.bitbuf_size = 0;
|
other.bitbuf_size = 0;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@ -204,17 +201,26 @@ public:
|
|||||||
/** Generate a random (bits)-bit integer. */
|
/** Generate a random (bits)-bit integer. */
|
||||||
uint64_t randbits(int bits) noexcept
|
uint64_t randbits(int bits) noexcept
|
||||||
{
|
{
|
||||||
if (bits == 0) {
|
Assume(bits <= 64);
|
||||||
return 0;
|
// Requests for the full 64 bits are passed through.
|
||||||
} else if (bits > 32) {
|
if (bits == 64) return Impl().rand64();
|
||||||
return Impl().rand64() >> (64 - bits);
|
uint64_t ret;
|
||||||
} else {
|
if (bits <= bitbuf_size) {
|
||||||
if (bitbuf_size < bits) FillBitBuffer();
|
// If there is enough entropy left in bitbuf, return its bottom bits bits.
|
||||||
uint64_t ret = bitbuf & (~uint64_t{0} >> (64 - bits));
|
ret = bitbuf;
|
||||||
bitbuf >>= bits;
|
bitbuf >>= bits;
|
||||||
bitbuf_size -= 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).
|
/** Generate a random integer in the range [0..range).
|
||||||
|
@ -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(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(-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(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(4652286523065884857, 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(-8813961240025683129, 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(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());
|
||||||
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<std::pair<uint64_t, uint64_t>> 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. */
|
/** Does-it-compile test for compatibility with standard library RNG interface. */
|
||||||
BOOST_AUTO_TEST_CASE(stdrandom_test)
|
BOOST_AUTO_TEST_CASE(stdrandom_test)
|
||||||
{
|
{
|
||||||
|
@ -76,3 +76,4 @@ shift-base:crypto/
|
|||||||
shift-base:streams.h
|
shift-base:streams.h
|
||||||
shift-base:FormatHDKeypath
|
shift-base:FormatHDKeypath
|
||||||
shift-base:xoroshiro128plusplus.h
|
shift-base:xoroshiro128plusplus.h
|
||||||
|
shift-base:RandomMixin<*>::randbits
|
||||||
|
Loading…
x
Reference in New Issue
Block a user