比特幣源碼分析--深入理解比特幣交易

    交易是比特幣最重要的一塊,比特幣系統的其他部分都是爲交易服務的。前面的章節中已經學習了各種共識算法以及比特幣PoW共識的實現,本文來分析比特幣中的交易相關的源碼。

1 初識比特幣交易

    通過比特幣核心客戶端的命令getrawtransaction和decoderawtransaction可以檢索到比特幣區塊鏈上任意一筆交易的詳細信息,以下是運行這兩個命令後得到的某筆交易的詳細信息,該示例摘自《精通比特幣》一書:

{
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid":"7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
      "vout": 0,
      "scriptSig": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
      "sequence": 4294967295
    }
 ],
  "vout": [
    {
      "value": 0.01500000,
      "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      "value": 0.08450000,
      "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
    }
  ]
}

    我們仔細分析一下上面這個輸出,來看看一筆比特幣的交易到底包含了哪些東西。

    首先是vin字段,這是一個json數組,數組中的每個元素代表一筆交易的輸入,在這個例子中的交易,只有一筆輸入;

    其次是vout字段,這也是一個json數組,數組中的每個元素代表一筆未花費的輸出(UTXO),在這個例子中的交易產生了兩筆新的UTXO。

    OK,我們已經看到一筆比特幣交易包含了輸入和輸出兩個部分,其中輸入表示要花費的比特幣來自哪裏,而輸出則表示輸入所指向的比特幣去了哪裏,換句話說,比特幣的交易實際上隱含着價值的轉移。以示例中的交易爲例,該交易所花費的比特幣來自於另外一筆交易7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18的索引爲0的UTXO,該UTXO的去向在vout中指定,0.015個比特幣去了公鑰爲ab68025513c3dbd2f7b92a94e0581f5d50f654e7對應的錢包,而0.0845個比特幣則流向了公鑰爲7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8對應的錢包。

1.1 交易輸出

    交易的輸出通常也稱爲UTXO,即未花費交易輸出,從例子中可以看到一筆交易可能產生多個UTXO,這些UTXO在後續交易中會被花費。

    交易輸出包含下面一些內容:

    value:該UTXO的比特幣數量;

    scriptPubKey:通常稱爲鎖定腳本,決定了誰可以花費這筆UTXO,只有提供了正確的解鎖腳本才能解鎖並花費該UTXO;

1.2 交易輸入

    交易的輸入可以理解爲一個指向一筆UTXO的指針,表示該交易要花費的UTXO在哪裏。交易輸出包含如下內容:

    txid:該交易要花費的UTXO所在的交易的hash;

    vout:索引。一筆交易可能產生多個UTXO存放在數組中,該索引即爲UTXO在數組中的下標。通過(txid, vout)就能檢索到交易中的UTXO;

    scriptSig:解鎖腳本,用於解鎖(txid, vout)所指向的UTXO。前文提到交易生成的每一筆UTXO都會設定一個鎖定腳本即scriptPubKey,解鎖腳本scriptSig用來解鎖。如果把UTXO比作一個包含了比特幣的寶箱,那麼scriptPubKey就是給該寶箱上了一把鎖,而scriptSig則是鑰匙,只有提供真確的鑰匙才能解開鎖並花費寶箱裏的比特幣。

1.3 交易鏈

    比特幣的交易實際上是以鏈的形式串聯在一起的,一筆交易與其前驅的交易通過交易輸入串聯起來。假設張三的錢包裏有一筆2比特幣的UTXO,然後張三給自己的好友李四轉了0.5個比特幣,於是生成一筆類似下面這樣的交易:

    

    交易T1的輸入指向了交易T0的UTXO,該UTXO被分成了兩部分,形成兩筆新的UTXO:0.5BTC歸李四所有,剩下的1.5BTC作爲找零又回到了張三的錢包。假設之後李四在咖啡館將收到的0.5BTC消費掉了0.1BTC,則交易鏈條如下:

    

    應該注意到這樣一個重要事實:每一筆新生成的交易,其交易的輸入一定指向另外一筆交易的輸出。比特幣的交易通過這種鏈條的形式串聯在一起,通過交易的輸入就能找到其依賴的另外一筆交易。

2 交易相關的數據結構

    現在我們已經從直觀上知道了比特幣的交易長什麼樣子,本節我們看看在代碼中,交易是如何表示的。

2.1 交易輸入的數據結構

    交易的輸入用如下的數據結構來表示:

/** An input of a transaction.  It contains the location of the previous
 * transaction's output that it claims and a signature that matches the
 * output's public key.
 */
class CTxIn
{
public:
    //該輸入引用的UTXO
    COutPoint prevout;
    //解鎖腳本,用於解鎖輸入指向的UTXO
    CScript scriptSig;
    //相對時間鎖
    uint32_t nSequence;
    //見證腳本
    CScriptWitness scriptWitness; //! Only serialized through CTransaction

    /* Setting nSequence to this value for every input in a transaction
     * disables nLockTime. */
    static const uint32_t SEQUENCE_FINAL = 0xffffffff;

    /* Below flags apply in the context of BIP 68*/
    /* If this flag set, CTxIn::nSequence is NOT interpreted as a
     * relative lock-time. */
    static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31);

    /* If CTxIn::nSequence encodes a relative lock-time and this flag
     * is set, the relative lock-time has units of 512 seconds,
     * otherwise it specifies blocks with a granularity of 1. */
    static const uint32_t SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22);

    /* If CTxIn::nSequence encodes a relative lock-time, this mask is
     * applied to extract that lock-time from the sequence field. */
    static const uint32_t SEQUENCE_LOCKTIME_MASK = 0x0000ffff;

    /* In order to use the same number of bits to encode roughly the
     * same wall-clock duration, and because blocks are naturally
     * limited to occur every 600s on average, the minimum granularity
     * for time-based relative lock-time is fixed at 512 seconds.
     * Converting from CTxIn::nSequence to seconds is performed by
     * multiplying by 512 = 2^9, or equivalently shifting up by
     * 9 bits. */
    static const int SEQUENCE_LOCKTIME_GRANULARITY = 9;

    CTxIn()
    {
        nSequence = SEQUENCE_FINAL;
    }

    explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL);
    CTxIn(uint256 hashPrevTx, uint32_t nOut, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL);

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(prevout);
        READWRITE(scriptSig);
        READWRITE(nSequence);
    }

    friend bool operator==(const CTxIn& a, const CTxIn& b)
    {
        return (a.prevout   == b.prevout &&
                a.scriptSig == b.scriptSig &&
                a.nSequence == b.nSequence);
    }

    friend bool operator!=(const CTxIn& a, const CTxIn& b)
    {
        return !(a == b);
    }

    std::string ToString() const;
};

    代碼中的COutPoint是該輸入所指向的UTXO,通過COutPoint定位到輸入指向的UTXO:

/** An outpoint - a combination of a transaction hash and an index n into its vout */
class COutPoint
{
public:
    //UTXO所在的交易hash
    uint256 hash;
    //UTXO的索引
    uint32_t n;

    COutPoint(): n((uint32_t) -1) { }
    COutPoint(const uint256& hashIn, uint32_t nIn): hash(hashIn), n(nIn) { }

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(hash);
        READWRITE(n);
    }

    void SetNull() { hash.SetNull(); n = (uint32_t) -1; }
    bool IsNull() const { return (hash.IsNull() && n == (uint32_t) -1); }

    friend bool operator<(const COutPoint& a, const COutPoint& b)
    {
        int cmp = a.hash.Compare(b.hash);
        return cmp < 0 || (cmp == 0 && a.n < b.n);
    }

    friend bool operator==(const COutPoint& a, const COutPoint& b)
    {
        return (a.hash == b.hash && a.n == b.n);
    }

    friend bool operator!=(const COutPoint& a, const COutPoint& b)
    {
        return !(a == b);
    }

    std::string ToString() const;
};

2.2 交易輸出的數據結構

    交易輸出的數據結構如下:

/** An output of a transaction.  It contains the public key that the next input
 * must be able to sign with to claim it.
 */
class CTxOut
{
public:
    CAmount nValue;
    CScript scriptPubKey;

    CTxOut()
    {
        SetNull();
    }

    CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn);

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(nValue);
        READWRITE(scriptPubKey);
    }

    void SetNull()
    {
        nValue = -1;
        scriptPubKey.clear();
    }

    bool IsNull() const
    {
        return (nValue == -1);
    }

    friend bool operator==(const CTxOut& a, const CTxOut& b)
    {
        return (a.nValue       == b.nValue &&
                a.scriptPubKey == b.scriptPubKey);
    }

    friend bool operator!=(const CTxOut& a, const CTxOut& b)
    {
        return !(a == b);
    }

    std::string ToString() const;
};

    可以看到定義非常簡單,只有兩個字段:CAmount表示該UTXO的比特幣數量,scriptPubKey表示該UTXO的鎖定腳本。

2.3 UTXO

    UTXO的概念在比特幣中非常重要,專門用一個類Coin來封裝:

/**
 * A UTXO entry.
 *
 * Serialized format:
 * - VARINT((coinbase ? 1 : 0) | (height << 1))
 * - the non-spent CTxOut (via CTxOutCompressor)
 */
class Coin
{
public:
    //! unspent transaction output
    //UTXO對應的急交易輸出
    CTxOut out;

    //! whether containing transaction was a coinbase
    //該UTXO是否是coinbase交易
    unsigned int fCoinBase : 1;

    //! at which height this containing transaction was included in the active block chain
    //包含該UTXO的交易所在區塊在區塊鏈上的高度
    uint32_t nHeight : 31;

    //! construct a Coin from a CTxOut and height/coinbase information.
    Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}
    Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}

    void Clear() {
        out.SetNull();
        fCoinBase = false;
        nHeight = 0;
    }

    //! empty constructor
    Coin() : fCoinBase(false), nHeight(0) { }

    bool IsCoinBase() const {
        return fCoinBase;
    }

    template<typename Stream>
    void Serialize(Stream &s) const {
        assert(!IsSpent());
        uint32_t code = nHeight * 2 + fCoinBase;
        ::Serialize(s, VARINT(code));
        ::Serialize(s, CTxOutCompressor(REF(out)));
    }

    template<typename Stream>
    void Unserialize(Stream &s) {
        uint32_t code = 0;
        ::Unserialize(s, VARINT(code));
        nHeight = code >> 1;
        fCoinBase = code & 1;
        ::Unserialize(s, CTxOutCompressor(out));
    }

    bool IsSpent() const {
        return out.IsNull();
    }

    size_t DynamicMemoryUsage() const {
        return memusage::DynamicUsage(out.scriptPubKey);
    }
};

    比特幣錢包實際上就是一個由Coin構成的DB。bitcoind在啓動的時候會從DB中加載Coin並存放至內存中。

2.4 交易腳本

    交易輸入的解鎖腳本scriptSig和交易輸出的鎖定腳本scriptPubKey都是CScript類型,CScript用來表示交易腳本。交易腳本是比特幣中一個非常重要的內容,用比特幣提供的腳本語言可以完成非常複雜的功能,本文稍後還會有更詳細介紹。

