经过一段时间的积累,终于来到了比特币源码阅读的环节。还是按照之前的节奏,我们就比对着精通比特币一书的进度,进行源码的阅读。

对于此文,只需你对比特币系统中私钥-公钥-地址的产生及关系有最基本的了解

因此你可以放心的直接阅读,如果遇到疑惑,可以返回来阅读以下资料,填补一些基本概念即可:

  • 精通比特币第4章
  • 椭圆曲线加密算法教程
  • 密码学知识汇总

下面进入正题,本文将对比特币源码中的私钥相关部分进行梳理。

在阅读代码前,先明确一个概念:私钥是如何产生的?

私钥如何产生

比特币的私钥就是一个256位二进制数字,就这么简单。

但是有一个条件,这个256位二进制数要小于一个非常大的质数n

n = 0xffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141

这是由于比特币使用的椭圆曲线secp256k1的方程所对应的循环子群的秩为n。

这一点了解即可,如果你想了解为什么,建议仔细阅读椭圆曲线加密算法教程

也就是说,你可以用硬币、铅笔和纸来随机生成你的私钥:

掷硬币256次,用纸和笔记录正反面并转换为0和1,随机得到的256位二进制数字可作为比特币钱包的私钥,只要其小于n。

当然更普遍的方法是使用代码生成,但是一定要注意:在你不够了解随机数产生器前,不要自己写代码或使用你的编程语言提供的简易随机数生成器来获得一个随机数作为私钥。应使用密码学安全的伪随机数生成器(CSPRNG),并且需要有一个来自具有足够熵值的源的种子。(这段警告来自于《精通比特币》,我目前还不知晓怎样才算一个足够安全的伪随机数发生器,也望大家告知交流)

代码阅读

明确了私钥的定义,我们来阅读源码。

首先,我们进入src目录下

使用lsgrep命令,试图找到私钥-公钥相关源文件的位置

一番探索后确定,头文件key.h中的CKey类便是私钥的定义。

