Merge bitcoin/bitcoin#29625: Several randomness improvements

ce8094246e random: replace construct/assign with explicit Reseed() (Pieter Wuille)
2ae392d561 random: use LogError for init failure (Pieter Wuille)
97e16f5704 tests: make fuzz tests (mostly) deterministic with fixed seed (Pieter Wuille)
2c91330dd6 random: cleanup order, comments, static (Pieter Wuille)
8e31cf9c9b net, net_processing: use existing RNG objects more (Pieter Wuille)
d5fcbe966b random: improve precision of MakeExponentiallyDistributed (Pieter Wuille)
cfb0dfe2cf random: convert GetExponentialRand into rand_exp_duration (Pieter Wuille)
4eaa239dc3 random: convert GetRand{Micros,Millis} into randrange (Pieter Wuille)
82de1b80d9 net: use GetRandMicros for cache expiration (Pieter Wuille)
ddc184d999 random: get rid of GetRand by inlining (Pieter Wuille)
e2d1f84858 random: make GetRand() support entire range (incl. max) (Pieter Wuille)
810cdf6b4e tests: overhaul deterministic test randomness (Pieter Wuille)
6cfdc5b104 random: convert XoRoShiRo128PlusPlus into full RNG (Pieter Wuille)
8cc2f45065 random: move XoRoShiRo128PlusPlus into random module (Pieter Wuille)
8f5ac0d0b6 xoroshiro128plusplus: drop comment about nonexisting copy() (Pieter Wuille)
8924f5120f random: modernize XoRoShiRo128PlusPlus a bit (Pieter Wuille)
ddb7d26cfd random: add RandomMixin::randbits with compile-known bits (Pieter Wuille)
21ce9d8658 random: Improve RandomMixin::randbits (Pieter Wuille)
9b14d3d2da random: refactor: move rand* utilities to RandomMixin (Pieter Wuille)
40dd86fc3b random: use BasicByte concept in randbytes (Pieter Wuille)
27cefc7fd6 random: add a few noexcepts to FastRandomContext (Pieter Wuille)
b3b382dde2 random: move rand256() and randbytes() to .h file (Pieter Wuille)
493a2e024e random: write rand256() in function of fillrand() (Pieter Wuille)

Pull request description:

  This PR contains a number of vaguely-related improvements to the random module.

  The specific changes and more detailed rationale is in the commit messages, but the highlights are:

  * `XoRoShiRo128PlusPlus` (previously a test-only RNG) moves to random.h and becomes `InsecureRandomContext`, which is even faster than `FastRandomContext` but non-cryptographic. It also gets all helper randomness functions (`randrange`, `fillrand`, ...), making it a lot more succinct to use.
  * During tests, **all** randomness is made deterministic (except for `GetStrongRandBytes`) but non-repeating (like `GetRand()` used to be when `g_mock_deterministic_tests` was used), either fixed, or from a random seed (overridden by env var).
  * Several infrequently used top-level functions (`GetRandMillis`, `GetRandMicros`, `GetExponentialRand`) are converted into member functions of `FastRandomContext` (and `InsecureRandomContext`).
  * `GetRand<T>()` (without argument) can now return the maximum value of the type (previously e.g. `GetRand<uint32_t>()` would never return 0xffffffff).

ACKs for top commit:
  achow101:
    ACK ce8094246e
  maflcko:
    re-ACK ce8094246e 🐈
  hodlinator:
    ACK ce8094246e
  dergoegge:
    utACK ce8094246e

Tree-SHA512: 79bc0cbafaf27e95012c1ce2947a8ca6f9a3c78af5f1f16e69354b6fc9b987a28858adf4cd356dc5baf21163e9af8dcc24e70f8d7173be870e8a3ddcdd47c02c
This commit is contained in:
merge-script 2024-07-04 11:26:43 +01:00
commit 5c0cd205a1
No known key found for this signature in database
GPG key ID: 2EEB9F5CC09526C1
42 changed files with 738 additions and 564 deletions

View file

@ -173,8 +173,7 @@ BITCOIN_TESTS =\
test/validation_flush_tests.cpp \ test/validation_flush_tests.cpp \
test/validation_tests.cpp \ test/validation_tests.cpp \
test/validationinterface_tests.cpp \ test/validationinterface_tests.cpp \
test/versionbits_tests.cpp \ test/versionbits_tests.cpp
test/xoroshiro128plusplus_tests.cpp
if ENABLE_WALLET if ENABLE_WALLET
BITCOIN_TESTS += \ BITCOIN_TESTS += \

View file

@ -23,8 +23,7 @@ TEST_UTIL_H = \
test/util/str.h \ test/util/str.h \
test/util/transaction_utils.h \ test/util/transaction_utils.h \
test/util/txmempool.h \ test/util/txmempool.h \
test/util/validation.h \ test/util/validation.h
test/util/xoroshiro128plusplus.h
if ENABLE_WALLET if ENABLE_WALLET
TEST_UTIL_H += wallet/test/util.h TEST_UTIL_H += wallet/test/util.h

View file

@ -53,7 +53,7 @@ template <typename Data>
bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data) bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data)
{ {
// Generate random temporary filename // Generate random temporary filename
const uint16_t randv{GetRand<uint16_t>()}; const uint16_t randv{FastRandomContext().rand<uint16_t>()};
std::string tmpfn = strprintf("%s.%04x", prefix, randv); std::string tmpfn = strprintf("%s.%04x", prefix, randv);
// open temp output file // open temp output file

View file

@ -776,7 +776,7 @@ std::pair<CAddress, NodeSeconds> AddrManImpl::Select_(bool new_only, std::option
const AddrInfo& info{it_found->second}; const AddrInfo& info{it_found->second};
// With probability GetChance() * chance_factor, return the entry. // 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"); LogPrint(BCLog::ADDRMAN, "Selected %s from %s\n", info.ToStringAddrPort(), search_tried ? "tried" : "new");
return {info, info.m_last_try}; return {info, info.m_last_try};
} }

View file

@ -239,7 +239,7 @@ bool CRollingBloomFilter::contains(Span<const unsigned char> vKey) const
void CRollingBloomFilter::reset() void CRollingBloomFilter::reset()
{ {
nTweak = GetRand<unsigned int>(); nTweak = FastRandomContext().rand<unsigned int>();
nEntriesThisGeneration = 0; nEntriesThisGeneration = 0;
nGeneration = 1; nGeneration = 1;
std::fill(data.begin(), data.end(), 0); std::fill(data.begin(), data.end(), 0);

View file

@ -25,7 +25,7 @@ static_assert(sizeof(CompressedHeader) == 48);
HeadersSyncState::HeadersSyncState(NodeId id, const Consensus::Params& consensus_params, HeadersSyncState::HeadersSyncState(NodeId id, const Consensus::Params& consensus_params,
const CBlockIndex* chain_start, const arith_uint256& minimum_required_work) : const CBlockIndex* chain_start, const arith_uint256& minimum_required_work) :
m_commit_offset(GetRand<unsigned>(HEADER_COMMITMENT_PERIOD)), m_commit_offset(FastRandomContext().randrange<unsigned>(HEADER_COMMITMENT_PERIOD)),
m_id(id), m_consensus_params(consensus_params), m_id(id), m_consensus_params(consensus_params),
m_chain_start(chain_start), m_chain_start(chain_start),
m_minimum_required_work(minimum_required_work), m_minimum_required_work(minimum_required_work),

View file

@ -1273,11 +1273,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
node.addrman = std::move(*addrman); node.addrman = std::move(*addrman);
} }
FastRandomContext rng;
assert(!node.banman); assert(!node.banman);
node.banman = std::make_unique<BanMan>(args.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); node.banman = std::make_unique<BanMan>(args.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
assert(!node.connman); assert(!node.connman);
node.connman = std::make_unique<CConnman>(GetRand<uint64_t>(), node.connman = std::make_unique<CConnman>(rng.rand64(),
GetRand<uint64_t>(), rng.rand64(),
*node.addrman, *node.netgroupman, chainparams, args.GetBoolArg("-networkactive", true)); *node.addrman, *node.netgroupman, chainparams, args.GetBoolArg("-networkactive", true));
assert(!node.fee_estimator); assert(!node.fee_estimator);

View file

@ -2481,9 +2481,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
auto start = GetTime<std::chrono::microseconds>(); auto start = GetTime<std::chrono::microseconds>();
// Minimum time before next feeler connection (in microseconds). // Minimum time before next feeler connection (in microseconds).
auto next_feeler = GetExponentialRand(start, FEELER_INTERVAL); auto next_feeler = start + rng.rand_exp_duration(FEELER_INTERVAL);
auto next_extra_block_relay = GetExponentialRand(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); auto next_extra_block_relay = start + rng.rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
auto next_extra_network_peer{GetExponentialRand(start, EXTRA_NETWORK_PEER_INTERVAL)}; auto next_extra_network_peer{start + rng.rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)};
const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED); const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED);
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS); bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
const bool use_seednodes{gArgs.IsArgSet("-seednode")}; const bool use_seednodes{gArgs.IsArgSet("-seednode")};
@ -2642,10 +2642,10 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// Because we can promote these connections to block-relay-only // Because we can promote these connections to block-relay-only
// connections, they do not get their own ConnectionType enum // connections, they do not get their own ConnectionType enum
// (similar to how we deal with extra outbound peers). // (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 + rng.rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
conn_type = ConnectionType::BLOCK_RELAY; conn_type = ConnectionType::BLOCK_RELAY;
} else if (now > next_feeler) { } else if (now > next_feeler) {
next_feeler = GetExponentialRand(now, FEELER_INTERVAL); next_feeler = now + rng.rand_exp_duration(FEELER_INTERVAL);
conn_type = ConnectionType::FEELER; conn_type = ConnectionType::FEELER;
fFeeler = true; fFeeler = true;
} else if (nOutboundFullRelay == m_max_outbound_full_relay && } else if (nOutboundFullRelay == m_max_outbound_full_relay &&
@ -2658,7 +2658,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// This is not attempted if the user changed -maxconnections to a value // This is not attempted if the user changed -maxconnections to a value
// so low that less than MAX_OUTBOUND_FULL_RELAY_CONNECTIONS are made, // so low that less than MAX_OUTBOUND_FULL_RELAY_CONNECTIONS are made,
// to prevent interactions with otherwise protected outbound peers. // to prevent interactions with otherwise protected outbound peers.
next_extra_network_peer = GetExponentialRand(now, EXTRA_NETWORK_PEER_INTERVAL); next_extra_network_peer = now + rng.rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL);
} else { } else {
// skip to next iteration of while loop // skip to next iteration of while loop
continue; continue;
@ -3475,7 +3475,8 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres
// nodes to be "terrible" (see IsTerrible()) if the timestamps are older than 30 days, // 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 // max. 24 hours of "penalty" due to cache shouldn't make any meaningful difference
// in terms of the freshness of the response. // 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 +
21h + FastRandomContext().randrange<std::chrono::microseconds>(6h);
} }
return cache_entry.m_addrs_response_cache; return cache_entry.m_addrs_response_cache;
} }

