从事区块链的开发,不了解其底层核心技术是不够的。许多人在看了比特币白皮书之后仍然不清楚比特币是怎样实现的,因为比特币的源码设计精巧,有许多设计白皮书未曾提及,加上本身比特币的文档稀少,加大了新手理解的困难程度。尽管现在已经有许多介绍区块链的书和文章,却很少是从源码着手分析的。我通过半年时间对于区块链的学习,开始撰写一份比特币源码的教程。本教程深入浅出,通过分析最经典的区块链——比特币的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();
}
该公钥为未压缩的格式,属于OpenSSL标准格式之一。在得到公钥之后,比特币客户端会将该公钥传递至PubKeyToAddress()并调用Hash160ToAddress()方法生成地址。最后返回的Base58编码字符串值便是一个新生成的比特币地址。Base58由1-9和除i,l,0,o之外的英文字符组成。
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笔输出交易。
这两种类型的定义位于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));
}
}
该方法位于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());
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;
}
当用户发送比特币到某一个地址时,比特币客户端会调用SendMoney()方法。该方法包含三个参数:
该方法的流程显而易见:
这四个方法都与wtxNew相关。我们在本章介绍了第一个,其余三个将会在后续文章中介绍。
该方法位于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 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 ourself
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;
}
调用该方法时,它所需要的四个参数如下:
该方法的流程如下:
这里是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;
}
现在来看一下如何通过SignSignature()签署新生成的交易wtxNew。
该方法位于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 solution
if (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()所做的工作:
我们一起看一下这三个函数。
该方法位于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' 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());
}
以下是该函数所需要的参数:
我们在此停留片刻,来思考一下脚本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,
};
在最后4行代码中,txTmp和nHashType变成序列化后的类型CDataStream对象。该类型包括一个装有数据的字符容器类型。所返回的哈希值是Hash()方法在计算序列化后的数据所得到的。
一笔交易可以包含多笔输入交易。SignatureHash()取其中一笔作为目标。它通过以下步骤生成哈希:
该方法位于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;
}
该方法位于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 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;
}
该函数首先会调用另一个有2个参数的Solver()。我们来研究一下。
该方法位于script.cpp。以下是带有2个参数的Solver()的源码:
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;
}
第二个参数用来存放输出交易。它是一个容器对,每个对由一个脚本运算符(opcodetype类型)和脚本操作元(valtype类型)构成。
该函数第8-10行首先定义两个模板:
很明显,模板A、模板B与脚本A、脚本B相对应。为了便于对比,以下是脚本A和B的内容:
该函数的作用是将scriptPubKey与两个模板相比较:
我们回到有4个参数的Solver()并继续对该函数的分析。现在我们清楚了该函数的工作原理。它会在两个分支中选择一个执行,取决于从vSolutionRet得到的对来自脚本A还是脚本B。如果来自脚本A,item.first == OP_PUBKEYHASH;如果来自脚本B,item.first == OP_PUBKEY。
该方法位于script.cpp。现在我们回到SignSignature()。在该函数的第12行之后,txin.scriptsig,即wtxNew的第nIn笔输入交易中的scriptSig部分,将插入一个签名。该签名可能是以下其中之一:
在下文当中,vchSig将被引用为<你的签名_vchSig>,vchPubKey则为<你的公钥_vchPubKey>,以强调它们分别是你本人的签名和公钥。
我们现在开始调查EvalScript(),该函数是SignSignature()调用的最后一个函数,位于第15行。EvalScript()带有3个参数,分别为:
验证过程我们会在后面详细讲述。简单地说,EvalScript()验证新创建交易wtxNew的第nIn笔输入交易是否包含有效的签名。至此,一笔新的比特币交易便创建完成。