下面是key.h的源码,我个人的理解直接放在注释中了

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2017 The Bitcoin Core developers
// Copyright (c) 2017 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.#ifndef BITCOIN_KEY_H
#define BITCOIN_KEY_H#include <pubkey.h>    // 显然这是定义公钥的代码
#include <serialize.h>
#include <support/allocators/secure.h>
#include <uint256.h>#include <stdexcept>
#include <vector>/*** CPrivKey本质就是一个vector<unsigned char>* 其allocator在support/allocators/secure.h中进行了重载(具体原因暂不清楚)* 这个CPrivKey并不是我们理解的256bit的随机数私钥* 而是一个编码后的一个私钥,编码的私钥长度为PRIVATE_KEY_SIZE或COMPRESSED_PRIVATE_KEY_SIZE。* 从代码来看,这个私钥应该是DER编码的,具体什么是DER,为什么这样编码我还不太清楚。*/
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;/** 封装的私钥类. */
class CKey
{
public:/*** 定义两个静态的大小用来表示普通私钥的长度和压缩后私钥的长度**/static const unsigned int PRIVATE_KEY_SIZE            = 279;static const unsigned int COMPRESSED_PRIVATE_KEY_SIZE = 214;/*** 压缩后的私钥必须要比压缩前小,这是合理的要求了,使用static_assert在编译时进行检查*/static_assert(PRIVATE_KEY_SIZE >= COMPRESSED_PRIVATE_KEY_SIZE,"COMPRESSED_PRIVATE_KEY_SIZE is larger than PRIVATE_KEY_SIZE");private:// 用于表示私钥是否有效// 因为每次key被修改的时候都会做正确性判断,所以fValid应该和真实的状态保持一致。bool fValid;// 表示对应私钥的公钥是否被压缩bool fCompressed;//! 实际的私钥数据。//! 这里存储的是我们所熟悉的256bit私钥(32字节)std::vector<unsigned char, secure_allocator<unsigned char> > keydata;//! 判断vch指向的32字节数据是否是有效的私钥数据bool static Check(const unsigned char* vch);public:// 构造函数,初始化fValid和fCompressed,设置keydata的长度为32CKey() : fValid(false), fCompressed(false){keydata.resize(32);}// 重载Ckey的==运算符,只要密钥数据一致,是否压缩也一直,就表示两个CKey数据相同friend bool operator==(const CKey& a, const CKey& b){return a.fCompressed == b.fCompressed &&a.size() == b.size() &&memcmp(a.keydata.data(), b.keydata.data(), a.size()) == 0;}// 设置密钥的内容,并通过check判断是否为有效密钥template <typename T>void Set(const T pbegin, const T pend, bool fCompressedIn){if (size_t(pend - pbegin) != keydata.size()) {fValid = false;} else if (Check(&pbegin[0])) {memcpy(keydata.data(), (unsigned char*)&pbegin[0], keydata.size());fValid = true;fCompressed = fCompressedIn;} else {fValid = false;}}// 这块是简单的加了几个方法,能让CKey的函数能够更方便的使用存储私钥的成员keydataunsigned int size() const { return (fValid ? keydata.size() : 0); }const unsigned char* begin() const { return keydata.data(); }const unsigned char* end() const { return keydata.data() + size(); }// 返回私钥是否有效bool IsValid() const { return fValid; }// 返回私钥(对应的公钥)是否是压缩格式的bool IsCompressed() const { return fCompressed; }//! 使用随机的方式创建一个新的密钥.void MakeNewKey(bool fCompressed);//! 获得私钥//! 返回的私钥是CPrivKey类型的,也就是编码后的私钥CPrivKey GetPrivKey() const;//! 获得公钥//! 通过私钥计算出公钥并返回CPubKey GetPubKey() const;/*** 签名,返回DER序列化的数字签名* @param[in] hash 要进行签名的哈希值* @param[out] 签名结果* @param[test_case] 我也没搞清楚这是干啥的,貌似是传一个和随机数有关的任意数*/bool Sign(const uint256& hash, std::vector<unsigned char>& vchSig, uint32_t test_case = 0) const;// Create a compact signature (65 bytes)bool SignCompact(const uint256& hash, std::vector<unsigned char>& vchSig) const;// Derive BIP32 child key.bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;// 验证私钥和公钥是否匹配。// 使用的机制并不是使用私钥再次生成公钥并比对bool VerifyPubKey(const CPubKey& vchPubKey) const;// 加载一个私钥,顺便判断下与公钥是否匹配bool Load(const CPrivKey& privkey, const CPubKey& vchPubKey, bool fSkipCheck);
};/*** 这也是一个私钥类型,应该是和HD钱包相关的* CKey, CPubKey, CExtKey, CExtPubKey 是bitcoin core中的四种密钥实现* 其中CKey, CPubKey是普通的私钥和公钥类型* 而如果想使用 HD Wallet,必须使用CExtKey, CExtPubKey* 由于还不了解HD钱包,这个类我也没有详细阅读* 或许可以看看这个https://medium.com/codechain/hd-wallet-observed-through-bitcoin-core-source-code-ce38f9eab371*/
struct CExtKey {unsigned char nDepth;unsigned char vchFingerprint[4];unsigned int nChild;ChainCode chaincode;CKey key;friend bool operator==(const CExtKey& a, const CExtKey& b){return a.nDepth == b.nDepth &&memcmp(&a.vchFingerprint[0], &b.vchFingerprint[0], sizeof(vchFingerprint)) == 0 &&a.nChild == b.nChild &&a.chaincode == b.chaincode &&a.key == b.key;}void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);bool Derive(CExtKey& out, unsigned int nChild) const;CExtPubKey Neuter() const;void SetMaster(const unsigned char* seed, unsigned int nSeedLen);template <typename Stream>void Serialize(Stream& s) const{unsigned int len = BIP32_EXTKEY_SIZE;::WriteCompactSize(s, len);unsigned char code[BIP32_EXTKEY_SIZE];Encode(code);s.write((const char *)&code[0], len);}template <typename Stream>void Unserialize(Stream& s){unsigned int len = ::ReadCompactSize(s);unsigned char code[BIP32_EXTKEY_SIZE];if (len != BIP32_EXTKEY_SIZE)throw std::runtime_error("Invalid extended key size\n");s.read((char *)&code[0], len);Decode(code);}
};// 使用椭圆算法加密前必须调用该程序启用上下文
void ECC_Start(void);// 使用椭圆加密后使用该函数销毁加密上下文
void ECC_Stop(void);// 获取运行时椭圆曲线需要的支持是否满足
bool ECC_InitSanityCheck(void);#endif // BITCOIN_KEY_H

