探索比特币源码5-私钥
經(jīng)過一段時間的積累,終于來到了比特幣源碼閱讀的環(huán)節(jié)。還是按照之前的節(jié)奏,我們就比對著精通比特幣一書的進(jìn)度,進(jìn)行源碼的閱讀。
對于此文,只需你對比特幣系統(tǒng)中私鑰-公鑰-地址的產(chǎn)生及關(guān)系有最基本的了解
因此你可以放心的直接閱讀,如果遇到疑惑,可以返回來閱讀以下資料,填補一些基本概念即可:
- 精通比特幣第4章
- 橢圓曲線加密算法教程
- 密碼學(xué)知識匯總
下面進(jìn)入正題,本文將對比特幣源碼中的私鑰相關(guān)部分進(jìn)行梳理。
在閱讀代碼前,先明確一個概念:私鑰是如何產(chǎn)生的?
私鑰如何產(chǎn)生
比特幣的私鑰就是一個256位二進(jìn)制數(shù)字,就這么簡單。
但是有一個條件,這個256位二進(jìn)制數(shù)要小于一個非常大的質(zhì)數(shù)n
n = 0xffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141
這是由于比特幣使用的橢圓曲線secp256k1的方程所對應(yīng)的循環(huán)子群的秩為n。
這一點了解即可,如果你想了解為什么,建議仔細(xì)閱讀橢圓曲線加密算法教程
也就是說,你可以用硬幣、鉛筆和紙來隨機生成你的私鑰:
擲硬幣256次,用紙和筆記錄正反面并轉(zhuǎn)換為0和1,隨機得到的256位二進(jìn)制數(shù)字可作為比特幣錢包的私鑰,只要其小于n。
當(dāng)然更普遍的方法是使用代碼生成,但是一定要注意:在你不夠了解隨機數(shù)產(chǎn)生器前,不要自己寫代碼或使用你的編程語言提供的簡易隨機數(shù)生成器來獲得一個隨機數(shù)作為私鑰。應(yīng)使用密碼學(xué)安全的偽隨機數(shù)生成器(CSPRNG),并且需要有一個來自具有足夠熵值的源的種子。(這段警告來自于《精通比特幣》,我目前還不知曉怎樣才算一個足夠安全的偽隨機數(shù)發(fā)生器,也望大家告知交流)
代碼閱讀
明確了私鑰的定義,我們來閱讀源碼。
首先,我們進(jìn)入src目錄下
使用ls和grep命令,試圖找到私鑰-公鑰相關(guān)源文件的位置
一番探索后確定,頭文件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本質(zhì)就是一個vector<unsigned char>* 其allocator在support/allocators/secure.h中進(jìn)行了重載(具體原因暫不清楚)* 這個CPrivKey并不是我們理解的256bit的隨機數(shù)私鑰* 而是一個編碼后的一個私鑰,編碼的私鑰長度為PRIVATE_KEY_SIZE或COMPRESSED_PRIVATE_KEY_SIZE。* 從代碼來看,這個私鑰應(yīng)該是DER編碼的,具體什么是DER,為什么這樣編碼我還不太清楚。*/ typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;/** 封裝的私鑰類. */ class CKey { public:/*** 定義兩個靜態(tài)的大小用來表示普通私鑰的長度和壓縮后私鑰的長度**/static const unsigned int PRIVATE_KEY_SIZE = 279;static const unsigned int COMPRESSED_PRIVATE_KEY_SIZE = 214;/*** 壓縮后的私鑰必須要比壓縮前小,這是合理的要求了,使用static_assert在編譯時進(jìn)行檢查*/static_assert(PRIVATE_KEY_SIZE >= COMPRESSED_PRIVATE_KEY_SIZE,"COMPRESSED_PRIVATE_KEY_SIZE is larger than PRIVATE_KEY_SIZE");private:// 用于表示私鑰是否有效// 因為每次key被修改的時候都會做正確性判斷,所以fValid應(yīng)該和真實的狀態(tài)保持一致。bool fValid;// 表示對應(yīng)私鑰的公鑰是否被壓縮bool fCompressed;//! 實際的私鑰數(shù)據(jù)。//! 這里存儲的是我們所熟悉的256bit私鑰(32字節(jié))std::vector<unsigned char, secure_allocator<unsigned char> > keydata;//! 判斷vch指向的32字節(jié)數(shù)據(jù)是否是有效的私鑰數(shù)據(jù)bool static Check(const unsigned char* vch);public:// 構(gòu)造函數(shù),初始化fValid和fCompressed,設(shè)置keydata的長度為32CKey() : fValid(false), fCompressed(false){keydata.resize(32);}// 重載Ckey的==運算符,只要密鑰數(shù)據(jù)一致,是否壓縮也一直,就表示兩個CKey數(shù)據(jù)相同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;}// 設(shè)置密鑰的內(nèi)容,并通過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的函數(shù)能夠更方便的使用存儲私鑰的成員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; }// 返回私鑰(對應(yīng)的公鑰)是否是壓縮格式的bool IsCompressed() const { return fCompressed; }//! 使用隨機的方式創(chuàng)建一個新的密鑰.void MakeNewKey(bool fCompressed);//! 獲得私鑰//! 返回的私鑰是CPrivKey類型的,也就是編碼后的私鑰CPrivKey GetPrivKey() const;//! 獲得公鑰//! 通過私鑰計算出公鑰并返回CPubKey GetPubKey() const;/*** 簽名,返回DER序列化的數(shù)字簽名* @param[in] hash 要進(jìn)行簽名的哈希值* @param[out] 簽名結(jié)果* @param[test_case] 我也沒搞清楚這是干啥的,貌似是傳一個和隨機數(shù)有關(guān)的任意數(shù)*/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); };/*** 這也是一個私鑰類型,應(yīng)該是和HD錢包相關(guān)的* CKey, CPubKey, CExtKey, CExtPubKey 是bitcoin core中的四種密鑰實現(xiàn)* 其中CKey, CPubKey是普通的私鑰和公鑰類型* 而如果想使用 HD Wallet,必須使用CExtKey, CExtPubKey* 由于還不了解HD錢包,這個類我也沒有詳細(xì)閱讀* 或許可以看看這個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);} };// 使用橢圓算法加密前必須調(diào)用該程序啟用上下文 void ECC_Start(void);// 使用橢圓加密后使用該函數(shù)銷毀加密上下文 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隨機數(shù)(32字節(jié)長)
可以看到,代碼中使用GetStrongRandBytes()函數(shù)生成強隨機性的私鑰
使用grep命令搜索該方法
$ grep -rlw "GetStrongRandBytes" * key.cpp random.cpp random.h wallet/wallet.cpp最終確定,該方法位于random.h中,可在其中探尋詳細(xì)代碼
可以看到MakeNewKey()方法,通過不停調(diào)用GetStrongRandBytes(),直到找到能夠滿足要求的隨機數(shù)作為私鑰
Check()方法調(diào)用libsecp256k1加密庫中的函數(shù)secp256k1_ec_seckey_verify進(jìn)行驗證
獲取私鑰的方法如下
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; }該函數(shù)核心是向ec_privkey_export_der()提供私鑰數(shù)據(jù)keydata(通過begin()傳遞),進(jìn)行DER序列化,并返回CPrivKey類型的私鑰
至于比特幣系統(tǒng)中為什么不傳遞原始的私鑰,而是傳遞DER編碼的私鑰,相信隨著不斷學(xué)習(xí),會得到答案。
獲取公鑰的方法如下
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; }可以看到,公鑰的獲取過程首先調(diào)用了libsecp256k1加密庫中的函數(shù)secp256k1_ec_pubkey_create()生成了secp256k1_pubkey類型的公鑰pubkey
然后又調(diào)用libsecp256k1加密庫中的函數(shù)secp256k1_ec_pubkey_serialize將secp256k1_pubkey類型的公鑰序列化為Bitcoin Core中的自定義公鑰類型CPubKey
關(guān)于私鑰的源碼閱讀就到這里。
由于剛剛窺探Bitcoin Core源碼的冰山一角,難免出現(xiàn)理解錯誤的地方,本文中也列出了我目前還不理解的地方,還望各位能夠指出~
總結(jié)
以上是生活随笔為你收集整理的探索比特币源码5-私钥的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IBinder获取手机服务信息异常
- 下一篇: spark sql hbase java