继续源码的阅读,本文将对比特币源码中的公钥相关部分进行梳理。

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

公钥如何产生

我们已经知道,比特币的私钥就是一个256位二进制数字。

通过椭圆曲线乘法可以很容易的从私钥计算得到公钥,这是不可逆转的过程:

K = k * G

其中k是私钥,G是被称为生成点的常数点(xG,yG),而K是所得公钥。

比特币系统所使用的椭圆曲线叫做secp256k1,具体参数如下:

  • p = 0xffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f
  • a = 0
  • b = 7
  • xG = 0x79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 59f2815b 16f81798
  • yG = 0x483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 9c47d08f fb10d4b8
  • n = 0xffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141
  • h = 1

通过椭圆曲线乘法得到的公钥K是二维离散域内的一个点(x,y)

因此公钥K包含两个256bit的二进制数,分别对应其x和y坐标。

需要注意的是,由同一个私钥生成的公钥是唯一的,但是同一个公钥可以有两种不同的格式:

压缩格式非压缩格式(这是由于y可以由x推导出,于是公钥同时存储x和y就浪费了一半的空间,因此有了压缩格式的公钥)

而两种格式后续将会得到两个不同的比特币地址,但任何一个都是合法的。

为了标识公钥使用的哪种格式,需要前缀进行标识。

04用于标识非压缩格式

0203用于标识压缩格式,y为奇数用03,y为偶数用02

以这样一个公钥为例,其坐标x和y如下:

x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A

y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB

如果用非压缩格式表达,是一个520比特的数字(8+256+256)。这个520比特的数字以前缀04开头,紧接着是x及y坐标,组成格式为04 x y:

K = 04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE 52DDFE2E505BDB

如果用压缩格式表达,是一个264比特数字(8+256),其中前缀03表示y坐标是一个奇数:

K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A

代码阅读

公钥的相关源码位于pubkey.hpubkey.cpp

