Changeset View
Changeset View
Standalone View
Standalone View
src/random.cpp
Show All 16 Lines | |||||
#include <util/time.h> // for GetTime() | #include <util/time.h> // for GetTime() | ||||
#include <openssl/conf.h> | #include <openssl/conf.h> | ||||
#include <openssl/err.h> | #include <openssl/err.h> | ||||
#include <openssl/rand.h> | #include <openssl/rand.h> | ||||
#include <chrono> | #include <chrono> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
#include <limits> | |||||
#include <mutex> | #include <mutex> | ||||
#include <thread> | #include <thread> | ||||
#ifndef WIN32 | #ifndef WIN32 | ||||
#include <fcntl.h> | #include <fcntl.h> | ||||
#include <sys/time.h> | #include <sys/time.h> | ||||
#endif | #endif | ||||
Show All 40 Lines | |||||
#else | #else | ||||
// Fall back to using C++11 clock (usually microsecond or nanosecond | // Fall back to using C++11 clock (usually microsecond or nanosecond | ||||
// precision) | // precision) | ||||
return std::chrono::high_resolution_clock::now().time_since_epoch().count(); | return std::chrono::high_resolution_clock::now().time_since_epoch().count(); | ||||
#endif | #endif | ||||
} | } | ||||
#if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) | #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) | ||||
static bool rdrand_supported = false; | 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_F1_ECX_RDRAND = 0x40000000; | ||||
static 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 | |||||
#ifdef bit_RDSEED | |||||
static_assert(CPUID_F7_EBX_RDSEED == bit_RDSEED, | |||||
"Unexpected value for bit_RDSEED"); | |||||
#endif | |||||
static void inline GetCPUID(uint32_t leaf, uint32_t subleaf, uint32_t &a, | |||||
uint32_t &b, uint32_t &c, uint32_t &d) { | |||||
// We can't use __get_cpuid as it doesn't support subleafs. | |||||
#ifdef __GNUC__ | |||||
__cpuid_count(leaf, subleaf, a, b, c, d); | |||||
#else | |||||
__asm__("cpuid" | |||||
: "=a"(a), "=b"(b), "=c"(c), "=d"(d) | |||||
: "0"(leaf), "2"(subleaf)); | |||||
#endif | |||||
} | |||||
static void InitHardwareRand() { | static void InitHardwareRand() { | ||||
uint32_t eax, ebx, ecx, edx; | uint32_t eax, ebx, ecx, edx; | ||||
if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx & CPUID_F1_ECX_RDRAND)) { | GetCPUID(1, 0, eax, ebx, ecx, edx); | ||||
rdrand_supported = true; | if (ecx & CPUID_F1_ECX_RDRAND) { | ||||
g_rdrand_supported = true; | |||||
} | |||||
GetCPUID(7, 0, eax, ebx, ecx, edx); | |||||
if (ebx & CPUID_F7_EBX_RDSEED) { | |||||
g_rdseed_supported = true; | |||||
} | } | ||||
} | } | ||||
static void ReportHardwareRand() { | static void ReportHardwareRand() { | ||||
if (rdrand_supported) { | |||||
// This must be done in a separate function, as HWRandInit() may be | // This must be done in a separate function, as HWRandInit() may be | ||||
// indirectly called from global constructors, before logging is | // indirectly called from global constructors, before logging is | ||||
// initialized. | // initialized. | ||||
if (g_rdseed_supported) { | |||||
LogPrintf("Using RdSeed as additional entropy source\n"); | |||||
} | |||||
if (g_rdrand_supported) { | |||||
LogPrintf("Using RdRand as an additional entropy source\n"); | LogPrintf("Using RdRand as an additional entropy source\n"); | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Read 64 bits of entropy using rdrand. | |||||
* | |||||
* Must only be called when RdRand is supported. | |||||
*/ | |||||
static uint64_t GetRdRand() noexcept { | |||||
// RdRand may very rarely fail. Invoke it up to 10 times in a loop to reduce | |||||
// this risk. | |||||
#ifdef __i386__ | |||||
uint8_t ok; | |||||
uint32_t r1, r2; | |||||
for (int i = 0; i < 10; ++i) { | |||||
// rdrand %eax | |||||
__asm__ volatile(".byte 0x0f, 0xc7, 0xf0; setc %1" | |||||
: "=a"(r1), "=q"(ok)::"cc"); | |||||
if (ok) { | |||||
break; | |||||
} | |||||
} | |||||
for (int i = 0; i < 10; ++i) { | |||||
// rdrand %eax | |||||
__asm__ volatile(".byte 0x0f, 0xc7, 0xf0; setc %1" | |||||
: "=a"(r2), "=q"(ok)::"cc"); | |||||
if (ok) { | |||||
break; | |||||
} | |||||
} | |||||
return (uint64_t(r2) << 32) | r1; | |||||
#elif defined(__x86_64__) || defined(__amd64__) | |||||
uint8_t ok; | |||||
uint64_t r1; | |||||
for (int i = 0; i < 10; ++i) { | |||||
// rdrand %rax | |||||
__asm__ volatile(".byte 0x48, 0x0f, 0xc7, 0xf0; setc %1" | |||||
: "=a"(r1), "=q"(ok)::"cc"); | |||||
if (ok) { | |||||
break; | |||||
} | |||||
} | |||||
return r1; | |||||
#else | |||||
#error "RdRand is only supported on x86 and x86_64" | |||||
#endif | |||||
} | |||||
/** | |||||
* Read 64 bits of entropy using rdseed. | |||||
* | |||||
* Must only be called when RdSeed is supported. | |||||
*/ | |||||
static 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. | |||||
#ifdef __i386__ | |||||
uint8_t ok; | |||||
uint32_t r1, r2; | |||||
do { | |||||
// rdseed %eax | |||||
__asm__ volatile(".byte 0x0f, 0xc7, 0xf8; setc %1" | |||||
: "=a"(r1), "=q"(ok)::"cc"); | |||||
if (ok) { | |||||
break; | |||||
} | |||||
__asm__ volatile("pause"); | |||||
} while (true); | |||||
do { | |||||
// rdseed %eax | |||||
__asm__ volatile(".byte 0x0f, 0xc7, 0xf8; setc %1" | |||||
: "=a"(r2), "=q"(ok)::"cc"); | |||||
if (ok) { | |||||
break; | |||||
} | |||||
__asm__ volatile("pause"); | |||||
} while (true); | |||||
return (uint64_t(r2) << 32) | r1; | |||||
#elif defined(__x86_64__) || defined(__amd64__) | |||||
uint8_t ok; | |||||
uint64_t r1; | |||||
do { | |||||
// rdseed %rax | |||||
__asm__ volatile(".byte 0x48, 0x0f, 0xc7, 0xf8; setc %1" | |||||
: "=a"(r1), "=q"(ok)::"cc"); | |||||
if (ok) { | |||||
break; | |||||
} | |||||
__asm__ volatile("pause"); | |||||
} while (true); | |||||
return r1; | |||||
#else | |||||
#error "RdSeed is only supported on x86 and x86_64" | |||||
#endif | |||||
} | |||||
#else | #else | ||||
/** | /** | ||||
* Access to other hardware random number generators could be added here later, | * Access to other hardware random number generators could be added here later, | ||||
* assuming it is sufficiently fast (in the order of a few hundred CPU cycles). | * assuming it is sufficiently fast (in the order of a few hundred CPU cycles). | ||||
* Slower sources should probably be invoked separately, and/or only from | * Slower sources should probably be invoked separately, and/or only from | ||||
* RandAddSeedSleep (which is called during idle background operation). | * RandAddSeedSleep (which is called during idle background operation). | ||||
*/ | */ | ||||
static void InitHardwareRand() {} | static void InitHardwareRand() {} | ||||
static void ReportHardwareRand() {} | static void ReportHardwareRand() {} | ||||
#endif | #endif | ||||
static bool GetHardwareRand(uint8_t *ent32) noexcept { | /** | ||||
* Add 64 bits of entropy gathered from hardware to hasher. Do nothing if not | |||||
* supported. | |||||
*/ | |||||
static void SeedHardwareFast(CSHA512 &hasher) noexcept { | |||||
#if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) | #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) | ||||
if (rdrand_supported) { | if (g_rdrand_supported) { | ||||
uint8_t ok; | uint64_t out = GetRdRand(); | ||||
// Not all assemblers support the rdrand instruction, write it in hex. | hasher.Write((const uint8_t *)&out, sizeof(out)); | ||||
#ifdef __i386__ | return; | ||||
for (int iter = 0; iter < 4; ++iter) { | |||||
uint32_t r1, r2; | |||||
__asm__ volatile(".byte 0x0f, 0xc7, 0xf0;" // rdrand %eax | |||||
".byte 0x0f, 0xc7, 0xf2;" // rdrand %edx | |||||
"setc %2" | |||||
: "=a"(r1), "=d"(r2), "=q"(ok)::"cc"); | |||||
if (!ok) { | |||||
return false; | |||||
} | } | ||||
WriteLE32(ent32 + 8 * iter, r1); | #endif | ||||
WriteLE32(ent32 + 8 * iter + 4, r2); | |||||
} | } | ||||
#else | |||||
uint64_t r1, r2, r3, r4; | /** | ||||
__asm__ volatile(".byte 0x48, 0x0f, 0xc7, 0xf0, " // rdrand %rax | * Add 256 bits of entropy gathered from hardware to hasher. Do nothing if not | ||||
"0x48, 0x0f, 0xc7, 0xf3, " // rdrand %rbx | * supported. | ||||
"0x48, 0x0f, 0xc7, 0xf1, " // rdrand %rcx | */ | ||||
"0x48, 0x0f, 0xc7, 0xf2; " // rdrand %rdx | static void SeedHardwareSlow(CSHA512 &hasher) noexcept { | ||||
"setc %4" | #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) | ||||
: "=a"(r1), "=b"(r2), "=c"(r3), "=d"(r4), | // When we want 256 bits of entropy, prefer RdSeed over RdRand, as it's | ||||
"=q"(ok)::"cc"); | // guaranteed to produce independent randomness on every call. | ||||
if (!ok) { | if (g_rdseed_supported) { | ||||
return false; | for (int i = 0; i < 4; ++i) { | ||||
uint64_t out = GetRdSeed(); | |||||
hasher.Write((const uint8_t *)&out, sizeof(out)); | |||||
} | } | ||||
WriteLE64(ent32, r1); | return; | ||||
WriteLE64(ent32 + 8, r2); | } | ||||
WriteLE64(ent32 + 16, r3); | // When falling back to RdRand, XOR the result of 1024 results. | ||||
WriteLE64(ent32 + 24, r4); | // This guarantees a reseeding occurs between each. | ||||
#endif | if (g_rdrand_supported) { | ||||
return true; | for (int i = 0; i < 4; ++i) { | ||||
uint64_t out = 0; | |||||
for (int j = 0; j < 1024; ++j) { | |||||
out ^= GetRdRand(); | |||||
} | |||||
hasher.Write((const uint8_t *)&out, sizeof(out)); | |||||
} | |||||
return; | |||||
} | } | ||||
#endif | #endif | ||||
return false; | |||||
} | } | ||||
static void RandAddSeedPerfmon(CSHA512 &hasher) { | static void RandAddSeedPerfmon(CSHA512 &hasher) { | ||||
#ifdef WIN32 | #ifdef WIN32 | ||||
// Don't need this on Linux, OpenSSL automatically uses /dev/urandom | // Don't need this on Linux, OpenSSL automatically uses /dev/urandom | ||||
// Seed with the entire set of perfmon data | // Seed with the entire set of perfmon data | ||||
// This can take up to 2 seconds, so only do it every 10 minutes | // This can take up to 2 seconds, so only do it every 10 minutes | ||||
▲ Show 20 Lines • Show All 263 Lines • ▼ Show 20 Lines | |||||
static void SeedFast(CSHA512 &hasher) noexcept { | static void SeedFast(CSHA512 &hasher) noexcept { | ||||
uint8_t buffer[32]; | uint8_t buffer[32]; | ||||
// Stack pointer to indirectly commit to thread/callstack | // Stack pointer to indirectly commit to thread/callstack | ||||
const uint8_t *ptr = buffer; | const uint8_t *ptr = buffer; | ||||
hasher.Write((const uint8_t *)&ptr, sizeof(ptr)); | hasher.Write((const uint8_t *)&ptr, sizeof(ptr)); | ||||
// Hardware randomness is very fast when available; use it always. | // Hardware randomness is very fast when available; use it always. | ||||
bool have_hw_rand = GetHardwareRand(buffer); | SeedHardwareFast(hasher); | ||||
if (have_hw_rand) { | |||||
hasher.Write(buffer, sizeof(buffer)); | |||||
} | |||||
// High-precision timestamp | // High-precision timestamp | ||||
SeedTimestamp(hasher); | SeedTimestamp(hasher); | ||||
} | } | ||||
static void SeedSlow(CSHA512 &hasher) noexcept { | static void SeedSlow(CSHA512 &hasher) noexcept { | ||||
uint8_t buffer[32]; | uint8_t buffer[32]; | ||||
Show All 34 Lines | static void SeedSleep(CSHA512 &hasher) { | ||||
RandAddSeedPerfmon(hasher); | RandAddSeedPerfmon(hasher); | ||||
} | } | ||||
static void SeedStartup(CSHA512 &hasher) noexcept { | static void SeedStartup(CSHA512 &hasher) noexcept { | ||||
#ifdef WIN32 | #ifdef WIN32 | ||||
RAND_screen(); | RAND_screen(); | ||||
#endif | #endif | ||||
// Gather 256 bits of hardware randomness, if available | |||||
SeedHardwareSlow(hasher); | |||||
// Everything that the 'slow' seeder includes. | // Everything that the 'slow' seeder includes. | ||||
SeedSlow(hasher); | SeedSlow(hasher); | ||||
// Windows performance monitor data. | // Windows performance monitor data. | ||||
RandAddSeedPerfmon(hasher); | RandAddSeedPerfmon(hasher); | ||||
} | } | ||||
enum class RNGLevel { | enum class RNGLevel { | ||||
▲ Show 20 Lines • Show All 187 Lines • Show Last 20 Lines |