View file

@ -936,7 +936,7 @@ private:
* accurately determine when we received the transaction (and potentially * accurately determine when we received the transaction (and potentially
* determine the transaction's origin). */ * determine the transaction's origin). */
std::chrono::microseconds NextInvToInbounds(std::chrono::microseconds now, 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 // 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 // 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. // 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. // 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 + m_rng.rand_exp_duration(average_interval);
} }
return m_next_inv_to_inbounds; return m_next_inv_to_inbounds;
} }
@ -1698,7 +1698,7 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler)
// Schedule next run for 10-15 minutes in the future. // Schedule next run for 10-15 minutes in the future.
// We add randomness on every cycle to avoid the possibility of P2P fingerprinting. // 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<std::chrono::milliseconds>(5min);
scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); 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}); scheduler.scheduleEvery([this] { this->CheckForStaleTipAndEvictPeers(); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL});
// schedule next run for 10-15 minutes in the future // schedule next run for 10-15 minutes in the future
const std::chrono::milliseconds delta = 10min + GetRandMillis(5min); const auto delta = 10min + FastRandomContext().randrange<std::chrono::milliseconds>(5min);
scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta);
} }
@ -2124,7 +2124,7 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo
*/ */
void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock)
{ {
auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock, GetRand<uint64_t>()); auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock, FastRandomContext().rand64());
LOCK(cs_main); 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()) { if (a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) {
MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, *a_recent_compact_block); MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, *a_recent_compact_block);
} else { } else {
CBlockHeaderAndShortTxIDs cmpctblock{*pblock, GetRand<uint64_t>()}; CBlockHeaderAndShortTxIDs cmpctblock{*pblock, FastRandomContext().rand64()};
MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, cmpctblock); MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, cmpctblock);
} }
} else { } else {
@ -5617,7 +5617,7 @@ void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::mic
if (pingSend) { if (pingSend) {
uint64_t nonce; uint64_t nonce;
do { do {
nonce = GetRand<uint64_t>(); nonce = FastRandomContext().rand64();
} while (nonce == 0); } while (nonce == 0);
peer.m_ping_queued = false; peer.m_ping_queued = false;
peer.m_ping_start = now; peer.m_ping_start = now;
@ -5654,13 +5654,13 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
CAddress local_addr{*local_service, peer.m_our_services, Now<NodeSeconds>()}; CAddress local_addr{*local_service, peer.m_our_services, Now<NodeSeconds>()};
PushAddress(peer, local_addr); 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 + m_rng.rand_exp_duration(AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
} }
// We sent an `addr` message to this peer recently. Nothing more to do. // We sent an `addr` message to this peer recently. Nothing more to do.
if (current_time <= peer.m_next_addr_send) return; 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 + m_rng.rand_exp_duration(AVG_ADDRESS_BROADCAST_INTERVAL);
if (!Assume(peer.m_addrs_to_send.size() <= MAX_ADDR_TO_SEND)) { if (!Assume(peer.m_addrs_to_send.size() <= MAX_ADDR_TO_SEND)) {
// Should be impossible since we always check size before adding to // 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); MakeAndPushMessage(pto, NetMsgType::FEEFILTER, filterToSend);
peer.m_fee_filter_sent = 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 + 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 // 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. // 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 && 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)) { (currentFilter < 3 * peer.m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_fee_filter_sent / 3)) {
peer.m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY); peer.m_next_send_feefilter = current_time + m_rng.randrange<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY);
} }
} }
@ -5984,7 +5984,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
CBlock block; CBlock block;
const bool ret{m_chainman.m_blockman.ReadBlockFromDisk(block, *pBestIndex)}; const bool ret{m_chainman.m_blockman.ReadBlockFromDisk(block, *pBestIndex)};
assert(ret); assert(ret);
CBlockHeaderAndShortTxIDs cmpctblock{block, GetRand<uint64_t>()}; CBlockHeaderAndShortTxIDs cmpctblock{block, m_rng.rand64()};
MakeAndPushMessage(*pto, NetMsgType::CMPCTBLOCK, cmpctblock); MakeAndPushMessage(*pto, NetMsgType::CMPCTBLOCK, cmpctblock);
} }
state.pindexBestHeaderSent = pBestIndex; state.pindexBestHeaderSent = pBestIndex;
@ -6059,7 +6059,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
if (pto->IsInboundConn()) { if (pto->IsInboundConn()) {
tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL);
} else { } else {
tx_relay->m_next_inv_send_time = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); tx_relay->m_next_inv_send_time = current_time + m_rng.rand_exp_duration(OUTBOUND_INVENTORY_BROADCAST_INTERVAL);
} }
} }

View file