pubkey.h的源码如下:

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 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_PUBKEY_H
#define BITCOIN_PUBKEY_H#include <hash.h>
#include <serialize.h>
#include <uint256.h>#include <stdexcept>
#include <vector>const unsigned int BIP32_EXTKEY_SIZE = 74;   // 按照上一篇的分析,这应该是HD wallet使用的公钥的大小/*** A reference to a CKey: the Hash160 of its serialized public key* CKeyID用来作为一个CPubKey公钥实例的ID,用公钥的Hash160(SHA-256 + RIPEMD-160)作为唯一引用ID* 因此CKeyID继承自定义类型uint160,本质就是一个160bit的hash二进制数组* 通过实现来看,CKeyID = RIPEMD-160(SHA-256(publicKey))* 因此CKeyID其实就是未经 Base58 Check 编码的比特币地址
*/
class CKeyID : public uint160
{
public:CKeyID() : uint160() {}explicit CKeyID(const uint160& in) : uint160(in) {}
};typedef uint256 ChainCode;   // CExtPubKey中的成员变量/** 封装的公钥. */
class CPubKey
{
public:/*** secp256k1:*/static constexpr unsigned int PUBLIC_KEY_SIZE             = 65;  // 非压缩格式公钥的大小(65byte = 520bit)static constexpr unsigned int COMPRESSED_PUBLIC_KEY_SIZE  = 33;  // 压缩格式公钥的大小(33byte = 264bit)static constexpr unsigned int SIGNATURE_SIZE              = 72;static constexpr unsigned int COMPACT_SIGNATURE_SIZE      = 65;/*** see www.keylength.com* script supports up to 75 for single byte push*/static_assert(PUBLIC_KEY_SIZE >= COMPRESSED_PUBLIC_KEY_SIZE,"COMPRESSED_PUBLIC_KEY_SIZE is larger than PUBLIC_KEY_SIZE");private:/*** Just store the serialized data. 公钥数据* Its length can very cheaply be computed from the first byte.* 由于第一个字节代表前缀,公钥的长度可以通过前缀轻松的得到*/unsigned char vch[PUBLIC_KEY_SIZE];//! Compute the length of a pubkey with a given first byte.//  通过一个字节(前缀)得到公钥的长度//  函数私有,程序应时刻维护前缀与实际数据相对应。unsigned int static GetLen(unsigned char chHeader){if (chHeader == 2 || chHeader == 3) // 2或3代表压缩格式公钥,2代表y为偶数,3代表y为奇数return COMPRESSED_PUBLIC_KEY_SIZE;if (chHeader == 4 || chHeader == 6 || chHeader == 7)  // 4代表非压缩公钥,6和7???return PUBLIC_KEY_SIZE;return 0;}//! Set this key data to be invalid 使用0xFF前缀标识公钥无效void Invalidate(){vch[0] = 0xFF;}public:// 验证一个vector<unsigned char>数组的大小是否满足公钥的要求bool static ValidSize(const std::vector<unsigned char> &vch) {return vch.size() > 0 && GetLen(vch[0]) == vch.size();}//! Construct an invalid public key.//  初始化一个空的公钥,状态为无效,需要使用Set函数赋值vchCPubKey(){Invalidate();}//! Initialize a public key using begin/end iterators to byte data.//  初始化公钥数据vsh的函数,通过迭代器 begin/end 的方式template <typename T>void Set(const T pbegin, const T pend){int len = pend == pbegin ? 0 : GetLen(pbegin[0]);if (len && len == (pend - pbegin))memcpy(vch, (unsigned char*)&pbegin[0], len);elseInvalidate(); // 公钥无效(提供数据长度为0 或 数据长度与前缀不一致)}//! Construct a public key using begin/end iterators to byte data.template <typename T>CPubKey(const T pbegin, const T pend){Set(pbegin, pend);}//! Construct a public key from a byte vector.// 直接用vector<unsigned char>构造explicit CPubKey(const std::vector<unsigned char>& _vch){Set(_vch.begin(), _vch.end());}//! Simple read-only vector-like interface to the pubkey data.//  获取公钥数据的一些只读的接口unsigned int size() const { return GetLen(vch[0]); }const unsigned char* data() const { return vch; }const unsigned char* begin() const { return vch; }const unsigned char* end() const { return vch + size(); }const unsigned char& operator[](unsigned int pos) const { return vch[pos]; }//! Comparator implementation. 比较两个CPubKey// vch是私有成员,因此定义为友元friend bool operator==(const CPubKey& a, const CPubKey& b){return a.vch[0] == b.vch[0] &&memcmp(a.vch, b.vch, a.size()) == 0;}friend bool operator!=(const CPubKey& a, const CPubKey& b){return !(a == b);}// 比较两个公钥可以理解,为什么要比较大小???friend bool operator<(const CPubKey& a, const CPubKey& b){return a.vch[0] < b.vch[0] ||(a.vch[0] == b.vch[0] && memcmp(a.vch, b.vch, a.size()) < 0);}//! Implement serialization, as if this was a byte vector.template <typename Stream>void Serialize(Stream& s) const{unsigned int len = size();::WriteCompactSize(s, len);  // 函数定义于serialize.hs.write((char*)vch, len);}template <typename Stream>void Unserialize(Stream& s){unsigned int len = ::ReadCompactSize(s);  // 函数定义于serialize.hif (len <= PUBLIC_KEY_SIZE) {s.read((char*)vch, len);  // 读取Stream中的数据} else {// invalid pubkey, skip available data(将Stream中的数据丢弃)char dummy;while (len--)s.read(&dummy, 1);Invalidate();}}//! Get the KeyID of this public key (hash of its serialization)//  获得公钥数据的Hash160(SHA-256 + RIPEMD-160)作为ID//  由此看来,CKeyID其实就是公钥对应的未经Base58校验编码的比特币地址CKeyID GetID() const{return CKeyID(Hash160(vch, vch + size()));  // Hash160函数定义于hash.h中}//! Get the 256-bit hash of this public key.uint256 GetHash() const{return Hash(vch, vch + size());}/** Check syntactic correctness. 检查公钥是否有效* IsValid()仅仅按照协议查看前缀,只要不是0xFF,说明有效* Note that this is consensus critical as CheckSig() calls it!*/bool IsValid() const{return size() > 0;}//! fully validate whether this is a valid public key (more expensive than IsValid())//  完全验证这是否是有效的公钥//  IsFullyValid()比IsValid()的验证更彻底,进行数学上的验证。bool IsFullyValid() const;//! Check whether this is a compressed public key.//  本质是检查第一个字节的标识符bool IsCompressed() const{return size() == COMPRESSED_PUBLIC_KEY_SIZE;}/*** Verify a DER signature (~72 bytes).* If this public key is not fully valid, the return value will be false.*/bool Verify(const uint256& hash, const std::vector<unsigned char>& vchSig) const;/*** Check whether a signature is normalized (lower-S).*/static bool CheckLowS(const std::vector<unsigned char>& vchSig);//! Recover a public key from a compact signature.bool RecoverCompact(const uint256& hash, const std::vector<unsigned char>& vchSig);//! Turn this public key into an uncompressed public key.bool Decompress();//! Derive BIP32 child pubkey.bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
};struct CExtPubKey {unsigned char nDepth;unsigned char vchFingerprint[4];unsigned int nChild;ChainCode chaincode;CPubKey pubkey;friend bool operator==(const CExtPubKey &a, const CExtPubKey &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.pubkey == b.pubkey;}void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);bool Derive(CExtPubKey& out, unsigned int nChild) const;void Serialize(CSizeComputer& s) const{// Optimized implementation for ::GetSerializeSize that avoids copying.s.seek(BIP32_EXTKEY_SIZE + 1); // add one byte for the size (compact int)}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);}
};/** Users of this module must hold an ECCVerifyHandle. The constructor and*  destructor of these are not allowed to run in parallel, though. */
class ECCVerifyHandle
{static int refcount;public:ECCVerifyHandle();~ECCVerifyHandle();
};#endif // BITCOIN_PUBKEY_H

