探索比特币源码6-公钥

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

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

公钥如何产生

我们已经知道,比特币的私钥就是一个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函数赋值vch
    CPubKey()
    {
        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);
        else
            Invalidate(); // 公钥无效(提供数据长度为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.h
        s.write((char*)vch, len);
    }
    template <typename Stream>
    void Unserialize(Stream& s)
    {
        unsigned int len = ::ReadCompactSize(s);  // 函数定义于serialize.h
        if (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
阅读更多

更多精彩内容