比特币源码--交易的产生(四)--脚本和签名

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37847176/article/details/82866757

交易的产生(一)–生成地址
交易的产生(二)–创建交易
交易的产生(三)–提交交易
交易的产生(四)–脚本和签名



在sendmany里为每个接受者地址构建脚本是

CScript scriptPubKey = GetScriptForDestination(address.Get());

1.address

这里的address是CBitcoinAddress address(name_);,调用了CTxDestination CBitcoinAddress::Get()
在这里插入图片描述

typedef boost::variant<CNoDestination, CKeyID, CScriptID> CTxDestination;

介绍一下variant,variant相当于增强型的union,也就是CTxDestination可以是CNoDestination, CKeyID, CScriptID中的任意一个类型
vchVersion是CBitcoinAddress的父类CBase58Data的保护变量

CTxDestination CBitcoinAddress::Get() const
{
    if (!IsValid())
        return CNoDestination();
    uint160 id;
    memcpy(&id, &vchData[0], 20);
    if (vchVersion == Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS))
        return CKeyID(id);
    else if (vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS))
        return CScriptID(id);
    else
        return CNoDestination();
}

这段代码就是做逻辑判断,如果是公钥地址则调用CKeyID(id),如果是脚本地址则调用CScriptID(id)。
其中的CChainParams::PUBKEY_ADDRESS参数是

class CChainParams
{
public:
    enum Base58Type {
        PUBKEY_ADDRESS,
        SCRIPT_ADDRESS,
        SECRET_KEY,
        EXT_PUBLIC_KEY,
        EXT_SECRET_KEY,

        MAX_BASE58_TYPES
    };

这个CChainParams下还有三个子类,分别对应不同网络的可调整参数,我们先来看测试网的,其中一段

base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);
base58Prefixes[SECRET_KEY] =     std::vector<unsigned char>(1,239);
base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container<std::vector<unsigned char> >();
base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container<std::vector<unsigned char> >();

接下来看CKeyID

CKeyID(const uint160& in) : uint160(in) {}

就是对输入做了hash160的处理,那么来看CScriptID

CScriptID(const uint160& in) : uint160(in) {}

也是一样的处理


2.脚本构建

2.1GetScriptForDestination

接着来看GetScriptForDestination函数

CScript GetScriptForDestination(const CTxDestination& dest)
{
    CScript script;

    boost::apply_visitor(CScriptVisitor(&script), dest);
    return script;
}

因为CTxDestination是boost::variant类型的,根据CScriptVisitor的内部结构来看是用了访问者模式
CScriptVisitor继承类boost::static_visitor,在类里面需要重载()操作符,通过boost::apply_visitor来访问原始类型的值

namespace
{
class CScriptVisitor : public boost::static_visitor<bool>
{
private:
    CScript *script;
public:
    CScriptVisitor(CScript *scriptin) { script = scriptin; }

    bool operator()(const CNoDestination &dest) const {
        script->clear();
        return false;
    }
//这里是比特币地址的处理,script用于保存组装好的脚本
    bool operator()(const CKeyID &keyID) const {
        script->clear();
        *script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
        return true;
    }
//这里是支付到脚本地址的处理
    bool operator()(const CScriptID &scriptID) const {
        script->clear();
        *script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
        return true;
    }
};
}

回顾一下锁定脚本,给出的例子就是
1)P2PKH: OP_DUP OP_HSAH160 OP_EQUALVERIFY OP_CHECKSIG
2)P2SH: OP_HASH160 <20-byte hash of redeem script> OP_EQUAL
和上面的代码是符合的,另外脚本操作都定义在script.h中
在standard.cpp目录下还有多重签名脚本和隔离见证的脚本构造也一并介绍了

2.2GetScriptForMultisig

CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
{
    CScript script;

    script << CScript::EncodeOP_N(nRequired);
    BOOST_FOREACH(const CPubKey& key, keys)
        script << ToByteVector(key);
    script << CScript::EncodeOP_N(keys.size()) << OP_CHECKMULTISIG;
    return script;
}

多重签名的锁定脚本例子
M <Public Key 1> <Public Key 2> … N CHECKMULTISIG
其中M 是花费输出所需的签名的数量,N 是列出的公钥的总数

2.3GetScriptForWitness

CScript GetScriptForWitness(const CScript& redeemscript)
{
    CScript ret;

    txnouttype typ;
    std::vector<std::vector<unsigned char> > vSolutions;
    if (Solver(redeemscript, typ, vSolutions)) {
        if (typ == TX_PUBKEY) {
            unsigned char h160[20];
            CHash160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160);
            ret << OP_0 << std::vector<unsigned char>(&h160[0], &h160[20]);
            return ret;
        } else if (typ == TX_PUBKEYHASH) {
           ret << OP_0 << vSolutions[0];
           return ret;
        }
    }
    uint256 hash;
    CSHA256().Write(&redeemscript[0], redeemscript.size()).Finalize(hash.begin());
    ret << OP_0 << ToByteVector(hash);
    return ret;
}