/** Serialized script, used inside transaction inputs and outputs */
class CScript : public CScriptBase
{
protected:
    CScript& push_int64(int64_t n)
    {
        if (n == -1 || (n >= 1 && n <= 16))
        {
            push_back(n + (OP_1 - 1));
        }
        else if (n == 0)
        {
            push_back(OP_0);
        }
        else
        {
            *this << CScriptNum::serialize(n);
        }
        return *this;
    }
public:
    CScript() { }
    CScript(const_iterator pbegin, const_iterator pend) : CScriptBase(pbegin, pend) { }
    CScript(std::vector<unsigned char>::const_iterator pbegin, std::vector<unsigned char>::const_iterator pend) : CScriptBase(pbegin, pend) { }
    CScript(const unsigned char* pbegin, const unsigned char* pend) : CScriptBase(pbegin, pend) { }

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITEAS(CScriptBase, *this);
    }

    CScript& operator+=(const CScript& b)
    {
        reserve(size() + b.size());
        insert(end(), b.begin(), b.end());
        return *this;
    }

    friend CScript operator+(const CScript& a, const CScript& b)
    {
        CScript ret = a;
        ret += b;
        return ret;
    }

    CScript(int64_t b)        { operator<<(b); }

    explicit CScript(opcodetype b)     { operator<<(b); }
    explicit CScript(const CScriptNum& b) { operator<<(b); }
    explicit CScript(const std::vector<unsigned char>& b) { operator<<(b); }


    CScript& operator<<(int64_t b) { return push_int64(b); }

    CScript& operator<<(opcodetype opcode)
    {
        if (opcode < 0 || opcode > 0xff)
            throw std::runtime_error("CScript::operator<<(): invalid opcode");
        insert(end(), (unsigned char)opcode);
        return *this;
    }

    CScript& operator<<(const CScriptNum& b)
    {
        *this << b.getvch();
        return *this;
    }

    CScript& operator<<(const std::vector<unsigned char>& b)
    {
        if (b.size() < OP_PUSHDATA1)
        {
            insert(end(), (unsigned char)b.size());
        }
        else if (b.size() <= 0xff)
        {
            insert(end(), OP_PUSHDATA1);
            insert(end(), (unsigned char)b.size());
        }
        else if (b.size() <= 0xffff)
        {
            insert(end(), OP_PUSHDATA2);
            uint8_t _data[2];
            WriteLE16(_data, b.size());
            insert(end(), _data, _data + sizeof(_data));
        }
        else
        {
            insert(end(), OP_PUSHDATA4);
            uint8_t _data[4];
            WriteLE32(_data, b.size());
            insert(end(), _data, _data + sizeof(_data));
        }
        insert(end(), b.begin(), b.end());
        return *this;
    }

    CScript& operator<<(const CScript& b)
    {
        // I'm not sure if this should push the script or concatenate scripts.
        // If there's ever a use for pushing a script onto a script, delete this member fn
        assert(!"Warning: Pushing a CScript onto a CScript with << is probably not intended, use + to concatenate!");
        return *this;
    }


    bool GetOp(const_iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>& vchRet) const
    {
        return GetScriptOp(pc, end(), opcodeRet, &vchRet);
    }

    bool GetOp(const_iterator& pc, opcodetype& opcodeRet) const
    {
        return GetScriptOp(pc, end(), opcodeRet, nullptr);
    }


    /** Encode/decode small integers: */
    static int DecodeOP_N(opcodetype opcode)
    {
        if (opcode == OP_0)
            return 0;
        assert(opcode >= OP_1 && opcode <= OP_16);
        return (int)opcode - (int)(OP_1 - 1);
    }
    static opcodetype EncodeOP_N(int n)
    {
        assert(n >= 0 && n <= 16);
        if (n == 0)
            return OP_0;
        return (opcodetype)(OP_1+n-1);
    }

    /**
     * Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
     * as 20 sigops. With pay-to-script-hash, that changed:
     * CHECKMULTISIGs serialized in scriptSigs are
     * counted more accurately, assuming they are of the form
     *  ... OP_N CHECKMULTISIG ...
     */
    unsigned int GetSigOpCount(bool fAccurate) const;

    /**
     * Accurately count sigOps, including sigOps in
     * pay-to-script-hash transactions:
     */
    unsigned int GetSigOpCount(const CScript& scriptSig) const;

    bool IsPayToScriptHash() const;
    bool IsPayToWitnessScriptHash() const;
    bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const;

    /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */
    bool IsPushOnly(const_iterator pc) const;
    bool IsPushOnly() const;

    /** Check if the script contains valid OP_CODES */
    bool HasValidOps() const;

    /**
     * Returns whether the script is guaranteed to fail at execution,
     * regardless of the initial stack. This allows outputs to be pruned
     * instantly when entering the UTXO set.
     */
    bool IsUnspendable() const
    {
        return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
    }

    void clear()
    {
        // The default prevector::clear() does not release memory
        CScriptBase::clear();
        shrink_to_fit();
    }
};

    CScript繼承自ScriptBase:

/**
 * We use a prevector for the script to reduce the considerable memory overhead
 *  of vectors in cases where they normally contain a small number of small elements.
 * Tests in October 2015 showed use of this reduced dbcache memory usage by 23%
 *  and made an initial sync 13% faster.
 */
typedef prevector<28, unsigned char> CScriptBase;

    CScriptBase實際上一個自定義的vector。CScript重寫了<<操作符,可以很方便的向向量中添加數據。

2.5 交易

     比特幣的交易和我們已經看到的那樣,由一組輸入和一組輸出組成:

/** The basic transaction that is broadcasted on the network and contained in
 * blocks.  A transaction can contain multiple inputs and outputs.
 */
class CTransaction
{
public:
    // Default transaction version.
    static const int32_t CURRENT_VERSION=2;

    // Changing the default transaction version requires a two step process: first
    // adapting relay policy by bumping MAX_STANDARD_VERSION, and then later date
    // bumping the default CURRENT_VERSION at which point both CURRENT_VERSION and
    // MAX_STANDARD_VERSION will be equal.
    static const int32_t MAX_STANDARD_VERSION=2;

    // The local variables are made const to prevent unintended modification
    // without updating the cached hash value. However, CTransaction is not
    // actually immutable; deserialization and assignment are implemented,
    // and bypass the constness. This is safe, as they update the entire
    // structure, including the hash.
    //交易的全部輸入
    const std::vector<CTxIn> vin;
    //交易的全部輸出
    const std::vector<CTxOut> vout;
    //交易版本
    const int32_t nVersion;
    //交易鎖定時間,用來控制在一定的時間之後交易的輸出才能被花費
    const uint32_t nLockTime;

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

    uint256 ComputeHash() const;

public:
    /** Construct a CTransaction that qualifies as IsNull() */
    CTransaction();

    /** Convert a CMutableTransaction into a CTransaction. */
    CTransaction(const CMutableTransaction &tx);
    CTransaction(CMutableTransaction &&tx);

    template <typename Stream>
    inline void Serialize(Stream& s) const {
        SerializeTransaction(*this, s);
    }

    /** This deserializing constructor is provided instead of an Unserialize method.
     *  Unserialize is not possible, since it would require overwriting const fields. */
    template <typename Stream>
    CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {}

    bool IsNull() const {
        return vin.empty() && vout.empty();
    }

    const uint256& GetHash() const {
        return hash;
    }

    // Compute a hash that includes both transaction and witness data
    uint256 GetWitnessHash() const;

    // Return sum of txouts.
    CAmount GetValueOut() const;
    // GetValueIn() is a method on CCoinsViewCache, because
    // inputs must be known to compute value in.

    /**
     * Get the total transaction size in bytes, including witness data.
     * "Total Size" defined in BIP141 and BIP144.
     * @return Total transaction size in bytes
     */
    unsigned int GetTotalSize() const;

    bool IsCoinBase() const
    {
        return (vin.size() == 1 && vin[0].prevout.IsNull());
    }

    friend bool operator==(const CTransaction& a, const CTransaction& b)
    {
        return a.hash == b.hash;
    }

    friend bool operator!=(const CTransaction& a, const CTransaction& b)
    {
        return a.hash != b.hash;
    }

    std::string ToString() const;

    bool HasWitness() const
    {
        for (size_t i = 0; i < vin.size(); i++) {
            if (!vin[i].scriptWitness.IsNull()) {
                return true;
            }
        }
        return false;
    }
};

     除了交易輸入和輸出外,還有交易的版本和交易時間鎖nLockTime,交易時間鎖用來控制交易的輸出只有在一段時間後才能被花費,關於該字段在《精通比特幣》第2版有詳細說明。

    另外需要注意的是CTransaction中所有的字段全部用const修飾符來修飾,說明一旦創建出CTransaction對象以後,其中的內容就不能在更改了,因此CTransaction是一個不可變的對象,與之相對應的,還有一個交易的可變版本:

/** A mutable version of CTransaction. */
struct CMutableTransaction
{
    std::vector<CTxIn> vin;
    std::vector<CTxOut> vout;
    int32_t nVersion;
    uint32_t nLockTime;

    CMutableTransaction();
    CMutableTransaction(const CTransaction& tx);

    template <typename Stream>
    inline void Serialize(Stream& s) const {
        SerializeTransaction(*this, s);
    }


    template <typename Stream>
    inline void Unserialize(Stream& s) {
        UnserializeTransaction(*this, s);
    }

    template <typename Stream>
    CMutableTransaction(deserialize_type, Stream& s) {
        Unserialize(s);
    }

    /** Compute the hash of this CMutableTransaction. This is computed on the
     * fly, as opposed to GetHash() in CTransaction, which uses a cached result.
     */
    uint256 GetHash() const;

    friend bool operator==(const CMutableTransaction& a, const CMutableTransaction& b)
    {
        return a.GetHash() == b.GetHash();
    }

    bool HasWitness() const
    {
        for (size_t i = 0; i < vin.size(); i++) {
            if (!vin[i].scriptWitness.IsNull()) {
                return true;
            }
        }
        return false;
    }
};

    CMutableTransaction與CTransaction的字段完全相同,所不同的是字段前面少了const修飾符,因此一個CMutableTransaction對象生成以後,它的字段還可以重新賦值。

3 交易的創建

    瞭解了和交易相關的數據結構以後,本節我們來分析一下比特幣交易是如何創建的。

    通過比特幣的JSONAP命令createrawtransaction可以創建一筆交易,這個命令需要傳入以下形式的json參數:

"1. \"inputs\"                (array, required) A json array of json objects\n"
            "     [\n"
            "       {\n"
            "         \"txid\":\"id\",      (string, required) The transaction id\n"
            "         \"vout\":n,         (numeric, required) The output number\n"
            "         \"sequence\":n      (numeric, optional) The sequence number\n"
            "       } \n"
            "       ,...\n"
            "     ]\n"
            "2. \"outputs\"               (array, required) a json array with outputs (key-value pairs)\n"
            "   [\n"
            "    {\n"
            "      \"address\": x.xxx,    (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n"
            "    },\n"
            "    {\n"
            "      \"data\": \"hex\"        (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n"
            "    }\n"
            "    ,...                     More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
            "                             accepted as second parameter.\n"
            "   ]\n"
            "3. locktime                  (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
            "4. replaceable               (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n"
            "                             Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"

    需要在參數中指定每一筆輸入和輸出。實際中使用比特幣錢包時,這些髒活都由錢包幫我們做了。

    我們看看createrawtransaction是如何創建出一筆比特幣交易的,該命令的實現位於rawtransaction.cpp中:

static UniValue createrawtransaction(const JSONRPCRequest& request)
{
    //輸入參數不合法,拋出異常,提示參數格式
    if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
        throw std::runtime_error(
                // clang-format off
                "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n"
                        "\nCreate a transaction spending the given inputs and creating new outputs.\n"
                        "Outputs can be addresses or data.\n"
                        "Returns hex-encoded raw transaction.\n"
                        "Note that the transaction's inputs are not signed, and\n"
                        "it is not stored in the wallet or transmitted to the network.\n"

                        "\nArguments:\n"
                        "1. \"inputs\"                (array, required) A json array of json objects\n"
                        "     [\n"
                        "       {\n"
                        "         \"txid\":\"id\",      (string, required) The transaction id\n"
                        "         \"vout\":n,         (numeric, required) The output number\n"
                        "         \"sequence\":n      (numeric, optional) The sequence number\n"
                        "       } \n"
                        "       ,...\n"
                        "     ]\n"
                        "2. \"outputs\"               (array, required) a json array with outputs (key-value pairs)\n"
                        "   [\n"
                        "    {\n"
                        "      \"address\": x.xxx,    (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n"
                        "    },\n"
                        "    {\n"
                        "      \"data\": \"hex\"        (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n"
                        "    }\n"
                        "    ,...                     More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
                        "                             accepted as second parameter.\n"
                        "   ]\n"
                        "3. locktime                  (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
                        "4. replaceable               (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n"
                        "                             Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
                        "\nResult:\n"
                        "\"transaction\"              (string) hex string of the transaction\n"

                        "\nExamples:\n"
                + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"")
                + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
                + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"")
                + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
                // clang-format on
        );
    }

    //檢查參數
    RPCTypeCheck(request.params, {
                         UniValue::VARR,
                         UniValueType(), // ARR or OBJ, checked later
                         UniValue::VNUM,
                         UniValue::VBOOL
                 }, true
    );
    if (request.params[0].isNull() || request.params[1].isNull())
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");

    UniValue inputs = request.params[0].get_array();
    const bool outputs_is_obj = request.params[1].isObject();
    UniValue outputs = outputs_is_obj ?
                       request.params[1].get_obj() :
                       request.params[1].get_array();

    //生成交易對象
    CMutableTransaction rawTx;

    //從參數提取交易的鎖定時間(如果提供的話)
    if (!request.params[2].isNull()) {
        int64_t nLockTime = request.params[2].get_int64();
        if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max())
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
        rawTx.nLockTime = nLockTime;
    }

    bool rbfOptIn = request.params[3].isTrue();

    //解析參數,生成交易的輸入
    for (unsigned int idx = 0; idx < inputs.size(); idx++) {
        const UniValue& input = inputs[idx];
        const UniValue& o = input.get_obj();

        //該輸入指向的交易
        uint256 txid = ParseHashO(o, "txid");
        //該輸入指向的UTXO在其交易中的索引
        const UniValue& vout_v = find_value(o, "vout");
        if (!vout_v.isNum())
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key");
        int nOutput = vout_v.get_int();
        if (nOutput < 0)
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");

        uint32_t nSequence;
        if (rbfOptIn) {
            nSequence = MAX_BIP125_RBF_SEQUENCE;
        } else if (rawTx.nLockTime) {
            nSequence = std::numeric_limits<uint32_t>::max() - 1;
        } else {
            nSequence = std::numeric_limits<uint32_t>::max();
        }

        // set the sequence number if passed in the parameters object
        const UniValue& sequenceObj = find_value(o, "sequence");
        if (sequenceObj.isNum()) {
            int64_t seqNr64 = sequenceObj.get_int64();
            if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max()) {
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
            } else {
                nSequence = (uint32_t)seqNr64;
            }
        }

        CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);

        rawTx.vin.push_back(in);
    }

    std::set<CTxDestination> destinations;
    if (!outputs_is_obj) {
        // Translate array of key-value pairs into dict
        UniValue outputs_dict = UniValue(UniValue::VOBJ);
        for (size_t i = 0; i < outputs.size(); ++i) {
            const UniValue& output = outputs[i];
            if (!output.isObject()) {
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair not an object as expected");
            }
            if (output.size() != 1) {
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair must contain exactly one key");
            }
            outputs_dict.pushKVs(output);
        }
        outputs = std::move(outputs_dict);
    }
    //根據參數生成交易的輸出
    for (const std::string& name_ : outputs.getKeys()) {
        if (name_ == "data") {
            std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data");

            CTxOut out(0, CScript() << OP_rawTx.vout.push_back(out)RETURN << data);

        } else {
            //解析出目標地址(比特幣最終流向的地方)
            CTxDestination destination = DecodeDestination(name_);
            if (!IsValidDestination(destination)) {
                throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
            }

            if (!destinations.insert(destination).second) {
                throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
            }
            //根據地址生成交易輸出的鎖定腳本
            CScript scriptPubKey = GetScriptForDestination(destination);
            
            CAmount nAmount = AmountFromValue(outputs[name_]);

            CTxOut out(nAmount, scriptPubKey);
            rawTx.vout.push_back(out);
        }
    }

    if (!request.params[3].isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
    }

    //對交易進行編碼並返回
    return EncodeHexTx(rawTx);
}

    整體的過程並不複雜:從參數中解析出每一筆輸入和輸出,並填寫到CMutableTransaction對象中,最後將對象編碼後返回。但是這裏有兩個問題值得注意:

    (1) 從代碼中沒有看到交易輸入中的解鎖腳本scriptSig;

    (2) 交易輸出的鎖定腳本如何生成的需要了解;

    關於第一個問題,隨後在分析交易簽名時解答,下面我們先來看看第二個問題:交易輸出的鎖定腳本如何生成。生成鎖定腳本的代碼如下:

CScript scriptPubKey = GetScriptForDestination(destination);

    我們來看看這個函數的實現:

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

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

    首先,該方法接受CTxDestination類型的參數,該類型定義如下:

/**
 * A txout script template with a specific destination. It is either:
 *  * CNoDestination: no destination set
 *  * CKeyID: TX_PUBKEYHASH destination (P2PKH)
 *  * CScriptID: TX_SCRIPTHASH destination (P2SH)
 *  * WitnessV0ScriptHash: TX_WITNESS_V0_SCRIPTHASH destination (P2WSH)
 *  * WitnessV0KeyHash: TX_WITNESS_V0_KEYHASH destination (P2WPKH)
 *  * WitnessUnknown: TX_WITNESS_UNKNOWN destination (P2W???)
 *  A CTxDestination is the internal data type encoded in a bitcoin address
 */
typedef boost::variant<CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;

    CTxDestination是boost::variant類型,表示一個特定的比特幣地址。boost::variant可以理解爲一種增強的union類型,從該類型的定義我們也可以看出目前比特幣支持如下幾種類型的地址:

    CKeyID:公鑰,適用於P2PKH標準交易,鎖定腳本中指定比特幣接受者的公鑰;

    CScriptID:適用於P2SH標準交易的地址;

    WitnessV0ScriptHash:適用於P2WSH交易的地址;

    WitnessV0KeyHash:適用於P2WPKH交易的地址;

    可見,針對不同類型的交易,有不同類型的地址,因此生成交易輸出的鎖定腳本時也要根據交易類型來具體處理。爲了避免出現很多if-else分支,比特幣使用boost提供的visitor設計模式的實現來進行處理,提供了CScriptVisitor針對不同類型的地址生成對應的鎖定腳本:

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

    bool operator()(const CNoDestination &dest) const {
        script->clear();
        return false;
    }

    //P2PKH標準交易
    bool operator()(const CKeyID &keyID) const {
        script->clear();
        *script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
        return true;
    }
    //P2SH標準交易
    bool operator()(const CScriptID &scriptID) const {
        script->clear();
        *script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
        return true;
    }
    //P2WSH交易
    bool operator()(const WitnessV0KeyHash& id) const
    {
        script->clear();
        *script << OP_0 << ToByteVector(id);
        return true;
    }
    //P2WKH交易
    bool operator()(const WitnessV0ScriptHash& id) const
    {
        script->clear();
        *script << OP_0 << ToByteVector(id);
        return true;
    }

    bool operator()(const WitnessUnknown& id) const
    {
        script->clear();
        *script << CScript::EncodeOP_N(id.version) << std::vector<unsigned char>(id.program, id.program + id.length);
        return true;
    }
};

    現在,我們已經瞭解到交易輸出的鎖定腳本的生成過程了,暫時先不用管腳本是如何執行的,本文稍後還會詳細說明交易腳本的運行原理。

4 交易簽名

    本節來回答上一節提到的第一個問題:交易輸入的解鎖腳本scriptSig是如何生成的。我們先來搞清楚一個問題:爲什麼需要對交易簽名,簽名的原理又是怎樣?

4.1 爲什麼交易需要簽名

    在比特幣中對交易進行簽名的主要作用是證明某人對某一筆UTXO的所有權。假設張三給李四轉賬1BTC,交易中就會生成一個1BTC的UTXO,爲了確保這筆UTXO隨後只能被李四花費,必須要對交易進行數字簽名。

4.2 交易簽名的原理

    交易簽名實際上就是對交易進行數字簽名。數字簽名之前在加密算法中已經有說明,這裏我們再次回顧一下:假設張三在一條不可靠的通信信道上給李四發送了一條消息msg,李四如何確認發送消息的人就是張三而不是別人呢?

    (1) 張三用hash對msg生成摘要D:

    D=Hash(msg)

    (2) 張三用某種簽名算法F,加上自己的私鑰key對摘要D生成簽名S:

     S=F\left ( key,D \right )

    (3) 張三將簽名S和消息msg一併發送給李四;

    (4) 李四用張三的公鑰pubkey從收到的簽名S中解出消息摘要D:

    D=F\left ( pubkey,S \right )

    (5) 李四對收到的消息msg進行hash得到摘要D1,然後和解出的D對比是否相同,相同就能證明該消息確實來自於張三;

    比特幣交易簽名的是相同的道理,其中msg就是交易,F是比特幣採用的ECDSA橢圓曲線簽名算法,我們以最常見的P2PKH交易爲例來說明。

    假設張三給李四轉賬1BTC,於是張三的錢包生成了交易,交易T中有一筆指向李四的UTXO,價值1BTC。張三爲了確保這筆UTXO以後只能由李四消費,會在鎖定腳本scriptPubKey中設置兩個條件:

    (C1) 消費者必須提供自己的公鑰,並且對公鑰進行hash後的值需要與李四的公鑰的hash值相等,假設李四的公鑰爲P,消費者提供的公鑰爲pubkey,則必須滿足:

    Hash\left ( pubkey \right )=Hash\left ( P \right )

    張三會將李四的公鑰hash即Hash(P)寫入到scriptPubKey腳本中;

    (C2) 消費者提供的簽名必須正確。

    隨後,李四的錢包生成交易T,想花費這筆UTXO,則李四需要提供兩樣東西:李四的公鑰pubkey,和李四對交易T的簽名。

    (1) 李四對交易T採用hash生成摘要D:

    D=Hash\left ( T \right )

    (2) 李四用ECDSA簽名算法,用自己的私鑰key對摘要D生成數字簽名S:

    S=ECDSA\left ( key,D \right )

    (3) 李四將自己的公鑰pubkey和簽名S寫入到交易T的解鎖腳本scriptSig中,然後將交易T廣播到網絡中;

    (4) 網絡中的節點收到交易T,對交易進行驗證,確認李四確實可以花費這筆UTXO。首先對收到的交易T的鎖定腳本中的公鑰pubkey進行hash,看是否和UTXO的鎖定腳本中的公鑰hash相同(滿足條件C1);然後檢查簽名:首先節點對收到的交易進行hash生成交易的摘要D':

    D'=Hash\left ( T \right )

    然後用公鑰pubkey從簽名S中解出交易摘要D:

    D=ECDSA\left ( pubkey, S \right )

    如果D'==D則可以證明這筆交易T確實是李四生成,他有權花費這筆UTXO。

4.3 交易簽名的生成

    我們再次回顧了比特幣交易簽名的原理。接下來我們來看看交易輸入的解鎖腳本(公鑰+簽名)是如何生成的。第3節介紹了通過createrawtransaction生成交易的過程,但是createrawtransaction生成的交易的輸入中還缺少解鎖腳本scriptSig,解鎖腳本需要通過另一個jsonapi:signrawtransaction,這條命令需要的參數如下:

"1. \"hexstring\"     (string, required) The transaction hex string\n"
            "2. \"prevtxs\"       (string, optional) An json array of previous dependent transaction outputs\n"
            "     [               (json array of json objects, or 'null' if none provided)\n"
            "       {\n"
            "         \"txid\":\"id\",             (string, required) The transaction id\n"
            "         \"vout\":n,                  (numeric, required) The output number\n"
            "         \"scriptPubKey\": \"hex\",   (string, required) script key\n"
            "         \"redeemScript\": \"hex\",   (string, required for P2SH or P2WSH) redeem script\n"
            "         \"amount\": value            (numeric, required) The amount spent\n"
            "       }\n"
            "       ,...\n"
            "    ]\n"
            "3. \"privkeys\"     (string, optional) A json array of base58-encoded private keys for signing\n"
            "    [                  (json array of strings, or 'null' if none provided)\n"
            "      \"privatekey\"   (string) private key in base58-encoding\n"
            "      ,...\n"
            "    ]\n"
            "4. \"sighashtype\"     (string, optional, default=ALL) The signature hash type. Must be one of\n"
            "       \"ALL\"\n"
            "       \"NONE\"\n"
            "       \"SINGLE\"\n"
            "       \"ALL|ANYONECANPAY\"\n"
            "       \"NONE|ANYONECANPAY\"\n"
            "       \"SINGLE|ANYONECANPAY\"\n"

    prevtxs提供該交易的輸入所指向的UTXO,privkeys則是解鎖這些UTXO需要的私鑰,sighashtype則指定了只簽名交易中的一部分交易還是對全部交易輸入都進行簽名。

    簽名的過程如下:

