dash/src/random.h
Kittywhiskers Van Gogh 4bc727cd6c
stats: clean up randomization code, move FastRandomContext inward
We can skip the computationally expensive dice-roll if our sample rate
is zero as that means we should never send it. Also get rid of the
`FastRandomContext::operator()` that isn't used anywhere else in the
codebase.
2024-09-11 14:34:19 +00:00

299 lines
10 KiB
C++

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 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_RANDOM_H
#define BITCOIN_RANDOM_H
#include <crypto/chacha20.h>
#include <crypto/common.h>
#include <span.h>
#include <uint256.h>
#include <cassert>
#include <chrono> // For std::chrono::microseconds
#include <cstdint>
#include <limits>
#include <vector>
/**
* Overall design of the RNG and entropy sources.
*
* We maintain a single global 256-bit RNG state for all high-quality randomness.
* The following (classes of) functions interact with that state by mixing in new
* entropy, and optionally extracting random output from it:
*
* - The GetRand*() class of functions, as well as construction of FastRandomContext objects,
* perform 'fast' seeding, consisting of mixing in:
* - A stack pointer (indirectly committing to calling thread and call stack)
* - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise)
* - 64 bits from the hardware RNG (rdrand) when available.
* These entropy sources are very fast, and only designed to protect against situations
* where a VM state restore/copy results in multiple systems with the same randomness.
* FastRandomContext on the other hand does not protect against this once created, but
* is even faster (and acceptable to use inside tight loops).
*
* - The GetStrongRand*() class of function perform 'slow' seeding, including everything
* that fast seeding includes, but additionally:
* - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if
* this entropy source fails.
* - Another high-precision timestamp (indirectly committing to a benchmark of all the
* previous sources).
* These entropy sources are slower, but designed to make sure the RNG state contains
* fresh data that is unpredictable to attackers.
*
* - RandAddPeriodic() seeds everything that fast seeding includes, but additionally:
* - A high-precision timestamp
* - Dynamic environment data (performance monitoring, ...)
* - Strengthen the entropy for 10 ms using repeated SHA512.
* This is run once every minute.
*
* On first use of the RNG (regardless of what function is called first), all entropy
* sources used in the 'slow' seeder are included, but also:
* - 256 bits from the hardware RNG (rdseed or rdrand) when available.
* - Dynamic environment data (performance monitoring, ...)
* - Static environment data
* - Strengthen the entropy for 100 ms using repeated SHA512.
*
* When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and
* (up to) the first 32 bytes of H are produced as output, while the last 32 bytes
* become the new RNG state.
*/
/**
* Generate random data via the internal PRNG.
*
* These functions are designed to be fast (sub microsecond), but do not necessarily
* meaningfully add entropy to the PRNG state.
*
* Thread-safe.
*/
void GetRandBytes(Span<unsigned char> bytes) noexcept;
/** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */
uint64_t GetRand(uint64_t nMax) noexcept;
/** 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
* (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 occuring before time x is 1 - e^-(x/a) where a
* is the average interval between events.
* */
std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval);
int GetRandInt(int nMax) noexcept;
uint256 GetRandHash() noexcept;
bool GetRandBool(double rate);
/**
* 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;
/**
* Gather entropy from various expensive sources, and feed them to the PRNG state.
*
* Thread-safe.
*/
void RandAddPeriodic() noexcept;
/**
* Gathers entropy from the low bits of the time at which events occur. Should
* be called with a uint32_t describing the event at the time an event occurs.
*
* Thread-safe.
*/
void RandAddEvent(const uint32_t event_info) noexcept;
/**
* Fast randomness source. This is seeded once with secure random data, but
* is completely deterministic and does not gather more entropy after that.
*
* This class is not thread-safe.
*/
class FastRandomContext
{
private:
bool requires_seed;
ChaCha20 rng;
uint64_t bitbuf;
int bitbuf_size;
void RandomSeed();
void FillBitBuffer()
{
bitbuf = rand64();
bitbuf_size = 64;
}
public:
explicit FastRandomContext(bool fDeterministic = false) noexcept;
/** Initialize with explicit seed (only for testing) */
explicit FastRandomContext(const uint256& seed) noexcept;
// Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded).
FastRandomContext(const FastRandomContext&) = delete;
FastRandomContext(FastRandomContext&&) = delete;
FastRandomContext& operator=(const FastRandomContext&) = delete;
/** Move a FastRandomContext. If the original one is used again, it will be reseeded. */
FastRandomContext& operator=(FastRandomContext&& from) noexcept;
/** Generate a random 64-bit integer. */
uint64_t rand64() noexcept
{
if (requires_seed) RandomSeed();
std::array<std::byte, 8> buf;
rng.Keystream(buf);
return ReadLE64(UCharCast(buf.data()));
}
/** Generate a random (bits)-bit integer. */
uint64_t randbits(int bits) noexcept
{
if (bits == 0) {
return 0;
} else if (bits > 32) {
return rand64() >> (64 - bits);
} else {
if (bitbuf_size < bits) FillBitBuffer();
uint64_t ret = bitbuf & (~(uint64_t)0 >> (64 - bits));
bitbuf >>= bits;
bitbuf_size -= bits;
return ret;
}
}
/** Generate a random integer in the range [0..range).
* Precondition: range > 0.
*/
uint64_t randrange(uint64_t range) noexcept
{
assert(range);
--range;
int bits = CountBits(range);
while (true) {
uint64_t ret = randbits(bits);
if (ret <= range) return ret;
}
}
uint32_t rand32(uint32_t nMax) {
return rand32() % nMax;
}
/** 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(); }
};
/** More efficient than using std::shuffle on a FastRandomContext.
*
* This is more efficient as std::shuffle will consume entropy in groups of
* 64 bits at the time and throw away most.
*
* This also works around a bug in libstdc++ std::shuffle that may cause
* type::operator=(type&&) to be invoked on itself, which the library's
* debug mode detects and panics on. This is a known issue, see
* https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle
*/
template <typename I, typename R>
void Shuffle(I first, I last, R&& rng)
{
while (first != last) {
size_t j = rng.randrange(last - first);
if (j) {
using std::swap;
swap(*first, *(first + j));
}
++first;
}
}
/* Number of random bytes returned by GetOSRand.
* When changing this constant make sure to change all call sites, and make
* sure that the underlying OS APIs for all platforms support the number.
* (many cap out at 256 bytes).
*/
static const int NUM_OS_RANDOM_BYTES = 32;
/** Get 32 bytes of system entropy. Do not use this in application code: use
* GetStrongRandBytes instead.
*/
void GetOSRand(unsigned char* ent32);
/** Check that OS randomness is available and returning the requested number
* of bytes.
*/
bool Random_SanityCheck();
/**
* Initialize global RNG state and log any CPU features that are used.
*
* Calling this function is optional. RNG state will be initialized when first
* needed if it is not called.
*/
void RandomInit();
#endif // BITCOIN_RANDOM_H