探索比特币源码6-公钥相关推荐

  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. 探索比特币源码5-私钥

    经过一段时间的积累,终于来到了比特币源码阅读的环节.还是按照之前的节奏,我们就比对着精通比特币一书的进度,进行源码的阅读. 对于此文,只需你对比特币系统中私钥-公钥-地址的产生及关系有最基本的了解 因 ...

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

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

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

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

最新文章

  1. linux运行raxml,RAxML安装
  2. mysql宠物种类表,mysql中的表操作
  3. Java8 的 Stream 流式操作之王者归来
  4. eslint 无法格式化ts_VS Code Prettier + ESlint 格式化Vue代码及遇到问题
  5. python 控制 cmd 命令行颜色
  6. python3-字符串常用操作
  7. 开源数据库学习资料汇总
  8. 【数据库04】中级开发需要掌握哪些SQL进阶玩法
  9. js中的经典题Foo.getName
  10. 人工智能会取代艺术家?
  11. 俄罗斯方块30周年 设计者忆当年
  12. 空调主板电路设计特点
  13. matlab 重叠峰分解 算法,一种基于粒子群算法的光谱重叠峰分解方法与流程
  14. min-height不生效
  15. 开启 Win10 系统自带的 Ubuntu 系统
  16. SRAM 和 DRAM 的区别
  17. 在线测径仪钢材测量安装方法
  18. 音频格式DTS 和 AC3 和 AAC简单介绍及HDTV
  19. 电动车控制器 GPS 车充OC2004设计方案参考电路,可替MPS9486
  20. 关于 Oracle ACFS 相关知识的简单学习

热门文章

  1. 滑翔导弹末段多约束智能弹道规划
  2. BUAA_OO_第二单元作业总结
  3. 课后作业-阅读任务-阅读笔记4
  4. Tkinter图片按钮
  5. rem和css3的相关知识点
  6. Java魔法堂:初探MessageFormat.format和ChoiceFormat
  7. BZOJ-2463 谁能赢呢?
  8. [VBA] 设置行高和列宽,以及全选单元格
  9. Linux的基础知识——mmap父子通信进程和匿名通信
  10. 3.6数对 (Python)