1. 程式人生 > >比特幣原始碼情景分析之script指令碼驗證(2)

比特幣原始碼情景分析之script指令碼驗證(2)

    通過上一篇的分析,我們應該已經對script有了一定的理解,這章節我們以原始碼分析的方式來了解下指令碼驗證執行流程    bitcoin節點在處理一條交易時就需要驗證交易的txin,由於一條交易可能包含多個txin,因而需要執行多個指令碼驗證,自然需要並行化,因而系統允許定義多個指令碼執行執行緒以加速驗證過程。有了這個思考,我們從指令碼執行執行緒出發剝繭抽絲掀開指令碼執行的面紗。    先看指令碼執行執行緒的初始化,這個在init.cpp的AppInitMain裡指令碼驗證執行執行緒    LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);    if (nScriptCheckThreads) {         //根據引數建立對應數量的指令碼執行執行緒        for (int i=0; i<nScriptCheckThreads-1; i++)            threadGroup.create_thread(&ThreadScriptCheck);
    }執行緒的具體實現函式在 validataion.cppstatic CCheckQueue<CScriptCheck> scriptcheckqueue(128);void ThreadScriptCheck() {    RenameThread("bitcoin-scriptch");    scriptcheckqueue.Thread();}class CCheckQueue{  //! Worker thread  void Thread()    //CCheckQueue.Thread()    {        Loop();    }  bool Loop(bool fMaster = false)
    {        boost::condition_variable& cond = fMaster ? condMaster : condWorker;        std::vector<T> vChecks;        vChecks.reserve(nBatchSize);        unsigned int nNow = 0;        bool fOk = true;        do {            {               ……………..                // Decide how many work units to process now.
                // * Do not try to do everything at once, but aim for increasingly smaller batches so                //   all workers finish approximately simultaneously.                // * Try to account for idle jobs which will instantly start helping.                // * Don't do batches smaller than 1 (duh), or larger than nBatchSize.                nNow = std::max(1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1)));                vChecks.resize(nNow);                for (unsigned int i = 0; i < nNow; i++) {                    // We want the lock on the mutex to be as short as possible, so swap jobs from the global                    // queue to the local batch vector instead of copying.                    // 該執行緒選取一定量的指令碼待執行物件                    vChecks[i].swap(queue.back());                    queue.pop_back();                }                // Check whether we need to do work at all                fOk = fAllOk;            }            // execute work            for (T& check : vChecks)                if (fOk)                    //check()函式就是CScriptCheck::operator()()                     fOk = check();            vChecks.clear();        } while (true);    }新增指令碼驗證物件  //發現新塊並處理交易時會驗證指令碼bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex,                  CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck){    AssertLockHeld(cs_main);CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : nullptr);            std::vector<CScriptCheck> vChecks;            bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */            if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr))                return error("ConnectBlock(): CheckInputs on %s failed with %s",                    tx.GetHash().ToString(), FormatStateMessage(state));            //control負責將某一個具體的驗證執行物件新增到佇列中            control.Add(vChecks);}class CCheckQueueControl{    void Add(std::vector<T>& vChecks)    {        if (pqueue != nullptr)            pqueue->Add(vChecks);    }}指令碼驗證執行函式上面可知指令碼驗證執行緒最終會執行CScriptCheck::operatorbool CScriptCheck::operator()() {    const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;    const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;    return VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *txdata), &error);}P2SH指令碼驗證原始碼分析為了更完整的分析該過程,我以最複雜的指令碼模板P2SH為例分析,從上一章接的介紹我們知道,P2SH的指令碼如下:<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>OP_HASH160 8ac1d7a2fa204a16dc984fa81cfdf86a2a4e1731 OP_EQUAL這裡的scriptSig是 <Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>, scriptPubKey是OP_HASH160 8ac1d7a2fa204a16dc984fa81cfdf86a2a4e1731 OP_EQUALbool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror){    std::vector<std::vector<unsigned char> > stack, stackCopy;//執行解鎖指令碼,執行完後stack中有了<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>    //這三個資料物件    if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror))        // serror is set        return false;    //解鎖指令碼執行後    if (flags & SCRIPT_VERIFY_P2SH)        //由於下面EvalScript會破壞stack,而再後面仍然需要stack當前的資料,因而需要做一次拷貝        stackCopy = stack;    //執行鎖定指令碼 OP_HASH160 8ac1d7a2fa204a16dc984fa81cfdf86a2a4e1731 OP_EQUAL    //就是將<2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>資料做hash160,然後和鎖定指令碼中的hash比較    if (!EvalScript(stack, scriptPubKey, flags, checker, SigVersion::BASE, serror))        // serror is set        return false;    //這一步結束後,hash160被驗證通過了,此時棧只剩下<Sig1> <Sig2>    // Additional validation for spend-to-script-hash transactions:    //上一章節我們提到過,P2SH需要執行兩次指令碼驗證,即還有子鎖定指令碼,下面就是在做這個事    if ((flags & SCRIPT_VERIFY_P2SH) && scriptPubKey.IsPayToScriptHash())    {        // scriptSig must be literals-only or validation fails        if (!scriptSig.IsPushOnly())            return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY);        // Restore stack.// hash160驗證後,堆疊只剩下subscript.scriptSig了,我們換需要subscript.scriptPubKey// 恢復前面儲存下來的棧可以做到這一點,恢復後的棧結構. subscript.scriptSig + subscript.scriptPubKey_(_代表被序列化了,文字化了)       //即<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>        swap(stack, stackCopy);        // stack cannot be empty here, because if it was the        // P2SH  HASH <> EQUAL  scriptPubKey would be evaluated with        // an empty stack and the EvalScript above would return false.        assert(!stack.empty());        //從棧頂拿出subscript.scriptPubKey的指令碼文字,並構建CScript物件const valtype& pubKeySerialized = stack.back();        CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end());        //到這裡後,已經恢復出完整多簽名子指令碼了<Sig1> <Sig2> 2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG        //將subscript.scriptPubKey_文字彈出        //這一步相當於執行evalScript(subscript.scriptSig)        popstack(stack);        //然後evalScript(subscript.scriptPubKey)        //驗證給的pubkey是否能夠被解鎖        if (!EvalScript(stack, pubKey2, flags, checker, SigVersion::BASE, serror))            // serror is set            return false;    }    return set_success(serror);}bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror){    CScript::const_iterator pc = script.begin();    CScript::const_iterator pend = script.end();    CScript::const_iterator pbegincodehash = script.begin();    opcodetype opcode;    valtype vchPushValue;    std::vector<bool> vfExec;    std::vector<valtype> altstack;    try    {        while (pc < pend)        {            bool fExec = !count(vfExec.begin(), vfExec.end(), false);            //指令碼執行就是不停GetOp然後case處理的過程            if (!script.GetOp(pc, opcode, vchPushValue))                return set_error(serror, SCRIPT_ERR_BAD_OPCODE);            // Note how OP_RESERVED does not count towards the opcode limit.            if (opcode > OP_16 && ++nOpCount > MAX_OPS_PER_SCRIPT)                return set_error(serror, SCRIPT_ERR_OP_COUNT);            if (opcode == OP_CAT ||                opcode == OP_SUBSTR ||                ......                opcode == OP_RSHIFT)                return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE); // Disabled opcodes.            if (fExec && 0 <= opcode && opcode <= OP_PUSHDATA4) {                if (fRequireMinimal && !CheckMinimalPush(vchPushValue, opcode)) {                    return set_error(serror, SCRIPT_ERR_MINIMALDATA);                }                stack.push_back(vchPushValue);            } else if (fExec || (OP_IF <= opcode && opcode <= OP_ENDIF))            switch (opcode)            {                //                // Push value                //  case OP_1NEGATE:                case OP_1:                case OP_2:                .....                case OP_15:                case OP_16:                {                    // ( -- value)                    CScriptNum bn((int)opcode - (int)(OP_1 - 1));                    stack.push_back(bn.getvch());                    // The result of these opcodes should always be the minimal way to push the data                    // they push, so no need for a CheckMinimalPush here.                }                break;                …..    }    catch (...)    {        return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);    }}從上可知,目前實現的執行多次指令碼驗證不是一個通用方案,而是通過檢測指令碼型別(P2SH)而做的特殊處理,只支援P2SH這個型別的指令碼型別P2SH鎖定指令碼scriptSig生成下面來看看p2sh的scriptSig怎麼生成的bool ProduceSignature(const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata){    std::vector<valtype> result;    txnouttype whichType;    //這裡會檢測出fromPubKey是P2SH指令碼,然後就會返回對應的Redeemscript給result當做scriptSig    bool solved = SignStep(creator, fromPubKey, result, whichType, SigVersion::BASE);    bool P2SH = false;    CScript subscript;    sigdata.scriptWitness.stack.clear();    //第一次SignStep執行後whichType == TX_SCRIPTHASH    //得到的result為Redeemscript的序列化文字    if (solved && whichType == TX_SCRIPTHASH)    {        // Solver returns the subscript that needs to be evaluated;        // the final scriptSig is the signatures from that        // and then the serialized subscript:        //這裡將Redeemscript文字反序列化為多簽名鎖定指令碼        //2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG        subscript = CScript(result[0].begin(), result[0].end());//第二次SignStep會將作為pubKey的subscript解釋為multsig指令碼,然後會返回<Sig1><Sig2>資料到result當做scriptSig        solved = solved && SignStep(creator, subscript, result, whichType, SigVersion::BASE) && whichType != TX_SCRIPTHASH;        P2SH = true;    }    //上面result裡不是已經有subscript了啊,為啥要再次push_back呢    //因為SignStep函式每次都會清空result資料,所以需要再次push_back subscript資料    if (P2SH) {        result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end()));    }    //將真正的scriptSig返回    sigdata.scriptSig = PushAll(result);}static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptPubKey,                     std::vector<valtype>& ret, txnouttype& whichTypeRet, SigVersion sigversion){    CScript scriptRet;    CScript scriptRet;    uint160 h160;    ret.clear();    std::vector<valtype> vSolutions;    if (!Solver(scriptPubKey, whichTypeRet, vSolutions))        return false;    CKeyID keyID;    switch (whichTypeRet)    {    case TX_SCRIPTHASH:        //這裡會返回Redeemscript作為scriptSig解鎖指令碼        if (creator.Provider().GetCScript(uint160(vSolutions[0]), scriptRet)) {            ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));            return true;        }        return false;    }}bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const{    LOCK(cs_KeyStore);    ScriptMap::const_iterator mi = mapScripts.find(hash);    if (mi != mapScripts.end())    {        redeemScriptOut = (*mi).second;        return true;    }    return false;}其實,還有一個疑問點,就是怎麼根據P2SH的鎖定指令碼資料找到解鎖指令碼Redeemscript指令碼的呢?P2SH的鎖定指令碼資料有用的資料只有hash值,也即是如何通過hash找到Redeemscript指令碼的呢?仔細看的話,應該能看出一些端倪,就是mapScripts維護了一個hash和具體Redeemscript型別的scriptSig指令碼的map關係.那你可能會說,整個鏈上這麼多P2SH交易,hash就會很多,那這個mapScripts得多大啊。由於P2SH這個hash是<2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>生成的,所以只有屬於本地節點多賬號簽名生成的P2SH才需要新增到mapScripts.且肯定是先有多簽名賬號才有P2SH交易,因此只需要在建立多賬號簽名的點建立對應的Redeemscript並儲存在mapScripts即可,事實上確實如此,流程如下。UniValue addmultisigaddress(const JSONRPCRequest& request)    // Construct using pay-to-script-hash:    CScript inner = CreateMultisigRedeemscript(required, pubkeys);    pwallet->AddCScript(inner);}// Creates a multisig redeemscript from a given list of public keys and number required.CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys){    CScript result = GetScriptForMultisig(required, pubkeys);    return result;}CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys){    CScript script;    script << CScript::EncodeOP_N(nRequired);    for (const CPubKey& key : keys)        script << ToByteVector(key);    script << CScript::EncodeOP_N(keys.size()) << OP_CHECKMULTISIG;    return script;}bool CBasicKeyStore::AddCScript(const CScript& redeemScript){    if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE)        return error("CBasicKeyStore::AddCScript(): redeemScripts > %i bytes are invalid", MAX_SCRIPT_ELEMENT_SIZE);    LOCK(cs_KeyStore);    mapScripts[CScriptID(redeemScript)] = redeemScript;    return true;}Solver函式
    上面在分析P2SH的scriptSig就提到過Resolver,那Solver函式究竟做啥用的,它是用來解釋scriptPubKey的,比如解釋出scriptPubKey是什麼型別的指令碼,比如分析出判斷一個scriptPubKey是否是P2SH,檢驗資料的合法性並取出bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet){    // Templates    static std::multimap<txnouttype, CScript> mTemplates;    if (mTemplates.empty())    {        // Standard tx, sender provides pubkey, receiver adds signature mTemplates.insert(std::make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG));        // Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey        mTemplates.insert(std::make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));        // Sender provides N pubkeys, receivers provides M signatures        mTemplates.insert(std::make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG));    }    vSolutionsRet.clear();    //解釋scriptPubkey的過程就是將scriptPubkey和模板對比,同時取出裡面的資料比如    // Scan templates    const CScript& script1 = scriptPubKey;    for (const std::pair<txnouttype, CScript>& tplate : mTemplates)    {        const CScript& script2 = tplate.second;        vSolutionsRet.clear();        opcodetype opcode1, opcode2;        std::vector<unsigned char> vch1, vch2;        // Compare        CScript::const_iterator pc1 = script1.begin();        CScript::const_iterator pc2 = script2.begin();        while (true)        {            if (pc1 == script1.end() && pc2 == script2.end())            {                // Found a match                typeRet = tplate.first;                if (typeRet == TX_MULTISIG)                {                    // Additional checks for TX_MULTISIG:                    unsigned char m = vSolutionsRet.front()[0];                    unsigned char n = vSolutionsRet.back()[0];                    if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n)                        return false;                }                return true;            }            if (!script1.GetOp(pc1, opcode1, vch1))                break;            if (!script2.GetOp(pc2, opcode2, vch2))                break;            // Template matching opcodes:            if (opcode2 == OP_PUBKEYS)            {                while (vch1.size() >= 33 && vch1.size() <= 65)                {                    vSolutionsRet.push_back(vch1);                    if (!script1.GetOp(pc1, opcode1, vch1))                        break;                }                if (!script2.GetOp(pc2, opcode2, vch2))                    break;                // Normal situation is to fall through                // to other if/else statements            }            if (opcode2 == OP_PUBKEY)            {                if (vch1.size() < 33 || vch1.size() > 65)                    break;                vSolutionsRet.push_back(vch1);            }            else if (opcode2 == OP_PUBKEYHASH)            {                if (vch1.size() != sizeof(uint160))                    break;                vSolutionsRet.push_back(vch1);            }            else if (opcode1 != opcode2 || vch1 != vch2)            {                // Others must match exactly                break;            }        }    }    vSolutionsRet.clear();    typeRet = TX_NONSTANDARD;    return false;}附錄:指令碼指令解釋過程分析 上面在分析道evalScript執行函式時提到指令碼執行就是不停GetOp然後case處理的過程,下面就來分析下GetOp    bool GetOp(const_iterator& pc, opcodetype& opcodeRet) const    {        return GetOp2(pc, opcodeRet, nullptr);    }    bool GetOp2(const_iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet) const    {        opcodeRet = OP_INVALIDOPCODE;        if (pvchRet)            pvchRet->clear();        if (pc >= end())            return false;        // Read instruction        if (end() - pc < 1)            return false;        //第一位元組為指令        unsigned int opcode = *pc++;        // Immediate operand        if (opcode <= OP_PUSHDATA4)        {            unsigned int nSize = 0;            //[0x4] sig, 這類指令, opcode就是size            if (opcode < OP_PUSHDATA1)            {                nSize = opcode;            }            else if (opcode == OP_PUSHDATA1)            {                if (end() - pc < 1)                    return false;                nSize = *pc++;
            }            else if (opcode == OP_PUSHDATA2)            {                if (end() - pc < 2)                    return false;                nSize = ReadLE16(&pc[0]);                pc += 2;            }            else if (opcode == OP_PUSHDATA4)            {                if (end() - pc < 4)                    return false;                //進一步讀取資料size數值, OP_PUSHDATA4 [xx][xx][xx][xx] <largedata>                nSize = ReadLE32(&pc[0]);                pc += 4;            }            if (end() - pc < 0 || (unsigned int)(end() - pc) < nSize)                return false;            if (pvchRet)                //讀取真正的資料內容                pvchRet->assign(pc, pc + nSize);            pc += nSize;        }        opcodeRet = static_cast<opcodetype>(opcode);        return true;    }其他指令碼的scriptSig生成static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptPubKey,                     std::vector<valtype>& ret, txnouttype& whichTypeRet, SigVersion sigversion){    CScript scriptRet;    uint160 h160;    ret.clear();    std::vector<valtype> vSolutions;    if (!Solver(scriptPubKey, whichTypeRet, vSolutions))        return false;    CKeyID keyID;    switch (whichTypeRet)    {    case TX_NONSTANDARD:    case TX_NULL_DATA:    case TX_WITNESS_UNKNOWN:        return false;    case TX_PUBKEY:        //P2PK模板keyID = CPubKey(vSolutions[0]).GetID();        return Sign1(keyID, creator, scriptPubKey, ret, sigversion);    case TX_PUBKEYHASH://P2PKH        keyID = CKeyID(uint160(vSolutions[0]));        if (!Sign1(keyID, creator, scriptPubKey, ret, sigversion))            return false;        else        {            CPubKey vch;            creator.Provider().GetPubKey(keyID, vch);            ret.push_back(ToByteVector(vch));        }        return true;    case TX_SCRIPTHASH:        //P2SH        if (creator.Provider().GetCScript(uint160(vSolutions[0]), scriptRet)) {            ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));            return true;        }        return false;    case TX_MULTISIG:        //MS        ret.push_back(valtype()); // workaround CHECKMULTISIG bug        return (SignN(vSolutions, creator, scriptPubKey, ret, sigversion));    default:        return false;    }/********************************* 本文來自CSDN博主"愛踢門"******************************************/