比特币源码剖析(六)
本篇主要分析 Step 4: application initialization: dir lock, daemonize, pidfile, debug log 第四步应用程序初始化中初始化椭圆曲线代码的详细过程。
源码剖析
1.初始化椭圆曲线代码。 首先调用 ECC_Start() 函数启动椭圆曲线支持。该函数声明在“key.h”文件中。
/** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */
void ECC_Start(void); // 初始化椭圆曲线支持。如果不先调用 ECC_Stop,可能不会调用 2 次。
实现在“key.cpp”文件中,没有入参。
void ECC_Start() {
assert(secp256k1_context_sign == NULL);
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); // 1.创建一个 secp256k1 上下文对象
assert(ctx != NULL); // 检验是否创建成功
{
// Pass in a random blinding seed to the secp256k1 context. // 把随机致盲种子传递给 secp256k1 上下文。
unsigned char seed[32]; // 32 字节的种子
LockObject(seed); // 2.锁定内存栈对象
GetRandBytes(seed, 32); // 3.获取 32 字节的随机数做为种子
bool ret = secp256k1_context_randomize(ctx, seed); // 4.使用种子设置致盲值
assert(ret); // 检测随机化结果
UnlockObject(seed); // 5.解锁内存栈对象
}
secp256k1_context_sign = ctx;
}
1.1.创建一个 secp256k1 上下文对象。
1.2.锁定待生成的随机数种子数组。
1.3.生成 32 字节的随机数种子。
1.4.使用随机数种子设置致盲值。
1.5.解锁已生成的随机数种子数组。
1.1.调用 secp256k1_context_create(SECP256K1_CONTEXT_SIGN) 函数创建一个 secp256k1 上下文对象。 该函数声明在“secp256k1.h”文件中。
/** All flags' lower 8 bits indicate what they're for. Do not use directly. */ // 所有标志的低 8 位表示它们的用途。不要直接使用。
...
#define SECP256K1_FLAGS_TYPE_CONTEXT (1 << 0) // 1
...
/** The higher bits contain the actual data. Do not use directly. */ // 高位包含真正的数据。不要直接使用。
...
#define SECP256K1_FLAGS_BIT_CONTEXT_SIGN (1 << 9) // 512
...
/** Flags to pass to secp256k1_context_create. */ // 传递给 secp256k1_context_create 的标志
...
#define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN) // 513
...
/** Create a secp256k1 context object.
*
* Returns: a newly created context object.
* In: flags: which parts of the context to initialize.
*/ // 创建一个 secp256k1 上下文对象。
SECP256K1_API secp256k1_context* secp256k1_context_create(
unsigned int flags
) SECP256K1_WARN_UNUSED_RESULT;
实现在“secp256k1.c”文件中,入参为:SECP256K1_CONTEXT_SIGN。
secp256k1_context* secp256k1_context_create(unsigned int flags) {
secp256k1_context* ret = (secp256k1_context*)checked_malloc(&default_error_callback, sizeof(secp256k1_context));
ret->illegal_callback = default_illegal_callback;
ret->error_callback = default_error_callback;
if (EXPECT((flags & SECP256K1_FLAGS_TYPE_MASK) != SECP256K1_FLAGS_TYPE_CONTEXT, 0)) {
secp256k1_callback_call(&ret->illegal_callback,
"Invalid flags");
free(ret);
return NULL;
}
secp256k1_ecmult_context_init(&ret->ecmult_ctx);
secp256k1_ecmult_gen_context_init(&ret->ecmult_gen_ctx);
if (flags & SECP256K1_FLAGS_BIT_CONTEXT_SIGN) {
secp256k1_ecmult_gen_context_build(&ret->ecmult_gen_ctx, &ret->error_callback);
}
if (flags & SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) {
secp256k1_ecmult_context_build(&ret->ecmult_ctx, &ret->error_callback);
}
return ret;
}
1.2.调用 LockObject(seed) 函数锁定待生成的随机数种子内存栈空间。 该函数是一个模板函数,其模板定义在“pagelocker.h”文件中。
/**
* Singleton class to keep track of locked (ie, non-swappable) memory pages, for use in
* std::allocator templates.
*
* Some implementations of the STL allocate memory in some constructors (i.e., see
* MSVC's vector<T> implementation where it allocates 1 byte of memory in the allocator.)
* Due to the unpredictable order of static initializers, we have to make sure the
* LockedPageManager instance exists before any other STL-based objects that use
* secure_allocator are created. So instead of having LockedPageManager also be
* static-initialized, it is created on demand.
*/ // 用于跟踪锁定(即不可交换)的内存页的单例类,用于 std::allocator 模板中。
class LockedPageManager : public LockedPageManagerBase<MemoryPageLocker>
{
public:
static LockedPageManager& Instance() // 获取单例对象的引用
{
boost::call_once(LockedPageManager::CreateInstance, LockedPageManager::init_flag); // 保证只执行一次 LockedPageManager::CreateInstance 函数,线程安全
return *LockedPageManager::_instance; // 返回单例对象
}
private:
LockedPageManager();
static void CreateInstance() // 创建实例
{
// Using a local static instance guarantees that the object is initialized // 使用局部静态实例可确保在首次需要时初始化对象,
// when it's first needed and also deinitialized after all objects that use // 并在所有使用它的对象使用完成后对其进行取消初始化。
// it are done with it. I can think of one unlikely scenario where we may // 我可以想到一个不太可能出现静态取消初始化顺序/问题的情况,
// have a static deinitialization order/problem, but the check in // 但检查 LockedPageManagerBase 类的析构函数可以帮助我们侦测这种情况是否会发生。
// LockedPageManagerBase's destructor helps us detect if that ever happens.
static LockedPageManager instance; // 创建局部静态单例对象
LockedPageManager::_instance = &instance;
}
static LockedPageManager* _instance; // 实例指针
static boost::once_flag init_flag; // 初始化标志(静态初始化为 BOOST_ONCE_INIT)
};
//
// Functions for directly locking/unlocking memory objects.
// Intended for non-dynamically allocated structures.
// // 用于直接锁定/解锁内存对象的函数。用于非动态分配的结构。
template <typename T>
void LockObject(const T& t)
{
LockedPageManager::Instance().LockRange((void*)(&t), sizeof(T)); // 锁定
}
LockedPageManager 是派生于模板类 LockedPageManagerBase
成员函数 Instance() 内的 boost::call_once(LockedPageManager::CreateInstance, LockedPageManager::init_flag) 确保成员函数 LockedPageManager::CreateInstance 只执行一次,
以线程安全的方式来初始化数据,详见 [boost:call_once](https://www.boost.org/doc/libs/1_32_0/doc/html/call_once.html)。
LockedPageManager* LockedPageManager::_instance = NULL; // 懒汉式,单独无法保证前程安全
boost::once_flag LockedPageManager::init_flag = BOOST_ONCE_INIT; // 可保证线程安全
先调用 LockedPageManager::Instance() 获取锁定页面管理器单例对象,然后调用其基类成员函数 LockedPageManager::Instance().LockRange((void*)(&t), sizeof(T)) 来进行区域锁定。 其继承基类传入的类模板类型参数 MemoryPageLocker 是一个依赖操作系统的内存页加解锁类,该类定义在“pagelocker.h”文件中。
/**
* OS-dependent memory page locking/unlocking.
* Defined as policy class to make stubbing for test possible.
*/ // 依赖操作系统的内存页锁定/解锁。定义为策略类,为测试做准备。
class MemoryPageLocker
{
public:
/** Lock memory pages.
* addr and len must be a multiple of the system page size
*/ // 锁定内存页。地址和长度必须是系统页的倍数
bool Lock(const void* addr, size_t len);
/** Unlock memory pages.
* addr and len must be a multiple of the system page size
*/ // 解锁内存页。地址和长度必须是系统页的倍数
bool Unlock(const void* addr, size_t len);
};
其成员函数实现在“pagelocker.cpp”文件中。
bool MemoryPageLocker::Lock(const void* addr, size_t len)
{
#ifdef WIN32
return VirtualLock(const_cast<void*>(addr), len) != 0;
#else // UNIX/Linux
return mlock(addr, len) == 0; // 锁定内存页
#endif
}
bool MemoryPageLocker::Unlock(const void* addr, size_t len)
{
#ifdef WIN32
return VirtualUnlock(const_cast<void*>(addr), len) != 0;
#else // UNIX/Linux
return munlock(addr, len) == 0; // 解锁内存页
#endif
}
而基类成员函数 LockedPageManager::Instance().LockRange(…) 定义在“pagelocker.cpp”文件的 LockedPageManagerBase 类中。
/**
* Thread-safe class to keep track of locked (ie, non-swappable) memory pages.
*
* Memory locks do not stack, that is, pages which have been locked several times by calls to mlock()
* will be unlocked by a single call to munlock(). This can result in keying material ending up in swap when
* those functions are used naively. This class simulates stacking memory locks by keeping a counter per page.
*
* @note By using a map from each page base address to lock count, this class is optimized for
* small objects that span up to a few pages, mostly smaller than a page. To support large allocations,
* something like an interval tree would be the preferred data structure.
*/ // 用于跟踪锁定(即不可交换)的内存页的线程安全类。
template <class Locker>
class LockedPageManagerBase
{
public:
...
// For all pages in affected range, increase lock count // 对于受影响范围内的所有页面,增加锁定计数
void LockRange(void* p, size_t size)
{
boost::mutex::scoped_lock lock(mutex); // 区域锁
if (!size) // 若锁定区域大小为 0
return; // 直接返回
const size_t base_addr = reinterpret_cast<size_t>(p); // 强制转换
const size_t start_page = base_addr & page_mask; // 计算开始页
const size_t end_page = (base_addr + size - 1) & page_mask; // 计算结束页
for (size_t page = start_page; page <= end_page; page += page_size) { // 遍历每一页
Histogram::iterator it = histogram.find(page); // 在页面基址映射列表中查询
if (it == histogram.end()) // Newly locked page // 若未找到,说明还未上锁
{
locker.Lock(reinterpret_cast<void*>(page), page_size); // 锁定内存页
histogram.insert(std::make_pair(page, 1)); // 插入页面基址映射列表
} else // Page was already locked; increase counter // 若找到了,说明页面已锁,仅增加锁定计数
{
it->second += 1; // 锁定计数加 1
}
}
}
// For all pages in affected range, decrease lock count // 对于受影响范围内的所有页面,减少锁定计数
void UnlockRange(void* p, size_t size)
{
boost::mutex::scoped_lock lock(mutex); // 区域锁
if (!size) // 若解锁区域大小为 0
return; // 直接返回
const size_t base_addr = reinterpret_cast<size_t>(p); // 强制转换
const size_t start_page = base_addr & page_mask; // 计算开始页
const size_t end_page = (base_addr + size - 1) & page_mask; // 计算结束页
for (size_t page = start_page; page <= end_page; page += page_size) { // 遍历每一页
Histogram::iterator it = histogram.find(page); // 在页面基址映射列表中查询
assert(it != histogram.end()); // Cannot unlock an area that was not locked // 若未找到,则报错
// Decrease counter for page, when it is zero, the page will be unlocked // 否则,减少其锁定次数,当次数为 0 时,页面将解锁
it->second -= 1; // 锁定次数减 1
if (it->second == 0) // Nothing on the page anymore that keeps it locked // 页面上没有上锁
{
// Unlock page and remove the count from histogram // 解锁页面并基址映射列表中从移除该项
locker.Unlock(reinterpret_cast<void*>(page), page_size); // 先对该内存页解锁
histogram.erase(it); // 从映射列表中移除
}
}
}
...
private:
Locker locker; // 内存页加解锁对象
boost::mutex mutex; // 互斥锁
size_t page_size, page_mask; // 页面大小,页面掩码
// map of page base address to lock count // 用于锁定计数的页面基址的映射
typedef std::map<size_t, int> Histogram; // <页面起始地址, 锁定次数>
Histogram histogram; // 页面基址映射列表
};
1.3.调用 GetRandBytes(seed, 32) 获取 32 个字节的随机数,该函数声明在“random.h”文件中。
/**
* Functions to gather random data via the OpenSSL PRNG
*/ // 通过 OpenSSL 伪随机数生成器搜集随机数据的函数
void GetRandBytes(unsigned char* buf, int num); // 获取 num 字节的随机数
实现在“random.cpp”文件中,入参为:32 字节的数据(栈空间),待获取随机数的字节数。
void GetRandBytes(unsigned char* buf, int num)
{
if (RAND_bytes(buf, num) != 1) { // 通过加密算法生成 num 位随机数,实际还是伪随机数,若提前设定种子,则该随机数无法被预先计算
LogPrintf("%s: OpenSSL RAND_bytes() failed with error: %s\n", __func__, ERR_error_string(ERR_get_error(), NULL));
assert(false);
}
}
直接调用 OpenSSL 库的 RAND_bytes(buf, num) 函数获取 num 字节的随机数到 buf 中。 详见/docs/manmaster/man3/RAND_bytes。
1.4.调用 secp256k1_context_randomize(ctx, seed) 函数设置致盲值,该函数实现在“secp256k1.c”文件中。
int secp256k1_context_randomize(secp256k1_context* ctx, const unsigned char *seed32) {
VERIFY_CHECK(ctx != NULL); // 验证 secp256k1 上下文对象是否创建
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); // 检查 secp256k1_ecmult_gen_context 对象的数据成员是否为空
secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, seed32); // 设置 secp256k1_ecmult_gen 的盲值
return 1; // 成功返回 1
}
1.5.调用 UnLockObject(seed) 函数解锁已生成的随机数种子内存栈空间。 该函数是一个模板函数,其模板定义在“pagelocker.h”文件中。
template <typename T>
void UnlockObject(const T& t)
{
memory_cleanse((void*)(&t), sizeof(T)); // 先清空指定区域的数据
LockedPageManager::Instance().UnlockRange((void*)(&t), sizeof(T)); // 解锁
}
首先调用 memory_cleanse((void*)(&t), sizeof(T)) 函数清空指定区域,该函数声明在“cleanse.h”文件中。 然后解锁该区域的内存,详见 1.2。
void memory_cleanse(void *ptr, size_t len); // 清空敏感数据(写入随机数或纯 0)
实现在“cleanse.cpp”文件中,入参为:指向某内存空间的指针(地址),长度(字节)。
void memory_cleanse(void *ptr, size_t len)
{
OPENSSL_cleanse(ptr, len); // 使用 0 字符串填充从 ptr 指向位置开始 len 大小字节
}
内部调用 OpenSSL 库 OPENSSL_cleanse(ptr, len) 函数把指定空间填充为 0, 详见 /docs/manmaster/man3/OPENSSL_cleanse。
然后调用 globalVerifyHandle.reset(new ECCVerifyHandle()) 函数创建椭圆曲线验证对象,类 ECCVerifyHandle 定义在“pubkey.h”文件中。
/** Users of this module must hold an ECCVerifyHandle. The constructor and
* destructor of these are not allowed to run in parallel, though. */
class ECCVerifyHandle // 该模块的用户必须持有 ECCVerifyHandle。但不允许构造函数和析构函数并行执行。
{
static int refcount; // 引用计数
public:
ECCVerifyHandle();
~ECCVerifyHandle();
};
全局静态智能指针 globalVerifyHandle 定义在“init.cpp”文件中。
static boost::scoped_ptr<ECCVerifyHandle> globalVerifyHandle; // 该智能指针与 STL 的 std::unique_ptr 类似
智能指针 boost::scoped_ptr 不能复制或移动,类似于 STL 的 std::unique_ptr。
参考链接
- bitcoin/key.h at v0.12.1 · bitcoin/bitcoin
- bitcoin/key.cpp at v0.12.1 · bitcoin/bitcoin
- bitcoin/secp256k1.h at v0.12.1 · bitcoin/bitcoin
- bitcoin/secp256k1.c at v0.12.1 · bitcoin/bitcoin
- bitcoin/pagelocker.h at v0.12.1 · bitcoin/bitcoin
- bitcoin/pagelocker.cpp at v0.12.1 · bitcoin/bitcoin
- bitcoin/random.h at v0.12.1 · bitcoin/bitcoin
- bitcoin/random.cpp at v0.12.1 · bitcoin/bitcoin
- bitcoin/pubkey.h at v0.12.1 · bitcoin/bitcoin
- bitcoin/init.cpp at v0.12.1 · bitcoin/bitcoin
- Boost.SmartPtr: The Smart Pointer Library - 1.73.0