關(guān)于交易部分可以先閱讀《精通比特幣》第五章
本文內(nèi)容參考自https://blog.csdn.net/g2com/article/details/64386251
對于初次分析比特幣源代碼蜕乡,建議先閱讀最原始版本的比特幣源代碼original-bitcoin氢烘。此版本源代碼比較簡單,可以幫助快速理解比特幣各個(gè)階段的工作流程及原理。
1.SendMoney()
當(dāng)比特幣客戶端向某個(gè)地址發(fā)送比特幣時(shí)线椰,便會(huì)調(diào)用該函數(shù)镇防。函數(shù)位于'src/main.cpp'第2625行歉铝。
bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew)
{
CRITICAL_BLOCK(cs_main)
{
int64 nFeeRequired;
if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired))
{
strig 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;
}
該方法包含三個(gè)參數(shù):
- scriptPubKey為收款人公鑰鎖定腳本拧额,關(guān)于鎖定腳本和解鎖腳本將會(huì)在下章做分析。
- nValue表示將要轉(zhuǎn)賬的金額常遂。該金額并未包含交易費(fèi)nTrasactionFee纳令。
- wtxNew是一個(gè)CWalletTx類的本地變量。該變量目前的值為空克胳,之后會(huì)包含若干CMerkleTX類對象平绩。該類由CTransaction衍生而來,并且添加了若干方法漠另。我們暫時(shí)先不管具體細(xì)節(jié)捏雌,僅將其看作CTransaction類。
SendMoney()
首先調(diào)用了CreateTransaction()
函數(shù)酗钞,這個(gè)函數(shù)作用便是構(gòu)造一筆新的交易腹忽,也是本文重點(diǎn)分析的函數(shù)。該函數(shù)源代碼如下:
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;
}
調(diào)用該方法時(shí)砚作,它所需要的四個(gè)參數(shù)如下:
- scriptPubKey即腳本代碼
- nValue是將要轉(zhuǎn)賬的數(shù)額窘奏,交易費(fèi)nTransactionFee并未包括在內(nèi)。
- wtxNew是一個(gè)新的Tx實(shí)例葫录。
- nFeeRequiredRet是一筆用來支付交易費(fèi)的輸出交易着裹,在該方法執(zhí)行完成之后獲得。
函數(shù)首先對實(shí)例wtxNew初始化米同,隨后計(jì)算總共費(fèi)用nValue=轉(zhuǎn)賬金額+交易費(fèi)骇扇,調(diào)用 SelectCoin()
尋找合適的交易輸入。
實(shí)際上面粮,并不存在儲(chǔ)存比特幣地址或賬戶余額的地點(diǎn)少孝,只有被所有者鎖住的、分散的UTXO熬苍∩宰撸“一個(gè)用戶的比特幣余額”,這個(gè)概念是一個(gè)通過比特幣錢包應(yīng)用創(chuàng)建的派生之物柴底。比特幣錢包通過掃描區(qū)塊鏈并聚合所有屬于該用戶的UTXO來計(jì)算該用戶的余額婿脸。
bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet)
{
setCoinsRet.clear();
// List of values less than target
int64 nLowestLarger = _I64_MAX;
CWalletTx* pcoinLowestLarger = NULL;
vector<pair<int64, CWalletTx*> > vValue;
int64 nTotalLower = 0;
...
}
我們知道比特幣是基于UTXO模型的,所以SelectCoin便負(fù)責(zé)從所有屬于該用戶的UTXO中找到一組符合轉(zhuǎn)賬金額的輸入。具體的尋找算法此處便不具體分析柄驻。
在得到一組輸入之后會(huì)計(jì)算所有輸入的總金額nValueIn狐树,一般輸入總金額是大于轉(zhuǎn)賬金額的,所以后面會(huì)構(gòu)造一筆轉(zhuǎn)給自己地址的輸出鸿脓,用于找零抑钟。
隨后調(diào)用wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey))
構(gòu)造第一筆輸出,指向該筆交易的轉(zhuǎn)賬地址野哭。關(guān)于CTxIn和CTxOut的數(shù)據(jù)結(jié)構(gòu)可以參考https://blog.csdn.net/pure_lady/article/details/77771392
如果需要找零(nValueIn > nValue)味赃,添加另一筆輸出交易至wtxNew并將零錢發(fā)回本人。該過程包含以下步驟:
從setCoin當(dāng)中獲取第一筆交易txFirst虐拓,依次檢查txFirst.vout中的輸出是否屬于本人心俗。如果是則從該筆輸出交易當(dāng)中提取出公鑰ExtractPubKey
,并放入本地變量vchPubKey
將vchPubKey放入腳本vchPubKey OP_CHECKSIG蓉驹,并使用這段腳本代碼為wtxNew添加一個(gè)支付給本人的輸出交易城榛。
因?yàn)閟etCoins包含支付給本人的交易,所以每筆交易一定包括至少一筆支付給本人的交易态兴。從第一筆交易txFirst中即可找到狠持。
至此,wtxNew的輸出交易容器vout已準(zhǔn)備就緒≌叭螅現(xiàn)在喘垂,該設(shè)置輸入交易容器vin甜刻。記住每一個(gè)輸入交易列表vin均引用一筆來源交易,而且wtxNew的每筆來源交易均可在setCoins中被找到正勒。對于每一筆setCoins中的交易pcoin得院,逐個(gè)遍歷其輸出交易pcoin->vout[nOut]。如果第nOut筆輸出支付給本人(意味著wtxNew從該筆輸出交易中獲得幣)章贞,則向wtxNew添加一筆新的輸入交易(wtxNew.vin(wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut))祥绞,第51行)。該輸入交易指向pcoin中的第nOut筆輸出交易鸭限,由此將wtxNew.vin與pcoin的第nOut筆輸出相連接蜕径。
對于setCoins當(dāng)中的每筆交易pcoin,逐個(gè)遍歷其所有輸出交易pcoin->vout[nOut]败京。如果該筆交易屬于本人兜喻,調(diào)用SignSignature(*pcoin,wtxNew, nIn++)為第nIn筆輸入交易添加簽名。注意nIn為wtxNew的輸入交易位置赡麦。
對于交易簽名函數(shù)SignSignature
虹统,以下為源代碼:
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;
}
首先需要注意的是,該函數(shù)有5個(gè)參數(shù)隧甚,而CreateTransaction()只有3個(gè)车荔。這是因?yàn)樵趕cript.h文件里,后兩個(gè)參數(shù)已默認(rèn)給出戚扳。
SignSignature(*pcoin, wtxNew, nIn++)
- txFrom是一個(gè)*pcoin對象忧便。即我們前面找到的setCoins中的每一個(gè)。
- txTo是CreateTransaction()里的wtxNew對象帽借。它是將要花費(fèi)來源交易txFrom的新交易珠增。新交易需要被簽署方可生效。
- nIn是指向txTo中輸入交易列表的索引位置砍艾。該輸入交易列表包含一個(gè)對txFrom的輸出交易列表的引用蒂教。更準(zhǔn)確地講,txin=txTo.vin[nIn](第4行)是txTo中的輸入交易脆荷;txout=txFrom.vout[txin.prev.out.n](第6行)是txin所指向的txFrom中的輸出交易凝垛。
以下是SignSignature()所做的工作:
- 根據(jù)索引位置找到對應(yīng)的輸入輸出交易。
- 調(diào)用SignatureHash()方法生成txTo的哈希值蜓谋。
- 調(diào)用Solver()函數(shù)簽署剛才生成的哈希梦皮。
- 調(diào)用EvalScript()來運(yùn)行一小段腳本并檢查簽名是否合法。
下面分別介紹這幾個(gè)函數(shù):
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());
}
SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);
以下是該函數(shù)所需要的參數(shù):
- txTo是將要被簽署的交易桃焕。它同時(shí)也是CreateTransaction()中的wtxNew對象剑肯。它的輸入交易列表中的第nIn項(xiàng),txTo.vin[nIn]观堂,是該函數(shù)將要起作用的目標(biāo)让网。
- scriptCode是scriptPrereq + txout.scriptPubKey呀忧,其中txout是SignSignature()中定義的來源交易txFrom()的輸出交易。由于此時(shí)scriptPrereq為空溃睹,scriptCode事實(shí)上是來源交易txFrom中的輸出交易列表當(dāng)中被txTo作為輸入交易引用的那筆的腳本代碼而账。txout.scriptPubKey有可能包含兩類腳本:
腳本A:OP_DUP OP_HASH160 <你地址的160位哈希> OP_EQUALVERIFY OP_CECKSIG。該腳本將來源交易txFrom中的幣發(fā)送給你丸凭,其中<你地址的160位哈希>是你的比特幣地址。
腳本B:<你的公鑰> OP_CHECKSIG腕铸。該腳本將剩余的幣退還至來源交易txFrom的發(fā)起人惜犀。由于你創(chuàng)建的新交易txTo/wtxNew將會(huì)花費(fèi)來自txFrom的幣,你必須同時(shí)也是txFrom的創(chuàng)建者狠裹。換句話講虽界,當(dāng)你在創(chuàng)建txFrom的時(shí)候,你其實(shí)是在花費(fèi)之前別人發(fā)送給你的幣涛菠。因此莉御,<你的公鑰>即是txFrom創(chuàng)建者的公鑰,也是你自己的公鑰俗冻。
在了解了輸入交易之后礁叔,我們來一起了解SignatureHash()是怎樣工作的。
SignatureHash()首先將txTO拷貝至txTmp迄薄,接著清空txTmp.vin中每一筆輸入交易的scriptSig琅关,除了txTmp.vin[nIn]之外,該輸入交易的scriptSig被設(shè)為scriptCode(第14讥蔽、15行)涣易。
接著,該函數(shù)檢驗(yàn)nHashType的值冶伞。根據(jù)不同的nHAshType選擇不同的置空操作新症。
- SIGHASH_ALL是默認(rèn)選項(xiàng),具體流程是把所有的TxOut都納入臨時(shí)Tx中用來生成被簽署的交易响禽,相當(dāng)于針對這個(gè)TxIn徒爹,這個(gè)交易中的所有的TxOut都已經(jīng)被這個(gè)TxIn承認(rèn),不可改
- SIGHASH_NONE芋类,具體流程是把所有的TxOut都置空瀑焦,相當(dāng)于針對這個(gè)TxIn,不關(guān)心這個(gè)交易的TxOut是什么情況梗肝,即使被替換了也是可以的
- SIGHASH_SINGLE榛瓮,具體流程是只保留和自已同樣index的out,其他的out都置空巫击,表示只關(guān)心和自己同樣index的out禀晓,其他的out不關(guān)心精续。比如當(dāng)前的txin是這個(gè)交易的第3個(gè)in(index=2),那么這個(gè)交易的第3個(gè)out保留粹懒,其他的out都置空重付。
- SIGHASH_ANYONECANPAY比較特殊,他是獨(dú)立的凫乖∪返妫可以和另3個(gè)標(biāo)志取并集。它表示簽署這個(gè)TxIn的時(shí)候我連其他的TxIn都不關(guān)心帽芽,可以和前面3個(gè)并存删掀。
在最后4行代碼中,txTmp和nHashType變成序列化后的類型CDataStream對象导街。該類型包括一個(gè)裝有數(shù)據(jù)的字符容器類型披泪。所返回的哈希值是Hash()方法在計(jì)算序列化后的數(shù)據(jù)所得到的。
到這里我們便生成了txOut的哈希值hash搬瑰,接下來會(huì)調(diào)用Solver()函數(shù)簽署剛才生成hash款票。
Solver()
Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)
其源代碼如下:
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;
}
以下是該方法所需要的4個(gè)參數(shù):
- 位于第10行的調(diào)用函數(shù)SignSignature()將txOut.scriptPubKey,來源交易txFrom的輸出腳本泽论,作為輸入值傳入第一個(gè)參數(shù)scriptPubKey艾少。記住它可能包含腳本A或者腳本B。
- 第二個(gè)參數(shù)hash是由SignatureHash()生成的哈希值翼悴。
- 第三個(gè)參數(shù)nHashType的值默為SIGHASH_ALL姆钉。其余三種值見上一個(gè)函數(shù)的解釋。
- 第四個(gè)參數(shù)是該函數(shù)的返回值抄瓦,即調(diào)用函數(shù)SignSIgnature()中位于第12行的txin.scriptSig潮瓶。記住txin是新生成的交易wtxNew(在調(diào)用函數(shù)SignSignature()中作為txTo引用)位于第nIn的輸入交易。因此钙姊,wtxNew第nIn筆輸入交易的scriptSig將存放該函數(shù)返回的簽名毯辅。
該函數(shù)首先將scriptSigRet清空,隨后調(diào)用Solver(scriptPubKey, vSolution)
煞额,此Solver函數(shù)有兩個(gè)輸入思恐。其源代碼為:
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;
}
該函數(shù)的作用是將scriptPubKey與兩個(gè)模板相比較:
如果輸入腳本為腳本A,則將模板A中的OP_PUBKEYHASH與腳本A中的<你的地址160位哈希>配對膊毁,并將該對放入vSolutionRet胀莹。
如果輸入腳本為腳本B,則從模板B中提取運(yùn)算符OP_PUBKEY婚温,和從腳本B中提取運(yùn)算元<你的公鑰>描焰,將二者配對并放入vSolutionRet。
如果輸入腳本與兩個(gè)模板均不匹配,則返回false荆秦。
回到有4個(gè)參數(shù)的Solver()并繼續(xù)對該函數(shù)的分析±榻撸現(xiàn)在我們清楚了該函數(shù)的工作原理。它會(huì)在兩個(gè)分支中選擇一個(gè)執(zhí)行步绸,取決于從vSolutionRet得到的對來自腳本A還是腳本B掺逼。如果來自腳本A,item.first == OP_PUBKEYHASH瓤介;如果來自腳本B吕喘,item.first == OP_PUBKEY。
- item.first == OP_PUBKEY(腳本B)刑桑。在該情形下氯质,item.second包含<你的公鑰>。全局變量mapKeys將你的全部公鑰映射至與之對應(yīng)的私鑰漾月。如果mapKeys當(dāng)中沒有該公鑰病梢,則報(bào)錯(cuò)(第16行)胃珍。否則梁肿,用從mapKeys中提取出的私鑰簽署新生成的交易wtxNew的哈希值,其中哈希值作為第2個(gè)被傳入的參數(shù)(CKey::Sign(mapKeys[vchPubKey], hash, vchSig)觅彰,第23行)吩蔑,再將結(jié)果放入vchSig,接著將其序列化成scriptSigRet(scriptSigRet << vchSig填抬,第24行)并返回烛芬。
- item.first == OP_PUBKEYHASH(腳本A)。在該情形下飒责,item.second包含<你的地址160位哈希>赘娄。該比特幣地址將被用于從位于第23行的全局映射mapPubKeys中找到其所對應(yīng)的公鑰。全局映射mapPubKeys將你的地址與生成它們的公鑰建立一一對應(yīng)關(guān)系(查看函數(shù)AddKey())宏蛉。接著遣臼,通過該公鑰從mapKeys中找到所對應(yīng)的私鑰,并用該私鑰簽署第二個(gè)參數(shù)hash拾并。簽名和公鑰將一同被序列化至scriptSigRet并返回(scriptSig << vchSig << vchPubkey揍堰,第24行)
EvalScript()
最后將調(diào)用EvalScript()來運(yùn)行一小段腳本并檢查簽名是否合法。
EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn)
其源代碼如下:
bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType,
vector<vector<unsigned char> >* pvStackRet)
{
CAutoBN_CTX pctx;
CScript::const_iterator pc = script.begin();
CScript::const_iterator pend = script.end();
CScript::const_iterator pbegincodehash = script.begin();
vector<bool> vfExec;
vector<valtype> stack;
vector<valtype> altstack;
if (pvStackRet)
pvStackRet->clear();
while (pc < pend)
{
bool fExec = !count(vfExec.begin(), vfExec.end(), false);
...
}
if (pvStackRet)
*pvStackRet = stack;
return (stack.empty() ? false : CastToBool(stack.back()));
}
EvalScript()帶有3個(gè)參數(shù)嗅义,分別為:
- 第一個(gè)參數(shù)為txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey屏歹。它有可能是:
驗(yàn)證情形A:<你的簽名_vchSig> <你的公鑰_vchPubKey> OP_CODESEPARATOR OP_DUP OP_HASH160 <你的地址160位哈希> OP_EQUALVERIFY OP_CHECKSIG,即簽名A + OP_CODESEPARATOR + 腳本A之碗。
驗(yàn)證情形B:<你的簽名_vchSig> OP_CODESEPARATOR <你的公鑰_vchPubKey> OP_CHECKSIG蝙眶,即簽名B + OP_CODESEPARATOR + 腳本B。- 第二個(gè)參數(shù)為新創(chuàng)建的交易txTo褪那,即CreateTransaction()中的wtxNew械馆。
- 第三個(gè)參數(shù)為nIn胖眷,即將被驗(yàn)證的交易在txTo輸入交易列表中的位置。
該函數(shù)將根據(jù)你輸入的腳本霹崎,依次取出腳本中的操作代碼進(jìn)行相應(yīng)操作珊搀,并對最后的模擬執(zhí)行結(jié)果做判斷,返回執(zhí)行結(jié)果尾菇。如果結(jié)果為true境析,則完成了SignSignature()
,此時(shí)便生成了一筆新的交易派诬。
回到SendMoney()
生成了一筆新的交易后劳淆,利用函數(shù)CommitTransactionSpent(wtxNet)
嘗試將這筆交易提交至數(shù)據(jù)庫,之后判斷交易是否提交成功默赂,如果該筆交易提交成功wtxNew.AcceptTransaction()=true
沛鸵,將這筆交易廣播至其他peer節(jié)點(diǎn)wtxNew.RelayWalletTransaction()
。
當(dāng)?shù)V工收到這筆交易的廣播之后會(huì)對交易進(jìn)行相應(yīng)操作缆八,之后的章節(jié)我們將對新區(qū)塊的處理部分做詳細(xì)分析曲掰。
總結(jié)
比特幣生成一筆新的交易大致分為如下幾個(gè)階段:
- 根據(jù)轉(zhuǎn)賬金額以及交易費(fèi)用從UTXO中尋找一組滿足條件的輸入。
- 對于這組輸入以及轉(zhuǎn)賬地址構(gòu)造一個(gè)新的交易:輸入分別對應(yīng)UTXO中的不同輸出奈辰,第一個(gè)輸出指向轉(zhuǎn)賬地址栏妖,如果有找零,則計(jì)算找零金額奖恰,將其放入第二個(gè)輸出吊趾,同時(shí)指向自身的錢包地址。
- 對構(gòu)造好的輸入輸出分別進(jìn)行簽名瑟啃。
- 利用Solver函數(shù)對簽名完的交易做模擬運(yùn)行论泛,如果運(yùn)行通過則生成完畢。
- 將交易提交至數(shù)據(jù)庫并廣播到其他節(jié)點(diǎn)蛹屿。