@ -567,8 +567,8 @@ class CServiceHash
{ {
public: public:
CServiceHash() CServiceHash()
: m_salt_k0{GetRand<uint64_t>()}, : m_salt_k0{FastRandomContext().rand64()},
m_salt_k1{GetRand<uint64_t>()} m_salt_k1{FastRandomContext().rand64()}
{ {
} }

View file

@ -85,7 +85,7 @@ public:
LOCK(m_txreconciliation_mutex); LOCK(m_txreconciliation_mutex);
LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Pre-register peer=%d\n", peer_id); 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 // 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. // safe to assume we don't have this record yet.

View file

@ -23,6 +23,7 @@
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <optional>
#include <thread> #include <thread>
#ifdef WIN32 #ifdef WIN32
@ -44,13 +45,23 @@
#include <sys/auxv.h> #include <sys/auxv.h>
#endif #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"); LogError("Failed to read randomness, aborting\n");
std::abort(); std::abort();
} }
static inline int64_t GetPerformanceCounter() noexcept inline int64_t GetPerformanceCounter() noexcept
{ {
// Read the hardware time stamp counter when available. // Read the hardware time stamp counter when available.
// See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information. // See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information.
@ -71,10 +82,10 @@ static inline int64_t GetPerformanceCounter() noexcept
} }
#ifdef HAVE_GETCPUID #ifdef HAVE_GETCPUID
static bool g_rdrand_supported = false; bool g_rdrand_supported = false;
static bool g_rdseed_supported = false; bool g_rdseed_supported = false;
static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000;
static constexpr uint32_t CPUID_F7_EBX_RDSEED = 0x00040000; constexpr uint32_t CPUID_F7_EBX_RDSEED = 0x00040000;
#ifdef bit_RDRND #ifdef bit_RDRND
static_assert(CPUID_F1_ECX_RDRAND == bit_RDRND, "Unexpected value for bit_RDRND"); static_assert(CPUID_F1_ECX_RDRAND == bit_RDRND, "Unexpected value for bit_RDRND");
#endif #endif
@ -82,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"); static_assert(CPUID_F7_EBX_RDSEED == bit_RDSEED, "Unexpected value for bit_RDSEED");
#endif #endif
static void InitHardwareRand() void InitHardwareRand()
{ {
uint32_t eax, ebx, ecx, edx; uint32_t eax, ebx, ecx, edx;
GetCPUID(1, 0, eax, ebx, ecx, edx); GetCPUID(1, 0, eax, ebx, ecx, edx);
@ -95,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 // This must be done in a separate function, as InitHardwareRand() may be indirectly called
// from global constructors, before logging is initialized. // from global constructors, before logging is initialized.
@ -111,7 +122,7 @@ static void ReportHardwareRand()
* *
* Must only be called when RdRand is supported. * 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. // RdRand may very rarely fail. Invoke it up to 10 times in a loop to reduce this risk.
#ifdef __i386__ #ifdef __i386__
@ -146,7 +157,7 @@ static uint64_t GetRdRand() noexcept
* *
* Must only be called when RdSeed is supported. * 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, // RdSeed may fail when the HW RNG is overloaded. Loop indefinitely until enough entropy is gathered,
// but pause after every failure. // but pause after every failure.
@ -180,16 +191,16 @@ static uint64_t GetRdSeed() noexcept
#elif defined(__aarch64__) && defined(HWCAP2_RNG) #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) { if (getauxval(AT_HWCAP2) & HWCAP2_RNG) {
g_rndr_supported = true; g_rndr_supported = true;
} }
} }
static void ReportHardwareRand() void ReportHardwareRand()
{ {
// This must be done in a separate function, as InitHardwareRand() may be indirectly called // This must be done in a separate function, as InitHardwareRand() may be indirectly called
// from global constructors, before logging is initialized. // from global constructors, before logging is initialized.
@ -202,7 +213,7 @@ static void ReportHardwareRand()
* *
* Must only be called when RNDR is supported. * Must only be called when RNDR is supported.
*/ */
static uint64_t GetRNDR() noexcept uint64_t GetRNDR() noexcept
{ {
uint8_t ok; uint8_t ok;
uint64_t r1; uint64_t r1;
@ -220,7 +231,7 @@ static uint64_t GetRNDR() noexcept
* *
* Must only be called when RNDRRS is supported. * Must only be called when RNDRRS is supported.
*/ */
static uint64_t GetRNDRRS() noexcept uint64_t GetRNDRRS() noexcept
{ {
uint8_t ok; uint8_t ok;
uint64_t r1; uint64_t r1;
@ -240,12 +251,12 @@ static uint64_t GetRNDRRS() noexcept
* Slower sources should probably be invoked separately, and/or only from * Slower sources should probably be invoked separately, and/or only from
* RandAddPeriodic (which is called once a minute). * RandAddPeriodic (which is called once a minute).
*/ */
static void InitHardwareRand() {} void InitHardwareRand() {}
static void ReportHardwareRand() {} void ReportHardwareRand() {}
#endif #endif
/** Add 64 bits of entropy gathered from hardware to hasher. Do nothing if not supported. */ /** 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 defined(__x86_64__) || defined(__amd64__) || defined(__i386__)
if (g_rdrand_supported) { if (g_rdrand_supported) {
uint64_t out = GetRdRand(); uint64_t out = GetRdRand();
@ -262,7 +273,7 @@ static void SeedHardwareFast(CSHA512& hasher) noexcept {
} }
/** Add 256 bits of entropy gathered from hardware to hasher. Do nothing if not supported. */ /** 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__) #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__)
// When we want 256 bits of entropy, prefer RdSeed over RdRand, as it's // When we want 256 bits of entropy, prefer RdSeed over RdRand, as it's
// guaranteed to produce independent randomness on every call. // guaranteed to produce independent randomness on every call.
@ -295,7 +306,7 @@ static void SeedHardwareSlow(CSHA512& hasher) noexcept {
} }
/** Use repeated SHA512 to strengthen the randomness in seed32, and feed into hasher. */ /** 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; CSHA512 inner_hasher;
inner_hasher.Write(seed, sizeof(seed)); inner_hasher.Write(seed, sizeof(seed));
@ -326,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 /** Fallback: get 32 bytes of system entropy from /dev/urandom. The most
* compatible way to get cryptographic randomness on UNIX-ish platforms. * 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); int f = open("/dev/urandom", O_RDONLY);
if (f == -1) { if (f == -1) {
@ -401,8 +412,6 @@ void GetOSRand(unsigned char *ent32)
#endif #endif
} }
namespace {
class RNGState { class RNGState {
Mutex m_mutex; Mutex m_mutex;
/* The RNG state consists of 256 bits of entropy, taken from the output of /* The RNG state consists of 256 bits of entropy, taken from the output of
@ -417,6 +426,10 @@ class RNGState {
uint64_t m_counter GUARDED_BY(m_mutex) = 0; uint64_t m_counter GUARDED_BY(m_mutex) = 0;
bool m_strongly_seeded GUARDED_BY(m_mutex) = false; 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<ChaCha20> m_deterministic_prng GUARDED_BY(m_mutex);
Mutex m_events_mutex; Mutex m_events_mutex;
CSHA256 m_events_hasher GUARDED_BY(m_events_mutex); CSHA256 m_events_hasher GUARDED_BY(m_events_mutex);
@ -457,11 +470,21 @@ public:
m_events_hasher.Write(events_hash, 32); 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. /** 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 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); assert(num <= 32);
unsigned char buf[64]; unsigned char buf[64];
@ -479,6 +502,13 @@ public:
hasher.Finalize(buf); hasher.Finalize(buf);
// Store the last 32 bytes of the hash output as new RNG state. // Store the last 32 bytes of the hash output as new RNG state.
memcpy(m_state, buf + 32, 32); 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 desired, copy (up to) the first 32 bytes of the hash output as output.
if (num) { if (num) {
@ -499,20 +529,19 @@ RNGState& GetRNGState() noexcept
static std::vector<RNGState, secure_allocator<RNGState>> g_rng(1); static std::vector<RNGState, secure_allocator<RNGState>> g_rng(1);
return g_rng[0]; return g_rng[0];
} }
}
/* A note on the use of noexcept in the seeding functions below: /* A note on the use of noexcept in the seeding functions below:
* *
* None of the RNG code should ever throw any exception. * 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(); int64_t perfcounter = GetPerformanceCounter();
hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter));
} }
static void SeedFast(CSHA512& hasher) noexcept void SeedFast(CSHA512& hasher) noexcept
{ {
unsigned char buffer[32]; unsigned char buffer[32];
@ -527,7 +556,7 @@ static void SeedFast(CSHA512& hasher) noexcept
SeedTimestamp(hasher); SeedTimestamp(hasher);
} }
static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept
{ {
unsigned char buffer[32]; unsigned char buffer[32];
@ -549,16 +578,17 @@ static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept
} }
/** Extract entropy from rng, strengthen it, and feed it into hasher. */ /** 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. // 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]; 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 the seed, and feed it into hasher.
Strengthen(strengthen_seed, dur, hasher); 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 // Everything that the 'fast' seeder includes
SeedFast(hasher); SeedFast(hasher);
@ -578,7 +608,7 @@ static void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept
SeedStrengthen(hasher, rng, 10ms); 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 // Gather 256 bits of hardware randomness, if available
SeedHardwareSlow(hasher); SeedHardwareSlow(hasher);
@ -604,7 +634,7 @@ enum class RNGLevel {
PERIODIC, //!< Called by RandAddPeriodic() PERIODIC, //!< Called by RandAddPeriodic()
}; };
static void ProcRand(unsigned char* out, int num, RNGLevel level) 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). // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available).
RNGState& rng = GetRNGState(); RNGState& rng = GetRNGState();
@ -625,65 +655,61 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept
} }
// Combine with and update state // 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(). // On the first invocation, also seed with SeedStartup().
CSHA512 startup_hasher; CSHA512 startup_hasher;
SeedStartup(startup_hasher, rng); 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<unsigned char> bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST); } } // namespace
void GetStrongRandBytes(Span<unsigned char> bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::SLOW); }
void RandAddPeriodic() noexcept { ProcRand(nullptr, 0, RNGLevel::PERIODIC); }
/** Internal function to set g_determinstic_rng. Only accessed from tests. */
void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept
{
GetRNGState().MakeDeterministic(seed);
}
void GetRandBytes(Span<unsigned char> bytes) noexcept
{
ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST, /*always_use_real_rng=*/false);
}
void GetStrongRandBytes(Span<unsigned char> 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); } void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); }
bool g_mock_deterministic_tests{false}; void FastRandomContext::RandomSeed() noexcept
uint64_t GetRandInternal(uint64_t nMax) noexcept
{
return FastRandomContext(g_mock_deterministic_tests).randrange(nMax);
}
uint256 GetRandHash() noexcept
{
uint256 hash;
GetRandBytes(hash);
return hash;
}
void FastRandomContext::RandomSeed()
{ {
uint256 seed = GetRandHash(); uint256 seed = GetRandHash();
rng.SetKey(MakeByteSpan(seed)); rng.SetKey(MakeByteSpan(seed));
requires_seed = false; requires_seed = false;
} }
uint256 FastRandomContext::rand256() noexcept void FastRandomContext::fillrand(Span<std::byte> output) noexcept
{
if (requires_seed) RandomSeed();
uint256 ret;
rng.Keystream(MakeWritableByteSpan(ret));
return ret;
}
template <typename B>
std::vector<B> FastRandomContext::randbytes(size_t len)
{
std::vector<B> ret(len);
fillrand(MakeWritableByteSpan(ret));
return ret;
}
template std::vector<unsigned char> FastRandomContext::randbytes(size_t);
template std::vector<std::byte> FastRandomContext::randbytes(size_t);
void FastRandomContext::fillrand(Span<std::byte> output)
{ {
if (requires_seed) RandomSeed(); if (requires_seed) RandomSeed();
rng.Keystream(output); 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)) {}
void FastRandomContext::Reseed(const uint256& seed) noexcept
{
FlushCache();
requires_seed = false;
rng = {MakeByteSpan(seed)};
}
bool Random_SanityCheck() bool Random_SanityCheck()
{ {
@ -726,41 +752,38 @@ bool Random_SanityCheck()
CSHA512 to_add; CSHA512 to_add;
to_add.Write((const unsigned char*)&start, sizeof(start)); to_add.Write((const unsigned char*)&start, sizeof(start));
to_add.Write((const unsigned char*)&stop, sizeof(stop)); 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; return true;
} }
static constexpr std::array<std::byte, ChaCha20::KEYLEN> ZERO_KEY{}; static constexpr std::array<std::byte, ChaCha20::KEYLEN> 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 // 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 // fDeterministic. That means the rng will be reinitialized with a secure random key upon first
// use. // use.
} }
FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept
{
requires_seed = from.requires_seed;
rng = from.rng;
bitbuf = from.bitbuf;
bitbuf_size = from.bitbuf_size;
from.requires_seed = true;
from.bitbuf_size = 0;
return *this;
}
void RandomInit() void RandomInit()
{ {
// Invoke RNG code to trigger initialization (if not already performed) // 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(); ReportHardwareRand();
} }
std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval) double MakeExponentiallyDistributed(uint64_t uniform) noexcept
{ {
double unscaled = -std::log1p(GetRand(uint64_t{1} << 48) * -0.0000000000000035527136788 /* -1/2^48 */); // To convert uniform into an exponentially-distributed double, we use two steps:
return now + std::chrono::duration_cast<std::chrono::microseconds>(unscaled * average_interval + 0.5us); // - 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);
} }

