From 2c91330dd68064e402e8eceea3df9474bb7afd48 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 11 Mar 2024 14:10:44 -0400 Subject: [PATCH] random: cleanup order, comments, static --- src/random.cpp | 79 ++++++++++++----------- src/random.h | 111 ++++++++++++++++++++------------- src/test/cuckoocache_tests.cpp | 2 +- 3 files changed, 109 insertions(+), 83 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index cb5c127e0d..21a08c7fd3 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -45,13 +45,23 @@ #include #endif -[[noreturn]] static void RandFailure() +namespace { + +/* Number of random bytes returned by GetOSRand. + * When changing this constant make sure to change all call sites, and make + * sure that the underlying OS APIs for all platforms support the number. + * (many cap out at 256 bytes). + */ +static const int NUM_OS_RANDOM_BYTES = 32; + + +[[noreturn]] void RandFailure() { LogPrintf("Failed to read randomness, aborting\n"); std::abort(); } -static inline int64_t GetPerformanceCounter() noexcept +inline int64_t GetPerformanceCounter() noexcept { // Read the hardware time stamp counter when available. // See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information. @@ -72,10 +82,10 @@ static inline int64_t GetPerformanceCounter() noexcept } #ifdef HAVE_GETCPUID -static bool g_rdrand_supported = false; -static bool g_rdseed_supported = false; -static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; -static constexpr uint32_t CPUID_F7_EBX_RDSEED = 0x00040000; +bool g_rdrand_supported = false; +bool g_rdseed_supported = false; +constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; +constexpr uint32_t CPUID_F7_EBX_RDSEED = 0x00040000; #ifdef bit_RDRND static_assert(CPUID_F1_ECX_RDRAND == bit_RDRND, "Unexpected value for bit_RDRND"); #endif @@ -83,7 +93,7 @@ static_assert(CPUID_F1_ECX_RDRAND == bit_RDRND, "Unexpected value for bit_RDRND" static_assert(CPUID_F7_EBX_RDSEED == bit_RDSEED, "Unexpected value for bit_RDSEED"); #endif -static void InitHardwareRand() +void InitHardwareRand() { uint32_t eax, ebx, ecx, edx; GetCPUID(1, 0, eax, ebx, ecx, edx); @@ -96,7 +106,7 @@ static void InitHardwareRand() } } -static void ReportHardwareRand() +void ReportHardwareRand() { // This must be done in a separate function, as InitHardwareRand() may be indirectly called // from global constructors, before logging is initialized. @@ -112,7 +122,7 @@ static void ReportHardwareRand() * * Must only be called when RdRand is supported. */ -static uint64_t GetRdRand() noexcept +uint64_t GetRdRand() noexcept { // RdRand may very rarely fail. Invoke it up to 10 times in a loop to reduce this risk. #ifdef __i386__ @@ -147,7 +157,7 @@ static uint64_t GetRdRand() noexcept * * Must only be called when RdSeed is supported. */ -static uint64_t GetRdSeed() noexcept +uint64_t GetRdSeed() noexcept { // RdSeed may fail when the HW RNG is overloaded. Loop indefinitely until enough entropy is gathered, // but pause after every failure. @@ -181,16 +191,16 @@ static uint64_t GetRdSeed() noexcept #elif defined(__aarch64__) && defined(HWCAP2_RNG) -static bool g_rndr_supported = false; +bool g_rndr_supported = false; -static void InitHardwareRand() +void InitHardwareRand() { if (getauxval(AT_HWCAP2) & HWCAP2_RNG) { g_rndr_supported = true; } } -static void ReportHardwareRand() +void ReportHardwareRand() { // This must be done in a separate function, as InitHardwareRand() may be indirectly called // from global constructors, before logging is initialized. @@ -203,7 +213,7 @@ static void ReportHardwareRand() * * Must only be called when RNDR is supported. */ -static uint64_t GetRNDR() noexcept +uint64_t GetRNDR() noexcept { uint8_t ok; uint64_t r1; @@ -221,7 +231,7 @@ static uint64_t GetRNDR() noexcept * * Must only be called when RNDRRS is supported. */ -static uint64_t GetRNDRRS() noexcept +uint64_t GetRNDRRS() noexcept { uint8_t ok; uint64_t r1; @@ -241,12 +251,12 @@ static uint64_t GetRNDRRS() noexcept * Slower sources should probably be invoked separately, and/or only from * RandAddPeriodic (which is called once a minute). */ -static void InitHardwareRand() {} -static void ReportHardwareRand() {} +void InitHardwareRand() {} +void ReportHardwareRand() {} #endif /** Add 64 bits of entropy gathered from hardware to hasher. Do nothing if not supported. */ -static void SeedHardwareFast(CSHA512& hasher) noexcept { +void SeedHardwareFast(CSHA512& hasher) noexcept { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) if (g_rdrand_supported) { uint64_t out = GetRdRand(); @@ -263,7 +273,7 @@ static void SeedHardwareFast(CSHA512& hasher) noexcept { } /** Add 256 bits of entropy gathered from hardware to hasher. Do nothing if not supported. */ -static void SeedHardwareSlow(CSHA512& hasher) noexcept { +void SeedHardwareSlow(CSHA512& hasher) noexcept { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) // When we want 256 bits of entropy, prefer RdSeed over RdRand, as it's // guaranteed to produce independent randomness on every call. @@ -296,7 +306,7 @@ static void SeedHardwareSlow(CSHA512& hasher) noexcept { } /** Use repeated SHA512 to strengthen the randomness in seed32, and feed into hasher. */ -static void Strengthen(const unsigned char (&seed)[32], SteadyClock::duration dur, CSHA512& hasher) noexcept +void Strengthen(const unsigned char (&seed)[32], SteadyClock::duration dur, CSHA512& hasher) noexcept { CSHA512 inner_hasher; inner_hasher.Write(seed, sizeof(seed)); @@ -327,7 +337,7 @@ static void Strengthen(const unsigned char (&seed)[32], SteadyClock::duration du /** Fallback: get 32 bytes of system entropy from /dev/urandom. The most * compatible way to get cryptographic randomness on UNIX-ish platforms. */ -[[maybe_unused]] static void GetDevURandom(unsigned char *ent32) +[[maybe_unused]] void GetDevURandom(unsigned char *ent32) { int f = open("/dev/urandom", O_RDONLY); if (f == -1) { @@ -402,8 +412,6 @@ void GetOSRand(unsigned char *ent32) #endif } -namespace { - class RNGState { Mutex m_mutex; /* The RNG state consists of 256 bits of entropy, taken from the output of @@ -521,20 +529,19 @@ RNGState& GetRNGState() noexcept static std::vector> g_rng(1); return g_rng[0]; } -} /* A note on the use of noexcept in the seeding functions below: * * None of the RNG code should ever throw any exception. */ -static void SeedTimestamp(CSHA512& hasher) noexcept +void SeedTimestamp(CSHA512& hasher) noexcept { int64_t perfcounter = GetPerformanceCounter(); hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); } -static void SeedFast(CSHA512& hasher) noexcept +void SeedFast(CSHA512& hasher) noexcept { unsigned char buffer[32]; @@ -549,7 +556,7 @@ static void SeedFast(CSHA512& hasher) noexcept SeedTimestamp(hasher); } -static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept +void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept { unsigned char buffer[32]; @@ -571,7 +578,7 @@ static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept } /** Extract entropy from rng, strengthen it, and feed it into hasher. */ -static void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration dur) noexcept +void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration dur) noexcept { // Generate 32 bytes of entropy from the RNG, and a copy of the entropy already in hasher. // Never use the deterministic PRNG for this, as the result is only used internally. @@ -581,7 +588,7 @@ static void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration Strengthen(strengthen_seed, dur, hasher); } -static void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept +void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept { // Everything that the 'fast' seeder includes SeedFast(hasher); @@ -601,7 +608,7 @@ static void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept SeedStrengthen(hasher, rng, 10ms); } -static void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept +void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept { // Gather 256 bits of hardware randomness, if available SeedHardwareSlow(hasher); @@ -627,7 +634,7 @@ enum class RNGLevel { PERIODIC, //!< Called by RandAddPeriodic() }; -static void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_use_real_rng) noexcept +void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_use_real_rng) noexcept { // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available). RNGState& rng = GetRNGState(); @@ -656,6 +663,9 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_us } } +} // namespace + + /** Internal function to set g_determinstic_rng. Only accessed from tests. */ void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept { @@ -679,13 +689,6 @@ void RandAddPeriodic() noexcept void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } -uint256 GetRandHash() noexcept -{ - uint256 hash; - GetRandBytes(hash); - return hash; -} - void FastRandomContext::RandomSeed() noexcept { uint256 seed = GetRandHash(); diff --git a/src/random.h b/src/random.h index 821c84fce3..ea517d2d2e 100644 --- a/src/random.h +++ b/src/random.h @@ -28,8 +28,8 @@ * The following (classes of) functions interact with that state by mixing in new * entropy, and optionally extracting random output from it: * - * - The GetRand*() class of functions, as well as construction of FastRandomContext objects, - * perform 'fast' seeding, consisting of mixing in: + * - GetRandBytes, GetRandHash, GetRandDur, as well as construction of FastRandomContext + * objects, perform 'fast' seeding, consisting of mixing in: * - A stack pointer (indirectly committing to calling thread and call stack) * - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise) * - 64 bits from the hardware RNG (rdrand) when available. @@ -38,7 +38,7 @@ * FastRandomContext on the other hand does not protect against this once created, but * is even faster (and acceptable to use inside tight loops). * - * - The GetStrongRand*() class of function perform 'slow' seeding, including everything + * - The GetStrongRandBytes() function performs 'slow' seeding, including everything * that fast seeding includes, but additionally: * - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if * this entropy source fails. @@ -53,12 +53,12 @@ * - Strengthen the entropy for 10 ms using repeated SHA512. * This is run once every minute. * - * On first use of the RNG (regardless of what function is called first), all entropy - * sources used in the 'slow' seeder are included, but also: - * - 256 bits from the hardware RNG (rdseed or rdrand) when available. - * - Dynamic environment data (performance monitoring, ...) - * - Static environment data - * - Strengthen the entropy for 100 ms using repeated SHA512. + * - On first use of the RNG (regardless of what function is called first), all entropy + * sources used in the 'slow' seeder are included, but also: + * - 256 bits from the hardware RNG (rdseed or rdrand) when available. + * - Dynamic environment data (performance monitoring, ...) + * - Static environment data + * - Strengthen the entropy for 100 ms using repeated SHA512. * * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and * (up to) the first 32 bytes of H are produced as output, while the last 32 bytes @@ -71,27 +71,16 @@ * only depends on the seed it was initialized with, possibly until it is reinitialized. */ -/** - * Generate random data via the internal PRNG. - * - * These functions are designed to be fast (sub microsecond), but do not necessarily - * meaningfully add entropy to the PRNG state. - * - * Thread-safe. - */ -void GetRandBytes(Span bytes) noexcept; -uint256 GetRandHash() noexcept; +/* ============================= INITIALIZATION AND ADDING ENTROPY ============================= */ /** - * Gather entropy from various sources, feed it into the internal PRNG, and - * generate random data using it. + * Initialize global RNG state and log any CPU features that are used. * - * This function will cause failure whenever the OS RNG fails. - * - * Thread-safe. + * Calling this function is optional. RNG state will be initialized when first + * needed if it is not called. */ -void GetStrongRandBytes(Span bytes) noexcept; +void RandomInit(); /** * Gather entropy from various expensive sources, and feed them to the PRNG state. @@ -108,6 +97,47 @@ void RandAddPeriodic() noexcept; */ void RandAddEvent(const uint32_t event_info) noexcept; + +/* =========================== BASE RANDOMNESS GENERATION FUNCTIONS =========================== + * + * All produced randomness is eventually generated by one of these functions. + */ + +/** + * Generate random data via the internal PRNG. + * + * These functions are designed to be fast (sub microsecond), but do not necessarily + * meaningfully add entropy to the PRNG state. + * + * In test mode (see SeedRandomForTest in src/test/util/random.h), the normal PRNG state is + * bypassed, and a deterministic, seeded, PRNG is used instead. + * + * Thread-safe. + */ +void GetRandBytes(Span bytes) noexcept; + +/** + * Gather entropy from various sources, feed it into the internal PRNG, and + * generate random data using it. + * + * This function will cause failure whenever the OS RNG fails. + * + * The normal PRNG is never bypassed here, even in test mode. + * + * Thread-safe. + */ +void GetStrongRandBytes(Span bytes) noexcept; + + +/* ============================= RANDOM NUMBER GENERATION CLASSES ============================= + * + * In this section, 3 classes are defined: + * - RandomMixin: a base class that adds functionality to all RNG classes. + * - FastRandomContext: a cryptographic RNG (seeded through GetRandBytes in its default + * constructor). + * - InsecureRandomContext: a non-cryptographic, very fast, RNG. + */ + // Forward declaration of RandomMixin, used in RandomNumberGenerator concept. template class RandomMixin; @@ -430,6 +460,17 @@ public: } }; + +/* ==================== CONVENIENCE FUNCTIONS FOR COMMONLY USED RANDOMNESS ==================== */ + +/** Generate a random uint256. */ +inline uint256 GetRandHash() noexcept +{ + uint256 hash; + GetRandBytes(hash); + return hash; +} + /** More efficient than using std::shuffle on a FastRandomContext. * * This is more efficient as std::shuffle will consume entropy in groups of @@ -453,29 +494,11 @@ void Shuffle(I first, I last, R&& rng) } } -/* Number of random bytes returned by GetOSRand. - * When changing this constant make sure to change all call sites, and make - * sure that the underlying OS APIs for all platforms support the number. - * (many cap out at 256 bytes). - */ -static const int NUM_OS_RANDOM_BYTES = 32; - -/** Get 32 bytes of system entropy. Do not use this in application code: use - * GetStrongRandBytes instead. - */ -void GetOSRand(unsigned char* ent32); +/* ============================= MISCELLANEOUS TEST-ONLY FUNCTIONS ============================= */ /** Check that OS randomness is available and returning the requested number * of bytes. */ bool Random_SanityCheck(); -/** - * Initialize global RNG state and log any CPU features that are used. - * - * Calling this function is optional. RNG state will be initialized when first - * needed if it is not called. - */ -void RandomInit(); - #endif // BITCOIN_RANDOM_H diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index 906fbb4afa..fc22daeb57 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -33,7 +33,7 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests); /* Test that no values not inserted into the cache are read out of it. * - * There are no repeats in the first 200000 insecure_GetRandHash calls + * There are no repeats in the first 200000 InsecureRand256() calls */ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) {