【区块链】比特币源码学习 - 1 - 交易

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

比特币源码学习 - 1 - 交易

参考博客:here and here

一、交易概念

1、 交易形式

比特币交易中的基础构建单元是交易输出。在比特币的世界里既没有账户,也没有余额,只有分散到区块链里的UTXO[未花费的交易输出]。

例如,你有20比特币的UTXO并且想支付1比特币,那么你的交易必须消耗掉整个20比特币的UTXO并且产生两个输出:一个是支付了1比特币给接收人,另一个是支付19比特币的找零到你的钱包。

image

代码目录:src/coins.h

// 一个基本的UTXO,未花费交易输出
class Coin
{
public:
    CTxOut out;
    // 该UTXO是否是coinbase交易
    unsigned int fCoinBase : 1;
	// 包含该UTXO的交易所在区块在区块链上的高度
    uint32_t nHeight : 31;
    ...
};

2、交易结构

代码目录:src/primitives/transaction.h

A. 交易输入

被消耗的UTXO

class CTxIn
{
public:
    COutPoint prevout;             // 前一个交易对应的输出,该输入引用的UTXO
    CScript scriptSig;             // 解锁脚本
    uint32_t nSequence;            // 指定交易什么时候可以被写到区块链中
    CScriptWitness scriptWitness;  // ! 隔离见证

    // 规则1:如果nSequence被赋值为这个值,交易立刻执行,nLockTime无效,无需考虑锁定时间和要到达那个区块号再执行
    static const uint32_t SEQUENCE_FINAL = 0xffffffff;

    // 规则2:如果设置了这个变量,那么规则1就失效了[同交易立刻执行,nLockTime无效,无需考虑锁定时间和要到达那个区块号再执行]
    static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31);

    // 规则3:如果规则1有效并且设置了此变量,那么相对锁定时间就为512秒,否则锁定时间就为1个区块
    static const uint32_t SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22);

    // 规则4:如果规则1有效,那么这个变量就用来从nSequence计算对应的锁定时间
    static const uint32_t SEQUENCE_LOCKTIME_MASK = 0x0000ffff;

    // 区块生成时间:512 = 2^9
    static const int SEQUENCE_LOCKTIME_GRANULARITY = 9;

    ...
};

其中COutPoint: 用于定位该交易输入的来源(UTXO),起到point的作用

class COutPoint
{
public:
    uint256 hash;    // UTXO所在的交易的哈希值
    uint32_t n;      // UTXO的索引号
    ...
};
B. 交易输出

由交易创建的UTXO,即未花费交易输出

class CTxOut
{
public:
    CAmount nValue;            // 比特币数量
    CScript scriptPubKey;      // 锁定脚本,决定了谁可以花费这笔UTXO
    ...
};
C. 交易

下面就是在网络中广播然后被打包进区块的最基本的交易的结构,一个交易可能包含多个交易输入和输出。

class CTransaction
{
public:
    // 默认的交易版本信息。0.13版本以前的代码是1,从0.14版本开始版本是2
    static const int32_t CURRENT_VERSION=2;

    // 标准交易的最高版本,为的是兼容原来的版本。这个参数是从0.12的版本出现的。
    static const int32_t MAX_STANDARD_VERSION=2;

    const std::vector<CTxIn> vin;		// 交易输入
    const std::vector<CTxOut> vout;		// 交易输出
    const int32_t nVersion;				// 版本
    const uint32_t nLockTime;			// 交易时间锁,用来控制交易的输出只有在一段时间后才能被花费

private:
    /** Memory only. */
    const uint256 hash;
    const uint256 m_witness_hash;

    uint256 ComputeHash() const;
    uint256 ComputeWitnessHash() const;
    ...
};

二、交易脚本

参考文章:here and here

1、基本概念

前面提到交易的基本结构,我们注意到比特币流从一个交易流动到了另一个交易,像这样一直传递下去。CTxIn 和 CTxOut 的属性 scriptSig和scriptPubkey 就是钥匙和锁。scriptSig[解锁脚本] 就是用于对应签名的钥匙,而 scriptPubkey[锁定脚本]就是根据地址而生成的锁。

2、数据结构

代码目录:src/script/script.h

CScript 实际上就是一个vector<unsigned char>也就是说 Script 实际上就是一串 Bytes 流。只不过这个字节流是可以被解析为 <指令> 或者 <指令> <数据> 这样的一个一个元信息。而一个 Script 就是这些元信息组成的字节流。

其中我们需要关注的是指令:它的验证需要一个 VM 来执行(脚本)

/** Script opcodes */
enum opcodetype
{
    OP_0 = 0x00,
    OP_FALSE = OP_0,
    ...
    OP_DEPTH = 0x74,
    OP_DROP = 0x75,
    OP_DUP = 0x76,
    OP_NIP = 0x77,
    ...
    OP_AND = 0x84,
    OP_OR = 0x85,
    OP_XOR = 0x86,
    OP_EQUAL = 0x87,
    ...
};