View file

@ -10,12 +10,15 @@
#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>
#include <chrono> #include <chrono>
#include <concepts>
#include <cstdint> #include <cstdint>
#include <limits> #include <limits>
#include <type_traits>
#include <vector> #include <vector>
/** /**
@ -25,8 +28,8 @@
* The following (classes of) functions interact with that state by mixing in new * The following (classes of) functions interact with that state by mixing in new
* entropy, and optionally extracting random output from it: * entropy, and optionally extracting random output from it:
* *
* - The GetRand*() class of functions, as well as construction of FastRandomContext objects, * - GetRandBytes, GetRandHash, GetRandDur, as well as construction of FastRandomContext
* perform 'fast' seeding, consisting of mixing in: * objects, perform 'fast' seeding, consisting of mixing in:
* - A stack pointer (indirectly committing to calling thread and call stack) * - A stack pointer (indirectly committing to calling thread and call stack)
* - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise) * - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise)
* - 64 bits from the hardware RNG (rdrand) when available. * - 64 bits from the hardware RNG (rdrand) when available.
@ -35,7 +38,7 @@
* FastRandomContext on the other hand does not protect against this once created, but * FastRandomContext on the other hand does not protect against this once created, but
* is even faster (and acceptable to use inside tight loops). * 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: * that fast seeding includes, but additionally:
* - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if * - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if
* this entropy source fails. * this entropy source fails.
@ -50,75 +53,34 @@
* - Strengthen the entropy for 10 ms using repeated SHA512. * - Strengthen the entropy for 10 ms using repeated SHA512.
* This is run once every minute. * This is run once every minute.
* *
* On first use of the RNG (regardless of what function is called first), all entropy * - 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: * sources used in the 'slow' seeder are included, but also:
* - 256 bits from the hardware RNG (rdseed or rdrand) when available. * - 256 bits from the hardware RNG (rdseed or rdrand) when available.
* - Dynamic environment data (performance monitoring, ...) * - Dynamic environment data (performance monitoring, ...)
* - Static environment data * - Static environment data
* - Strengthen the entropy for 100 ms using repeated SHA512. * - Strengthen the entropy for 100 ms using repeated SHA512.
* *
* When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and * 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 * (up to) the first 32 bytes of H are produced as output, while the last 32 bytes
* become the new RNG state. * 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.
*/ */
/**
* Generate random data via the internal PRNG. /* ============================= INITIALIZATION AND ADDING ENTROPY ============================= */
*
* 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<unsigned char> 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<T>::max()
* Precondition: nMax > 0, T is an integral type, no larger than uint64_t
*/
template<typename T>
T GetRand(T nMax=std::numeric_limits<T>::max()) noexcept {
static_assert(std::is_integral<T>(), "T must be integral");
static_assert(std::numeric_limits<T>::max() <= std::numeric_limits<uint64_t>::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 <typename D>
D GetRandomDuration(typename std::common_type<D>::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<std::chrono::microseconds>;
constexpr auto GetRandMillis = GetRandomDuration<std::chrono::milliseconds>;
/** /**
* Return a timestamp in the future sampled from an exponential distribution * Initialize global RNG state and log any CPU features that are used.
* (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 * Calling this function is optional. RNG state will be initialized when first
* is the average interval between events. * needed if it is not called.
* */
std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval);
uint256 GetRandHash() 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.
*
* Thread-safe.
*/ */
void GetStrongRandBytes(Span<unsigned char> bytes) noexcept; void RandomInit();
/** /**
* Gather entropy from various expensive sources, and feed them to the PRNG state. * Gather entropy from various expensive sources, and feed them to the PRNG state.
@ -135,42 +97,299 @@ void RandAddPeriodic() noexcept;
*/ */
void RandAddEvent(const uint32_t event_info) 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<unsigned char> 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<unsigned char> 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<typename T>
class RandomMixin;
/** A concept for RandomMixin-based random number generators. */
template<typename T>
concept RandomNumberGenerator = requires(T& rng, Span<std::byte> s) {
// A random number generator must provide rand64().
{ rng.rand64() } noexcept -> std::same_as<uint64_t>;
// A random number generator must derive from RandomMixin, which adds other rand* functions.
requires std::derived_from<std::remove_reference_t<T>, RandomMixin<std::remove_reference_t<T>>>;
};
/** A concept for C++ std::chrono durations. */
template<typename T>
concept StdChronoDuration = requires {
[]<class Rep, class Period>(std::type_identity<std::chrono::duration<Rep, Period>>){}(
std::type_identity<T>());
};
/** 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.
* An RNG class FunkyRNG would derive publicly from RandomMixin<FunkyRNG>. 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<typename T>
class RandomMixin
{
private:
uint64_t bitbuf{0};
int bitbuf_size{0};
/** Access the underlying generator.
*
* This also enforces the RandomNumberGenerator concept. We cannot declare that in the template
* (no template<RandomNumberGenerator T>) because the type isn't fully instantiated yet there.
*/
RandomNumberGenerator auto& Impl() noexcept { return static_cast<T&>(*this); }
protected:
constexpr void FlushCache() noexcept
{
bitbuf = 0;
bitbuf_size = 0;
}
public:
constexpr RandomMixin() noexcept = default;
// Do not permit copying or moving an RNG.
RandomMixin(const RandomMixin&) = delete;
RandomMixin& operator=(const RandomMixin&) = delete;
RandomMixin(RandomMixin&&) = delete;
RandomMixin& operator=(RandomMixin&&) = delete;
/** Generate a random (bits)-bit integer. */
uint64_t randbits(int bits) noexcept
{
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;
} 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);
}
/** Same as above, but with compile-time fixed bits count. */
template<int Bits>
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), with range > 0. */
template<std::integral I>
I randrange(I range) noexcept
{
static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::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 <= maxval) return ret;
}
}
/** Fill a Span with random bytes. */
void fillrand(Span<std::byte> 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 a random integer in its entire (non-negative) range. */
template<std::integral I>
I rand() noexcept
{
static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
static constexpr auto BITS = std::bit_width(uint64_t(std::numeric_limits<I>::max()));
static_assert(std::numeric_limits<I>::max() == std::numeric_limits<uint64_t>::max() >> (64 - BITS));
return I(Impl().template randbits<BITS>());
}
/** Generate random bytes. */
template <BasicByte B = unsigned char>
std::vector<B> randbytes(size_t len) noexcept
{
std::vector<B> ret(len);
Impl().fillrand(MakeWritableByteSpan(ret));
return ret;
}
/** Generate a random 32-bit integer. */
uint32_t rand32() noexcept { return Impl().template 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().template randbits<1>(); }
/** Return the time point advanced by a uniform random duration. */
template <typename Tp>
Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) noexcept
{
return time + Impl().template rand_uniform_duration<Tp>(range);
}
/** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */
template <typename Chrono> requires StdChronoDuration<typename Chrono::duration>
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};
};
/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */
template <StdChronoDuration Dur>
Dur randrange(typename std::common_type_t<Dur> 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())};
}
/**
* 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<std::chrono::microseconds>(unscaled * mean + 0.5us);
}
// 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<uint64_t>::max(); }
inline uint64_t operator()() noexcept { return Impl().rand64(); }
};
/** /**
* Fast randomness source. This is seeded once with secure random data, but * Fast randomness source. This is seeded once with secure random data, but
* is completely deterministic and does not gather more entropy after that. * is completely deterministic and does not gather more entropy after that.
* *
* This class is not thread-safe. * This class is not thread-safe.
*/ */
class FastRandomContext class FastRandomContext : public RandomMixin<FastRandomContext>
{ {
private: private:
bool requires_seed; bool requires_seed;
ChaCha20 rng; ChaCha20 rng;
uint64_t bitbuf; void RandomSeed() noexcept;
int bitbuf_size;
void RandomSeed();
void FillBitBuffer()
{
bitbuf = rand64();
bitbuf_size = 64;
}
public: public:
/** Construct a FastRandomContext with GetRandHash()-based entropy (or zero key if fDeterministic). */
explicit FastRandomContext(bool fDeterministic = false) noexcept; explicit FastRandomContext(bool fDeterministic = false) noexcept;
/** Initialize with explicit seed (only for testing) */ /** Initialize with explicit seed (only for testing) */
explicit FastRandomContext(const uint256& seed) noexcept; explicit FastRandomContext(const uint256& seed) noexcept;
// Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded). /** Reseed with explicit seed (only for testing). */
FastRandomContext(const FastRandomContext&) = delete; void Reseed(const uint256& seed) noexcept;
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;
/** Generate a random 64-bit integer. */ /** Generate a random 64-bit integer. */
uint64_t rand64() noexcept uint64_t rand64() noexcept
@ -181,76 +400,64 @@ public:
return ReadLE64(UCharCast(buf.data())); return ReadLE64(UCharCast(buf.data()));
} }
/** Generate a random (bits)-bit integer. */ /** Fill a byte Span with random bytes. This overrides the RandomMixin version. */
uint64_t randbits(int bits) noexcept void fillrand(Span<std::byte> output) 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 <typename B = unsigned char>
std::vector<B> randbytes(size_t len);
/** Fill a byte Span with random bytes. */
void fillrand(Span<std::byte> output);
/** Generate a random 32-bit integer. */
uint32_t rand32() noexcept { return randbits(32); }
/** generate a random uint256. */
uint256 rand256() noexcept;
/** Generate a random boolean. */
bool randbool() noexcept { return randbits(1); }
/** Return the time point advanced by a uniform random duration. */
template <typename Tp>
Tp rand_uniform_delay(const Tp& time, typename Tp::duration range)
{
return time + rand_uniform_duration<Tp>(range);
}
/** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */
template <typename Chrono>
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() { return 0; }
static constexpr uint64_t max() { return std::numeric_limits<uint64_t>::max(); }
inline uint64_t operator()() noexcept { return rand64(); }
}; };
/** xoroshiro128++ PRNG. Extremely fast, not appropriate for cryptographic purposes.
*
* 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 InsecureRandomContext : public RandomMixin<InsecureRandomContext>
{
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:
constexpr explicit InsecureRandomContext(uint64_t seedval) noexcept
: m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {}
constexpr void Reseed(uint64_t seedval) noexcept
{
FlushCache();
m_s0 = SplitMix64(seedval);
m_s1 = SplitMix64(seedval);
}
constexpr uint64_t rand64() 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;
}
};
/* ==================== 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. /** More efficient than using std::shuffle on a FastRandomContext.
* *
* This is more efficient as std::shuffle will consume entropy in groups of * This is more efficient as std::shuffle will consume entropy in groups of
@ -261,7 +468,7 @@ public:
* debug mode detects and panics on. This is a known issue, see * debug mode detects and panics on. This is a known issue, see
* https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle * https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle
*/ */
template <typename I, typename R> template <typename I, RandomNumberGenerator R>
void Shuffle(I first, I last, R&& rng) void Shuffle(I first, I last, R&& rng)
{ {
while (first != last) { while (first != last) {
@ -274,29 +481,11 @@ void Shuffle(I first, I last, R&& rng)
} }
} }
/* Number of random bytes returned by GetOSRand. /* ============================= MISCELLANEOUS TEST-ONLY FUNCTIONS ============================= */
* 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);
/** Check that OS randomness is available and returning the requested number /** Check that OS randomness is available and returning the requested number
* of bytes. * of bytes.
*/ */
bool Random_SanityCheck(); 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 #endif // BITCOIN_RANDOM_H

