PrefaceIt is not enough to engage in blockchain development without understanding its underlying core technology. Many people still do not know how Bitcoin is implemented after reading the Bitcoin white paper, because the source code of Bitcoin is exquisitely designed, and there are many designs that are not mentioned in the white paper. In addition, the documentation of Bitcoin itself is scarce, which increases the difficulty for novices to understand. Although there are many books and articles introducing blockchain, few of them start from the source code. After half a year of learning about blockchain, I started to write a tutorial on Bitcoin source code. This tutorial is easy to understand and allows developers to get started with blockchain technology in the shortest time by analyzing the most classic blockchain - Bitcoin's C++ client source code. Understanding Bitcoin source code can help developers better understand the working principle of blockchain and make modifications and adjustments in applications according to actual conditions. The source code cited in this article is from the original version of the Bitcoin client, which is the first version of the source code released by Satoshi Nakamoto. The client includes about 16,000 lines of code. Although the Bitcoin client has undergone several major updates over the years, its data structure and principles have continued from the day it was born to this day. This article will try its best to ensure the rigor and accuracy of the text, and there will inevitably be omissions in the expression, and corrections are welcome. Chapter 1This section describes how the Bitcoin client generates Bitcoin addresses and creates new transactions. Let's take a look at the GenerateNewKey() method, which is located in 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(); } This method generates a new public key pair by following these steps:
mapKeys establishes a one-to-one correspondence between public keys and private keys.
The public key is in an uncompressed format and is one of the OpenSSL standard formats. After obtaining the public key, the Bitcoin client passes the public key to CTransaction ClassThe definition of CTransaction is located in main.h. In Bitcoin, the concept of a coin is actually a combination of a series of transactions Tx. Although this method is more complicated to implement, it improves the security of Bitcoin. Users can create a new address for each transaction, and the address can be immediately invalidated after being used once. Therefore, CTransaction is one of the most important classes in the Bitcoin client. class CTransaction { public: int nVersion; vector<CTxIn> vin; vector<CTxOut> vout; int nLockTime; //...... } CTransaction contains two container types: input transaction vin and output transaction vout. Each vin is composed of several CTxIn objects, and each vout is composed of CTxOut objects. The input transaction (CTxIn class) of each transaction Tx contains a COutPoint object prevout, which references the output transaction of another transaction Tx as the source transaction. The source transaction enables the current transaction Tx to obtain spendable bitcoins from another transaction. A transaction Tx can have any input transactions. Any transaction is uniquely identified by a 256-bit uint256 hash. To reference a specific output transaction in a source transaction TxSource, we need two pieces of information: the hash of TxSource, and the position n of the output transaction in the output transaction. These two pieces of information constitute the COutPoint class. A COutPoint object points to an output transaction uint256 and uint160 classesThe definitions of these two types are located in uint.h. A uint256 class contains a 256-bit hash. It consists of an unsigned int array of length 256/32=8. A similar data structure is uint160, which is defined in the same file. Since the length of SHA-256 is 256 bits, it is not difficult for readers to infer that the function of uint160 is to store RIPEMD-160 hashes. Both uint256 and uint160 are inherited from the base_uint class. 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)); } } This class overloads several operators. In addition, this class has three serialization member functions, SendMoney()This method is located in main.cpp. The following is the source code of this method: 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()); else strError = "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()); // Broadcast if (!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; } When a user sends bitcoins to an address, the Bitcoin client calls the SendMoney() method. This method contains three parameters:
The flow of this method is obvious:
These four methods are all related to wtxNew. We introduced the first one in this chapter, and the other three will be introduced in subsequent articles. CreateTransaction()This method is located in main.cpp. The following is the source code of this method: bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet) { nFeeRequiredRet = 0; CRITICAL_BLOCK(cs_main) { // txdb must be opened before the mapWallet lock CTxDB 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 use set<CWalletTx*> setCoins; if (!SelectCoins(nValue, setCoins)) return false; int64 nValueIn = 0; foreach(CWalletTx* pcoin, setCoins) nValueIn += pcoin->GetCredit(); // Fill vout[0] to the payee wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey)); // Fill vout[1] back to self with any change if (nValueIn > nValue) { // Use the same key as one of the coins vector<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 ourselves CScript scriptPubKey; scriptPubKey << vchPubKey << OP_CHECKSIG; wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey)); } // Fill vin foreach(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)); //Sign int 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 included if (nFee < wtxNew.GetMinFee(true)) { nFee = nFeeRequiredRet = wtxNew.GetMinFee(true); continue; } // Fill vtxPrev by copying from previous transactions vtxPrev wtxNew.AddSupportingTransactions(txdb); wtxNew.fTimeReceivedIsTxTime = true; break; } } } return true; } When calling this method, it requires four parameters as follows:
The process of this method is as follows:
Here is the source code for 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; }
Now let's see how to sign the newly generated transaction wtxNew through SignSignature(). SignSignature()This method is located in script.cpp. The following is the source code of this method: 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 solution if (scriptPrereq.empty()) if (!EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn)) return false; return true; } The first thing to note is that this function has 5 parameters, while The following are the three parameters passed to
Here’s what SignSignature() does:
Let's look at these three functions together. SignatureHash() This method is located in 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' signatures for (int i = 0; i < txTmp.vin.size(); i++) txTmp.vin[i].scriptSig = CScript(); txTmp.vin[nIn].scriptSig = scriptCode; // Blank out some of the outputs if ((nHashType & 0x1f) == SIGHASH_NONE) { // Wildcard payee txTmp.vout.clear(); // Let the others update at will for (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 txin unsigned 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 will for (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 transactions if (nHashType & SIGHASH_ANYONECANPAY) { txTmp.vin[0] = txTmp.vin[nIn]; txTmp.vin.resize(1); } // Serialize and hash CDataStream ss(SER_GETHASH); ss.reserve(10000); ss << txTmp << nHashType; return Hash(ss.begin(), ss.end()); } The following are the parameters required by this function:
Let's stop here for a moment and think about Script A and Script B. You may ask where these scripts come from. When Satoshi Nakamoto created Bitcoin, he added a scripting language system to Bitcoin, so all transactions in Bitcoin are completed by script code. This scripting system is actually the prototype of smart contracts. Script A comes from line 29, located in the method CSendDialog::OnButtonSend(), and Script B comes from line 44, located in the method CreateTransaction().
After understanding the input transaction, let's understand how SignatureHash() works. SignatureHash() first copies txTO to txTmp, then clears the scriptSig of each input transaction in txTmp.vin, except for txTmp.vin[nIn], where the scriptSig of the input transaction is set to scriptCode (lines 14 and 15). Next, the function checks the value of nHashType. The caller of the function passes an enumeration value to the function nHashType = SIGHASH_ALL. enum { SIGHASH_ALL = 1, SIGHASH_NONE = 2, SIGHASH_SINGLE = 3, SIGHASH_ANYONECANPAY = 0x80, }; Since nHashType = SIGHASH_ALL, all if-else conditions are not met, and the function will directly execute the last 4 lines of code.In the last 4 lines of code, txTmp and nHashType become serialized type CDataStream objects. This type includes a character container type that holds the data. The returned hash value is the result of the Hash() method calculating the serialized data. A transaction can contain multiple input transactions. SignatureHash() takes one of them as the target. It generates the hash by the following steps:
Hash()This method is located in util.h. The following is the source code of the method Hash() that generates a hash value: 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; } This function executes the SHA256() method twice on the target data and returns the result. The declaration of SHA256() can be found in openssl/sha.h.Solver()This method is located in script.cpp. Solver() is executed in SignSignature() immediately after SignatureHash(). It is the function that actually generates the signature for the hash value returned by 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 solution CRITICAL_BLOCK(cs_mapKeys) { foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution) { if (item.first == OP_PUBKEY) { //Sign const 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 pubkey map<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; } The following are the 4 parameters required by this method:
The function first calls another Solver() with 2 parameters. Let's examine it. Solver() with 2 parametersThis method is located in script.cpp. The following is the source code of Solver() with 2 parameters: bool Solver(const CScript& scriptPubKey, vector<pair<opcodetype, valtype> >& vSolutionRet) { // Templates static vector<CScript> vTemplates; if (vTemplates.empty()) { // Standard tx, sender provides pubkey, receiver adds signature vTemplates.push_back(CScript() << OP_PUBKEY << OP_CHECKSIG); // Short account number tx, sender provides hash of pubkey, receiver provides signature and pubkey vTemplates.push_back(CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG); } // Scan templates const CScript& script1 = scriptPubKey; foreach(const CScript& script2, vTemplates) { vSolutionRet.clear(); opcodetype opcode1, opcode2; vector<unsigned char> vch1, vch2; // Compare CScript::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) { // Success reverse(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; } The first parameter, scriptPubKey, may contain script A or script B. Once again, it is the output script of the source transaction txFrom in SignSignature().The second parameter is used to store output transactions. It is a container pair, each pair consists of a script operator (opcodetype type) and a script operation element (valtype type). Lines 8-10 of this function first define two templates:
Obviously, Template A and Template B correspond to Script A and Script B. For comparison purposes, here are the contents of Script A and Script B:
This function compares the scriptPubKey with two templates:
Back to Solver()Let's go back to Solver() with 4 parameters and continue the analysis of this function. Now we know how this function works. It will choose one of two branches to execute, depending on whether the pair obtained from vSolutionRet comes from script A or script B. If it comes from script A, item.first == OP_PUBKEYHASH; if it comes from script B, item.first == OP_PUBKEY.
EvalScript()This method is located in script.cpp. Now let's go back to SignSignature(). After line 12 of this function, txin.scriptsig, the scriptSig part of the nInth input transaction of wtxNew, will insert a signature. This signature may be one of the following:
In the following text, vchSig will be referenced as <your signature_vchSig> and vchPubKey will be referenced as <your public key_vchPubKey> to emphasize that they are your signature and public key, respectively. We now start investigating EvalScript(), which is the last function called by SignSignature(), located on line 15. EvalScript() takes 3 parameters, namely:
We will describe the verification process in detail later. In short, EvalScript() verifies whether the nInth input transaction of the newly created transaction wtxNew contains a valid signature. At this point, a new Bitcoin transaction is created. |
<<: 4 Ways to Tell If Bitcoin Is in a Bubble
>>: Alibaba takes action against counterfeiting and begins using blockchain to combat food fraud
According to Beijing Business Daily, today, under...
Today is another good day for mining. Take the lo...
The waist supports the human body, connecting the...
Most people hope to find their true love and end ...
On October 30, the “The First Practical Applicati...
As one of the traditional physiognomy techniques, ...
A person with a bad character does not seem to be...
The appearance of a noble person can often bring ...
Detailed explanation of the five major lines on w...
How to tell if you are blessed 1Plump red lips (l...
A person’s career status can be seen from his fac...
Financial luck is one of the various fortunes in ...
Regarding the recent DAO attack, it seems that ev...
There are so many people in the world, and the li...
Sometimes, when we make friends, we pay close att...