aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/random.cpp185
-rw-r--r--src/random.h31
-rw-r--r--src/scheduler.cpp2
3 files changed, 138 insertions, 80 deletions
diff --git a/src/random.cpp b/src/random.cpp
index 6699318ee0..ca54cc9962 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -145,18 +145,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
@@ -180,7 +170,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
@@ -288,13 +278,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 {
@@ -303,6 +286,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<Mutex[]> m_mutex_openssl;
RNGState()
@@ -319,14 +303,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()
@@ -337,14 +313,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;
{
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
@@ -363,6 +344,7 @@ struct RNGState {
// Best effort cleanup of internal state
hasher.Reset();
memory_cleanse(buf, 64);
+ return ret;
}
};
@@ -386,61 +368,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
+ MilliSleep(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)
@@ -539,8 +588,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;
}
@@ -571,7 +622,7 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce
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 221545a8ef..592f36a406 100644
--- a/src/random.h
+++ b/src/random.h
@@ -13,11 +13,13 @@
#include <stdint.h>
#include <limits>
-/* 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 sources 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 b2da62fc75..fdc859b3a0 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -41,7 +41,7 @@ void CScheduler::serviceQueue()
try {
if (!shouldStop() && taskQueue.empty()) {
reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
- // Use this chance to get a tiny bit more entropy
+ // Use this chance to get more entropy
RandAddSeedSleep();
}
while (!shouldStop() && taskQueue.empty()) {