View file

@ -463,8 +463,7 @@ static std::vector<unsigned char> RandomData()
BOOST_AUTO_TEST_CASE(rolling_bloom) BOOST_AUTO_TEST_CASE(rolling_bloom)
{ {
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
g_mock_deterministic_tests = true;
// last-100-entry, 1% false positive: // last-100-entry, 1% false positive:
CRollingBloomFilter rb1(100, 0.01); CRollingBloomFilter rb1(100, 0.01);
@ -491,7 +490,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
++nHits; ++nHits;
} }
// Expect about 100 hits // Expect about 100 hits
BOOST_CHECK_EQUAL(nHits, 75U); BOOST_CHECK_EQUAL(nHits, 71U);
BOOST_CHECK(rb1.contains(data[DATASIZE-1])); BOOST_CHECK(rb1.contains(data[DATASIZE-1]));
rb1.reset(); rb1.reset();
@ -519,7 +518,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
++nHits; ++nHits;
} }
// Expect about 5 false positives // Expect about 5 false positives
BOOST_CHECK_EQUAL(nHits, 6U); BOOST_CHECK_EQUAL(nHits, 3U);
// last-1000-entry, 0.01% false positive: // last-1000-entry, 0.01% false positive:
CRollingBloomFilter rb2(1000, 0.001); CRollingBloomFilter rb2(1000, 0.001);
@ -530,7 +529,6 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
for (int i = 0; i < DATASIZE; i++) { for (int i = 0; i < DATASIZE; i++) {
BOOST_CHECK(rb2.contains(data[i])); BOOST_CHECK(rb2.contains(data[i]));
} }
g_mock_deterministic_tests = false;
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View file

@ -307,8 +307,7 @@ UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
// has the expected effect (the other duplicate is overwritten at all cache levels) // has the expected effect (the other duplicate is overwritten at all cache levels)
BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
{ {
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
g_mock_deterministic_tests = true;
bool spent_a_duplicate_coinbase = false; bool spent_a_duplicate_coinbase = false;
// A simple map to track what we expect the cache stack to represent. // 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. // Verify coverage.
BOOST_CHECK(spent_a_duplicate_coinbase); BOOST_CHECK(spent_a_duplicate_coinbase);
g_mock_deterministic_tests = false;
} }
BOOST_AUTO_TEST_CASE(ccoins_serialization) BOOST_AUTO_TEST_CASE(ccoins_serialization)

View file

@ -1195,7 +1195,7 @@ BOOST_AUTO_TEST_CASE(muhash_tests)
uint256 res; uint256 res;
int table[4]; int table[4];
for (int i = 0; i < 4; ++i) { 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) { for (int order = 0; order < 4; ++order) {
MuHash3072 acc; MuHash3072 acc;
@ -1215,8 +1215,8 @@ BOOST_AUTO_TEST_CASE(muhash_tests)
} }
} }
MuHash3072 x = FromInt(g_insecure_rand_ctx.randbits(4)); // x=X 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 y = FromInt(g_insecure_rand_ctx.randbits<4>()); // x=X, y=Y
MuHash3072 z; // x=X, y=Y, z=1 MuHash3072 z; // x=X, y=Y, z=1
z *= x; // x=X, y=Y, z=X z *= x; // x=X, y=Y, z=X
z *= y; // x=X, y=Y, z=X*Y z *= y; // x=X, y=Y, z=X*Y

View file

