diff --git a/src/random.h b/src/random.h --- a/src/random.h +++ b/src/random.h @@ -47,14 +47,18 @@ * additionally: * - A high-precision timestamp before and after sleeping 1ms. * - (On Windows) Once every 10 minutes, performance monitoring data from the - * OS. These just exploit the fact the system is idle to improve the quality of - * the RNG slightly. + * OS. + * - Once every minute, strengthen the entropy for 10 ms using repeated + * SHA512. + * These just exploit the fact the system is idle to improve the quality + * of the RNG slightly. * * 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. * - (On Windows) Performance monitoring data from the OS. * - (On Windows) Through OpenSSL, the screen contents. + * - 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 diff --git a/src/random.cpp b/src/random.cpp --- a/src/random.cpp +++ b/src/random.cpp @@ -275,6 +275,37 @@ #endif } +/** + * Use repeated SHA512 to strengthen the randomness in seed32, and feed into + * hasher. + */ +static void Strengthen(const uint8_t (&seed)[32], int microseconds, + CSHA512 &hasher) noexcept { + CSHA512 inner_hasher; + inner_hasher.Write(seed, sizeof(seed)); + + // Hash loop + uint8_t buffer[64]; + int64_t stop = GetTimeMicros() + microseconds; + do { + for (int i = 0; i < 1000; ++i) { + inner_hasher.Finalize(buffer); + inner_hasher.Reset(); + inner_hasher.Write(buffer, sizeof(buffer)); + } + // Benchmark operation and feed it into outer hasher. + int64_t perf = GetPerformanceCounter(); + hasher.Write((const uint8_t *)&perf, sizeof(perf)); + } while (GetTimeMicros() < stop); + + // Produce output from inner state and feed it to outer hasher. + inner_hasher.Finalize(buffer); + hasher.Write(buffer, sizeof(buffer)); + // Try to clean up. + inner_hasher.Reset(); + memory_cleanse(buffer, sizeof(buffer)); +} + static void RandAddSeedPerfmon(CSHA512 &hasher) { #ifdef WIN32 // Don't need this on Linux, OpenSSL automatically uses /dev/urandom @@ -580,7 +611,25 @@ SeedTimestamp(hasher); } -static void SeedSleep(CSHA512 &hasher) { +/** Extract entropy from rng, strengthen it, and feed it into hasher. */ +static void SeedStrengthen(CSHA512 &hasher, RNGState &rng) noexcept { + static std::atomic last_strengthen{0}; + int64_t last_time = last_strengthen.load(); + int64_t current_time = GetTimeMicros(); + // Only run once a minute + if (current_time > last_time + 60000000) { + // Generate 32 bytes of entropy from the RNG, and a copy of the entropy + // already in hasher. + uint8_t strengthen_seed[32]; + rng.MixExtract(strengthen_seed, sizeof(strengthen_seed), + CSHA512(hasher), false); + // Strengthen it for 10ms (100ms on first run), and feed it into hasher. + Strengthen(strengthen_seed, last_time == 0 ? 100000 : 10000, hasher); + last_strengthen = current_time; + } +} + +static void SeedSleep(CSHA512 &hasher, RNGState &rng) { // Everything that the 'fast' seeder includes SeedFast(hasher); @@ -596,9 +645,12 @@ // Windows performance monitor data (once every 10 minutes) RandAddSeedPerfmon(hasher); + + // Strengthen + SeedStrengthen(hasher, rng); } -static void SeedStartup(CSHA512 &hasher) noexcept { +static void SeedStartup(CSHA512 &hasher, RNGState &rng) noexcept { #ifdef WIN32 RAND_screen(); #endif @@ -611,6 +663,9 @@ // Windows performance monitor data. RandAddSeedPerfmon(hasher); + + // Strengthen + SeedStrengthen(hasher, rng); } enum class RNGLevel { @@ -635,7 +690,7 @@ SeedSlow(hasher); break; case RNGLevel::SLEEP: - SeedSleep(hasher); + SeedSleep(hasher, rng); break; } @@ -643,7 +698,7 @@ if (!rng.MixExtract(out, num, std::move(hasher), false)) { // On the first invocation, also seed with SeedStartup(). CSHA512 startup_hasher; - SeedStartup(startup_hasher); + SeedStartup(startup_hasher, rng); rng.MixExtract(out, num, std::move(startup_hasher), true); }