UniValue signrawtransaction(const JSONRPCRequest& request) {
#ifdef ENABLE_WALLET
    CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
#endif

    //檢查參數格式,如果格式不正確,拋出異常提示正確用法
    if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
        throw std::runtime_error(
                "signrawtransaction \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype )\n"
                        "\nDEPRECATED. Sign inputs for raw transaction (serialized, hex-encoded).\n"
                        "The second optional argument (may be null) is an array of previous transaction outputs that\n"
                        "this transaction depends on but may not yet be in the block chain.\n"
                        "The third optional argument (may be null) is an array of base58-encoded private\n"
                        "keys that, if given, will be the only keys used to sign the transaction.\n"
#ifdef ENABLE_WALLET
                        + HelpRequiringPassphrase(pwallet) + "\n"
#endif
                        "\nArguments:\n"
                        "1. \"hexstring\"     (string, required) The transaction hex string\n"
                        "2. \"prevtxs\"       (string, optional) An json array of previous dependent transaction outputs\n"
                        "     [               (json array of json objects, or 'null' if none provided)\n"
                        "       {\n"
                        "         \"txid\":\"id\",             (string, required) The transaction id\n"
                        "         \"vout\":n,                  (numeric, required) The output number\n"
                        "         \"scriptPubKey\": \"hex\",   (string, required) script key\n"
                        "         \"redeemScript\": \"hex\",   (string, required for P2SH or P2WSH) redeem script\n"
                        "         \"amount\": value            (numeric, required) The amount spent\n"
                        "       }\n"
                        "       ,...\n"
                        "    ]\n"
                        "3. \"privkeys\"     (string, optional) A json array of base58-encoded private keys for signing\n"
                        "    [                  (json array of strings, or 'null' if none provided)\n"
                        "      \"privatekey\"   (string) private key in base58-encoding\n"
                        "      ,...\n"
                        "    ]\n"
                        "4. \"sighashtype\"     (string, optional, default=ALL) The signature hash type. Must be one of\n"
                        "       \"ALL\"\n"
                        "       \"NONE\"\n"
                        "       \"SINGLE\"\n"
                        "       \"ALL|ANYONECANPAY\"\n"
                        "       \"NONE|ANYONECANPAY\"\n"
                        "       \"SINGLE|ANYONECANPAY\"\n"

                        "\nResult:\n"
                        "{\n"
                        "  \"hex\" : \"value\",           (string) The hex-encoded raw transaction with signature(s)\n"
                        "  \"complete\" : true|false,   (boolean) If the transaction has a complete set of signatures\n"
                        "  \"errors\" : [                 (json array of objects) Script verification errors (if there are any)\n"
                        "    {\n"
                        "      \"txid\" : \"hash\",           (string) The hash of the referenced, previous transaction\n"
                        "      \"vout\" : n,                (numeric) The index of the output to spent and used as input\n"
                        "      \"scriptSig\" : \"hex\",       (string) The hex-encoded signature script\n"
                        "      \"sequence\" : n,            (numeric) Script sequence number\n"
                        "      \"error\" : \"text\"           (string) Verification or signing error related to the input\n"
                        "    }\n"
                        "    ,...\n"
                        "  ]\n"
                        "}\n"

                        "\nExamples:\n"
                + HelpExampleCli("signrawtransaction", "\"myhex\"")
                + HelpExampleRpc("signrawtransaction", "\"myhex\"")
        );

    if (!IsDeprecatedRPCEnabled("signrawtransaction")) {
        throw JSONRPCError(RPC_METHOD_DEPRECATED,
                           "signrawtransaction is deprecated and will be fully removed in v0.18. "
                                   "To use signrawtransaction in v0.17, restart bitcoind with -deprecatedrpc=signrawtransaction.\n"
                                   "Projects should transition to using signrawtransactionwithkey and signrawtransactionwithwallet before upgrading to v0.18");
    }

    //檢查參數
    RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR},
                 true);

    // Make a JSONRPCRequest to pass on to the right signrawtransaction* command
    JSONRPCRequest new_request;
    new_request.id = request.id;
    new_request.params.setArray();

    // For signing with private keys
    if (!request.params[2].isNull()) {
        //通過參數提供的私鑰進行簽名
        new_request.params.push_back(request.params[0]);
        // Note: the prevtxs and privkeys are reversed for signrawtransactionwithkey
        new_request.params.push_back(request.params[2]);
        new_request.params.push_back(request.params[1]);
        new_request.params.push_back(request.params[3]);
        return signrawtransactionwithkey(new_request);
    } else {
#ifdef ENABLE_WALLET
        // Otherwise sign with the wallet which does not take a privkeys parameter
        //通過錢包進行簽名
        new_request.params.push_back(request.params[0]);
        new_request.params.push_back(request.params[1]);
        new_request.params.push_back(request.params[3]);
        return signrawtransactionwithwallet(new_request);
#else
        // If we have made it this far, then wallet is disabled and no private keys were given, so fail here.
        throw JSONRPCError(RPC_INVALID_PARAMETER, "No private keys available.");
#endif
    }

    我們這裏只分析通過提供的私鑰進行簽名的過程,這個是通過signrawtransactionwithkey方法實現:


static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
{
    //參數檢查
    if (request.fHelp || request.params.size() < 2 || request.params.size() > 4)
        throw std::runtime_error(
                "signrawtransactionwithkey \"hexstring\" [\"privatekey1\",...] ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n"
                        "\nSign inputs for raw transaction (serialized, hex-encoded).\n"
                        "The second argument is an array of base58-encoded private\n"
                        "keys that will be the only keys used to sign the transaction.\n"
                        "The third optional argument (may be null) is an array of previous transaction outputs that\n"
                        "this transaction depends on but may not yet be in the block chain.\n"

                        "\nArguments:\n"
                        "1. \"hexstring\"                      (string, required) The transaction hex string\n"
                        "2. \"privkeys\"                       (string, required) A json array of base58-encoded private keys for signing\n"
                        "    [                               (json array of strings)\n"
                        "      \"privatekey\"                  (string) private key in base58-encoding\n"
                        "      ,...\n"
                        "    ]\n"
                        "3. \"prevtxs\"                        (string, optional) An json array of previous dependent transaction outputs\n"
                        "     [                              (json array of json objects, or 'null' if none provided)\n"
                        "       {\n"
                        "         \"txid\":\"id\",               (string, required) The transaction id\n"
                        "         \"vout\":n,                  (numeric, required) The output number\n"
                        "         \"scriptPubKey\": \"hex\",     (string, required) script key\n"
                        "         \"redeemScript\": \"hex\",     (string, required for P2SH or P2WSH) redeem script\n"
                        "         \"amount\": value            (numeric, required) The amount spent\n"
                        "       }\n"
                        "       ,...\n"
                        "    ]\n"
                        "4. \"sighashtype\"                    (string, optional, default=ALL) The signature hash type. Must be one of\n"
                        "       \"ALL\"\n"
                        "       \"NONE\"\n"
                        "       \"SINGLE\"\n"
                        "       \"ALL|ANYONECANPAY\"\n"
                        "       \"NONE|ANYONECANPAY\"\n"
                        "       \"SINGLE|ANYONECANPAY\"\n"

                        "\nResult:\n"
                        "{\n"
                        "  \"hex\" : \"value\",                  (string) The hex-encoded raw transaction with signature(s)\n"
                        "  \"complete\" : true|false,          (boolean) If the transaction has a complete set of signatures\n"
                        "  \"errors\" : [                      (json array of objects) Script verification errors (if there are any)\n"
                        "    {\n"
                        "      \"txid\" : \"hash\",              (string) The hash of the referenced, previous transaction\n"
                        "      \"vout\" : n,                   (numeric) The index of the output to spent and used as input\n"
                        "      \"scriptSig\" : \"hex\",          (string) The hex-encoded signature script\n"
                        "      \"sequence\" : n,               (numeric) Script sequence number\n"
                        "      \"error\" : \"text\"              (string) Verification or signing error related to the input\n"
                        "    }\n"
                        "    ,...\n"
                        "  ]\n"
                        "}\n"

                        "\nExamples:\n"
                + HelpExampleCli("signrawtransactionwithkey", "\"myhex\"")
                + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\"")
        );

    RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);

    //解碼出原始交易
    CMutableTransaction mtx;
    if (!DecodeHexTx(mtx, request.params[0].get_str(), true)) {
        throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
    }

    //從提供的私鑰得到對應的公鑰,並且將私鑰-公鑰對保存在keystore中
    CBasicKeyStore keystore;
    const UniValue& keys = request.params[1].get_array();
    for (unsigned int idx = 0; idx < keys.size(); ++idx) {
        UniValue k = keys[idx];
        CKey key = DecodeSecret(k.get_str());
        if (!key.IsValid()) {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
        }
        keystore.AddKey(key);
    }

    //對交易輸入進行簽名,生成解鎖腳本scriptSig
    return SignTransaction(mtx, request.params[2], &keystore, true, request.params[3]);
}

    這個函數中從提供的私鑰得到對應的公鑰,然後將私鑰-公鑰的配對存放在keystore中,方便後續進行檢索。真正的交易簽名是在SignTransaction中:

UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType)
{
    // Fetch previous transactions (inputs):
    CCoinsView viewDummy;
    CCoinsViewCache view(&viewDummy);
    {
        LOCK2(cs_main, mempool.cs);
        CCoinsViewCache &viewChain = *pcoinsTip;
        CCoinsViewMemPool viewMempool(&viewChain, mempool);
        view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view

        for (const CTxIn& txin : mtx.vin) {
            view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
        }

        view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
    }

    // Add previous txouts given in the RPC call:
    //查找交易輸入指向的UTXO並加入到內存中
    if (!prevTxsUnival.isNull()) {
        UniValue prevTxs = prevTxsUnival.get_array();
        for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) {
            const UniValue& p = prevTxs[idx];
            if (!p.isObject()) {
                throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}");
            }

            UniValue prevOut = p.get_obj();

            //參數檢查
            RPCTypeCheckObj(prevOut,
                            {
                                    {"txid", UniValueType(UniValue::VSTR)},
                                    {"vout", UniValueType(UniValue::VNUM)},
                                    {"scriptPubKey", UniValueType(UniValue::VSTR)},
                            });
            //從參數中得到txid和vout,生成COutPoint指向交易輸入引用的UTXO
            uint256 txid = ParseHashO(prevOut, "txid");

            int nOut = find_value(prevOut, "vout").get_int();
            if (nOut < 0) {
                throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive");
            }

            COutPoint out(txid, nOut);

            //解析參數,得到交易輸入指向的UTXO的鎖定腳本
            std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
            CScript scriptPubKey(pkData.begin(), pkData.end());

            //查找交易輸入指向的UTXO
            {
                const Coin& coin = view.AccessCoin(out);
                if (!coin.IsSpent() && coin.out.scriptPubKey != scriptPubKey) {
                    std::string err("Previous output scriptPubKey mismatch:\n");
                    err = err + ScriptToAsmStr(coin.out.scriptPubKey) + "\nvs:\n"+
                          ScriptToAsmStr(scriptPubKey);
                    throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
                }
                Coin newcoin;
                newcoin.out.scriptPubKey = scriptPubKey;
                newcoin.out.nValue = 0;
                if (prevOut.exists("amount")) {
                    newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount"));
                }
                newcoin.nHeight = 1;
                view.AddCoin(out, std::move(newcoin), true);
            }

            // if redeemScript given and not using the local wallet (private keys
            // given), add redeemScript to the keystore so it can be signed:
            //如果是P2SH或者P2WSH交易,如果指定了贖回腳本也需要加入到keystore中
            if (is_temp_keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) {
                RPCTypeCheckObj(prevOut,
                                {
                                        {"redeemScript", UniValueType(UniValue::VSTR)},
                                });
                UniValue v = find_value(prevOut, "redeemScript");
                if (!v.isNull()) {
                    std::vector<unsigned char> rsData(ParseHexV(v, "redeemScript"));
                    CScript redeemScript(rsData.begin(), rsData.end());
                    keystore->AddCScript(redeemScript);
                    // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
                    keystore->AddCScript(GetScriptForWitness(redeemScript));
                }
            }
        }
    }

    //解析hashtype
    int nHashType = SIGHASH_ALL;
    if (!hashType.isNull()) {
        static std::map<std::string, int> mapSigHashValues = {
                {std::string("ALL"), int(SIGHASH_ALL)},
                {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)},
                {std::string("NONE"), int(SIGHASH_NONE)},
                {std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)},
                {std::string("SINGLE"), int(SIGHASH_SINGLE)},
                {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)},
        };
        std::string strHashType = hashType.get_str();
        if (mapSigHashValues.count(strHashType)) {
            nHashType = mapSigHashValues[strHashType];
        } else {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param");
        }
    }

    bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);

    // Script verification errors
    UniValue vErrors(UniValue::VARR);

    // Use CTransaction for the constant parts of the
    // transaction to avoid rehashing.
    const CTransaction txConst(mtx);
    // Sign what we can:
    //對交易輸入生成簽名
    for (unsigned int i = 0; i < mtx.vin.size(); i++) {
        //找到交易輸入指向的UTXO
        CTxIn& txin = mtx.vin[i];
        const Coin& coin = view.AccessCoin(txin.prevout);
        if (coin.IsSpent()) {
            TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
            continue;
        }
        //拿到輸入指向的UTXO的鎖定腳本
        const CScript& prevPubKey = coin.out.scriptPubKey;
        const CAmount& amount = coin.out.nValue;

        SignatureData sigdata;
        // Only sign SIGHASH_SINGLE if there's a corresponding output:

        //生成交易的解鎖腳本,存放在sigdata中
        if (!fHashSingle || (i < mtx.vout.size())) {
            ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
        }
        sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i));

        //將生成的解鎖腳本填充到交易輸入中
        UpdateTransaction(mtx, i, sigdata);

        ScriptError serror = SCRIPT_ERR_OK;
        //腳本校驗
        if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
            if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
                // Unable to sign input and verification failed (possible attempt to partially sign).
                TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
            } else {
                TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
            }
        }
    }
    bool fComplete = vErrors.empty();

    UniValue result(UniValue::VOBJ);
    result.pushKV("hex", EncodeHexTx(mtx));
    result.pushKV("complete", fComplete);
    if (!vErrors.empty()) {
        result.pushKV("errors", vErrors);
    }

    return result;
}

    如果拋開細節問題,這個函數主要做的其實就是兩件事:爲交易輸入生成解鎖腳本以及校驗腳本。

