diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 190f255709d..5e5896362b7 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -215,7 +215,7 @@ void PaperWalletDialog::setClientModel(ClientModel *_clientModel) void PaperWalletDialog::setModel(WalletModel *model) { - RandAddSeed(); + RandAddSeedSleep(); this->model = model; this->on_getNewAddress_clicked(); } diff --git a/src/random.cpp b/src/random.cpp index 89e77dcb70c..736c8e505fb 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -12,7 +12,7 @@ #include #endif #include "util.h" // for LogPrint() -#include "sync.h" // for WAIT_LOCK +#include "sync.h" // for LOCK #include "utiltime.h" // for GetTime() #include @@ -142,18 +142,8 @@ static bool GetHardwareRand(unsigned char* ent32) { return false; } -void RandAddSeed() +static void RandAddSeedPerfmon(CSHA512& hasher) { - // Seed with CPU performance counter - int64_t nCounter = GetPerformanceCounter(); - RAND_add(&nCounter, sizeof(nCounter), 1.5); - memory_cleanse((void*)&nCounter, sizeof(nCounter)); -} - -static void RandAddSeedPerfmon() -{ - RandAddSeed(); - #ifdef WIN32 // Don't need this on Linux, OpenSSL automatically uses /dev/urandom // Seed with the entire set of perfmon data @@ -177,7 +167,7 @@ static void RandAddSeedPerfmon() } RegCloseKey(HKEY_PERFORMANCE_DATA); if (ret == ERROR_SUCCESS) { - RAND_add(vData.data(), nSize, nSize / 100.0); + hasher.Write(vData.data(), nSize); memory_cleanse(vData.data(), nSize); } else { // Performance data is only a best-effort attempt at improving the @@ -285,13 +275,6 @@ void GetOSRand(unsigned char *ent32) #endif } -void GetRandBytes(unsigned char* buf, int num) -{ - if (RAND_bytes(buf, num) != 1) { - RandFailure(); - } -} - void LockingCallbackOpenSSL(int mode, int i, const char* file, int line); namespace { @@ -300,6 +283,7 @@ struct RNGState { Mutex m_mutex; unsigned char m_state[32] GUARDED_BY(m_mutex) = {0}; uint64_t m_counter GUARDED_BY(m_mutex) = 0; + bool m_strongly_seeded GUARDED_BY(m_mutex) = false; std::unique_ptr m_mutex_openssl; RNGState() @@ -316,14 +300,6 @@ struct RNGState { // or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be // that the config appears to have been loaded and there are no modules/engines available. OPENSSL_no_config(); - -#ifdef WIN32 - // Seed OpenSSL PRNG with current contents of the screen - RAND_screen(); -#endif - - // Seed OpenSSL PRNG with performance counter - RandAddSeed(); } ~RNGState() @@ -334,14 +310,19 @@ struct RNGState { CRYPTO_set_locking_callback(nullptr); } - /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. */ - void MixExtract(unsigned char* out, size_t num, CSHA512&& 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. + */ + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) { assert(num <= 32); unsigned char buf[64]; static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size"); + bool ret; { - WAIT_LOCK(m_mutex, lock); + LOCK(m_mutex); + ret = (m_strongly_seeded |= strong_seed); // Write the current state of the RNG into the hasher hasher.Write(m_state, 32); // Write a new counter number into the state @@ -360,6 +341,7 @@ struct RNGState { // Best effort cleanup of internal state hasher.Reset(); memory_cleanse(buf, 64); + return ret; } }; @@ -383,61 +365,128 @@ void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THRE } } -static void AddDataToRng(void* data, size_t len, RNGState& rng); +static void SeedTimestamp(CSHA512& hasher) +{ + int64_t perfcounter = GetPerformanceCounter(); + hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); +} -void RandAddSeedSleep() +static void SeedFast(CSHA512& hasher) { - RNGState& rng = GetRNGState(); + unsigned char buffer[32]; - int64_t nPerfCounter1 = GetPerformanceCounter(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - int64_t nPerfCounter2 = GetPerformanceCounter(); + // Stack pointer to indirectly commit to thread/callstack + const unsigned char* ptr = buffer; + hasher.Write((const unsigned char*)&ptr, sizeof(ptr)); - // Combine with and update state - AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1), rng); - AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2), rng); + // Hardware randomness is very fast when available; use it always. + bool have_hw_rand = GetHardwareRand(buffer); + if (have_hw_rand) hasher.Write(buffer, sizeof(buffer)); - memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1)); - memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2)); + // High-precision timestamp + SeedTimestamp(hasher); } -static void AddDataToRng(void* data, size_t len, RNGState& rng) { - CSHA512 hasher; - hasher.Write((const unsigned char*)&len, sizeof(len)); - hasher.Write((const unsigned char*)data, len); - rng.MixExtract(nullptr, 0, std::move(hasher)); +static void SeedSlow(CSHA512& hasher) +{ + unsigned char buffer[32]; + + // Everything that the 'fast' seeder includes + SeedFast(hasher); + + // OS randomness + GetOSRand(buffer); + hasher.Write(buffer, sizeof(buffer)); + + // OpenSSL RNG (for now) + RAND_bytes(buffer, sizeof(buffer)); + hasher.Write(buffer, sizeof(buffer)); + + // High-precision timestamp. + // + // Note that we also commit to a timestamp in the Fast seeder, so we indirectly commit to a + // benchmark of all the entropy gathering sources in this function). + SeedTimestamp(hasher); } -void GetStrongRandBytes(unsigned char* out, int num) +static void SeedSleep(CSHA512& hasher) { - RNGState& rng = GetRNGState(); + // Everything that the 'fast' seeder includes + SeedFast(hasher); - assert(num <= 32); - CSHA512 hasher; - unsigned char buf[64]; + // High-precision timestamp + SeedTimestamp(hasher); - // First source: OpenSSL's RNG - RandAddSeedPerfmon(); - GetRandBytes(buf, 32); - hasher.Write(buf, 32); + // Sleep for 1ms + UninterruptibleSleep(std::chrono::milliseconds{1}); - // Second source: OS RNG - GetOSRand(buf); - hasher.Write(buf, 32); + // High-precision timestamp after sleeping (as we commit to both the time before and after, this measures the delay) + SeedTimestamp(hasher); - // Third source: HW RNG, if available. - if (GetHardwareRand(buf)) { - hasher.Write(buf, 32); + // Windows performance monitor data (once every 10 minutes) + RandAddSeedPerfmon(hasher); +} + +static void SeedStartup(CSHA512& hasher) +{ +#ifdef WIN32 + RAND_screen(); +#endif + + // Everything that the 'slow' seeder includes. + SeedSlow(hasher); + + // Windows performance monitor data. + RandAddSeedPerfmon(hasher); +} + +enum class RNGLevel { + FAST, //!< Automatically called by GetRandBytes + SLOW, //!< Automatically called by GetStrongRandBytes + SLEEP, //!< Called by RandAddSeedSleep() +}; + +static void ProcRand(unsigned char* out, int num, RNGLevel level) +{ + // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available). + RNGState& rng = GetRNGState(); + + assert(num <= 32); + + CSHA512 hasher; + switch (level) { + case RNGLevel::FAST: + SeedFast(hasher); + break; + case RNGLevel::SLOW: + SeedSlow(hasher); + break; + case RNGLevel::SLEEP: + SeedSleep(hasher); + break; } // Combine with and update state - rng.MixExtract(out, num, std::move(hasher)); + if (!rng.MixExtract(out, num, std::move(hasher), false)) { + // On the first invocation, also seed with SeedStartup(). + CSHA512 startup_hasher; + SeedStartup(startup_hasher); + rng.MixExtract(out, num, std::move(startup_hasher), true); + } - // Produce output - memcpy(out, buf, num); - memory_cleanse(buf, 64); + // For anything but the 'fast' level, feed the resulting RNG output (after an additional hashing step) back into OpenSSL. + if (level != RNGLevel::FAST) { + unsigned char buf[64]; + CSHA512().Write(out, num).Finalize(buf); + RAND_add(buf, sizeof(buf), num); + memory_cleanse(buf, 64); + } } +void GetRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::FAST); } +void GetStrongRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::SLOW); } +void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); } + uint64_t GetRand(uint64_t nMax) { if (nMax == 0) @@ -535,8 +584,10 @@ bool Random_SanityCheck() if (stop == start) return false; // We called GetPerformanceCounter. Use it as entropy. - RAND_add((const unsigned char*)&start, sizeof(start), 1); - RAND_add((const unsigned char*)&stop, sizeof(stop), 1); + CSHA512 to_add; + to_add.Write((const unsigned char*)&start, sizeof(start)); + to_add.Write((const unsigned char*)&stop, sizeof(stop)); + GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false); return true; } @@ -553,7 +604,7 @@ FastRandomContext::FastRandomContext(bool fDeterministic) : requires_seed(!fDete void RandomInit() { // Invoke RNG code to trigger initialization (if not already performed) - GetRNGState(); + ProcRand(nullptr, 0, RNGLevel::FAST); ReportHardwareRand(); } diff --git a/src/random.h b/src/random.h index 7f7674ee739..333e5f7a283 100644 --- a/src/random.h +++ b/src/random.h @@ -13,11 +13,13 @@ #include #include -/* Seed OpenSSL PRNG with additional entropy data */ -void RandAddSeed(); - /** - * Functions to gather random data via the OpenSSL PRNG + * 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(unsigned char* buf, int num); uint64_t GetRand(uint64_t nMax); @@ -25,21 +27,26 @@ int GetRandInt(int nMax); uint256 GetRandHash(); /** - * Add a little bit of randomness to the output of GetStrongRangBytes. - * This sleeps for a millisecond, so should only be called when there is - * no other work to be done. + * 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 RandAddSeedSleep(); +void GetStrongRandBytes(unsigned char* buf, int num); /** - * Function to gather random data from multiple sources, failing whenever any - * of those source fail to provide a result. + * Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG state. + * + * Thread-safe. */ -void GetStrongRandBytes(unsigned char* buf, int num); +void RandAddSeedSleep(); /** * Fast randomness source. This is seeded once with secure random data, but - * is completely deterministic and insecure after that. + * is completely deterministic and does not gather more entropy after that. + * * This class is not thread-safe. */ class FastRandomContext { diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 167eebf83c5..6063adf2659 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -41,7 +41,7 @@ void CScheduler::serviceQueue() try { if (!shouldStop() && taskQueue.empty()) { reverse_lock > rlock(lock); - // Use this chance to get a tiny bit more entropy + // Use this chance to get more entropy RandAddSeedSleep(); } while (!shouldStop() && taskQueue.empty()) {