比特币源码阅读 —— 签名的内容 (对进行签名)

比特币中最常用的交易方式 P2PKH 中签名的内容 ‘message’ 的组成

一、解锁和锁定脚本 (P2PKH:对公钥哈希的付款)

比特币源码阅读 —— 签名的内容 (对进行签名)_第1张图片
上图是 P2PKH 形式的交易的解锁使用过程。也是比特币最常用的交易方式。
记录一下sig的生成过程。

二、交易的创建

1 遍历自己地址的可用 UTXO 是否大于 交易的数额 amount。
2 计算交易费用fee
3 如果如果可用UTXO >= fee + amount 则开始组织交易。
	1> 生成输出 一个一个的 TxOut,并添加到数组中 (包含转账地址和找零地址)。
	2> 根据UTXO生成 TxIn,并添加到数组中。
	3> 对 TxIn 数组中的 TxIn 进行逐一签名。
	4> 发送交易信息。

其中对TxIn进行逐一签名,签名的包括下面几种类型:
以下表格信息取自: btcd源码解析 - 签名机制(1) - 基础知识
“签名类型”,更准确地称为Signature Hash Types,主要包括4种基本类型:SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAY. 4种类型的简述如下表所示

基本类型 简单描述 用途
SIGHASH_ALL message中包含所有的output 默认类型,使用最为广泛
SIGHASH_NONE message中不包含任何output 允许任何人通过构建output来花费这笔钱
SIGHASH_SINGLE message中包含特定 (对应input的) output 保证自己的output不被篡改,但允许其他人的output被改动
SIGHASH_ANYONECANPAY message中包含当前input 允许任何人构建input,也即允许任何人往交易里面输入金额

容易发现,前三者 (SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE)主要是用来对output进行限制,而SIGHASH_ANYONECANPAY是对input进行限制。在任何一次签名中,需要指定前三者中的一项,(可选择地)搭配使用SIGHASH_ANYONECANPAY进行修饰。

分类 组合类型 简单描述 用途
无修饰类 SIGHASH_ALL message中包含所有的inputoutput 默认类型,使用最为广泛
无修饰类 SIGHASH_NONE message中包含所有的input,不包含任何output 允许任何人通过构建output来花费这笔钱
无修饰类 SIGHASH_SINGLE message中包含所有input和特定 (对应input的) output 保证自己的output不被篡改,但允许其他人的output被改动
有修饰类 SIGHASH_ALL | SIGHASH_ANYONECANPAY message中包含所有output和当前input 常被用来做资金“众筹”
有修饰类 SIGHASH_NONE | SIGHASH_ANYONECANPAY message中只包含当前input,不包含任何output 允许任何人输入金额,也允许任何人花费金额
有修饰类 SIGHASH_SINGLE | SIGHASH_ANYONECANPAY message中只包含当前input,和对应的output 允许任何人输入金额,也保证对应的output不被篡改

这里我们只关注最常见的形式 SIGHASH_ALL.

三、签名内容(message)的组织过程 (SIGHASH_ALL 类型)

1 hashPrevouts; // 所有输入(vin数组)中引用的UTXO的 交易id(hash) 和 序号n 汇总后 做sha256得到的hash。
2 hashSequence; // 所有如数中的 nSequence汇总后 做sha256得到的hash。
3 hashOutputs;  // 所有输出 (vout数组) 的 nValue和 scriptPubKey 汇总后 做sha256得到的hash。

除了上述三个变量外,还有其他包括 nVersion 、nIn(一共多少个输入)等等信息组成。

四、源码相关

函数调用流程:
其中Solver(…)函数是 获取脚本锁定方式为 P2PKH 还是 P2SH 等等。
CWallet::CreateTransaction(…) -> ProduceSignature(…) -> SignStep(…) -> Solver(…) ->Sign1(…) -> TransactionSignatureCreator::CreateSig(…) -> SignatureHash(…)CKey::Sign(…);
这里主要贴一下 SignatureHash() 函数

uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
{
    assert(nIn < txTo.vin.size());

    if (sigversion == SIGVERSION_WITNESS_V0) {
        uint256 hashPrevouts;
        uint256 hashSequence;
        uint256 hashOutputs;
        const bool cacheready = cache && cache->ready;

		// 获取 上述三个主要数据
        if (!(nHashType & SIGHASH_ANYONECANPAY)) {
            hashPrevouts = cacheready ? cache->hashPrevouts : GetPrevoutHash(txTo);
        }

        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) {
            hashOutputs = cacheready ? cache->hashOutputs : GetOutputsHash(txTo);
        } else if ((nHashType & 0x1f) == SIGHASH_SINGLE && nIn < txTo.vout.size()) {
            CHashWriter ss(SER_GETHASH, 0);
            ss << txTo.vout[nIn];
            hashOutputs = ss.GetHash();
        }

		// 汇总包括上面三个主要数据在内的所有用到的签名数据(message)
        CHashWriter ss(SER_GETHASH, 0);
        // Version
        ss << txTo.nVersion;
        // Input prevouts/nSequence (none/all, depending on flags)
        ss << hashPrevouts;
        ss << hashSequence;
        // The input being signed (replacing the scriptSig with scriptCode + amount)
        // The prevout may already be contained in hashPrevout, and the nSequence
        // may already be contain in hashSequence.
        ss << txTo.vin[nIn].prevout;
        ss << scriptCode;
        ss << amount;
        ss << txTo.vin[nIn].nSequence;
        // Outputs (none/one/all, depending on flags)
        ss << hashOutputs;
        // Locktime
        ss << txTo.nLockTime;
        // Sighash type
        ss << nHashType;

        return ss.GetHash();
    }

其中三个主要数据 hashPrevouts、hashSequence、hashOutputs 的生成过程:

uint256 GetPrevoutHash(const CTransaction& txTo) {
    CHashWriter ss(SER_GETHASH, 0);
    for (const auto& txin : txTo.vin) {
        ss << txin.prevout; // prevout是 COutPoint类型
    }
    return ss.GetHash();
}

class COutPoint
{
public:
    uint256 hash;
    uint32_t n;
	...
    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    // 将 hash 和 n 序列化到 字节流s中
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(hash);
        READWRITE(n);
    }
    ...
};

uint256 GetSequenceHash(const CTransaction& txTo) {
    CHashWriter ss(SER_GETHASH, 0);
    for (const auto& txin : txTo.vin) {
        ss << txin.nSequence; // nSequence是int类型
    }
    return ss.GetHash();
}

uint256 GetOutputsHash(const CTransaction& txTo) {
    CHashWriter ss(SER_GETHASH, 0);
    for (const auto& txout : txTo.vout) {
        ss << txout; // txout 是 CTxOut类型
    }
    return ss.GetHash();
}

class CTxOut
{
public:
    CAmount nValue;
    CScript scriptPubKey;
	...
    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    // 将 nValue 和 scriptPubKey 序列化到 字节流 s中
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(nValue);
        READWRITE(scriptPubKey);
    }
    ...
};

CKey::Sign() 函数就是使用 secp256k1 椭圆曲线加密函数库进行签名的过程了:

bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& vchSig, uint32_t test_case) const 
{
    if (!fValid)
        return false;
    vchSig.resize(72);
    size_t nSigLen = 72;
    unsigned char extra_entropy[32] = {0};
    WriteLE32(extra_entropy, test_case);
    secp256k1_ecdsa_signature sig;
    int ret = secp256k1_ecdsa_sign(secp256k1_context_sign, &sig, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, test_case ? extra_entropy : nullptr);
    assert(ret);
    secp256k1_ecdsa_signature_serialize_der(secp256k1_context_sign, (unsigned char*)vchSig.data(), &nSigLen, &sig);
    vchSig.resize(nSigLen);
    return true;
}

通过 SignatureHash() 函数获取到需要签名的所有内容后,调用 Key::Sign() 函数进行签名。然后将签名的信息更新到vin中的 scriptSig字段,就完成了。

*注:本文是个人笔记之用,如有不妥,还望指正。


欢迎关注 [懒人漫说] 公众号,期待与你一起分享技术。
比特币源码阅读 —— 签名的内容 (对进行签名)_第2张图片

你可能感兴趣的:(比特币源码阅读)