@ -33,11 +33,11 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests);
/* Test that no values not inserted into the cache are read out of it. /* 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) BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes)
{ {
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
CuckooCache::cache<uint256, SignatureCacheHasher> cc{}; CuckooCache::cache<uint256, SignatureCacheHasher> cc{};
size_t megabytes = 4; size_t megabytes = 4;
cc.setup_bytes(megabytes << 20); cc.setup_bytes(megabytes << 20);
@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes)
template <typename Cache> template <typename Cache>
static double test_cache(size_t megabytes, double load) static double test_cache(size_t megabytes, double load)
{ {
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
std::vector<uint256> hashes; std::vector<uint256> hashes;
Cache set{}; Cache set{};
size_t bytes = megabytes * (1 << 20); size_t bytes = megabytes * (1 << 20);
@ -126,7 +126,7 @@ template <typename Cache>
static void test_cache_erase(size_t megabytes) static void test_cache_erase(size_t megabytes)
{ {
double load = 1; double load = 1;
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
std::vector<uint256> hashes; std::vector<uint256> hashes;
Cache set{}; Cache set{};
size_t bytes = megabytes * (1 << 20); size_t bytes = megabytes * (1 << 20);
@ -189,7 +189,7 @@ template <typename Cache>
static void test_cache_erase_parallel(size_t megabytes) static void test_cache_erase_parallel(size_t megabytes)
{ {
double load = 1; double load = 1;
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
std::vector<uint256> hashes; std::vector<uint256> hashes;
Cache set{}; Cache set{};
size_t bytes = megabytes * (1 << 20); 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 // iterations with non-deterministic values, so it isn't "overfit" to the
// specific entropy in FastRandomContext(true) and implementation of the // specific entropy in FastRandomContext(true) and implementation of the
// cache. // cache.
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
// block_activity models a chunk of network activity. n_insert elements are // 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 // added to the cache. The first and last n/4 are stored for removal later

View file

@ -124,7 +124,7 @@ public:
explicit AddrManDeterministic(const NetGroupManager& netgroupman, FuzzedDataProvider& fuzzed_data_provider) explicit AddrManDeterministic(const NetGroupManager& netgroupman, FuzzedDataProvider& fuzzed_data_provider)
: AddrMan(netgroupman, /*deterministic=*/true, GetCheckRatio()) : 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)));
} }
/** /**

View file

@ -4,11 +4,11 @@
#include <bip324.h> #include <bip324.h>
#include <chainparams.h> #include <chainparams.h>
#include <random.h>
#include <span.h> #include <span.h>
#include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h> #include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h> #include <test/fuzz/util.h>
#include <test/util/xoroshiro128plusplus.h>
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
@ -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 // (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 // reading the actual data for those from the fuzzer input (which would need large amounts of
// data). // data).
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>()); InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
// Compare session IDs and garbage terminators. // Compare session IDs and garbage terminators.
assert(initiator.GetSessionID() == responder.GetSessionID()); 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_bits = 2 * ((mode >> 5) & 7);
unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1); unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1);
// Generate aad and content. // Generate aad and content.
std::vector<std::byte> aad(aad_length); auto aad = rng.randbytes<std::byte>(aad_length);
for (auto& val : aad) val = std::byte{(uint8_t)rng()}; auto contents = rng.randbytes<std::byte>(length);
std::vector<std::byte> contents(length);
for (auto& val : contents) val = std::byte{(uint8_t)rng()};
// Pick sides. // Pick sides.
auto& sender{from_init ? initiator : responder}; auto& sender{from_init ? initiator : responder};

View file

@ -2,9 +2,9 @@
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <random.h>
#include <span.h> #include <span.h>
#include <test/fuzz/util.h> #include <test/fuzz/util.h>
#include <test/util/xoroshiro128plusplus.h>
#include <util/bitset.h> #include <util/bitset.h>
#include <bitset> #include <bitset>
@ -29,7 +29,7 @@ void TestType(FuzzBufferType buffer)
* bitsets and their simulations do not matter for the purpose of detecting edge cases, thus * 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 * 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. */ * 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<S::Size()>; using Sim = std::bitset<S::Size()>;
// Up to 4 real BitSets (initially 2). // Up to 4 real BitSets (initially 2).
@ -124,7 +124,7 @@ void TestType(FuzzBufferType buffer)
sim[dest].reset(); sim[dest].reset();
real[dest] = S{}; real[dest] = S{};
for (unsigned i = 0; i < S::Size(); ++i) { for (unsigned i = 0; i < S::Size(); ++i) {
if (rng() & 1) { if (rng.randbool()) {
sim[dest][i] = true; sim[dest][i] = true;
real[dest].Set(i); real[dest].Set(i);
} }
@ -132,9 +132,9 @@ void TestType(FuzzBufferType buffer)
break; break;
} else if (dest < sim.size() && command-- == 0) { } else if (dest < sim.size() && command-- == 0) {
/* Assign initializer list. */ /* Assign initializer list. */
unsigned r1 = rng() % S::Size(); unsigned r1 = rng.randrange(S::Size());
unsigned r2 = rng() % S::Size(); unsigned r2 = rng.randrange(S::Size());
unsigned r3 = rng() % S::Size(); unsigned r3 = rng.randrange(S::Size());
compare_fn(dest); compare_fn(dest);
sim[dest].reset(); sim[dest].reset();
real[dest] = {r1, r2, r3}; real[dest] = {r1, r2, r3};
@ -166,8 +166,8 @@ void TestType(FuzzBufferType buffer)
break; break;
} else if (sim.size() < 4 && command-- == 0) { } else if (sim.size() < 4 && command-- == 0) {
/* Construct with initializer list. */ /* Construct with initializer list. */
unsigned r1 = rng() % S::Size(); unsigned r1 = rng.randrange(S::Size());
unsigned r2 = rng() % S::Size(); unsigned r2 = rng.randrange(S::Size());
sim.emplace_back(); sim.emplace_back();
sim.back().set(r1); sim.back().set(r1);
sim.back().set(r2); sim.back().set(r2);

View file

@ -3,10 +3,10 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <crypto/chacha20.h> #include <crypto/chacha20.h>
#include <random.h>
#include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h> #include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h> #include <test/fuzz/util.h>
#include <test/util/xoroshiro128plusplus.h>
#include <array> #include <array>
#include <cstddef> #include <cstddef>
@ -53,7 +53,7 @@ namespace
once for a large block at once, and then the same data in chunks, comparing once for a large block at once, and then the same data in chunks, comparing
the outcome. 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. If not, Keystream() is used directly, or sequences of 0x00 are encrypted.
*/ */
template<bool UseCrypt> template<bool UseCrypt>
@ -78,25 +78,11 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
data1.resize(total_bytes); data1.resize(total_bytes);
data2.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. // stream.
if constexpr (UseCrypt) { if constexpr (UseCrypt) {
uint64_t seed = provider.ConsumeIntegral<uint64_t>(); InsecureRandomContext(provider.ConsumeIntegral<uint64_t>()).fillrand(data1);
XoRoShiRo128PlusPlus rng(seed); std::copy(data1.begin(), data1.end(), data2.begin());
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);
}
} }
// Whether UseCrypt is used or not, the two byte arrays must match. // Whether UseCrypt is used or not, the two byte arrays must match.

View file

@ -6,6 +6,7 @@
#include <netaddress.h> #include <netaddress.h>
#include <netbase.h> #include <netbase.h>
#include <test/util/random.h>
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
#include <util/check.h> #include <util/check.h>
#include <util/fs.h> #include <util/fs.h>
@ -101,6 +102,12 @@ void ResetCoverageCounters() {}
void initialize() 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. // Terminate immediately if a fuzzing harness ever tries to create a socket.
// Individual tests can override this by pointing CreateSock to a mocked alternative. // Individual tests can override this by pointing CreateSock to a mocked alternative.
CreateSock = [](int, int, int) -> std::unique_ptr<Sock> { std::terminate(); }; CreateSock = [](int, int, int) -> std::unique_ptr<Sock> { std::terminate(); };

View file

@ -10,7 +10,6 @@
#include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h> #include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h> #include <test/fuzz/util.h>
#include <test/util/xoroshiro128plusplus.h>
#include <util/chaintype.h> #include <util/chaintype.h>
#include <cassert> #include <cassert>
@ -104,7 +103,7 @@ FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serial
namespace { namespace {
template<typename R> template<RandomNumberGenerator R>
void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDataProvider& provider) void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDataProvider& provider)
{ {
// Simulation test with two Transport objects, which send messages to each other, with // Simulation test with two Transport objects, which send messages to each other, with
@ -165,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). // Determine size of message to send (limited to 75 kB for performance reasons).
size_t size = provider.ConsumeIntegralInRange<uint32_t>(0, 75000); size_t size = provider.ConsumeIntegralInRange<uint32_t>(0, 75000);
// Get payload of message from RNG. // Get payload of message from RNG.
msg.data.resize(size); msg.data = rng.randbytes(size);
for (auto& v : msg.data) v = uint8_t(rng());
// Return. // Return.
return msg; return msg;
}; };
@ -337,7 +335,7 @@ std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept
return std::make_unique<V1Transport>(nodeid); return std::make_unique<V1Transport>(nodeid);
} }
template<typename RNG> template<RandomNumberGenerator RNG>
std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider) std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider)
{ {
// Retrieve key // Retrieve key
@ -353,8 +351,7 @@ std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& r
} else { } else {
// If it's longer, generate it from the RNG. This avoids having large amounts of // 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. // (hopefully) irrelevant data needing to be stored in the fuzzer data.
garb.resize(garb_len); garb = rng.randbytes(garb_len);
for (auto& v : garb) v = uint8_t(rng());
} }
// Retrieve entropy // Retrieve entropy
auto ent = provider.ConsumeBytes<std::byte>(32); auto ent = provider.ConsumeBytes<std::byte>(32);
@ -378,7 +375,7 @@ FUZZ_TARGET(p2p_transport_bidirectional, .init = initialize_p2p_transport_serial
{ {
// Test with two V1 transports talking to each other. // Test with two V1 transports talking to each other.
FuzzedDataProvider provider{buffer.data(), buffer.size()}; FuzzedDataProvider provider{buffer.data(), buffer.size()};
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>()); InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
auto t1 = MakeV1Transport(NodeId{0}); auto t1 = MakeV1Transport(NodeId{0});
auto t2 = MakeV1Transport(NodeId{1}); auto t2 = MakeV1Transport(NodeId{1});
if (!t1 || !t2) return; if (!t1 || !t2) return;
@ -389,7 +386,7 @@ FUZZ_TARGET(p2p_transport_bidirectional_v2, .init = initialize_p2p_transport_ser
{ {
// Test with two V2 transports talking to each other. // Test with two V2 transports talking to each other.
FuzzedDataProvider provider{buffer.data(), buffer.size()}; FuzzedDataProvider provider{buffer.data(), buffer.size()};
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>()); InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider); auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider);
auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider); auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
if (!t1 || !t2) return; if (!t1 || !t2) return;
@ -400,7 +397,7 @@ FUZZ_TARGET(p2p_transport_bidirectional_v1v2, .init = initialize_p2p_transport_s
{ {
// Test with a V1 initiator talking to a V2 responder. // Test with a V1 initiator talking to a V2 responder.
FuzzedDataProvider provider{buffer.data(), buffer.size()}; FuzzedDataProvider provider{buffer.data(), buffer.size()};
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>()); InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
auto t1 = MakeV1Transport(NodeId{0}); auto t1 = MakeV1Transport(NodeId{0});
auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider); auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
if (!t1 || !t2) return; if (!t1 || !t2) return;

View file

@ -2,13 +2,13 @@
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <random.h>
#include <span.h> #include <span.h>
#include <support/allocators/pool.h> #include <support/allocators/pool.h>
#include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h> #include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h> #include <test/fuzz/util.h>
#include <test/util/poolresourcetester.h> #include <test/util/poolresourcetester.h>
#include <test/util/xoroshiro128plusplus.h>
#include <cstdint> #include <cstdint>
#include <tuple> #include <tuple>
@ -71,41 +71,14 @@ public:
void RandomContentFill(Entry& entry) void RandomContentFill(Entry& entry)
{ {
XoRoShiRo128PlusPlus rng(entry.seed); InsecureRandomContext(entry.seed).fillrand(entry.span);
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);
}
} }
void RandomContentCheck(const Entry& entry) void RandomContentCheck(const Entry& entry)
{ {
XoRoShiRo128PlusPlus rng(entry.seed); std::vector<std::byte> expect(entry.span.size());
auto ptr = entry.span.data(); InsecureRandomContext(entry.seed).fillrand(expect);
auto size = entry.span.size(); assert(entry.span == expect);
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);
}
} }
void Deallocate(const Entry& entry) void Deallocate(const Entry& entry)

