比特幣交易源代碼分析

關(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)蛹屿。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屁奏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜡峰,更是在濱河造成了極大的恐慌了袁,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湿颅,死亡現(xiàn)場離奇詭異载绿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)油航,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門崭庸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事怕享≈瓷模” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵函筋,是天一觀的道長沙合。 經(jīng)常有香客問我,道長跌帐,這世上最難降的妖魔是什么首懈? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮谨敛,結(jié)果婚禮上究履,老公的妹妹穿的比我還像新娘。我一直安慰自己脸狸,他們只是感情好最仑,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炊甲,像睡著了一般泥彤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜜葱,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天全景,我揣著相機(jī)與錄音耀石,去河邊找鬼牵囤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滞伟,可吹牛的內(nèi)容都是我干的揭鳞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼梆奈,長吁一口氣:“原來是場噩夢啊……” “哼野崇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起亩钟,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤乓梨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后清酥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扶镀,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年焰轻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了臭觉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝠筑,靈堂內(nèi)的尸體忽然破棺而出狞膘,到底是詐尸還是另有隱情,我是刑警寧澤什乙,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布挽封,位于F島的核電站,受9級特大地震影響臣镣,放射性物質(zhì)發(fā)生泄漏场仲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一退疫、第九天 我趴在偏房一處隱蔽的房頂上張望渠缕。 院中可真熱鬧,春花似錦褒繁、人聲如沸亦鳞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燕差。三九已至,卻和暖如春坝冕,著一層夾襖步出監(jiān)牢的瞬間徒探,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工喂窟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留测暗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓磨澡,卻偏偏與公主長得像碗啄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子稳摄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容