比特币探究之交易签名

作者: 魏兆华 | 来源:发表于2018-08-10 00:14 被阅读56次

    比特币探究之交易创建这篇文章里,可以看到在交易创建的最后,需要进行一个交易签名操作。它其实就是交易发起方要提供一个证据,证明自己可以花费这项交易的每一笔输入,也就是说提供的签名scriptSig,能够跟prevOut的scriptPubKey运算,只要最终返回结果是TRUE,就能证明交易的合法性。比特币网络上的其他节点,也是通过这样一个过程来进行交易验证,确认OK之后,交易才会放入交易池,等待打包。

    比特币交易创建函数CreateTransaction的最后,调用了如下代码来对交易进行签名:

    const CScript& scriptPubKey = coin.txout.scriptPubKey;
    SignatureData sigdata;
    if (!ProduceSignature(*this, 
                          MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL),
                          scriptPubKey, sigdata)) 
    {
        strFailReason = _("Signing transaction failed");
        return false;
    } else {
        UpdateInput(txNew.vin.at(nIn), sigdata);
    }
    
    这里的ProduceSignature函数,内部流程也比较复杂。当然它的复杂,来源于签名机制的复杂。为了帮助理解,我画了一个简要流程图。 交易签名流程图

    先从末端起,自底向上,理解下面几个函数。

    首先是MutableTransactionSignatureCreator.CreateSig函数,顾名思义就是对CMutableTransaction进行签名。它定义在src/script/sign.cpp中。源码如下:

    bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, 
        std::vector<unsigned char>& vchSig, const CKeyID& address, 
        const CScript& scriptCode, SigVersion sigversion) const
    {
        CKey key;
        if (!provider.GetKey(address, key))
            return false;
        //见证脚本必须是压缩版
        if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed())
            return false;
        //生成交易哈希,用于签名
        uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
        if (!key.Sign(hash, vchSig))  //使用ECDSA椭圆曲线加密算法进行签名
            return false;
        vchSig.push_back((unsigned char)nHashType);
        return true;
    }
    

    CKey.Sign函数逻辑很简单,这里就不贴了。SignatureHash函数是根据交易信息生成哈希值,主要源码如下(涉及隔离见证部分可参见比特币探究之隔离见证那篇文章):

    template <class T>
    uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, 
        const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
    {
        if (sigversion == SigVersion::WITNESS_V0) {  //如果是隔离见证,根据BIP-143的规定简化签名内容
            uint256 hashPrevouts, hashSequence, hashOutputs;
            const bool cacheready = cache && cache->ready;
            //非任何人可付,为所有PrevOut的哈希,否则全0
            if (!(nHashType & SIGHASH_ANYONECANPAY)) {
                hashPrevouts = cacheready ? cache->hashPrevouts : GetPrevoutHash(txTo);
            }
            //非任何人可付,不是SINGLE或NONE,为所有序列号哈希,否则全0
            if (!(nHashType & SIGHASH_ANYONECANPAY) && (nHashType & 0x1f) != SIGHASH_SINGLE 
                                                    && (nHashType & 0x1f) != SIGHASH_NONE) {
                hashSequence = cacheready ? cache->hashSequence : GetSequenceHash(txTo);
            }
            if ((nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) {
                //非SINGLE和NONE,为所有输出的哈希
                hashOutputs = cacheready ? cache->hashOutputs : GetOutputsHash(txTo);
            } else if ((nHashType & 0x1f) == SIGHASH_SINGLE && nIn < txTo.vout.size()) {
                //如果是SINGLE,为同一序列号输出的哈希。其他情况全0
                CHashWriter ss(SER_GETHASH, 0);
                ss << txTo.vout[nIn];
                hashOutputs = ss.GetHash();
            }
    
            CHashWriter ss(SER_GETHASH, 0);
            ss << txTo.nVersion;
            ss << hashPrevouts;
            ss << hashSequence;
            ss << txTo.vin[nIn].prevout;
            ss << scriptCode;
            ss << amount;
            ss << txTo.vin[nIn].nSequence;
            ss << hashOutputs;
            ss << txTo.nLockTime;
            ss << nHashType;
    
            return ss.GetHash();
        }
    
        //如果不是隔离见证,调用Serializer输出哈希,根据所有的输入输出计算得出,其复杂度高于隔离见证版
        CTransactionSignatureSerializer<T> txTmp(txTo, scriptCode, nIn, nHashType);
        CHashWriter ss(SER_GETHASH, 0);
        ss << txTmp << nHashType;
        return ss.GetHash();
    }
    

    接着看Solver函数。它根据传入的scriptPubKey,判断交易输出类型(P2PKH、P2SH、P2WPKH或P2WSH),并返回相应的数据(参见交易签名流程图)。该函数定义在src/script/standard.cpp中。源码如下:

    bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, 
                std::vector<std::vector<unsigned char> >& vSolutionsRet)
    {
        vSolutionsRet.clear();
    
        //P2SH类型,格式为 OP_HASH160 20 [20 byte hash] OP_EQUAL
        if (scriptPubKey.IsPayToScriptHash())
        {
            typeRet = TX_SCRIPTHASH;
            std::vector<unsigned char> hashBytes(scriptPubKey.begin()+2, scriptPubKey.begin()+22);
            //返回20字节的Redeem Script(赎回脚本)的Hash
            vSolutionsRet.push_back(hashBytes);
            return true;
        }
    
        int witnessversion;
        std::vector<unsigned char> witnessprogram;
        //如果采用了隔离见证,那应该是[见证版本] [见证程序]的格式
        if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
            //长度20,说明是P2WPKH
            if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_KEYHASH_SIZE) {
                typeRet = TX_WITNESS_V0_KEYHASH;
                vSolutionsRet.push_back(witnessprogram);    //返回20字节PubKey Hash
                return true;
            }
            //长度32,说明是P2WSH
            if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
                typeRet = TX_WITNESS_V0_SCRIPTHASH;
                vSolutionsRet.push_back(witnessprogram); //返回见证脚本
                return true;
            }
            if (witnessversion != 0) {  //向前兼容,当前隔离见证版本号只有0
                typeRet = TX_WITNESS_UNKNOWN;
                vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
                vSolutionsRet.push_back(std::move(witnessprogram));
                return true;
            }
            typeRet = TX_NONSTANDARD;  //其他情况就是非标准交易了
            return false;
        }
    
        //用OP_RETURN带的一堆直推数据
        if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN 
                                     && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) {
            typeRet = TX_NULL_DATA;
            return true;
        }
    
        std::vector<unsigned char> data;
        //65/33 [65/33字节公钥] OP_CHECKSIG,33为压缩版
        if (MatchPayToPubkey(scriptPubKey, data)) {
            typeRet = TX_PUBKEY;
            vSolutionsRet.push_back(std::move(data));
            return true;
        }
    
        //OP_DUP OP_HASH160 20 [20字节公钥哈希] OP_EQUALVERIFY OP_CHECKSIG
        if (MatchPayToPubkeyHash(scriptPubKey, data)) {
            typeRet = TX_PUBKEYHASH;
            vSolutionsRet.push_back(std::move(data));
            return true;
        }
    
        unsigned int required;
        std::vector<std::vector<unsigned char>> keys;
        //多重签名:<required> <A pubkey> [B pubkey] [C pubkey...] <keys.size()> OP_CHECKMULTISIG
        if (MatchMultisig(scriptPubKey, required, keys)) {
            typeRet = TX_MULTISIG;
            vSolutionsRet.push_back({static_cast<unsigned char>(required)});
            vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end());
            vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())});
            return true;
        }
    
        vSolutionsRet.clear();
        typeRet = TX_NONSTANDARD;  //以上都不是,那就是非标准交易了
        return false;
    }
    

    Solver函数是被SignStep函数调用的。函数名意思是分步签名,如果是P2SH,或者是隔离见证的P2WPHK/P2WSH,那么SignStep函数会调用两次,第一次返回赎回脚本或见证脚本,第二次才能签名。它定义在src/script/sigh.cpp里。源码如下:

    static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator& creator, 
                         const CScript& scriptPubKey, std::vector<valtype>& ret, txnouttype& whichTypeRet, 
                         SigVersion sigversion, SignatureData& sigdata)
    {
        CScript scriptRet;
        uint160 h160;
        ret.clear();
        std::vector<unsigned char> sig;
    
        std::vector<valtype> vSolutions;
        if (!Solver(scriptPubKey, whichTypeRet, vSolutions))    //判断scriptPubKey类型
            return false;
    
        switch (whichTypeRet)
        {
        case TX_NONSTANDARD:
        case TX_NULL_DATA:
        case TX_WITNESS_UNKNOWN:
            return false;
        case TX_PUBKEY:  //公钥,调用CreateSig生成签名
            if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]).GetID(), scriptPubKey, sigversion)) 
                return false;
            ret.push_back(std::move(sig));
            return true;
        case TX_PUBKEYHASH: {  //公钥哈希,调用CreateSig生成签名,连同公钥一并返回
            CKeyID keyID = CKeyID(uint160(vSolutions[0]));
            if (!CreateSig(creator, sigdata, provider, sig, keyID, scriptPubKey, sigversion)) return false;
            ret.push_back(std::move(sig));
            CPubKey pubkey;
            GetPubKey(provider, sigdata, keyID, pubkey);
            ret.push_back(ToByteVector(pubkey));
            return true;
        }
        case TX_SCRIPTHASH:  //脚本哈希,取出赎回脚本redeem script
            if (GetCScript(provider, sigdata, uint160(vSolutions[0]), scriptRet)) {
                ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
                return true;
            }
            return false;
        case TX_MULTISIG: {  //多重签名
            size_t required = vSolutions.front()[0];
            ret.push_back(valtype());
            for (size_t i = 1; i < vSolutions.size() - 1; ++i) {
                CPubKey pubkey = CPubKey(vSolutions[i]);
                if (ret.size() < required + 1 && CreateSig(creator, sigdata, provider, sig, 
                                                           pubkey.GetID(), scriptPubKey, sigversion)) {
                    ret.push_back(std::move(sig));
                }
            }
            bool ok = ret.size() == required + 1; //签名数量够不够?
            for (size_t i = 0; i + ret.size() < required + 1; ++i) {
                ret.push_back(valtype());
            }
            return ok;
        }
        case TX_WITNESS_V0_KEYHASH:  //P2WPKH,直接返回20字节Key Hash
            ret.push_back(vSolutions[0]);
            return true;
        case TX_WITNESS_V0_SCRIPTHASH:  //P2WSH,返回见证脚本(m keys n)
            CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160.begin());
            if (GetCScript(provider, sigdata, h160, scriptRet)) {
                ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
                return true;
            }
            return false;
        default:
            return false;
        }
    }
    

    现在可以看ProduceSignature函数了,它的源代码如下:

    bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, 
                          const CScript& fromPubKey, SignatureData& sigdata)
    {
        if (sigdata.complete) return true;
    
        std::vector<valtype> result;
        txnouttype whichType;
        bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata);
        bool P2SH = false;
        CScript subscript;
        sigdata.scriptWitness.stack.clear();
    
        if (solved && whichType == TX_SCRIPTHASH)
        {   //P2SH,对子脚本(赎回脚本)二次签名
            subscript = CScript(result[0].begin(), result[0].end());
            sigdata.redeem_script = subscript;
            solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE, sigdata)
                            && whichType != TX_SCRIPTHASH;
            P2SH = true;
        }
    
        if (solved && whichType == TX_WITNESS_V0_KEYHASH)
        {   //P2WPKH,先组建P2PKH,二次签名
            CScript witnessscript;
            witnessscript << OP_DUP << OP_HASH160 << ToByteVector(result[0]) << OP_EQUALVERIFY << OP_CHECKSIG;
            txnouttype subType;
            solved = solved && SignStep(provider, creator, witnessscript, result, subType,
                                        SigVersion::WITNESS_V0, sigdata);
            sigdata.scriptWitness.stack = result;
            sigdata.witness = true;
            result.clear();
        }
        else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH)
        {   //P2WSH,m keys n再调SignStep完成多重签名
            CScript witnessscript(result[0].begin(), result[0].end());
            sigdata.witness_script = witnessscript;
            txnouttype subType;
            solved = solved 
                     && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata) 
                     && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH 
                     && subType != TX_WITNESS_V0_KEYHASH;
            result.push_back(std::vector<unsigned char>(witnessscript.begin(), witnessscript.end()));
            sigdata.scriptWitness.stack = result;
            sigdata.witness = true;
            result.clear();
        } else if (solved && whichType == TX_WITNESS_UNKNOWN) {
            sigdata.witness = true;
        }
    
        if (P2SH) {  //子脚本加上去
            result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end()));
        }
        sigdata.scriptSig = PushAll(result);  //填入scriptSig,注意如果是隔离见证,此前已经clear
    
        //最后还要验证一下
        sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, 
                                                  STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
        return sigdata.complete;
    }
    

    关于交易验证,还是待下次开发间隙再去探究吧。


    你的支持,我的动力!

    相关文章

      网友评论

        本文标题:比特币探究之交易签名

        本文链接:https://www.haomeiwen.com/subject/nytimftx.html