4.3.1 爲交易輸入生成解鎖腳本

    給交易輸入生成解鎖腳本是在ProduceSignature方法中進行:

bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
{
    std::vector<valtype> result;
    txnouttype whichType;
    //進行簽名
    bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE);
    bool P2SH = false;
    CScript subscript;
    sigdata.scriptWitness.stack.clear();

    //P2SH交易,需要對子腳本進行簽名
    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:
        subscript = CScript(result[0].begin(), result[0].end());
        solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE) && whichType != TX_SCRIPTHASH;
        P2SH = true;
    }

    //P2WKH交易,需要對見證腳本簽名
    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(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0);
        sigdata.scriptWitness.stack = result;
        result.clear();
    }
    //P2WSH交易
    else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH)
    {
        CScript witnessscript(result[0].begin(), result[0].end());
        txnouttype subType;
        solved = solved && SignStep(provider, 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中
    sigdata.scriptSig = PushAll(result);

    // 校驗腳本
    return solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
}

    先來看看函數的參數:provider:keystore,存放了公鑰-私鑰配對,之前的代碼中已經提到過;

    creator:BaseSignatureCreator類型的實例,用於最後對交易生成簽名;

    fromPubKey:CScript類型,交易輸入引用的UTXO的鎖定腳本;

    sigData:SignatureData類型,是輸出參數,用於存放生成的解鎖腳本;

    像洋蔥一樣一層又一層後,最終對交易輸入完成簽名的是SignStep這個方法:

/**
 * Sign scriptPubKey using signature made with creator.
 * Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed),
 * unless whichTypeRet is TX_SCRIPTHASH, in which case scriptSigRet is the redemption script.
 * Returns false if scriptPubKey could not be completely satisfied.
 */
static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey,
                     std::vector<valtype>& ret, txnouttype& whichTypeRet, SigVersion sigversion)
{
    CScript scriptRet;
    uint160 h160;
    ret.clear();

    std::vector<valtype> vSolutions;
    //解析交易輸入引用的UTXO的鎖定腳本,鎖定腳本的類型存在輸出參數whichTypeRet中,鎖定腳本的數據存放在向量vSolutions中
    if (!Solver(scriptPubKey, whichTypeRet, vSolutions))
        return false;

    CKeyID keyID;
    //根據不同的鎖定腳本的類型執行簽名
    switch (whichTypeRet)
    {
        case TX_NONSTANDARD:
        case TX_NULL_DATA:
        case TX_WITNESS_UNKNOWN:
            return false;
        case TX_PUBKEY:     //鎖定腳本是P2PK類型
            keyID = CPubKey(vSolutions[0]).GetID();
            return Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion);
        case TX_PUBKEYHASH: //鎖定腳本是P2PKH類型
            keyID = CKeyID(uint160(vSolutions[0]));
            if (!Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion))
                return false;
            else
            {
                CPubKey vch;
                provider.GetPubKey(keyID, vch);
                ret.push_back(ToByteVector(vch));
            }
            return true;
        case TX_SCRIPTHASH:    //鎖定腳本是P2SH類型
            if (provider.GetCScript(uint160(vSolutions[0]), scriptRet)) {
                ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
                return true;
            }
            return false;

        case TX_MULTISIG:    //鎖定腳本是MultiSig(多重簽名)
            ret.push_back(valtype()); // workaround CHECKMULTISIG bug
            return (SignN(provider, vSolutions, creator, scriptPubKey, ret, sigversion));

        case TX_WITNESS_V0_KEYHASH:    //鎖定腳本是P2WKH類型
            ret.push_back(vSolutions[0]);
            return true;

        case TX_WITNESS_V0_SCRIPTHASH:    //鎖定腳本是P2WSH類型
            CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160.begin());
            if (provider.GetCScript(h160, scriptRet)) {
                ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
                return true;
            }
            return false;

        default:
            return false;
    }
}

    首先是解析交易輸入引用的UTXO的鎖定腳本,然後根據不同的鎖定腳本類型進行簽名。此處我們以最常見的P2PKH交易來作爲例子,其他的交易類型原理差不多。

    (1) 解析鎖定腳本

    先來過下源碼,看看鎖定腳本是如何解析的:

bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet)
{
    // Templates
    //P2PK/P2PKH/MULTISIG交易的鎖定腳本模板
    static std::multimap<txnouttype, CScript> mTemplates;
    if (mTemplates.empty())
    {
        // Standard tx, sender provides pubkey, receiver adds signature
        mTemplates.insert(std::make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG));

        // Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey
        mTemplates.insert(std::make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));

        // Sender provides N pubkeys, receivers provides M signatures
        mTemplates.insert(std::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
    //鎖定腳本是P2SH類型,這種類型的鎖定腳本的格式:OP_HASH160 20 [20 byte hash] OP_EQUAL,腳本中2-22爲20字節的數據,放到vSolutionsRet中
    if (scriptPubKey.IsPayToScriptHash())
    {
        typeRet = TX_SCRIPTHASH;
        std::vector<unsigned char> hashBytes(scriptPubKey.begin()+2, scriptPubKey.begin()+22);
        vSolutionsRet.push_back(hashBytes);
        return true;
    }

    //P2WKH/P2WSH類型的處理
    int witnessversion;
    std::vector<unsigned char> witnessprogram;
    if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
        if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_KEYHASH_SIZE) {
            typeRet = TX_WITNESS_V0_KEYHASH;
            vSolutionsRet.push_back(witnessprogram);
            return true;
        }
        if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
            typeRet = TX_WITNESS_V0_SCRIPTHASH;
            vSolutionsRet.push_back(witnessprogram);
            return true;
        }
        if (witnessversion != 0) {
            typeRet = TX_WITNESS_UNKNOWN;
            vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
            vSolutionsRet.push_back(std::move(witnessprogram));
            return true;
        }
        return false;
    }

    // Provably prunable, data-carrying output
    //
    // So long as script passes the IsUnspendable() test and all but the first
    // byte passes the IsPushOnly() test we don't care what exactly is in the
    // script.
    if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) {
        typeRet = TX_NULL_DATA;
        return true;
    }

    // Scan templates
    //P2PKH/P2PK/MULTISIG類型,掃描模板進行解析
    const CScript& script1 = scriptPubKey;
    for (const std::pair<txnouttype, CScript>& tplate : mTemplates)
    {
        const CScript& script2 = tplate.second;
        vSolutionsRet.clear();

        opcodetype opcode1, opcode2;
        std::vector<unsigned char> vch1, vch2;

        // Compare
        //pc1指向UTXO的鎖定腳本,pc2指向腳本模板
        CScript::const_iterator pc1 = script1.begin();
        CScript::const_iterator pc2 = script2.begin();
        while (true)
        {
            //兩個指針同時到了結尾,則找到和模板匹配的交易類型,返回
            if (pc1 == script1.end() && pc2 == script2.end())
            {
                // Found a match
                typeRet = tplate.first;
                if (typeRet == TX_MULTISIG)
                {
                    // Additional checks for TX_MULTISIG:
                    unsigned char m = vSolutionsRet.front()[0];
                    unsigned char n = vSolutionsRet.back()[0];
                    if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n)
                        return false;
                }
                return true;
            }
            //得到操作符和操作數
            if (!script1.GetOp(pc1, opcode1, vch1))
                break;
            if (!script2.GetOp(pc2, opcode2, vch2))
                break;

            // Template matching opcodes:
            //幾種操作符的處理
            if (opcode2 == OP_PUBKEYS)
            {
                while (CPubKey::ValidSize(vch1))
                {
                    vSolutionsRet.push_back(vch1);
                    if (!script1.GetOp(pc1, opcode1, vch1))
                        break;
                }
                if (!script2.GetOp(pc2, opcode2, vch2))
                    break;
                // Normal situation is to fall through
                // to other if/else statements
            }

            if (opcode2 == OP_PUBKEY)
            {
                if (!CPubKey::ValidSize(vch1))
                    break;
                vSolutionsRet.push_back(vch1);
            }
            else if (opcode2 == OP_PUBKEYHASH)
            {
                if (vch1.size() != sizeof(uint160))
                    break;
                vSolutionsRet.push_back(vch1);
            }
            else if (opcode2 == OP_SMALLINTEGER)
            {   // Single-byte small integer pushed onto vSolutions
                if (opcode1 == OP_0 ||
                    (opcode1 >= OP_1 && opcode1 <= OP_16))
                {
                    char n = (char)CScript::DecodeOP_N(opcode1);
                    vSolutionsRet.push_back(valtype(1, n));
                }
                else
                    break;
            }
            else if (opcode1 != opcode2 || vch1 != vch2)
            {
                // Others must match exactly
                break;
            }
        }
    }

    vSolutionsRet.clear();
    typeRet = TX_NONSTANDARD;
    return false;
}

    上面的代碼針對不同的類型有不同的處理,對於P2PKH/P2PK/MULTISIG,是通過模板來進行匹配處理的。看代碼可能不能一下子理解這裏的邏輯,畫個圖來說明一下會非常清楚。以P2PKH爲例:

    首先鎖定腳本的模板如下:

    然後看看UTXO的鎖定腳本是什麼樣的,回顧之前創建交易的代碼中爲交易輸出生成鎖定腳本的代碼(CScriptVisitor),對於P2PKH類型:

bool operator()(const CKeyID &keyID) const {
        script->clear();
        *script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
        return true;
    }

    這裏要注意下CScript重載的<<操作符的以vector爲參數的實現,其中keyID是160位無符號整數,佔20字節。根據CScript重載的<<操作符的實現,最終生成的鎖定腳本的長下面這樣:

    

   20代表腳本中隨後的一個元素是20字節的公鑰。

    根據代碼,解析的時候將從模板腳本和鎖定腳本的開始處逐一解析每個操作符,首先OP_DUP和OP_HASH什麼都不做,指針向前移動即可,進行到下面的狀態的時候,需要留神了:

   模板的指針好處理,直接前移,但是鎖定腳本的處理就不一樣了,看看解析操作符和參數的代碼,已經根據例子對代碼做了註釋:

bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet)
{
    opcodeRet = OP_INVALIDOPCODE;
    if (pvchRet)
        pvchRet->clear();
    if (pc >= end)
        return false;

    // Read instruction
    if (end - pc < 1)
        return false;
    //在我們的例子中opcode將會是20,然後指針前移到下一個元素(20字節的公鑰)
    unsigned int opcode = *pc++;

    // Immediate operand
    //OP_PUSHDATA4的值爲0x4e,例子中opcode爲20,所以進入分支
    if (opcode <= OP_PUSHDATA4)
    {
        unsigned int nSize = 0;
        //opcode爲20,OP_PUSHDATA1爲0x4c,進入此分支
        if (opcode < OP_PUSHDATA1)
        {
            nSize = opcode;
        }
        else if (opcode == OP_PUSHDATA1)
        {
            if (end - pc < 1)
                return false;
            nSize = *pc++;
        }
        else if (opcode == OP_PUSHDATA2)
        {
            if (end - pc < 2)
                return false;
            nSize = ReadLE16(&pc[0]);
            pc += 2;
        }
        else if (opcode == OP_PUSHDATA4)
        {
            if (end - pc < 4)
                return false;
            nSize = ReadLE32(&pc[0]);
            pc += 4;
        }
        if (end - pc < 0 || (unsigned int)(end - pc) < nSize)
            return false;
        //將隨後20字節的公鑰填充的輸出參數中
        if (pvchRet)
            pvchRet->assign(pc, pc + nSize);
        //指針移動20字節
        pc += nSize;
    }

    opcodeRet = static_cast<opcodetype>(opcode);
    return true;
}

    最終,20字節的公鑰值被提取並保存再來,執行完操作符解析後的狀態如下:

   (2) 簽名

    解析完UTXO的鎖定腳本後,接下來就要開始簽名了,還是以上面的例子來分析,SignStep函數在調用Solver解析完鎖定腳本,得到鎖定腳本的類型和其中的參數,然後根據不同的鎖定腳本類型進行處理,對於P2PKH類型,處理如下:

case TX_PUBKEYHASH:
        keyID = CKeyID(uint160(vSolutions[0]));
        if (!Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion))
            return false;
        else
        {
            CPubKey vch;
            provider.GetPubKey(keyID, vch);
            ret.push_back(ToByteVector(vch));
        }
        return true;

    首先拿到Solver函數解析出的20字節的公鑰,然後調用Sign1函數進行簽名:

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

    很簡單,通過傳入的參數BaseSignatureCreator::CreateSig進行簽名,將生成的簽名保存在輸出參數ret中。這裏參數中傳入的BaseSignatureCreator是MutableTransactionSignatureCreator類型,回憶下前面ProduceSignature的代碼:

if (!fHashSingle || (i < mtx.vout.size())) {
            ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
        }

    可以看到,創建MutableTransactionSignatureCreator時,傳入了交易、要簽名的交易輸入的索引,交易輸入引用的UTXO的比特幣數量以及hashtype,來看看簽名的過程:

bool TransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
{
    //從keystore中拿到與公鑰對應的私鑰
    CKey key;
    if (!provider.GetKey(address, key))
        return false;

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

    //對交易生成hash摘要
    uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
    
    //用私鑰對交易的hash摘要進行數字簽名,簽名保存在輸出參數vchSig中
    if (!key.Sign(hash, vchSig))
        return false;
    vchSig.push_back((unsigned char)nHashType);
    return true;
}

    到此爲止,對交易的簽名就算是完成了。

4.3.2 校驗腳本

   對交易簽名完成以後,交易的輸入就有了解鎖腳本,接下來就要進行校驗,看生成的這個解鎖腳本是不是能夠和交易輸入指向的UTXO的鎖定腳本匹配(一把鑰匙一把鎖,一個蘿蔔一個坑)。無論是本地還是網絡中的其他節點,對於新交易,都必須對腳本進行校驗後才能將交易添加到內存交易池裏等待被礦工挖取。

4.3.2.1 比特幣腳本語言

    比特幣的腳本語言是一種基於堆棧的非圖靈完備的腳本語言。我們知道區塊鏈2.0的一個標識是智能合約,其代表就是以太坊,以太坊提供了solidity語言,利用該語言可以編寫出非常複雜的去中心化DApp。比特幣的腳本語言的作用和solidity類似,利用該腳本語言同樣也可以寫出具有複雜邏輯的執行腳本。我們之前介紹的P2PKH就是用腳本語言編寫的一個簡單腳本的例子。

    比特幣的腳本語言不支持類似於for的循環,這樣可以避免不懷好意的節點編寫出具有無限循環的惡意腳本引發DoS攻擊。但是像if-else這樣的邏輯是支持的。

    比特幣腳本語言支持的操作符定義在枚舉opcode中,這裏列出來,讀者可以瞭解一下:

/** Script opcodes */
enum opcodetype
{
    // push value
    OP_0 = 0x00,
    OP_FALSE = OP_0,
    OP_PUSHDATA1 = 0x4c,
    OP_PUSHDATA2 = 0x4d,
    OP_PUSHDATA4 = 0x4e,
    OP_1NEGATE = 0x4f,
    OP_RESERVED = 0x50,
    OP_1 = 0x51,
    OP_TRUE=OP_1,
    OP_2 = 0x52,
    OP_3 = 0x53,
    OP_4 = 0x54,
    OP_5 = 0x55,
    OP_6 = 0x56,
    OP_7 = 0x57,
    OP_8 = 0x58,
    OP_9 = 0x59,
    OP_10 = 0x5a,
    OP_11 = 0x5b,
    OP_12 = 0x5c,
    OP_13 = 0x5d,
    OP_14 = 0x5e,
    OP_15 = 0x5f,
    OP_16 = 0x60,

    // control
    OP_NOP = 0x61,
    OP_VER = 0x62,
    OP_IF = 0x63,
    OP_NOTIF = 0x64,
    OP_VERIF = 0x65,
    OP_VERNOTIF = 0x66,
    OP_ELSE = 0x67,
    OP_ENDIF = 0x68,
    OP_VERIFY = 0x69,
    OP_RETURN = 0x6a,

    // stack ops
    OP_TOALTSTACK = 0x6b,
    OP_FROMALTSTACK = 0x6c,
    OP_2DROP = 0x6d,
    OP_2DUP = 0x6e,
    OP_3DUP = 0x6f,
    OP_2OVER = 0x70,
    OP_2ROT = 0x71,
    OP_2SWAP = 0x72,
    OP_IFDUP = 0x73,
    OP_DEPTH = 0x74,
    OP_DROP = 0x75,
    OP_DUP = 0x76,
    OP_NIP = 0x77,
    OP_OVER = 0x78,
    OP_PICK = 0x79,
    OP_ROLL = 0x7a,
    OP_ROT = 0x7b,
    OP_SWAP = 0x7c,
    OP_TUCK = 0x7d,

    // splice ops
    OP_CAT = 0x7e,
    OP_SUBSTR = 0x7f,
    OP_LEFT = 0x80,
    OP_RIGHT = 0x81,
    OP_SIZE = 0x82,

    // bit logic
    OP_INVERT = 0x83,
    OP_AND = 0x84,
    OP_OR = 0x85,
    OP_XOR = 0x86,
    OP_EQUAL = 0x87,
    OP_EQUALVERIFY = 0x88,
    OP_RESERVED1 = 0x89,
    OP_RESERVED2 = 0x8a,

    // numeric
    OP_1ADD = 0x8b,
    OP_1SUB = 0x8c,
    OP_2MUL = 0x8d,
    OP_2DIV = 0x8e,
    OP_NEGATE = 0x8f,
    OP_ABS = 0x90,
    OP_NOT = 0x91,
    OP_0NOTEQUAL = 0x92,

    OP_ADD = 0x93,
    OP_SUB = 0x94,
    OP_MUL = 0x95,
    OP_DIV = 0x96,
    OP_MOD = 0x97,
    OP_LSHIFT = 0x98,
    OP_RSHIFT = 0x99,

    OP_BOOLAND = 0x9a,
    OP_BOOLOR = 0x9b,
    OP_NUMEQUAL = 0x9c,
    OP_NUMEQUALVERIFY = 0x9d,
    OP_NUMNOTEQUAL = 0x9e,
    OP_LESSTHAN = 0x9f,
    OP_GREATERTHAN = 0xa0,
    OP_LESSTHANOREQUAL = 0xa1,
    OP_GREATERTHANOREQUAL = 0xa2,
    OP_MIN = 0xa3,
    OP_MAX = 0xa4,

    OP_WITHIN = 0xa5,

    // crypto
    OP_RIPEMD160 = 0xa6,
    OP_SHA1 = 0xa7,
    OP_SHA256 = 0xa8,
    OP_HASH160 = 0xa9,
    OP_HASH256 = 0xaa,
    OP_CODESEPARATOR = 0xab,
    OP_CHECKSIG = 0xac,
    OP_CHECKSIGVERIFY = 0xad,
    OP_CHECKMULTISIG = 0xae,
    OP_CHECKMULTISIGVERIFY = 0xaf,

    // expansion
    OP_NOP1 = 0xb0,
    OP_CHECKLOCKTIMEVERIFY = 0xb1,
    OP_NOP2 = OP_CHECKLOCKTIMEVERIFY,
    OP_CHECKSEQUENCEVERIFY = 0xb2,
    OP_NOP3 = OP_CHECKSEQUENCEVERIFY,
    OP_NOP4 = 0xb3,
    OP_NOP5 = 0xb4,
    OP_NOP6 = 0xb5,
    OP_NOP7 = 0xb6,
    OP_NOP8 = 0xb7,
    OP_NOP9 = 0xb8,
    OP_NOP10 = 0xb9,


    // template matching params
    OP_SMALLINTEGER = 0xfa,
    OP_PUBKEYS = 0xfb,
    OP_PUBKEYHASH = 0xfd,
    OP_PUBKEY = 0xfe,

    OP_INVALIDOPCODE = 0xff,
};

    這些操作符分爲幾類,比如OP_N(N=0,1,...,16)用來push值,if,else等流程控制的操作符,加減乘除等數學運算操作符,用來操作堆棧的操作符等等。

4.3.2.2 標準的比特幣交易

    比特幣當前支持的標準交易都是用腳本語言編寫的可執行腳本的例子,可以理解成是比特幣上用腳本語言編寫的智能合約。本小節我們簡單的過一下這些標準交易。

    (1)P2PKH交易

     最常見的比特幣標準交易,全稱爲Pay to public key hash的簡稱。從名字中就可以猜出這種交易是基於公鑰的。這種交易會指定新的UTXO的接收者的公鑰,花費UTXO的人須提供自己的公鑰和交易簽名,只有滿足下面兩個條件時纔可以消耗UTXO:

     (C1) 消費者提供的公鑰的hash必須和UTXO上指定的公鑰的hash相同;

     (C2) 消費者的簽名必須正確(對交易生成摘要D,然後用給定的公鑰從簽名中求得的交易摘要D‘必須和D相同)

     只要能滿足上面兩個條件,就能證明某人對UTXO的所有權,而能滿足上面這兩個條件的腳本如下:

     Signature  |  Public key  |  OP_DU  |  OP_HASH160  |  OP_PUBKEYHASH  |  OP_EQUALVERIFY  |  OP_CHECKSIG

     將交易的解鎖腳本和鎖定腳本合併起來就形成了上述腳本,這段腳本最終由腳本引擎解釋並執行。

     (2) P2PK交易

    Pay To Public Key的簡稱,這種交易更簡單,與P2PKH不同的地方在於他不用驗證UTXO上指定的公鑰與消費者提供的公鑰的hash,只檢查簽名,相應的腳本如下:

     Signature  |  Public Key  |  OP_CHECKSIG

     (3) MULTISIG交易

  多重簽名交易,這種交易用在要求更加嚴格的場合,簡單的說就是要支出一筆錢,需要得到N個人中至少M個人的簽字認可(M <= N)才行。這種交易會在UTXO上指定N個公鑰(N個監管人的公鑰),需要提取資金時,必須提供至少M個簽名,只有所有的簽名都能滿足才行。以3個人管理一筆資金,提取時必須得到其中2個人的簽名爲例,滿足條件的腳本如下:

     0  |  Sig1 |  Sig2  |  2  |  Public Key 1  | Public Key 2  | Public Key 3  |  3  |  OP_CHECKMULTISIG

     注意這裏MULTISIG的腳本必須以0開始,這源自與OP_CHECKMULTISIG操作符在執行過程中的一個bug。

     (4) P2SH交易

    注意到MULTISIG多重簽名的交易存在一些問題,假設A要向B支付一筆資金,但是B要求這筆資金需要MULTISIG,也就是要花這筆錢需要B自己以及他的若干合夥人同時簽名。這樣A的錢包生成給B轉賬的交易時,交易的鎖定腳本會包含很多長度很長的公鑰,導致交易所佔用的體積會相當大,這會導致A要多支付不少的交易費用。P2SH交易可以解決這個問題。如果一個交易的鎖定腳本非常複雜,就可以考慮用P2SH。

     仍舊以2-3多重簽名爲例,鎖定腳本我們假設爲S,則:

     S = 2  |  Public Key 1  | Public Key 2  | Public Key 3  |  3  |  OP_CHECKMULTISIG

     由於公鑰的長度很長,所以上面的鎖定腳本最終的體積會很大,如果改用P2SH交易,只需要對上面長長的鎖定腳本進行hash處理,生成20字節的腳本hash,這樣鎖定腳本就變成了下面這樣:

    OP_HASH160  |  [20 bytes script hash]  |  OP_EQUAL

    然後消費者爲了支出資金,必須提供一個贖回腳本(redeem script),這個贖回腳本redeem和S是相同的,當執行腳本校驗時,首先將消費者提供的贖回腳本進行hash,然後比較hash是否和鎖定腳本中的hash相同:

     redeem  |  OP_HASH160  |  20 bytes script hash |  OP_EQUAL

     如果上一步執行成功,則執行解鎖腳本:

     Sig1  |  Sig2  |  2  |  Public Key 1  | Public Key 2  | Public Key 3  |  3  |  OP_CHECKMULTISIG

  與多重簽名相比,P2SH最大的不同是鎖定腳本變短了,而解鎖腳本變長了,這樣額外的交易費變成了由UTXO的消費者來支付,而不是支付者。

    以上這些標準交易腳本最終由腳本引擎解釋執行,我們在下節在詳細說明。