3、标准交易

A. P2PKH

例如:A支付B 0.1BTC

锁定脚本:OP_DUP OP_HASH160 <B Public Key Hash> OP_EQUAL OP_CHECKSIG

解锁脚本:<B Signature> <B Public Key>

B. P2PK

例如:A支付B 0.1BTC

锁定脚本:<Public Key A> OP_CHECKSIG

解锁脚本:<Signature from Private Key A>

C. MS[多重签名]

例如:M-N多重签名,M是使得多重签名生效的最少数目,如2-3:

锁定脚本:2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG

解锁脚本(3个存档公钥中的任意2个相一致的私钥签名组合予以解锁):OP_0 <Signature B> <Signature C>

D. P2SH

P2SH是MS多重签名的简化版本,对锁定脚本进行加密,验证过程分两步,首先验证的是接收方附上的赎回脚本是否符合发送方的锁定脚本,如果是,便执行该脚本,进行多重签名的验证。这样暂缓节点存储的压力。

锁定脚本(1):2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG

对于锁定脚本(1)首先采用SHA256哈希算法,随后对其运用RIPEMD160算法得到:

锁定脚本(2): OP_HASH160 8ac1d7a2fa204a16dc984fa81cfdf86a2a4e1731 OP_EQUAL

解锁脚本: <Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>

4、脚本执行

我们以P2PKH交易为例,A支付B 0.1BTC,来看一下脚本执行流程:

交易输入,包含解锁脚本:<A Signature> <A Public Key>

上一笔交易的输出,包含锁定脚本:OP_DUP OP_HASH160 <A Public Key Hash> OP_EQUAL OP_CHECKSIG

代码目录:src/script/interpreter.cpp

bool 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中包含 <A Signature> <A Public Key>
    if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror))
        return false;
    // 由于下面EvalScript会破坏stack,而再后面仍然需要stack当前的数据,因而需要做一次拷贝
    if (flags & SCRIPT_VERIFY_P2SH)
        stackCopy = stack;
    // 执行锁定脚本
    if (!EvalScript(stack, scriptPubKey, flags, checker, SigVersion::BASE, serror))
        return false;
    ...
}

三、脚本创建

参考博客:here and here

1. 锁定脚本创建

交易输出的锁定脚本如何生成。生成锁定脚本的代码如下:

CScript scriptPubKey = GetScriptForDestination(destination);

代码目录:src/script/standard.cpp

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

    boost::apply_visitor(CScriptVisitor(&script), dest);
    return script;
}
// 可以看出一个标准的支付脚本
bool operator()(const CKeyID &keyID) const {
    script->clear();
    *script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
    return true;
}

2. 解锁脚本创建

参考博客:here

交易输入中包含解锁脚本[scriptSig]:<A Signature> <A Public Key>

代码目录:src/script/sign.cpp

A. ProduceSignature
/** 入参
 * provider: keystore,存放了公钥-私钥配对
 * creator:BaseSignatureCreator类型的实例,用于最后对交易生成签名
 * fromPubKey:CScript类型,交易输入引用的UTXO的锁定脚本
 * sigData:SignatureData类型,是输出参数,用于存放生成的解锁脚本
 */
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
{
    // 进行签名
    bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata);
    bool P2SH = false;
    CScript subscript;
    sigdata.scriptWitness.stack.clear();

    // P2SH交易,需要对子脚本进行签名
    if (solved && whichType == TX_SCRIPTHASH) { ... }
	// P2WKH交易,需要对见证脚本签名
    if (solved && whichType == TX_WITNESS_V0_KEYHASH) { ... }
    // P2WSH交易
    else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH) { ... }
	else if (solved && whichType == TX_WITNESS_UNKNOWN) { ... }
    if (P2SH) { ... }
    
    // 将生成的解锁脚本写入到sigdata中
    sigdata.scriptSig = PushAll(result);

    // 校验脚本
    sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
    return sigdata.complete;
}
B. SignStep
static bool SignStep ...
{
    ...
    // 解析交易输入引用的UTXO的锁定脚本,锁定脚本的类型存在输出参数whichTypeRet中,锁定脚本的数据存放在向量vSolutions中
    if (!Solver(scriptPubKey, whichTypeRet, vSolutions))
        return false;
	// 根据不同的锁定脚本的类型执行签名
    switch (whichTypeRet)
    {
    ...
    case TX_PUBKEY:
        if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]).GetID(), scriptPubKey, sigversion)) return false;
        ret.push_back(std::move(sig));
        return true;
	...
    }
}
C. Solver

它根据传入的scriptPubKey,判断交易输出类型(P2PKH、P2SH、P2WPKH或P2WSH),并返回相应的数据

