本文由【区块链研习社】优质内容计划支持,更多关于区块链的深度好文,请点击【区块链研习社】
本文作者:区块链研习比特币源码研读班 韬声依旧在路上
从今天开始我为大家解读比特币源码的随机种子生成功能,该源码所在位置为bitcoin/src/random.h
,bitcoin/src/random.cpp
。该源文件主要实现的功能就是实现随机数的生成,然后用于随后的私钥生成最后生成比特币地址。
使用场景假设
我们先搜索一下随机种子生成用到哪些地方,在VSCode的搜索标签中输入src/*.cpp
,然后按回车即可在左侧窗口看到如下结果。
那我们大胆猜测一下随机种子生成使用的可能的场景如下
文件名 | 可能场景 |
---|---|
addrdb.cpp | 数据库读写 |
blockencodings.php | 区块头部数据写入 |
bloom.cpp | 随机数据结构过滤 |
coins.cpp | 比特币批量写入 |
dbwrapper.cpp | LevelDB数据库封装 |
httprpc.cpp | HTTPRPC接口 |
核心方法声明
我们先来看bitcoin/src/random.h
文件中定义了那些方法
/* Seed OpenSSL PRNG with additional entropy data */
void RandAddSeed();
/**
* Functions to gather random data via the OpenSSL PRNG
*/
void GetRandBytes(unsigned char* buf, int num);
uint64_t GetRand(uint64_t nMax);
int GetRandInt(int nMax);
uint256 GetRandHash();
void RandAddSeedSleep();
/**
* Function to gather random data from multiple sources, failing whenever any
* of those source fail to provide a result.
*/
void GetStrongRandBytes(unsigned char* buf, int num);
/** Get 32 bytes of system entropy. Do not use this in application code: use
* GetStrongRandBytes instead.
*/
void GetOSRand(unsigned char *ent32);
/** Check that OS randomness is available and returning the requested number
* of bytes.
*/
bool Random_SanityCheck();
/** Initialize the RNG. */
void RandomInit();
方法名称 | 实现功能 |
---|---|
void RandAddSeed(); | 利用OpenSSL的伪随机数发生器(PRNG)生成带有熵数据的种子 |
void GetRandBytes(unsigned char* buf, int num); | 通过OpenSSL的PRNG生成随机数据 |
uint64_t GetRand(uint64_t nMax); | 获取随机数 |
int GetRandInt(int nMax); | 获取随机整数 |
uint256 GetRandHash(); | 获取随机哈希值 |
void RandAddSeedSleep(); | 设置随机数生成休眠时间并写入数据到RNG |
void GetStrongRandBytes(unsigned char* buf, int num); | 从多个数据源获取随机数,若失败则返回 |
void GetOSRand(unsigned char *ent32); | 获取操作系统中的随机数 |
bool Random_SanityCheck(); | 检查操作系统中随机函数是否可用 |
void RandomInit(); | 初始化RNG |
重要方法解读
RandAddSeed方法
方法定义位置bitcoin/src/random.h
文件的第16行,具体实现在bitcoin/src/random.cpp
的第130到136行。
void RandAddSeed()
{
// Seed with CPU performance counter
int64_t nCounter = GetPerformanceCounter();
RAND_add(&nCounter, sizeof(nCounter), 1.5);
memory_cleanse((void*)&nCounter, sizeof(nCounter));
}
具体执行的步骤如下
- 获取硬件的时间戳计数器
- 生成随机数
- 执行内存清理
GetOSRand方法
方法定义位置bitcoin/src/random.h
文件的第136行,具体实现在bitcoin/src/random.cpp
的第201到271行。
/** Get 32 bytes of system entropy. */
void GetOSRand(unsigned char *ent32)
{
#if defined(WIN32)
HCRYPTPROV hProvider;
int ret = CryptAcquireContextW(&hProvider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
if (!ret) {
RandFailure();
}
ret = CryptGenRandom(hProvider, NUM_OS_RANDOM_BYTES, ent32);
if (!ret) {
RandFailure();
}
CryptReleaseContext(hProvider, 0);
#elif defined(HAVE_SYS_GETRANDOM)
/* Linux. From the getrandom(2) man page:
* "If the urandom source has been initialized, reads of up to 256 bytes
* will always return as many bytes as requested and will not be
* interrupted by signals."
*/
int rv = syscall(SYS_getrandom, ent32, NUM_OS_RANDOM_BYTES, 0);
if (rv != NUM_OS_RANDOM_BYTES) {
if (rv < 0 && errno == ENOSYS) {
/* Fallback for kernel <3.17: the return value will be -1 and errno
* ENOSYS if the syscall is not available, in that case fall back
* to /dev/urandom.
*/
GetDevURandom(ent32);
} else {
RandFailure();
}
}
#elif defined(HAVE_GETENTROPY) && defined(__OpenBSD__)
/* On OpenBSD this can return up to 256 bytes of entropy, will return an
* error if more are requested.
* The call cannot return less than the requested number of bytes.
getentropy is explicitly limited to openbsd here, as a similar (but not
the same) function may exist on other platforms via glibc.
*/
if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) {
RandFailure();
}
#elif defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX)
// We need a fallback for OSX < 10.12
if (&getentropy != nullptr) {
if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) {
RandFailure();
}
} else {
GetDevURandom(ent32);
}
#elif defined(HAVE_SYSCTL_ARND)
/* FreeBSD and similar. It is possible for the call to return less
* bytes than requested, so need to read in a loop.
*/
static const int name[2] = {CTL_KERN, KERN_ARND};
int have = 0;
do {
size_t len = NUM_OS_RANDOM_BYTES - have;
if (sysctl(name, ARRAYLEN(name), ent32 + have, &len, nullptr, 0) != 0) {
RandFailure();
}
have += len;
} while (have < NUM_OS_RANDOM_BYTES);
#else
/* Fall back to /dev/urandom if there is no specific method implemented to
* get system entropy for this OS.
*/
GetDevURandom(ent32);
#endif
}
具体执行的步骤如下
- 根据宏定义判断是否为Windows32位系统并根据系统内置的函数提供者并配合随机函数生成随机数
- 如果是Linux系统则使用urandom来获取对应字节的随机数
- 如果存在系统熵并且系统是macOSX,那么判断系统版本并返回系统使用的urandom生成随机数
总结
现在才知道只是一个随机种子生成就这么多实现逻辑,那么对于整个比特币系统而言,那是多么一个庞大的工程,我自己也是刚刚上路,自己对于C++语言的掌握还停留在入门阶段,结合源码研读和手头上的几本书就开始研读之路了,我必须抱着谦虚敬畏的态度去看待比特币,我也相信时间会让我成长,也让我看清区块链行业的风险和机遇,我更是想通过研读比特币源码来提升自己的编程水平,当然看待这个行业的角度我希望有不一样的视角。本文会不断随着对比特币源码的解读不定期的进行更新,欢迎大家关注和探索比特币底层的逻辑世界。