探索比特币源码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用于標識非壓縮格式
02或03用于標識壓縮格式,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.h和pubkey.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-公钥的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 外设驱动库开发笔记4:AD9833函数发
 - 下一篇: Win32窗体应用程序如何添加资源文件?