本日の考察(その6)は、CTxInとCTxOutです。

CTransactionにあったvinとvoutの中身をみていきましょう。

class CTxIn
{
public:
    COutPoint prevout;
    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 = (1U << 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);

    SERIALIZE_METHODS(CTxIn, obj) { READWRITE(obj.prevout, obj.scriptSig, obj.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;
};

class CTxOut
{
public:
    CAmount nValue;
    CScript scriptPubKey;

    CTxOut()
    {
        SetNull();
    }

    CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn);

    SERIALIZE_METHODS(CTxOut, obj) { READWRITE(obj.nValue, obj.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;
};

これがCTxInとCTxOutです。それでは、Serializeの実装について見ていきましょう。しかし、Serializeが見当たらないようです。実は、ビットコインのコードベースには統一された書き方がありません。そのため、場所によってコードの書き方が大きく異なり、全体像を把握していないと正確に読み取るのが難しい状況です。この問題を解決するために、CTxInとCTxOutにSerializeを復活させると、以下のようにコードを書き換えることができます。

class CTxIn
{
public:
    COutPoint prevout;
    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 = (1U << 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);

    size_t GetSerializeSize() const {
        size_t size = 0;
        size += ::GetSerializeSize(prevout);
        size += ::GetSerializeSize(scriptSig);
        size += ::GetSerializeSize(nSequence);
        return size;
    }

    template &lttypename S>
    void Serialize(Stream &s) const {
        s << prevout;
        s << scriptSig;
        s << nSequence;
    }

    template &lttypename S>
    void Unserialize(Stream &s) {
        s >> prevout;
        s >> scriptSig;
        s >> 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;
};

class CTxOut
{
public:
    CAmount nValue;
    CScript scriptPubKey;

    CTxOut()
    {
        SetNull();
    }

    CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn);

    size_t GetSerializeSize() const {
        size_t size = 0;
        size += ::GetSerializeSize(nValue);
        size += ::GetSerializeSize(scriptPubKey);
        return size;
    }

    template <typename S>
    void Serialize(Stream &s) const {
        s << nValue;
        s << scriptPubKey;
    }

    template &lttypename S>
    void Unserialize(Stream &s) {
        s >> nValue;
        s >> 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;
};

このように、入力 (CTxIn) と出力 (CTxOut) の二つのクラスで構成されています。これらは、ビットコインの移動を管理するためのスクリプトを含みます。

CTxInのscriptSig

  • scriptSig: 署名スクリプト。このスクリプトには、トランザクションの入力が所有者によって正当に使用されることを証明するための署名が含まれています。scriptSigは、対応するCTxOutのscriptPubKeyと組み合わせて実行され、正当性を検証します。なお、SegWitはscriptSigではなく、別に割り当てられたフィールドを利用します。そこを利用すると、CTxInのGetSerializesizeから、scriptSigとして計上されていたはずのサイズが除外され、トランザクション手数料が安くなります。また、トランザクションID(txid)の計算から署名の情報が除外されるため、txidの変動がなくなります。このtxidの変動は、ECDSAの署名過程に乱数を利用することから起きておりました。つまり、署名としては有効ですが、同じ秘密鍵から得られる署名自体は乱数によりいつも変わるということです。ちなみにこの乱数は非常に重要で、第三者にばれてはいけない値です。

CTxOutのscriptPubKey

CTxOutは、特定のアドレスにコインを送信するためのスクリプトが含まれています。

  • scriptPubKey: 公開鍵スクリプト。これは、コインを受け取るアドレスや条件を指定するスクリプトです。このスクリプトは、新しいトランザクションの入力として使用される際にscriptSigと組み合わせて実行されます。

トランザクションの結合

CTxInとCTxOutを組み合わせることで、トランザクションが形成されます。

  1. 入力の参照: CTxInは、以前のトランザクションの出力(UTXO:未使用トランザクション出力)を参照します。このUTXOは、コインの現在の所有者が利用可能な資産を示します。
  2. 署名の検証: scriptSig(CTxIn内)とscriptPubKey(CTxOut内)を組み合わせてからEvalScriptで実行し、トランザクションの正当性を検証します。

トランザクションとブロックチェーン、そして同期までをみていきましょう

このようなトランザクションは、これらのCTxInとCTxOutを含むデータ構造としてブロックチェーンに取り込まれ、取り込まれると全ノードに伝播するためのinvがネットワーク全体に拡散します。

このinvを受け取り次第、そのノードはそのinvの送付先にこのトランザクションの情報の送信を要求するようになります。それにより交互に接続され、ブロックチェーンの同期が進んでいくという仕組みになっております。なぜなら、これで同期が完了したノードもまたinvを拡散して他のノードに情報伝播を試みる仕組みにより、そこで情報伝播の連鎖が起きるためです。

重要な部分だけ、軽く説明した感じになりました。つまり、トランザクションの詳細はブロックチェーンにすべて書き込まれ、第三者に公開される性質があります。