4.3.2.3 交易腳本的運行原理

    比特幣的腳本引擎是基於堆棧的,腳本解釋器會逐一解析腳本的操作符,根據操作符進行入棧和彈棧的操作。仍然以P2PKH交易爲例,比特幣會將UTXO的鎖定腳本和解鎖腳本合併在一起,然後在堆棧上進行操作,我們用圖來表示整個過程會更清楚一些:

    (1)簽名入棧

    

    (2)公鑰入棧

    

     (3)複製棧頂元素

    

     (4)棧頂元素彈棧,進行hash後的值入棧

    

     (5)公鑰hash入棧

    

     (6)OP_EQUALVERIFY將堆棧中的前兩個操作數彈棧,並進行比較,相同則繼續,不相等出錯。

    

     (7)彈出堆棧中的兩個操作數,進行簽名校驗,成功棧頂入棧TRUE,否則棧頂入棧FALSE

    

     最後,如果棧頂元素爲TRUE則腳本運行的結果爲真,腳本驗證通過。

     上面描述的交易腳本的運行過程,位於interpreter.cpp(名字很形象,一看就知道文件中是腳本解釋器相關的實現)的EvalScript函數中,該函數的原型如下:

bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)

     該函數的參數如下:

     stack:輸出參數,上文圖中的堆棧;

     script:要解釋的腳本;

     flags:校驗相關的標識;

     checker:用於校驗交易簽名;

     serror:存放錯誤信息;

     EvalScript在執行時,會從頭依次開始解析輸入腳本,並根據不同的操作符更改堆棧。整個過程就是前文圖中描述的那樣,讀者可以自行在源碼中閱讀這段代碼,這裏就不在列出了。

     我們在回頭看看交易簽名的最後一步,當ProduceSignature函數爲交易輸入生成了簽名以後,最後一步就是要進行腳本校驗了,如果腳本校驗通過,說明確實有權消費交易輸入引用的UTXO,該交易隨後就可以添加到內存池並廣播到網絡中了,在SignTransaction的最後:

if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
            if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
                // Unable to sign input and verification failed (possible attempt to partially sign).
                TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
            } else {
                TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
            }
        }

     可以看到調用了VerifyScript來校驗腳本,傳入了交易輸入的解鎖腳本以及輸入引用的UTXO的鎖定腳本,另外還有一個TransactionSignatureChecker對象,用於校驗交易簽名,這裏列出VerifyScript的源碼供讀者參考:

bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
{
    static const CScriptWitness emptyWitness;
    if (witness == nullptr) {
        witness = &emptyWitness;
    }
    bool hadWitness = false;

    set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);

    if ((flags & SCRIPT_VERIFY_SIGPUSHONLY) != 0 && !scriptSig.IsPushOnly()) {
        return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY);
    }

    //堆棧
    std::vector<std::vector<unsigned char> > stack, stackCopy;
    
    //解析並執行解鎖腳本,如果解析過程中出錯則返回,以P2PKH交易爲例,執行完後堆棧中將保存公鑰和簽名
    if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror))
        // serror is set
        return false;
    if (flags & SCRIPT_VERIFY_P2SH)
        stackCopy = stack;
    //繼續解析執行鎖定腳本
    if (!EvalScript(stack, scriptPubKey, flags, checker, SigVersion::BASE, serror))
        // serror is set
        return false;
    
    //如果最終堆棧爲空,則說明有錯誤,返回
    if (stack.empty())
        return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
    //堆棧中拿出腳本運行的結果
    if (CastToBool(stack.back()) == false)
        return set_error(serror, SCRIPT_ERR_EVAL_FALSE);

    //其他一些交易類型的處理
    // Bare witness programs
    int witnessversion;
    std::vector<unsigned char> witnessprogram;
    if (flags & SCRIPT_VERIFY_WITNESS) {
        if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
            hadWitness = true;
            if (scriptSig.size() != 0) {
                // The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
                return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
            }
            if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
                return false;
            }
            // Bypass the cleanstack check at the end. The actual stack is obviously not clean
            // for witness programs.
            stack.resize(1);
        }
    }

    // Additional validation for spend-to-script-hash transactions:
    if ((flags & SCRIPT_VERIFY_P2SH) && scriptPubKey.IsPayToScriptHash())
    {
        // scriptSig must be literals-only or validation fails
        if (!scriptSig.IsPushOnly())
            return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY);

        // Restore stack.
        swap(stack, stackCopy);

        // stack cannot be empty here, because if it was the
        // P2SH  HASH <> EQUAL  scriptPubKey would be evaluated with
        // an empty stack and the EvalScript above would return false.
        assert(!stack.empty());

        const valtype& pubKeySerialized = stack.back();
        CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end());
        popstack(stack);

        if (!EvalScript(stack, pubKey2, flags, checker, SigVersion::BASE, serror))
            // serror is set
            return false;
        if (stack.empty())
            return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
        if (!CastToBool(stack.back()))
            return set_error(serror, SCRIPT_ERR_EVAL_FALSE);

        // P2SH witness program
        if (flags & SCRIPT_VERIFY_WITNESS) {
            if (pubKey2.IsWitnessProgram(witnessversion, witnessprogram)) {
                hadWitness = true;
                if (scriptSig != CScript() << std::vector<unsigned char>(pubKey2.begin(), pubKey2.end())) {
                    // The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we
                    // reintroduce malleability.
                    return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
                }
                if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
                    return false;
                }
                // Bypass the cleanstack check at the end. The actual stack is obviously not clean
                // for witness programs.
                stack.resize(1);
            }
        }
    }

    // The CLEANSTACK check is only performed after potential P2SH evaluation,
    // as the non-P2SH evaluation of a P2SH script will obviously not result in
    // a clean stack (the P2SH inputs remain). The same holds for witness evaluation.
    if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0) {
        // Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK
        // would be possible, which is not a softfork (and P2SH should be one).
        assert((flags & SCRIPT_VERIFY_P2SH) != 0);
        assert((flags & SCRIPT_VERIFY_WITNESS) != 0);
        if (stack.size() != 1) {
            return set_error(serror, SCRIPT_ERR_CLEANSTACK);
        }
    }

    if (flags & SCRIPT_VERIFY_WITNESS) {
        // We can't check for correct unexpected witness data if P2SH was off, so require
        // that WITNESS implies P2SH. Otherwise, going from WITNESS->P2SH+WITNESS would be
        // possible, which is not a softfork.
        assert((flags & SCRIPT_VERIFY_P2SH) != 0);
        if (!hadWitness && !witness->IsNull()) {
            return set_error(serror, SCRIPT_ERR_WITNESS_UNEXPECTED);
        }
    }

    return set_success(serror);
}

     可以看到源碼中主要就是調用前面提到的EvalScript函數解析腳本並更新堆棧,還是比較容易理解的。

5 交易廣播與接收

    經過上一節的處理以後,交易的腳本以及腳本的驗證都通過,一筆交易就正式創建成功了,接下來就需要將交易加入到節點的 內存交易池中,等待被挖礦,同時節點還要將交易廣播到網絡中,其他節點對交易進行驗證,無誤以後也加入到自己的交易池裏。

5.1 廣播交易

    創建好的交易可以通過jsonapi sendrawtransaction命令廣播到網絡當中,我們直接看這個命令的實現:

static UniValue sendrawtransaction(const JSONRPCRequest& request)
{
    //參數檢查
    if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
        throw std::runtime_error(
                "sendrawtransaction \"hexstring\" ( allowhighfees )\n"
                        "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n"
                        "\nAlso see createrawtransaction and signrawtransaction calls.\n"
                        "\nArguments:\n"
                        "1. \"hexstring\"    (string, required) The hex string of the raw transaction)\n"
                        "2. allowhighfees    (boolean, optional, default=false) Allow high fees\n"
                        "\nResult:\n"
                        "\"hex\"             (string) The transaction hash in hex\n"
                        "\nExamples:\n"
                        "\nCreate a transaction\n"
                + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
                "Sign the transaction, and get back the hex\n"
                + HelpExampleCli("signrawtransaction", "\"myhex\"") +
                "\nSend the transaction (signed hex)\n"
                + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
                "\nAs a json rpc call\n"
                + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
        );

    std::promise<void> promise;

    RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});

    // parse hex string from parameter
    //解碼出交易
    CMutableTransaction mtx;
    if (!DecodeHexTx(mtx, request.params[0].get_str()))
        throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
    CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
    const uint256& hashTx = tx->GetHash();

    CAmount nMaxRawTxFee = maxTxFee;
    if (!request.params[1].isNull() && request.params[1].get_bool())
        nMaxRawTxFee = 0;

    { // cs_main scope
        LOCK(cs_main);
        CCoinsViewCache &view = *pcoinsTip;
        //判斷交易是否已經在內存池中,交易是否有輸出已經被花費掉了
        bool fHaveChain = false;
        for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
            const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o));
            fHaveChain = !existingCoin.IsSpent();
        }
        bool fHaveMempool = mempool.exists(hashTx);
        //如果交易在內存池中不存在,並且交易所有的輸出都未花費,則將交易加入內存交易池中
        if (!fHaveMempool && !fHaveChain) {
            // push to local node and sync with wallets
            CValidationState state;
            bool fMissingInputs;
            //嘗試將交易加入交易池
            if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
                                    nullptr /* plTxnReplaced */, false /* bypass_limits */, nMaxRawTxFee)) {
                if (state.IsInvalid()) {
                    throw JSONRPCError(RPC_TRANSACTION_REJECTED, FormatStateMessage(state));
                } else {
                    if (fMissingInputs) {
                        throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs");
                    }
                    throw JSONRPCError(RPC_TRANSACTION_ERROR, FormatStateMessage(state));
                }
            } else {
                // If wallet is enabled, ensure that the wallet has been made aware
                // of the new transaction prior to returning. This prevents a race
                // where a user might call sendrawtransaction with a transaction
                // to/from their wallet, immediately call some wallet RPC, and get
                // a stale result because callbacks have not yet been processed.
                CallFunctionInValidationInterfaceQueue([&promise] {
                    promise.set_value();
                });
            }
        } else if (fHaveChain) {
            throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
        } else {
            // Make sure we don't block forever if re-sending
            // a transaction already in mempool.
            promise.set_value();
        }

    } // cs_main

    promise.get_future().wait();

    if(!g_connman)
        throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");

    //發送INV消息,將交易廣播到網絡中
    CInv inv(MSG_TX, hashTx);
    g_connman->ForEachNode([&inv](CNode* pnode)
                           {
                               pnode->PushInventory(inv);
                           });

    return hashTx.GetHex();
}

    只有交易在交易池中不存在,並且交易的每一筆輸出都未被花費,才能將交易添加到交易池中。

    最後會生成一個INV消息加入到集合當中,等待廣播到網絡中。

5.2 接收交易

    再來看看網絡中的節點收到一筆新交易後如何處理。ProcessMessage中對INV消息的處理:

else if (strCommand == NetMsgType::INV)
{
    //讀出數據
    std::vector<CInv> vInv;
    vRecv >> vInv;
    if (vInv.size() > MAX_INV_SZ)
    {
        LOCK(cs_main);
        Misbehaving(pfrom->GetId(), 20, strprintf("message inv size() = %u", vInv.size()));
        return false;
    }

    bool fBlocksOnly = !fRelayTxes;

    // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true
    if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))
    fBlocksOnly = false;

    LOCK(cs_main);

    uint32_t nFetchFlags = GetFetchFlags(pfrom);

    //處理收到的每一個INV消息
    for (CInv &inv : vInv)
    {
        if (interruptMsgProc)
        return true;

        //判斷交易是否已經存在於區塊鏈上
        bool fAlreadyHave = AlreadyHave(inv);
        LogPrint(BCLog::NET, "got inv: %s  %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->GetId());

        if (inv.type == MSG_TX) {
            inv.type |= nFetchFlags;
        }

        //如果是一個區塊
        if (inv.type == MSG_BLOCK) {
            UpdateBlockAvailability(pfrom->GetId(), inv.hash);
            if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) {
                // We used to request the full block here, but since headers-announcements are now the
                // primary method of announcement on the network, and since, in the case that a node
                // fell back to inv we probably have a reorg which we should get the headers for first,
                // we now only provide a getheaders response here. When we receive the headers, we will
                // then ask for the blocks we need.
                connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), inv.hash));
                LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->GetId());
            }
        }
        //如果收到的是一筆交易
        else
        {
            pfrom->AddInventoryKnown(inv);
            if (fBlocksOnly) {
                LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId());
            } else if (!fAlreadyHave && !fImporting && !fReindex && !IsInitialBlockDownload()) {
                //如果交易不存在,則將該交易加入到請求集合中
                pfrom->AskFor(inv);
            }
        }

        // Track requests for our stuff
        GetMainSignals().Inventory(inv.hash);
    }
}

    如果節點不存在收到的交易hash,則會調用AskFor來請求交易數據,AskFor會將請求加入到隊列中(mapAskFor)。之後會遍歷該隊列,生成GETDATA消息,批量拉取本地缺失的交易(或區塊)數據(參考SendMessage函數):

//遍歷隊列
while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow)
{
    const CInv& inv = (*pto->mapAskFor.begin()).second;
    if (!AlreadyHave(inv))
    {
        //交易(或者區塊)數據不存在,插入vGetData集合,當集合中數據達到1000+時,發送GETDATA消息批量獲取數據
        LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId());
        vGetData.push_back(inv);
        if (vGetData.size() >= 1000)
        {
            connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
            vGetData.clear();
        }
    } else {
        //If we're not going to ask, don't expect a response.
        //已經存在了,從集合裏刪除
        pto->setAskFor.erase(inv.hash);
    }
    //從隊列中刪除
    pto->mapAskFor.erase(pto->mapAskFor.begin());
}

//如果集合不空
if (!vGetData.empty())
    connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));

    長話短說就是等隊列中集齊1000條或更多數據時,發送一條GETDATA消息,批量拉取數據。

    再來看看節點收到GETDATA消息後如何處理:

else if (strCommand == NetMsgType::GETDATA)
{
    //從流中讀取數據
    std::vector<CInv> vInv;
    vRecv >> vInv;
    if (vInv.size() > MAX_INV_SZ)
    {
        LOCK(cs_main);
        Misbehaving(pfrom->GetId(), 20, strprintf("message getdata size() = %u", vInv.size()));
        return false;
    }
    
    LogPrint(BCLog::NET, "received getdata (%u invsz) peer=%d\n", vInv.size(), pfrom->GetId());
    
    if (vInv.size() > 0) {
        LogPrint(BCLog::NET, "received getdata for: %s peer=%d\n", vInv[0].ToString(), pfrom->GetId());
    }
    
    //將所有GETDATA請求添加到集合中
    pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end());
    //處理請求
    ProcessGetData(pfrom, chainparams.GetConsensus(), connman, interruptMsgProc);
}

    很簡單,主要是調用ProcessGetData來處理,繼續跟進去一探究竟:

void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParams, CConnman* connman, const std::atomic<bool>& interruptMsgProc)
{
    AssertLockNotHeld(cs_main);

    std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
    std::vector<CInv> vNotFound;
    const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
    {
        LOCK(cs_main);

        //遍歷集合
        while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) {
            if (interruptMsgProc)
                return;
            // Don't bother if send buffer is too full to respond anyway
            if (pfrom->fPauseSend)
                break;

            const CInv &inv = *it;
            it++;

            // Send stream from relay memory
            //檢查mapRelay或者內存交易池中是否存在交易,如果存在發送TX消息,將交易數據發送給請求方
            bool push = false;
            auto mi = mapRelay.find(inv.hash);
            int nSendFlags = (inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0);
            if (mi != mapRelay.end()) {
                connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second));
                push = true;
            } else if (pfrom->timeLastMempoolReq) {
                auto txinfo = mempool.info(inv.hash);
                // To protect privacy, do not answer getdata using the mempool when
                // that TX couldn't have been INVed in reply to a MEMPOOL request.
                if (txinfo.tx && txinfo.nTime <= pfrom->timeLastMempoolReq) {
                    connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx));
                    push = true;
                }
            }
            if (!push) {
                vNotFound.push_back(inv);
            }

            // Track requests for our stuff.
            GetMainSignals().Inventory(inv.hash);
        }
    } // release cs_main

    if (it != pfrom->vRecvGetData.end() && !pfrom->fPauseSend) {
        const CInv &inv = *it;
        if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) {
            it++;
            ProcessGetBlockData(pfrom, consensusParams, inv, connman, interruptMsgProc);
        }
    }

    //處理完以後從集合中刪除掉
    pfrom->vRecvGetData.erase(pfrom->vRecvGetData.begin(), it);

    if (!vNotFound.empty()) {
        // Let the peer know that we didn't find what it asked for, so it doesn't
        // have to wait around forever. Currently only SPV clients actually care
        // about this message: it's needed when they are recursively walking the
        // dependencies of relevant unconfirmed transactions. SPV clients want to
        // do that because they want to know about (and store and rebroadcast and
        // risk analyze) the dependencies of transactions relevant to them, without
        // having to download the entire memory pool.
        connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::NOTFOUND, vNotFound));
    }
}

    節點會從RelayMap和內存交易池中尋找請求的交易,如果找到將向peer發送TX消息,將交易數據發送給peer。

    最後,peer收到TX消息,拿到交易,然後對交易進行校驗,驗證無誤後將交易加入到自己的交易池中,TX消息的處理比較複雜,涉及到孤立交易的處理(兩種情況:該交易依賴的交易還沒收到,此時收到的交易成爲孤立交易;另外一種情況是孤立交易池中有依賴於該交易的孤立交易,當該交易收到以後,需要將孤立交易從鼓勵交易池中移走並加入到內存交易池中),我們只截取一部分代碼,詳細的處理讀者可自行閱讀源碼net_processing.cpp的ProcessMessage方法。

else if (strCommand == NetMsgType::TX)
{
    // Stop processing the transaction early if
    // We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off
    if (!fRelayTxes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)))
    {
        LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId());
        return true;
    }

    std::deque<COutPoint> vWorkQueue;
    std::vector<uint256> vEraseQueue;

    //從流中讀取交易
    CTransactionRef ptx;
    vRecv >> ptx;
    const CTransaction& tx = *ptx;

    CInv inv(MSG_TX, tx.GetHash());
    pfrom->AddInventoryKnown(inv);

    LOCK2(cs_main, g_cs_orphans);

    bool fMissingInputs = false;
    CValidationState state;

    //交易已經獲取,從相關集合中刪除
    pfrom->setAskFor.erase(inv.hash);
    mapAlreadyAskedFor.erase(inv.hash);

    std::list<CTransactionRef> lRemovedTxn;
 
    //如果交易不存在,則調用AcceptToMemoryPool將交易加入到內存交易池中
    if (!AlreadyHave(inv) &&
    AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
        mempool.check(pcoinsTip.get());
        RelayTransaction(tx, connman);

    最終同樣調用AcceptToMemoryPool將交易添加到交易池中。AcceptToMemoryPool這個函數的處理邏輯非常複雜,會對交易進行各種檢查,包括交易的格式,交易是否是有雙花問題,交易簽名的驗證等等。這個函數非常重要,建議讀者可以仔細閱讀該函數的源碼加深對交易的理解。AcceptToMemoryPool 函數裏會調用CheckInputs檢查交易的每一筆輸入,其中就包含交易簽名的驗證:

/**
 * Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
 * This does not modify the UTXO set.
 *
 * If pvChecks is not nullptr, script checks are pushed onto it instead of being performed inline. Any
 * script checks which are not necessary (eg due to script execution cache hits) are, obviously,
 * not pushed onto pvChecks/run.
 *
 * Setting cacheSigStore/cacheFullScriptStore to false will remove elements from the corresponding cache
 * which are matched. This is useful for checking blocks where we will likely never need the cache
 * entry again.
 *
 * Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp
 */
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks)
{
    if (!tx.IsCoinBase())
    {
        if (pvChecks)
            pvChecks->reserve(tx.vin.size());

        // The first loop above does all the inexpensive checks.
        // Only if ALL inputs pass do we perform expensive ECDSA signature checks.
        // Helps prevent CPU exhaustion attacks.

        // Skip script verification when connecting blocks under the
        // assumevalid block. Assuming the assumevalid block is valid this
        // is safe because block merkle hashes are still computed and checked,
        // Of course, if an assumed valid block is invalid due to false scriptSigs
        // this optimization would allow an invalid chain to be accepted.
        if (fScriptChecks) {
            // First check if script executions have been cached with the same
            // flags. Note that this assumes that the inputs provided are
            // correct (ie that the transaction hash which is in tx's prevouts
            // properly commits to the scriptPubKey in the inputs view of that
            // transaction).
            uint256 hashCacheEntry;
            // We only use the first 19 bytes of nonce to avoid a second SHA
            // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64)
            static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache");
            CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());
            AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks
            if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) {
                return true;
            }

            //檢查交易的每一筆輸入
            for (unsigned int i = 0; i < tx.vin.size(); i++) {
                //找到輸入指向的UTXO,確保其並未被花費
                const COutPoint &prevout = tx.vin[i].prevout;
                const Coin& coin = inputs.AccessCoin(prevout);
                assert(!coin.IsSpent());

                // We very carefully only pass in things to CScriptCheck which
                // are clearly committed to by tx' witness hash. This provides
                // a sanity check that our caching is not introducing consensus
                // failures through additional data in, eg, the coins being
                // spent being checked as a part of CScriptCheck.

                // Verify signature
                //校驗交易簽名
                CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata);
                if (pvChecks) {
                    pvChecks->push_back(CScriptCheck());
                    check.swap(pvChecks->back());
                } else if (!check()) {
                    if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
                        // Check whether the failure was caused by a
                        // non-mandatory script verification check, such as
                        // non-standard DER encodings or non-null dummy
                        // arguments; if so, don't trigger DoS protection to
                        // avoid splitting the network between upgraded and
                        // non-upgraded nodes.
                        CScriptCheck check2(coin.out, tx, i,
                                            flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
                        if (check2())
                            return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
                    }
                    // Failures of other flags indicate a transaction that is
                    // invalid in new blocks, e.g. an invalid P2SH. We DoS ban
                    // such nodes as they are not following the protocol. That
                    // said during an upgrade careful thought should be taken
                    // as to the correct behavior - we may want to continue
                    // peering with non-upgraded nodes even after soft-fork
                    // super-majority signaling has occurred.
                    return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
                }
            }

            if (cacheFullScriptStore && !pvChecks) {
                // We executed all of the provided scripts, and were told to
                // cache the result. Do so now.
                scriptExecutionCache.insert(hashCacheEntry);
            }
        }
    }

    return true;
}

    代碼中通過仿函數CScriptCheck來對交易簽名進行校驗,以交易、交易輸入的索引、交易輸入指向的UTXO等作爲參數構建CScriptCheck對象,執行校驗的過程:

bool CScriptCheck::operator()() {
    const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
    const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;
    return VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *txdata), &error);
}

    這裏最終還是調用了我們在第4節介紹的VerifyScript函數來執行腳本的校驗,這裏就不在重複了。

5.3 時序

    代碼貼了一串又一串,可能很少有讀者能耐着性子看完,我們這裏用一張簡單的圖來說明一下整個過程:

    

    閱讀源代碼過程中只要順着圖裏面的這條主線一直往下看,就能捋清楚交易從生成、簽名、廣播、接收直到交易加入交易池的整個過程。

6 總結

    交易是比特幣中最重要的一塊內容。本文從源碼角度,分析了比特幣交易的創建、交易的簽名、‘’交易腳本的運行、交易的廣播和接收。讀者可以順着本文中主線來閱讀代碼,一旦搞懂了這一部分代碼,就能更深刻的理解比特幣交易。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章