下面简要的阅读源文件key.cpp

生成一个新的私钥的方法如下:

bool CKey::Check(const unsigned char *vch) {return secp256k1_ec_seckey_verify(secp256k1_context_sign, vch);
}void CKey::MakeNewKey(bool fCompressedIn) {do {GetStrongRandBytes(keydata.data(), keydata.size());} while (!Check(keydata.data()));fValid = true;fCompressed = fCompressedIn;
}

如前所述,比特币私钥其实就是一个256bit随机数(32字节长)

可以看到,代码中使用GetStrongRandBytes()函数生成强随机性的私钥

使用grep命令搜索该方法

$ grep -rlw "GetStrongRandBytes" *
key.cpp
random.cpp
random.h
wallet/wallet.cpp

最终确定,该方法位于random.h中,可在其中探寻详细代码

可以看到MakeNewKey()方法,通过不停调用GetStrongRandBytes(),直到找到能够满足要求的随机数作为私钥

Check()方法调用libsecp256k1加密库中的函数secp256k1_ec_seckey_verify进行验证

获取私钥的方法如下

CPrivKey CKey::GetPrivKey() const {assert(fValid);CPrivKey privkey;int ret;size_t privkeylen;privkey.resize(PRIVATE_KEY_SIZE);privkeylen = PRIVATE_KEY_SIZE;ret = ec_privkey_export_der(secp256k1_context_sign, privkey.data(), &privkeylen, begin(), fCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);assert(ret);privkey.resize(privkeylen);return privkey;
}

该函数核心是向ec_privkey_export_der()提供私钥数据keydata(通过begin()传递),进行DER序列化,并返回CPrivKey类型的私钥

至于比特币系统中为什么不传递原始的私钥,而是传递DER编码的私钥,相信随着不断学习,会得到答案。

获取公钥的方法如下

CPubKey CKey::GetPubKey() const {assert(fValid);secp256k1_pubkey pubkey;size_t clen = CPubKey::PUBLIC_KEY_SIZE;CPubKey result;int ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, begin());assert(ret);secp256k1_ec_pubkey_serialize(secp256k1_context_sign, (unsigned char*)result.begin(), &clen, &pubkey, fCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);assert(result.size() == clen);assert(result.IsValid());return result;
}

可以看到,公钥的获取过程首先调用了libsecp256k1加密库中的函数secp256k1_ec_pubkey_create()生成了secp256k1_pubkey类型的公钥pubkey

然后又调用libsecp256k1加密库中的函数secp256k1_ec_pubkey_serialize将secp256k1_pubkey类型的公钥序列化为Bitcoin Core中的自定义公钥类型CPubKey

关于私钥的源码阅读就到这里。

由于刚刚窥探Bitcoin Core源码的冰山一角,难免出现理解错误的地方,本文中也列出了我目前还不理解的地方,还望各位能够指出~