View file

@ -2,9 +2,9 @@
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <random.h>
#include <span.h> #include <span.h>
#include <test/fuzz/util.h> #include <test/fuzz/util.h>
#include <test/util/xoroshiro128plusplus.h>
#include <util/vecdeque.h> #include <util/vecdeque.h>
#include <deque> #include <deque>
@ -28,7 +28,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
{ {
FuzzedDataProvider provider(buffer.data(), buffer.size()); FuzzedDataProvider provider(buffer.data(), buffer.size());
// Local RNG, only used for the seeds to initialize T objects with. // Local RNG, only used for the seeds to initialize T objects with.
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>() ^ rng_tweak); InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>() ^ rng_tweak);
// Real circular buffers. // Real circular buffers.
std::vector<VecDeque<T>> real; std::vector<VecDeque<T>> real;
@ -175,7 +175,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_full && command-- == 0) { if (existing_buffer_non_full && command-- == 0) {
/* push_back() (copying) */ /* push_back() (copying) */
tmp = T(rng()); tmp = T(rng.rand64());
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity(); size_t old_cap = real[idx].capacity();
real[idx].push_back(*tmp); real[idx].push_back(*tmp);
@ -191,7 +191,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_full && command-- == 0) { if (existing_buffer_non_full && command-- == 0) {
/* push_back() (moving) */ /* push_back() (moving) */
tmp = T(rng()); tmp = T(rng.rand64());
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity(); size_t old_cap = real[idx].capacity();
sim[idx].push_back(*tmp); sim[idx].push_back(*tmp);
@ -207,7 +207,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_full && command-- == 0) { if (existing_buffer_non_full && command-- == 0) {
/* emplace_back() */ /* emplace_back() */
uint64_t seed{rng()}; uint64_t seed{rng.rand64()};
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity(); size_t old_cap = real[idx].capacity();
sim[idx].emplace_back(seed); sim[idx].emplace_back(seed);
@ -223,7 +223,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_full && command-- == 0) { if (existing_buffer_non_full && command-- == 0) {
/* push_front() (copying) */ /* push_front() (copying) */
tmp = T(rng()); tmp = T(rng.rand64());
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity(); size_t old_cap = real[idx].capacity();
real[idx].push_front(*tmp); real[idx].push_front(*tmp);
@ -239,7 +239,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_full && command-- == 0) { if (existing_buffer_non_full && command-- == 0) {
/* push_front() (moving) */ /* push_front() (moving) */
tmp = T(rng()); tmp = T(rng.rand64());
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity(); size_t old_cap = real[idx].capacity();
sim[idx].push_front(*tmp); sim[idx].push_front(*tmp);
@ -255,7 +255,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_full && command-- == 0) { if (existing_buffer_non_full && command-- == 0) {
/* emplace_front() */ /* emplace_front() */
uint64_t seed{rng()}; uint64_t seed{rng.rand64()};
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity(); size_t old_cap = real[idx].capacity();
sim[idx].emplace_front(seed); sim[idx].emplace_front(seed);
@ -271,7 +271,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_empty && command-- == 0) { if (existing_buffer_non_empty && command-- == 0) {
/* front() [modifying] */ /* front() [modifying] */
tmp = T(rng()); tmp = T(rng.rand64());
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
assert(sim[idx].front() == real[idx].front()); assert(sim[idx].front() == real[idx].front());
sim[idx].front() = *tmp; sim[idx].front() = *tmp;
@ -281,7 +281,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_empty && command-- == 0) { if (existing_buffer_non_empty && command-- == 0) {
/* back() [modifying] */ /* back() [modifying] */
tmp = T(rng()); tmp = T(rng.rand64());
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
assert(sim[idx].back() == real[idx].back()); assert(sim[idx].back() == real[idx].back());
sim[idx].back() = *tmp; sim[idx].back() = *tmp;
@ -291,7 +291,7 @@ void TestType(Span<const uint8_t> buffer, uint64_t rng_tweak)
} }
if (existing_buffer_non_empty && command-- == 0) { if (existing_buffer_non_empty && command-- == 0) {
/* operator[] [modifying] */ /* operator[] [modifying] */
tmp = T(rng()); tmp = T(rng.rand64());
size_t pos = provider.ConsumeIntegralInRange<size_t>(0, sim[idx].size() - 1); size_t pos = provider.ConsumeIntegralInRange<size_t>(0, sim[idx].size() - 1);
size_t old_size = real[idx].size(); size_t old_size = real[idx].size();
assert(sim[idx][pos] == real[idx][pos]); assert(sim[idx][pos] == real[idx][pos]);

View file

@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
// ecdsa_signature_parse_der_lax are executed during this test. // ecdsa_signature_parse_der_lax are executed during this test.
// Specifically branches that run only when an ECDSA // Specifically branches that run only when an ECDSA
// signature's R and S values have leading zeros. // 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; TxOrphanageTest orphanage;
CKey key; CKey key;

View file

@ -210,9 +210,9 @@ public:
} }
prevector_tester() { prevector_tester() {
SeedInsecureRand(); SeedRandomForTest();
rand_seed = InsecureRand256(); rand_seed = InsecureRand256();
rand_cache = FastRandomContext(rand_seed); rand_cache.Reseed(rand_seed);
} }
}; };

View file

@ -20,28 +20,39 @@ BOOST_AUTO_TEST_CASE(osrandom_tests)
BOOST_CHECK(Random_SanityCheck()); BOOST_CHECK(Random_SanityCheck());
} }
BOOST_AUTO_TEST_CASE(fastrandom_tests) BOOST_AUTO_TEST_CASE(fastrandom_tests_deterministic)
{ {
// Check that deterministic FastRandomContexts are deterministic // Check that deterministic FastRandomContexts are deterministic
g_mock_deterministic_tests = true; SeedRandomForTest(SeedRand::ZEROS);
FastRandomContext ctx1(true); FastRandomContext ctx1{true};
FastRandomContext ctx2(true); FastRandomContext ctx2{true};
for (int i = 10; i > 0; --i) { {
BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{10393729187455219830U}); BOOST_CHECK_EQUAL(FastRandomContext().rand<uint64_t>(), uint64_t{9330418229102544152u});
BOOST_CHECK_EQUAL(GetRand<int>(), int{769702006}); BOOST_CHECK_EQUAL(FastRandomContext().rand<int>(), int{618925161});
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654); BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::microseconds>(1h).count(), 1271170921);
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374); BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count(), 2803534);
BOOST_CHECK_EQUAL(FastRandomContext().rand<uint64_t>(), uint64_t{10170981140880778086u});
BOOST_CHECK_EQUAL(FastRandomContext().rand<int>(), int{1689082725});
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::microseconds>(1h).count(), 2464643716);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count(), 2312205);
BOOST_CHECK_EQUAL(FastRandomContext().rand<uint64_t>(), uint64_t{5689404004456455543u});
BOOST_CHECK_EQUAL(FastRandomContext().rand<int>(), int{785839937});
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::microseconds>(1h).count(), 93558804);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count(), 507022);
} }
{ {
constexpr SteadySeconds time_point{1s}; constexpr SteadySeconds time_point{1s};
FastRandomContext ctx{true}; FastRandomContext ctx{true};
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());
@ -65,15 +76,28 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests)
// Check with time-point type // Check with time-point type
BOOST_CHECK_EQUAL(2782, ctx.rand_uniform_duration<SteadySeconds>(9h).count()); BOOST_CHECK_EQUAL(2782, ctx.rand_uniform_duration<SteadySeconds>(9h).count());
} }
}
BOOST_AUTO_TEST_CASE(fastrandom_tests_nondeterministic)
{
// Check that a nondeterministic ones are not // Check that a nondeterministic ones are not
g_mock_deterministic_tests = false; {
for (int i = 10; i > 0; --i) { BOOST_CHECK(FastRandomContext().rand<uint64_t>() != uint64_t{9330418229102544152u});
BOOST_CHECK(GetRand<uint64_t>() != uint64_t{10393729187455219830U}); BOOST_CHECK(FastRandomContext().rand<int>() != int{618925161});
BOOST_CHECK(GetRand<int>() != int{769702006}); BOOST_CHECK(FastRandomContext().randrange<std::chrono::microseconds>(1h).count() != 1271170921);
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}) != std::chrono::microseconds{2917185654}); BOOST_CHECK(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count() != 2803534);
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}) != std::chrono::milliseconds{2144374});
BOOST_CHECK(FastRandomContext().rand<uint64_t>() != uint64_t{10170981140880778086u});
BOOST_CHECK(FastRandomContext().rand<int>() != int{1689082725});
BOOST_CHECK(FastRandomContext().randrange<std::chrono::microseconds>(1h).count() != 2464643716);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count() != 2312205);
BOOST_CHECK(FastRandomContext().rand<uint64_t>() != uint64_t{5689404004456455543u});
BOOST_CHECK(FastRandomContext().rand<int>() != int{785839937});
BOOST_CHECK(FastRandomContext().randrange<std::chrono::microseconds>(1h).count() != 93558804);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count() != 507022);
} }
{ {
FastRandomContext ctx3, ctx4; FastRandomContext ctx3, ctx4;
BOOST_CHECK(ctx3.rand64() != ctx4.rand64()); // extremely unlikely to be equal BOOST_CHECK(ctx3.rand64() != ctx4.rand64()); // extremely unlikely to be equal
@ -103,6 +127,70 @@ 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_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.
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_test1.randbits(bits);
// For certain bits counts, also test randbits<Bits> 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.
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)
{ {
@ -155,4 +243,21 @@ BOOST_AUTO_TEST_CASE(shuffle_stat_test)
BOOST_CHECK_EQUAL(sum, 12000U); BOOST_CHECK_EQUAL(sum, 12000U);
} }
BOOST_AUTO_TEST_CASE(xoroshiro128plusplus_reference_values)
{
// numbers generated from reference implementation
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.Reseed(0x1a26f3fa8546b47a);
BOOST_TEST(0xc8dc5e08d844ac7d == rng());
BOOST_TEST(0x5b5f1f6d499dad1b == rng());
BOOST_TEST(0xbeb0031f93313d6f == rng());
BOOST_TEST(0xbfbcf4f43a264497 == rng());
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View file

@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_skip)
BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
{ {
// Make this test deterministic. // Make this test deterministic.
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
fs::path streams_test_filename = m_args.GetDataDirBase() / "streams_test_tmp"; fs::path streams_test_filename = m_args.GetDataDirBase() / "streams_test_tmp";
for (int rep = 0; rep < 50; ++rep) { for (int rep = 0; rep < 50; ++rep) {

View file

@ -126,7 +126,7 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candida
/*fRelevantServices=*/random_context.randbool(), /*fRelevantServices=*/random_context.randbool(),
/*m_relay_txs=*/random_context.randbool(), /*m_relay_txs=*/random_context.randbool(),
/*fBloomFilter=*/random_context.randbool(), /*fBloomFilter=*/random_context.randbool(),
/*nKeyedNetGroup=*/random_context.randrange(100), /*nKeyedNetGroup=*/random_context.randrange(100u),
/*prefer_evict=*/random_context.randbool(), /*prefer_evict=*/random_context.randbool(),
/*m_is_local=*/random_context.randbool(), /*m_is_local=*/random_context.randbool(),
/*m_network=*/ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())], /*m_network=*/ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())],

View file

@ -13,21 +13,26 @@
FastRandomContext g_insecure_rand_ctx; FastRandomContext g_insecure_rand_ctx;
/** Return the unsigned from the environment var if available, otherwise 0 */ extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept;
static uint256 GetUintFromEnv(const std::string& env_name)
{
const char* num = std::getenv(env_name.c_str());
if (!num) return {};
return uint256S(num);
}
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"}; 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()); 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.Reseed(GetRandHash());
} }