这个函数的传入参数就是脚本,也就是之前构建好的脚本。这里的逻辑是针对公钥和公钥hash有不同的处理方法,以及对支付给脚本hash的处理
例子是 1)P2WPKH : OP_0 <20-byte witness program>
2)P2WSH:OP_0 <32-byte witness program>


3.签名

在createtransaction中的签名是调用

if (sign)
signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.first->vout[coin.second].nValue, SIGHASH_ALL), scriptPubKey, sigdata);

SIGHASH_ALL是签名哈希类型的一个,表示签名承诺的交易部分,all表示承诺所有的输入和输出
在这里插入图片描述

3.1ProduceSignature

先来看ProduceSignature这个函数,主要逻辑是判断锁定脚本类型,然后对应处理
如果是P2SH第一次调用SignStep返回的不是签名而是赎回脚本,需要另外处理,还有隔离见证的也需要。

bool ProduceSignature(const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
{
    CScript script = fromPubKey;
    bool solved = true;
    std::vector<valtype> result;
    txnouttype whichType;
    solved = SignStep(creator, script, result, whichType, SIGVERSION_BASE);//SIGVERSION_BASE=0
    bool P2SH = false;
    CScript subscript;
    sigdata.scriptWitness.stack.clear();

    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:
        script = subscript = CScript(result[0].begin(), result[0].end());
        solved = solved && SignStep(creator, script, result, whichType, SIGVERSION_BASE) && whichType != TX_SCRIPTHASH;
        P2SH = true;
    }

    if (solved && whichType == TX_WITNESS_V0_KEYHASH)//隔离见证
    {
        CScript witnessscript;
        witnessscript << OP_DUP << OP_HASH160 << ToByteVector(result[0]) << OP_EQUALVERIFY << OP_CHECKSIG;//隔离见证脚本的组装
        txnouttype subType;
        solved = solved && SignStep(creator, witnessscript, result, subType, SIGVERSION_WITNESS_V0);
        sigdata.scriptWitness.stack = result;
        result.clear();
    }
    else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH)//隔离见证
    {
        CScript witnessscript(result[0].begin(), result[0].end());
        txnouttype subType;
        solved = solved && SignStep(creator, witnessscript, result, subType, SIGVERSION_WITNESS_V0) && 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;
        result.clear();
    }

    if (P2SH) {//脚本公钥
        result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end()));
    }
    sigdata.scriptSig = PushAll(result);

    // Test solution
    return solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
}

签名数据保存在sigdata中

3.1.1SignStep

需要先了解SignStep,从注释来看

使用创建者(creator)签名签署scriptPubKey。 签名在scriptSigRet中返回(如果无法对scriptPubKey进行签名,则返回false),除非whichTypeRet是TX_SCRIPTHASH,在这种情况下,scriptSigRet是兑换脚本。 如果无法完全满足scriptPubKey,则返回false。

static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptPubKey,
                     std::vector<valtype>& ret, txnouttype& whichTypeRet, SigVersion sigversion)
{
    CScript scriptRet;
    uint160 h160;
    ret.clear();

    vector<valtype> vSolutions;
    if (!Solver(scriptPubKey, whichTypeRet, vSolutions))//whichTypeRet脚本类型,vSolutions保存脚本hash值
        return false;

    CKeyID keyID;
    switch (whichTypeRet)
    {
    case TX_NONSTANDARD:
    case TX_NULL_DATA:
        return false;
    case TX_PUBKEY:
        keyID = CPubKey(vSolutions[0]).GetID();
        return Sign1(keyID, creator, scriptPubKey, ret, sigversion);//签名保存在ret
    case TX_PUBKEYHASH:
        keyID = CKeyID(uint160(vSolutions[0]));
        if (!Sign1(keyID, creator, scriptPubKey, ret, sigversion))
            return false;
        else
        {
            CPubKey vch;
            creator.KeyStore().GetPubKey(keyID, vch);
            ret.push_back(ToByteVector(vch));
        }
        return true;
    case TX_SCRIPTHASH:
        if (creator.KeyStore().GetCScript(uint160(vSolutions[0]), scriptRet)) {
            ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
            return true;
        }
        return false;

    case TX_MULTISIG:
        ret.push_back(valtype()); // workaround CHECKMULTISIG bug
        return (SignN(vSolutions, creator, scriptPubKey, ret, sigversion));

    case TX_WITNESS_V0_KEYHASH:
        ret.push_back(vSolutions[0]);
        return true;

    case TX_WITNESS_V0_SCRIPTHASH:
        CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160.begin());
        if (creator.KeyStore().GetCScript(h160, scriptRet)) {
            ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
            return true;
        }
        return false;

    default:
        return false;
    }
}

可以看到有三个类型TX_SCRIPTHASH,TX_WITNESS_V0_KEYHASH,TX_WITNESS_V0_SCRIPTHASH没有调用签名函数,所以还要再次调用签名处理的。

3.1.2Solver

Return public keys or hashes from scriptPubKey, for ‘standard’ transaction types.
对于“标准”事务类型,从scriptPubKey返回公钥或哈希值。

鉴于这个代码也很长,不想说了,scriptPubKey是指锁定脚本,不是单指公钥脚本,好吧稍微写一点好了

bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsigned char> >& vSolutionsRet)
{// Templates
    static multimap<txnouttype, CScript> mTemplates;//构造模版
    if (mTemplates.empty())
    {
        // Standard tx, sender provides pubkey, receiver adds signature
        //标准交易,发送者提供公钥,接收者添加签名
        mTemplates.insert(make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG));

        // Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey
        //比特币地址交易,发送者提供公钥哈希,接收者提供签名和公钥
        mTemplates.insert(make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));

        // Sender provides N pubkeys, receivers provides M signatures
        //多重签名,发送者提供n个公钥,接收者提供m个签名
        mTemplates.insert(make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG));
    }

    vSolutionsRet.clear();

    // Shortcut for pay-to-script-hash, which are more constrained than the other types:
    // it is always OP_HASH160 20 [20 byte hash] OP_EQUAL
    //付费到脚本哈希的快捷方式,比其他类型更受限制:它总是OP_HASH160 20 [20字节哈希] OP_EQUAL
    if (scriptPubKey.IsPayToScriptHash())//对P2SH的处理
    {
        typeRet = TX_SCRIPTHASH;
        //20字节哈希我们是知道,前面两字节是操作OP_HASH160和代表长度的字节
        vector<unsigned char> hashBytes(scriptPubKey.begin()+2, scriptPubKey.begin()+22);
        vSolutionsRet.push_back(hashBytes);
        return true;
    }
    ····

判断是否脚本hash,写的比较明确了

bool CScript::IsPayToScriptHash() const
{
    // Extra-fast test for pay-to-script-hash CScripts:
    return (this->size() == 23 &&
            (*this)[0] == OP_HASH160 &&
            (*this)[1] == 0x14 &&
            (*this)[22] == OP_EQUAL);
}

vSolutionsRet保存的是hash值
函数运行后,typeRet会保存锁定脚本的类型,这是枚举类型

enum txnouttype
{
    TX_NONSTANDARD,
    // 'standard' transaction types:
    TX_PUBKEY,
    TX_PUBKEYHASH,
    TX_SCRIPTHASH,
    TX_MULTISIG,
    TX_NULL_DATA,
    TX_WITNESS_V0_SCRIPTHASH,
    TX_WITNESS_V0_KEYHASH,
};

在来说点函数里的代码,接上一段代码,这里是对隔离见证的处理,隔离见证也是固定的长度,20或者32字节

 int witnessversion;
    std::vector<unsigned char> witnessprogram;
    if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
        if (witnessversion == 0 && witnessprogram.size() == 20) {
            typeRet = TX_WITNESS_V0_KEYHASH;
            vSolutionsRet.push_back(witnessprogram);
            return true;
        }
        if (witnessversion == 0 && witnessprogram.size() == 32) {
            typeRet = TX_WITNESS_V0_SCRIPTHASH;
            vSolutionsRet.push_back(witnessprogram);
            return true;
        }
        return false;
    }

这里涉及到的判断,也写的很清楚,截取见证内容保存到program中

bool CScript::IsWitnessProgram(int& version, std::vector<unsigned char>& program) const
{
    if (this->size() < 4 || this->size() > 42) {
        return false;
    }
    if ((*this)[0] != OP_0 && ((*this)[0] < OP_1 || (*this)[0] > OP_16)) {
        return false;
    }
    if ((size_t)((*this)[1] + 2) == this->size()) {
        version = DecodeOP_N((opcodetype)(*this)[0]);
        program = std::vector<unsigned char>(this->begin() + 2, this->end());
        return true;
    }
    return false;
}

再返回函数部分,

if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) {
        typeRet = TX_NULL_DATA;
        return true;
    }

在这段之后就是对最开始的mTemplates的处理,对三种的处理,轮询处理

3.1.3Sign1

在解析出脚本类型和脚本hash或公钥后回到SignStep函数中,以公钥脚本为例,查看如何签名

 keyID = CPubKey(vSolutions[0]).GetID();
        return Sign1(keyID, creator, scriptPubKey, ret, sigversion);

来看Sign1这个函数,签名保存在ret变量中

static bool Sign1(const CKeyID& address, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector<valtype>& ret, SigVersion sigversion)
{
    vector<unsigned char> vchSig;
    if (!creator.CreateSig(vchSig, address, scriptCode, sigversion))
        return false;
    ret.push_back(vchSig);
    return true;
}

函数中是调用的CreateSig,不能用非压缩格式的私钥签名个隔离见证脚本


bool TransactionSignatureCreator::CreateSig(std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
{
    CKey key;
    if (!keystore->GetKey(address, key))
        return false;

    // Signing with uncompressed keys is disabled in witness scripts
    if (sigversion == SIGVERSION_WITNESS_V0 && !key.IsCompressed())
        return false;

    uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);//要签名的hash
    if (!key.Sign(hash, vchSig))//签名的调用
        return false;
    vchSig.push_back((unsigned char)nHashType);//加上hash类型
    return true;
}

到这里就有签名了

猜你喜欢

转载自blog.csdn.net/m0_37847176/article/details/82866757