类型 格式 返回值
P2SH OP_HASH160 20 [20 byte hash] OP_EQUAL 返回20字节的Redeem Script Hash
P2WPKH 隔离见证,{见证版本}{见证程序},长度20 返回20字节的PubKey Hash
P2WSH 隔离见证,{见证版本}{见证程序},长度32 返回32字节的Redeem Script Hash
P2PK / 返回65/33字节的PubKey Hash,33为压缩版
P2PKH / 返回20字节的PubKey Hash
MS {required} {A pubkey } {B pubkey} … OP_CHECKMULTISIG 返回m PubKeys n
D. CreateSig
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);
    // 使用ECDSA椭圆曲线加密算法进行签名
    if (!key.Sign(hash, vchSig))
        return false;
    vchSig.push_back((unsigned char)nHashType);
    return true;
}

四、交易创建

了解前面内容后,我们来看一下一个完整的交易创建,以A向B转账0.1BTC,采用P2PK为例:

代码目录:src/wallet/rpcwallet.cpp

发起转账:

// 转账
static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue, std::string fromAccount)
{
    CAmount curBalance = pwallet->GetBalance();

    // 检查金额
    if (nValue <= 0)
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");

    if (nValue > curBalance)
        throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");

    if (pwallet->GetBroadcastTransactions() && !g_connman) {
        throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
    }

    // 处理目的地址,生成锁定脚本
    CScript scriptPubKey = GetScriptForDestination(address);

    // 创建交易
    CReserveKey reservekey(pwallet);
    CAmount nFeeRequired;
    std::string strError;
    std::vector<CRecipient> vecSend;
    int nChangePosRet = -1;
    CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
    vecSend.push_back(recipient);
    CTransactionRef tx;
    if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) {
        if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
            strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
        throw JSONRPCError(RPC_WALLET_ERROR, strError);
    }
    // 确认并发送交易
    CValidationState state;
    if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, std::move(fromAccount), reservekey, g_connman.get(), state)) {
        strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state));
        throw JSONRPCError(RPC_WALLET_ERROR, strError);
    }
    return tx;
}

创建交易:

bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet,int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
{
    ...
    // 金额不能为负,至少有一个收款人
    for (const auto& recipient : vecSend) { ... }
    CMutableTransaction txNew;

    // 把nLockTime 设置为当前区块高度, 目地是为了防止费用狙击[fee sniping]
    txNew.nLockTime = chainActive.Height();

    // 偶尔允许nLockTime往前推, 因为确实有些交易会被耽误,比如高延迟混合网络,或者为了保护隐私的压缩交易
    if (GetRandInt(10) == 0)
        txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
	...
    /* 第一步:找到所有可用的币 */
    std::vector<COutput> vAvailableCoins;
    AvailableCoins(vAvailableCoins, true, &coin_control);
    // 选币参数
    CoinSelectionParams coin_selection_params;
    // 改变找零地址的脚本
    CScript scriptChange;
    
    /* 第二步:设定找零地址 */
    // 已设定了找零地址:直接用
    if (!boost::get<CNoDestination>(&coin_control.destChange)) {
        scriptChange = GetScriptForDestination(coin_control.destChange);
    } 
    // 未设定找零地址:生成新地址
    else { 
        // 从钥匙池中生成新地址
        if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
            strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
            return false;
        }
        ...
        scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type));
    }
 	...
    /* 第三步:计算交易需要的最低费率 */
    CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, ::mempool, ::feeEstimator, &feeCalc);

    nFeeRet = 0;
    bool pick_new_inputs = true;
    CAmount nValueIn = 0;
	
    // 从零费用开始循环,直到手续费足够
    while (true)
    {
        ...
        /* 第四步:凑出最接近目标值(含手续费)的交易集合 */
        setCoins.clear();
        if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
            {
                if (bnb_used) {
                    coin_selection_params.use_bnb = false;
                    continue;
                }
                else {
                    strFailReason = _("Insufficient funds");	// 金额不足
                    return false;
                }
            }
        }
    	/* 第五步:构造vout, v[0]接收者,v[1]自己*/
    	// 把接收者加入进vout 
    	txNew.vout.push_back(txout);
    	// 需要找零
        if (nChange > 0)
        {
            // 把自己加入进vout 
            CTxOut newTxOut(nChange, scriptChange);
            txNew.vout.insert(position, newTxOut); 
        } else {
            nChangePosInOut = -1;
        }
    	/* 第六步:用选出的交易,构造vin */
        for (const auto& coin : setCoins) {
            txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
        }
    	/* 第七步:估算手续费 */
    	...
    }
	// 如果没有找零,返回任何key
	if (nChangePosInOut == -1) reservekey.ReturnKey(); 
    // 打乱币序,填充最终vin
    txNew.vin.clear();
    std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
    std::shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
	...
    // 生成解锁脚本
    if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { ... }
	...
}

猜你喜欢

转载自blog.csdn.net/loy_184548/article/details/82881863