比特币探究之交易签名

比特币探究之交易创建这篇文章里,可以看到在交易创建的最后,需要进行一个交易签名操作。它其实就是交易发起方要提供一个证据,证明自己可以花费这项交易的每一笔输入,也就是说提供的签名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;
}

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


你的支持,我的动力!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容