探索比特币源码5-私钥相关推荐

  1. 探索比特币源码7-哈希

    在之前的两篇文章中,我们已经大致了解了私钥和公钥相关的代码: 探索比特币源码5-私钥 探索比特币源码6-公钥 接下来,我们打算阅读生成比特币地址的相关代码. 比特币地址的生成过程中,涉及了哈希函数的计 ...

  2. 探索比特币源码4-JSON-RPC接口的其他调用方法

    探索比特币源码4-JSON-RPC接口的其他调用方法 上一文探索比特币源码3-熟悉RPC接口中练习了使用bitcoin-cli客户端调用JSON-RPC接口的方法. 本文探索JSON-RPC接口的其他 ...

  3. 探索比特币源码3-熟悉RPC接口

    探索比特币源码3-熟悉RPC接口 比特币核心客户端实现了JSON-RPC接口,这个接口可以通过命令行帮助程序bitcoin-cli访问,也可以通过编程的形式在程序中访问. 本文主要探索3点: * 什么 ...

  4. 探索比特币源码2-配置Bitcoin Core节点

    探索比特币源码2-配置Bitcoin Core节点 回顾上文探索比特币源码1-运行比特币核心 我们已经运行上了Bitcoin Core,开始同步区块链数据 这个过程会持续一两天,我也不能干等着,便开始 ...

  5. 探索比特币源码1-运行 Bitcoin Core Node

    探索比特币源码1-运行 Bitcoin Core Node 上一文我们已经编译好了Bitcoin Core的可执行文件 本文,我们来尝试运行一个Bitcoin Core 注:本文是在学习Masteri ...

  6. 探索比特币源码0-bitcoin的编译

    探索比特币源码0-bitcoin的编译 前言 区块链领域作为一个新兴的热点,迭代的实在是太快了,刚刚入门,必须要抓紧追赶. 在阅读了一些优秀的文档之后,已经对比特币及其背后的实现技术--区块链有了一定 ...

  7. 探索比特币源码6-公钥

    继续源码的阅读,本文将对比特币源码中的公钥相关部分进行梳理. 在阅读代码前,先明确一个概念:公钥是如何定义和产生的? 公钥如何产生 我们已经知道,比特币的私钥就是一个256位二进制数字. 通过椭圆曲线 ...

  8. 探索比特币源码9-顺藤摸瓜之通过RPC读源码

    前言 阅读像比特币这样规模的源码,理清脉络很重要,一个文件一个文件阅读肯定不现实. 从比特币系统的RPC接口入手,不失为一个学习源码的好方法. 具体做法是,按照想要学习的功能,找到相应的RPC命令,然 ...

  9. 探索比特币源码8-哈希2

    上一文,我们介绍了Bitcoin Core中哈希相关的代码实现中的一部分 主要是存储哈希函数映射结果--哈希值的数据结构uint256.uint160及他们的基类base_blob 这篇文章我们继续来 ...

最新文章

  1. java list翻转_浅谈Java数据结构中的常见问题
  2. 需求调研计划_拆书营销管理 实施营销调研和预测需求
  3. Android 设计模式 - 观察者模式
  4. OPhone2.0应该重在中文应用体验
  5. scvmm live migration issue
  6. rpm批量卸载所有带有Java的文件
  7. Python里的dict和set的背后小秘密
  8. 入职体检——项目列表(7项)
  9. centos7怎么重置mysql密码_centOS7.4 重置mysql 密码
  10. 【计算机视觉】计算机视觉、模式识别、机器学习常用牛人主页链接
  11. lambdas for_Lambdas:来到您附近的Java 8!
  12. 自定义Valid注解,校验List集合
  13. Java并发——线程中断学习
  14. Hadoop学习总结之五:Hadoop的运行痕迹
  15. No module named flask.ext.script 解决方法
  16. Linux 命令 (tar,权限,管道)
  17. 三次样条插值-轨迹规划
  18. 机器学习第六回——无监督学习
  19. Linux用php上传表单文件,文件太大提示[413 Request Entity Too Large]
  20. Linux基金会宣布成立Ceph基金会

热门文章

  1. 斯坦福大学CS520知识图谱系列课程学习笔记:第三讲高级的知识图谱是什么样的
  2. gin-jwt对API进行权限控制
  3. SpringBoot学习笔记(16)----SpringBoot整合Swagger2
  4. linux 学习操作小计
  5. 不够优秀就不要腆着脸继续占便宜——作者:杨毅
  6. [转]该学Java或.NET?
  7. vue-day03-vue组件化开发
  8. java 数据库 事务 只读_java – odd SQLException – 无法检索转换只读状态服务器
  9. python table类中的 可以作为初步观察工具_Python程序设计基础_课程2020最新章节测试网课课后答案...
  10. stm32超声波测距代码_超声波模块另类用法,悬浮,你也能做到