View file

@ -19,27 +19,13 @@
*/ */
extern FastRandomContext g_insecure_rand_ctx; 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 { enum class SeedRand {
ZEROS, //!< Seed with a compile time constant of zeros 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 */ /** Seed the RNG for testing. This affects all randomness, except GetStrongRandBytes(). */
void Seed(FastRandomContext& ctx); void SeedRandomForTest(SeedRand seed = SeedRand::SEED);
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);
}
}
static inline uint32_t InsecureRand32() static inline uint32_t InsecureRand32()
{ {

View file

@ -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")) { if (!m_node.args->IsArgSet("-testdatadir")) {
// By default, the data directory has a random name // By default, the data directory has a random name
const auto rand_str{g_insecure_rand_ctx_temp_path.rand256().ToString()}; 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)); gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root));
SelectParams(chainType); SelectParams(chainType);
SeedInsecureRand();
if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN); if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN);
InitLogging(*m_node.args); InitLogging(*m_node.args);
AppInitParameterInteraction(*m_node.args); AppInitParameterInteraction(*m_node.args);

View file

@ -1,71 +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 <cstdint>
#include <limits>
/** 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 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);
}
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. If you need a copy, call copy().
XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete;
XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete;
// allow moves
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;
s1 ^= s0;
m_s0 = rotl(s0, 49) ^ s1 ^ (s1 << 21);
m_s1 = rotl(s1, 28);
return result;
}
static constexpr result_type min() noexcept { return std::numeric_limits<result_type>::min(); }
static constexpr result_type max() noexcept { return std::numeric_limits<result_type>::max(); }
static constexpr double entropy() noexcept { return 0.0; }
};
#endif // BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H

View file

@ -459,7 +459,7 @@ BOOST_AUTO_TEST_CASE(util_IsHexNumber)
BOOST_AUTO_TEST_CASE(util_seed_insecure_rand) BOOST_AUTO_TEST_CASE(util_seed_insecure_rand)
{ {
SeedInsecureRand(SeedRand::ZEROS); SeedRandomForTest(SeedRand::ZEROS);
for (int mod=2;mod<11;mod++) for (int mod=2;mod<11;mod++)
{ {
int mask = 1; int mask = 1;

View file

@ -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 <test/util/setup_common.h>
#include <test/util/xoroshiro128plusplus.h>
#include <boost/test/unit_test.hpp>
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()

View file

@ -657,7 +657,7 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei
{ {
if (m_opts.check_ratio == 0) return; 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); AssertLockHeld(::cs_main);
LOCK(cs); LOCK(cs);

View file

@ -113,8 +113,8 @@ class PriorityComputer {
const uint64_t m_k0, m_k1; const uint64_t m_k0, m_k1;
public: public:
explicit PriorityComputer(bool deterministic) : explicit PriorityComputer(bool deterministic) :
m_k0{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)}, m_k0{deterministic ? 0 : FastRandomContext().rand64()},
m_k1{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)} {} m_k1{deterministic ? 0 : FastRandomContext().rand64()} {}
Priority operator()(const uint256& txhash, NodeId peer, bool preferred) const Priority operator()(const uint256& txhash, NodeId peer, bool preferred) const
{ {

View file

@ -9,8 +9,8 @@
#include <vector> #include <vector>
ByteVectorHash::ByteVectorHash() : ByteVectorHash::ByteVectorHash() :
m_k0(GetRand<uint64_t>()), m_k0(FastRandomContext().rand64()),
m_k1(GetRand<uint64_t>()) m_k1(FastRandomContext().rand64())
{ {
} }

View file

@ -7,14 +7,18 @@
#include <span.h> #include <span.h>
#include <util/hasher.h> #include <util/hasher.h>
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} SaltedTxidHasher::SaltedTxidHasher() :
k0{FastRandomContext().rand64()},
k1{FastRandomContext().rand64()} {}
SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) : SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) :
k0(deterministic ? 0x8e819f2607a18de6 : GetRand<uint64_t>()), k0{deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64()},
k1(deterministic ? 0xf4020d2e3983b0eb : GetRand<uint64_t>()) k1{deterministic ? 0xf4020d2e3983b0eb : FastRandomContext().rand64()}
{} {}
SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand<uint64_t>()), m_k1(GetRand<uint64_t>()) {} SaltedSipHasher::SaltedSipHasher() :
m_k0{FastRandomContext().rand64()},
m_k1{FastRandomContext().rand64()} {}
size_t SaltedSipHasher::operator()(const Span<const unsigned char>& script) const size_t SaltedSipHasher::operator()(const Span<const unsigned char>& script) const
{ {

View file

@ -5193,7 +5193,7 @@ bool ChainstateManager::ShouldCheckBlockIndex() const
{ {
// Assert to verify Flatten() has been called. // Assert to verify Flatten() has been called.
if (!*Assert(m_options.check_block_index)) return false; 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; return true;
} }

View file

@ -56,7 +56,8 @@ unsigned-integer-overflow:CBlockPolicyEstimator::processBlockTx
unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal
unsigned-integer-overflow:prevector.h unsigned-integer-overflow:prevector.h
unsigned-integer-overflow:EvalScript unsigned-integer-overflow:EvalScript
unsigned-integer-overflow:xoroshiro128plusplus.h unsigned-integer-overflow:InsecureRandomContext::rand64
unsigned-integer-overflow:InsecureRandomContext::SplitMix64
unsigned-integer-overflow:bitset_detail::PopCount unsigned-integer-overflow:bitset_detail::PopCount
implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx
implicit-integer-sign-change:SetStdinEcho implicit-integer-sign-change:SetStdinEcho
@ -73,4 +74,6 @@ shift-base:arith_uint256.cpp
shift-base:crypto/ shift-base:crypto/
shift-base:streams.h shift-base:streams.h
shift-base:FormatHDKeypath shift-base:FormatHDKeypath
shift-base:xoroshiro128plusplus.h shift-base:InsecureRandomContext::rand64
shift-base:RandomMixin<*>::randbits
shift-base:RandomMixin<*>::randbits<*>