比特币源码学习笔记(一)
前言
從事區塊鏈的開發,不了解其底層核心技術是不夠的。許多人在看了比特幣白皮書之后仍然不清楚比特幣是怎樣實現的,因為比特幣的源碼設計精巧,有許多設計白皮書未曾提及,加上本身比特幣的文檔稀少,加大了新手理解的困難程度。盡管現在已經有許多介紹區塊鏈的書和文章,卻很少是從源碼著手分析的。我通過半年時間對于區塊鏈的學習,開始撰寫一份比特幣源碼的教程。本教程深入淺出,通過分析最經典的區塊鏈——比特幣的C++客戶端源碼,讓開發者用最短的時間上手區塊鏈技術。了解比特幣源碼可幫助開發者更好了解區塊鏈的工作原理并在應用當中根據實際情況做出修改和調整。
本文所引用的源碼均來自原始版比特幣客戶端,即由中本聰發布的第一版源碼。該客戶端包括大約16000行代碼。盡管經過數年的發展,比特幣客戶端經過了幾次較大更新,其數據結構和原理從誕生之日起一直延續至今。本文會盡可能保證文字的嚴謹準確,表達當中難免會產生疏漏,歡迎指正。
?
第一章
?
本章節講述比特幣客戶端是怎樣生成比特幣地址,并創建新的交易。
我們來看一下GenerateNewKey()方法,該方法位于main.cpp。
?
bool AddKey(const CKey& key) {CRITICAL_BLOCK(cs_mapKeys){mapKeys[key.GetPubKey()] = key.GetPrivKey();mapPubKeys[Hash160(key.GetPubKey())] = key.GetPubKey();}return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey()); }vector<unsigned char> GenerateNewKey() {CKey key;key.MakeNewKey();if (!AddKey(key))throw runtime_error("GenerateNewKey() : AddKey failed\n");return key.GetPubKey(); }該方法通過以下步驟生成一個新的公鑰對:
?
- 首先建立一個新的CKey類型對象(第13行)。
- 調用addKey()方法將新建的key添加至1)全局映射mapKeys?(第5行)2)全局map mapPubKeys(第6行)和錢包數據庫wallet.dat(第8行)。
mapKeys建立公鑰與私鑰的一一對應關系。
mapPubKeys建立公鑰的hash和公鑰本身的對應關系。
- 返回公鑰(第16行)。
該公鑰為未壓縮的格式,屬于OpenSSL標準格式之一。在得到公鑰之后,比特幣客戶端會將該公鑰傳遞至PubKeyToAddress()并調用Hash160ToAddress()方法生成地址。最后返回的Base58編碼字符串值便是一個新生成的比特幣地址。Base58由1-9和除i,l,0,o之外的英文字符組成。
CTransaction類
CTransaction的定義位于main.h。在比特幣當中,所謂幣的概念其實是一系列交易Tx的組合。這種方式雖然實現起來更為復雜,卻提高了比特幣的安全性。用戶可以為每一筆交易創建一個新的地址,地址在使用一次之后可以立即作廢。因此,CTransaction是比特幣客戶端最重要的類之一。
?
class CTransaction { public:int nVersion;vector<CTxIn> vin;vector<CTxOut> vout;int nLockTime;//...... }?
CTransaction包含兩個容器類型:輸入交易vin和輸出交易vout。每個vin由若干CTxIn對象組成,每個vout則由CTxOut組成。
每筆交易Tx的輸入交易(CTxIn類)包含一個COutPoint對象prevout,該對象引用另外一筆交易Tx的輸出交易作為來源交易。來源交易使當前交易Tx從另一筆交易當中得到可花費的比特幣。一筆交易Tx可以擁有任意筆輸入交易。
任何交易均由一個256位uint256哈希作為其唯一識別。若要引用某一筆來源交易TxSource當中某個特定的輸出交易,我們需要兩種信息:TxSource的哈希,和該輸出交易在輸出交易當中的位置n。這兩種信息構成COutPoint類。一個COutPoint對象指向來源交易的某一筆輸出交易TxSource.vout[n]。如果該筆輸出交易被另外一筆交易Tx的位置i的輸入交易所引用,例如Tx.vin[i].prevout,我們將其稱為Tx的第i筆輸入交易花費了TxSource中的第n筆輸出交易。
uint256和uint160類
這兩種類型的定義位于uint.h。一個uint256類包含有一個256位的哈希。它由一個長度為256/32=8的unsigned int數組構成。一個相似的數據結構是uint160,該結構的定義可在同一個文件當中找到。既然SHA-256的長度為256bit,讀者不難推斷出uint160的作用是存放RIPEMD-160哈希。uint256和uint160均由base_uint類繼承而來。
?
class base_uint { protected:enum { WIDTH = BITS / 32 };unsigned int pn[WIDTH];public:bool operator!() const{for (int i = 0; i < WIDTH; i++)if (pn[i] != 0)return false;return true;}//......unsigned int GetSerializeSize(int nType = 0, int nVersion = VERSION) const{return sizeof(pn);}template <typename Stream>void Serialize(Stream& s, int nType = 0, int nVersion = VERSION) const{s.write((char*)pn, sizeof(pn));}template <typename Stream>void Unserialize(Stream& s, int nType = 0, int nVersion = VERSION){s.read((char*)pn, sizeof(pn));} }該類重載了若干運算符。此外該類擁有3個序列化成員函數,GetSerializeSize()、Serialize()和Unserialize()。我們會在后面講到這三種方法是如何工作的。
?
SendMoney()
該方法位于main.cpp。以下是該方法的源碼:
?
bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) {CRITICAL_BLOCK(cs_main){int64 nFeeRequired;if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired)){string strError;if (nValue + nFeeRequired > GetBalance())strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str());elsestrError = "Error: Transaction creation failed ";wxMessageBox(strError, "Sending...");return error("SendMoney() : %s\n", strError.c_str());}if (!CommitTransactionSpent(wtxNew)){wxMessageBox("Error finalizing transaction", "Sending...");return error("SendMoney() : Error finalizing transaction");}printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0,6).c_str());// Broadcastif (!wtxNew.AcceptTransaction()){// This must not fail. The transaction has already been signed and recorded.throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n");wxMessageBox("Error: Transaction not valid", "Sending...");return error("SendMoney() : Error: Transaction not valid");}wtxNew.RelayWalletTransaction();}MainFrameRepaint();return true; }?
當用戶發送比特幣到某一個地址時,比特幣客戶端會調用SendMoney()方法。該方法包含三個參數:
該方法的流程顯而易見:
這四個方法都與wtxNew相關。我們在本章介紹了第一個,其余三個將會在后續文章中介紹。
CreateTransaction()
該方法位于main.cpp。以下是該方法的源碼:
?
bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet) {nFeeRequiredRet = 0;CRITICAL_BLOCK(cs_main){// txdb must be opened before the mapWallet lockCTxDB txdb("r");CRITICAL_BLOCK(cs_mapWallet){int64 nFee = nTransactionFee;loop{wtxNew.vin.clear();wtxNew.vout.clear();if (nValue < 0)return false;int64 nValueOut = nValue;nValue += nFee;// Choose coins to useset<CWalletTx*> setCoins;if (!SelectCoins(nValue, setCoins))return false;int64 nValueIn = 0;foreach(CWalletTx* pcoin, setCoins)nValueIn += pcoin->GetCredit();// Fill vout[0] to the payeewtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey));// Fill vout[1] back to self with any changeif (nValueIn > nValue){// Use the same key as one of the coinsvector<unsigned char> vchPubKey;CTransaction& txFirst = *(*setCoins.begin());foreach(const CTxOut& txout, txFirst.vout)if (txout.IsMine())if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))break;if (vchPubKey.empty())return false;// Fill vout[1] to ourselfCScript scriptPubKey;scriptPubKey << vchPubKey << OP_CHECKSIG;wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey));}// Fill vinforeach(CWalletTx* pcoin, setCoins)for (int nOut = 0; nOut < pcoin->vout.size(); nOut++)if (pcoin->vout[nOut].IsMine())wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut));// Signint nIn = 0;foreach(CWalletTx* pcoin, setCoins)for (int nOut = 0; nOut < pcoin->vout.size(); nOut++)if (pcoin->vout[nOut].IsMine())SignSignature(*pcoin, wtxNew, nIn++);// Check that enough fee is includedif (nFee < wtxNew.GetMinFee(true)){nFee = nFeeRequiredRet = wtxNew.GetMinFee(true);continue;}// Fill vtxPrev by copying from previous transactions vtxPrevwtxNew.AddSupportingTransactions(txdb);wtxNew.fTimeReceivedIsTxTime = true;break;}}}return true; }?
調用該方法時,它所需要的四個參數如下:
該方法的流程如下:
- 定義一個本地變量nValueOut = nValue來保存將轉賬的金額(第17行)。將nValue與交易費nFee相加得到新的包含轉賬費的nValue。
- 執行位于第21行的SelectCoins(nValue, setCoins)得到一系列幣,并放入setCoins。setCoins包含支付給你本人地址的交易,即你所擁有的幣。這些交易將成為wtxNew的來源交易。
- 執行位于第27行的wtxNew.vout.push_back(CTxOut (nValueOut,sciptPubKey))并添加一筆輸出交易至wtxNew。該筆輸出將支付給<收款人地址160位哈希>(包含在scriptPubKey里面)數量為的幣。
- 如果需要找零(nValueIn > nValue),添加另一筆輸出交易至wtxNew并將零錢發回本人。該過程包含以下步驟:
- 從setCoin當中獲取第一筆交易txFirst,依次檢查txFirst.vout中的交易是否屬于本人。如果是則從該筆輸出交易當中提取出公鑰,并放入本地變量vchPubKey
- 將vchPubKey放入腳本vchPubKey OP_CHECKSIG,并使用這段腳本代碼為wtxNew添加一個支付給本人的輸出交易(第45行)。
- 因為setCoins包含支付給本人的交易,所以每筆交易一定包括至少一筆支付給本人的交易。從第一筆交易txFirst中即可找到。
- 至此,wtxNew的輸出交易容器vout已準備就緒。現在,該設置輸入交易容器vin。記住每一個輸入交易列表vin均引用一筆來源交易,而且wtxNew的每筆來源交易均可在setCoins中被找到。對于每一筆setCoins中的交易pcoin,逐個遍歷其輸出交易pcoin->vout[nOut]。如果第nOut筆輸出支付給本人(意味著wtxNew從該筆輸出交易中獲得幣),則向wtxNew添加一筆新的輸入交易(wtxNew.vin(wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut)),第51行)。該輸入交易指向pcoin中的第nOut筆輸出交易,由此將wtxNew.vin與pcoin的第nOut筆輸出相連接。
- 對于setCoins當中的每筆交易pcoin,逐個遍歷其所有輸出交易pcoin->vout[nOut]。如果該筆交易屬于本人,調用SignSignature(*pcoin,wtxNew, nIn++)為第nIn筆輸入交易添加簽名。注意nIn為wtxNew的輸入交易位置。
- 如果交易費nFee小于wtxNet.GetMinFee(true),將nFee設為后者,清空wtxNew中的所有數據并重新開始整個過程。在位于第11行的第一次迭代當中,nFee是全局變量nTransactionFee = 0的本地復制。
- 如果你不明白為什么要如此費力地重新添滿wtxNew,源碼中的GetMinFee()提供了答案:交易的最低費用與交易的數據大小有關。wtxNew的大小只有在完整構建之后才可得知。如果wtxNew.GetMinFee(true)計算得到的最小交易費用大于之前創造wtxNew時假設的交易費nFee,則除了重新構建wtxNew之外別無他法。
- 這里遇到了一個先有雞還是先有蛋的局面:若想創建一筆新的交易,則必須知道交易費用是多少。而交易費只有在整個交易被創建以后才可得知。為了打破這個循環,本地變量nFee被用來放置預計的交易費用,并且新的交易構建在此基礎上。在構建完成之后,得到真實的交易費并與預估的交易費作比較。如果預估的交易費小于真實的交易費,則替換成真實交易費并重新構造整個交易。
這里是GetMinFee()的源碼:
?
int64 GetMinFee(bool fDiscount=false) const{unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK);if (fDiscount && nBytes < 10000)return 0;return (1 + (int64)nBytes / 1000) * CENT;}?
- 如果計算得到的交易費比之前預計的交易費更高,則跳出第11行開始的循環并返回整個函數(第67行)。在此之前,需要進行以下兩個步驟:
- 執行wtxNew.AddSupportingTransactions(txdb)。這一部分以后會進行更詳細介紹。
- 設置wtxNet.fTimeReceivedIsTxTime=true(第66行)。
現在來看一下如何通過SignSignature()簽署新生成的交易wtxNew。
SignSignature()
該方法位于script.cpp。以下是該方法的源碼:
?
bool SignSignature(const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType, CScript scriptPrereq) {assert(nIn < txTo.vin.size());CTxIn& txin = txTo.vin[nIn];assert(txin.prevout.n < txFrom.vout.size());const CTxOut& txout = txFrom.vout[txin.prevout.n];// Leave out the signature from the hash, since a signature can't sign itself.// The checksig op will also drop the signatures from its hash.uint256 hash = SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);if (!Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig))return false;txin.scriptSig = scriptPrereq + txin.scriptSig;// Test solutionif (scriptPrereq.empty())if (!EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn))return false;return true; }?
首先需要注意的是,該函數有5個參數,而CreateTransaction()只有3個。這是因為在script.h文件里,后兩個參數已默認給出。
以下是傳遞給CreateTransaction()中的3個參數:
以下是SignSignature()所做的工作:
我們一起看一下這三個函數。
SignatureHash()
該方法位于script.cpp。以下是SignatureHash()的源碼。
?
uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType) {if (nIn >= txTo.vin.size()){printf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn);return 1;}CTransaction txTmp(txTo);// In case concatenating two scripts ends up with two codeseparators,// or an extra one at the end, this prevents all those possible incompatibilities.scriptCode.FindAndDelete(CScript(OP_CODESEPARATOR));// Blank out other inputs' signaturesfor (int i = 0; i < txTmp.vin.size(); i++)txTmp.vin[i].scriptSig = CScript();txTmp.vin[nIn].scriptSig = scriptCode;// Blank out some of the outputsif ((nHashType & 0x1f) == SIGHASH_NONE){// Wildcard payeetxTmp.vout.clear();// Let the others update at willfor (int i = 0; i < txTmp.vin.size(); i++)if (i != nIn)txTmp.vin[i].nSequence = 0;}else if ((nHashType & 0x1f) == SIGHASH_SINGLE){// Only lockin the txout payee at same index as txinunsigned int nOut = nIn;if (nOut >= txTmp.vout.size()){printf("ERROR: SignatureHash() : nOut=%d out of range\n", nOut);return 1;}txTmp.vout.resize(nOut+1);for (int i = 0; i < nOut; i++)txTmp.vout[i].SetNull();// Let the others update at willfor (int i = 0; i < txTmp.vin.size(); i++)if (i != nIn)txTmp.vin[i].nSequence = 0;}// Blank out other inputs completely, not recommended for open transactionsif (nHashType & SIGHASH_ANYONECANPAY){txTmp.vin[0] = txTmp.vin[nIn];txTmp.vin.resize(1);}// Serialize and hashCDataStream ss(SER_GETHASH);ss.reserve(10000);ss << txTmp << nHashType;return Hash(ss.begin(), ss.end()); }以下是該函數所需要的參數:
?
- txTo是將要被簽署的交易。它同時也是CreateTransaction()中的wtxNew對象。它的輸入交易列表中的第nIn項,txTo.vin[nIn],是該函數將要起作用的目標。
- scriptCode是scriptPrereq + txout.scriptPubKey,其中txout是SignSignature()中定義的來源交易txFrom()的輸出交易。由于此時scriptPrereq為空,scriptCode事實上是來源交易txFrom中的輸出交易列表當中被txTo作為輸入交易引用的那筆的腳本代碼。txout.scriptPubKey有可能包含兩類腳本:
- 腳本A:OP_DUP OP_HASH160 <你地址的160位哈希> OP_EQUALVERIFY OP_CECKSIG。該腳本將來源交易txFrom中的幣發送給你,其中<你地址的160位哈希>是你的比特幣地址。
- 腳本B:<你的公鑰> OP_CHECKSIG。該腳本將剩余的幣退還至來源交易txFrom的發起人。由于你創建的新交易txTo/wtxNew將會花費來自txFrom的幣,你必須同時也是txFrom的創建者。換句話講,當你在創建txFrom的時候,你其實是在花費之前別人發送給你的幣。因此,<你的公鑰>即是txFrom創建者的公鑰,也是你自己的公鑰。
我們在此停留片刻,來思考一下腳本A和腳本B。你有可能會問,這些腳本是從哪來的。中本聰在創造比特幣的時候為比特幣添加了一套腳本語言系統,所以比特幣中的交易都是由腳本代碼完成的。該腳本系統其實也是后來智能合約的雛形。腳本A來自第29行,位于方法CSendDialog::OnButtonSend(),腳本B則來自第44行,位于方法CreateTransaction()。
在了解了輸入交易之后,我們來一起了解SignatureHash()是怎樣工作的。
SignatureHash()首先將txTO拷貝至txTmp,接著清空txTmp.vin中每一筆輸入交易的scriptSig,除了txTmp.vin[nIn]之外,該輸入交易的scriptSig被設為scriptCode(第14、15行)。
接著,該函數檢驗nHashType的值。該函數的調用者將一個枚舉值傳遞至該函數nHashType = SIGHASH_ALL。
?
enum {SIGHASH_ALL = 1,SIGHASH_NONE = 2,SIGHASH_SINGLE = 3,SIGHASH_ANYONECANPAY = 0x80, };由于nHashType = SIGHASH_ALL,所有的if-else條件均不成立,該函數將直接執行最后4行代碼。
?
在最后4行代碼中,txTmp和nHashType變成序列化后的類型CDataStream對象。該類型包括一個裝有數據的字符容器類型。所返回的哈希值是Hash()方法在計算序列化后的數據所得到的。
一筆交易可以包含多筆輸入交易。SignatureHash()取其中一筆作為目標。它通過以下步驟生成哈希:
Hash()
該方法位于util.h。以下是生成哈希值的方法Hash()的源碼:
?
template<typename T1> inline uint256 Hash(const T1 pbegin, const T1 pend) {uint256 hash1;SHA256((unsigned char*)&pbegin[0], (pend - pbegin) * sizeof(pbegin[0]), (unsigned char*)&hash1);uint256 hash2;SHA256((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2);return hash2; }該函數對目標數據執行兩次SHA256()方法并返回結果。SHA256()的聲明可在openssl/sha.h中找到。
?
Solver()
該方法位于script.cpp。Solver()在SignSignature()中緊接著SignatureHash()被執行。它是真正用來為SignatureHash()返回的哈希值生成簽名的函數。
?
bool Solver(const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet) {scriptSigRet.clear();vector<pair<opcodetype, valtype> > vSolution;if (!Solver(scriptPubKey, vSolution))return false;// Compile solutionCRITICAL_BLOCK(cs_mapKeys){foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution){if (item.first == OP_PUBKEY){// Signconst valtype& vchPubKey = item.second;if (!mapKeys.count(vchPubKey))return false;if (hash != 0){vector<unsigned char> vchSig;if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))return false;vchSig.push_back((unsigned char)nHashType);scriptSigRet << vchSig;}}else if (item.first == OP_PUBKEYHASH){// Sign and give pubkeymap<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second));if (mi == mapPubKeys.end())return false;const vector<unsigned char>& vchPubKey = (*mi).second;if (!mapKeys.count(vchPubKey))return false;if (hash != 0){vector<unsigned char> vchSig;if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))return false;vchSig.push_back((unsigned char)nHashType);scriptSigRet << vchSig << vchPubKey;}}}}return true; }以下是該方法所需要的4個參數:
?
該函數首先會調用另一個有2個參數的Solver()。我們來研究一下。
帶有2個參數的Solver()
該方法位于script.cpp。以下是帶有2個參數的Solver()的源碼:
?
bool Solver(const CScript& scriptPubKey, vector<pair<opcodetype, valtype> >& vSolutionRet) {// Templatesstatic vector<CScript> vTemplates;if (vTemplates.empty()){// Standard tx, sender provides pubkey, receiver adds signaturevTemplates.push_back(CScript() << OP_PUBKEY << OP_CHECKSIG);// Short account number tx, sender provides hash of pubkey, receiver provides signature and pubkeyvTemplates.push_back(CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG);}// Scan templatesconst CScript& script1 = scriptPubKey;foreach(const CScript& script2, vTemplates){vSolutionRet.clear();opcodetype opcode1, opcode2;vector<unsigned char> vch1, vch2;// CompareCScript::const_iterator pc1 = script1.begin();CScript::const_iterator pc2 = script2.begin();loop{bool f1 = script1.GetOp(pc1, opcode1, vch1);bool f2 = script2.GetOp(pc2, opcode2, vch2);if (!f1 && !f2){// Successreverse(vSolutionRet.begin(), vSolutionRet.end());return true;}else if (f1 != f2){break;}else if (opcode2 == OP_PUBKEY){if (vch1.size() <= sizeof(uint256))break;vSolutionRet.push_back(make_pair(opcode2, vch1));}else if (opcode2 == OP_PUBKEYHASH){if (vch1.size() != sizeof(uint160))break;vSolutionRet.push_back(make_pair(opcode2, vch1));}else if (opcode1 != opcode2){break;}}}vSolutionRet.clear();return false; }第一個參數scriptPubKey可能包含腳本A也可能是腳本B。再一次說明,它是SignSignature()中來源交易txFrom的輸出腳本。
?
第二個參數用來存放輸出交易。它是一個容器對,每個對由一個腳本運算符(opcodetype類型)和腳本操作元(valtype類型)構成。
該函數第8-10行首先定義兩個模板:
很明顯,模板A、模板B與腳本A、腳本B相對應。為了便于對比,以下是腳本A和B的內容:
該函數的作用是將scriptPubKey與兩個模板相比較:
回到Solver()
我們回到有4個參數的Solver()并繼續對該函數的分析。現在我們清楚了該函數的工作原理。它會在兩個分支中選擇一個執行,取決于從vSolutionRet得到的對來自腳本A還是腳本B。如果來自腳本A,item.first? == OP_PUBKEYHASH;如果來自腳本B,item.first? == OP_PUBKEY。
- item.first? == OP_PUBKEY(腳本B)。在該情形下,item.second包含<你的公鑰>。全局變量mapKeys將你的全部公鑰映射至與之對應的私鑰。如果mapKeys當中沒有該公鑰,則報錯(第16行)。否則,用從mapKeys中提取出的私鑰簽署新生成的交易wtxNew的哈希值,其中哈希值作為第2個被傳入的參數(CKey::Sign(mapKeys[vchPubKey], hash, vchSig),第23行),再將結果放入vchSig,接著將其序列化成scriptSigRet(scriptSigRet << vchSig,第24行)并返回。
- item.first? == OP_PUBKEYHASH(腳本A)。在該情形下,item.second包含<你的地址160位哈希>。該比特幣地址將被用于從位于第23行的全局映射mapPubKeys中找到其所對應的公鑰。全局映射mapPubKeys將你的地址與生成它們的公鑰建立一一對應關系(查看函數AddKey())。接著,通過該公鑰從mapKeys中找到所對應的私鑰,并用該私鑰簽署第二個參數hash。簽名和公鑰將一同被序列化至scriptSigRet并返回(scriptSig << vchSig << vchPubkey,第24行)。
EvalScript()
該方法位于script.cpp。現在我們回到SignSignature()。在該函數的第12行之后,txin.scriptsig,即wtxNew的第nIn筆輸入交易中的scriptSig部分,將插入一個簽名。該簽名可能是以下其中之一:
在下文當中,vchSig將被引用為<你的簽名_vchSig>,vchPubKey則為<你的公鑰_vchPubKey>,以強調它們分別是你本人的簽名和公鑰。
我們現在開始調查EvalScript(),該函數是SignSignature()調用的最后一個函數,位于第15行。EvalScript()帶有3個參數,分別為:
- 第一個參數為txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey。它有可能是:
- 驗證情形A:<你的簽名_vchSig> <你的公鑰_vchPubKey> OP_CODESEPARATOR OP_DUP OP_HASH160 <你的地址160位哈希> OP_EQUALVERIFY OP_CHECKSIG,即簽名A + OP_CODESEPARATOR + 腳本A。
- 驗證情形B:<你的簽名_vchSig> OP_CODESEPARATOR <你的公鑰_vchPubKey> OP_CHECKSIG,即簽名B + OP_CODESEPARATOR + 腳本B。
- 第二個參數為新創建的交易txTo,即CreateTransaction()中的wtxNew。
- 第三個參數為nIn,即將被驗證的交易在txTo輸入交易列表中的位置。
驗證過程我們會在后面詳細講述。簡單地說,EvalScript()驗證新創建交易wtxNew的第nIn筆輸入交易是否包含有效的簽名。至此,一筆新的比特幣交易便創建完成。
總結
以上是生活随笔為你收集整理的比特币源码学习笔记(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 成都欢乐谷寄存行李多少钱
- 下一篇: 韩剧安娜是翻拍吗