8, Today’s consideration (part 4) is about CTxIn and CTxOut.

Let’s take a look at the contents of vin and vout in CTransaction.

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;
};

These are CTxIn and CTxOut. Now, let's look at the implementation of Serialize. However, Serialize seems to be missing. Actually, there is no unified writing style in Bitcoin's codebase. Therefore, the code varies significantly depending on the location, making it difficult to read accurately without a comprehensive understanding. To solve this problem, we can restore Serialize to CTxIn and CTxOut, and the code can be rewritten as follows:

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;
};

As you can see, it consists of two classes: input (CTxIn) and output (CTxOut). These include scripts to manage the movement of Bitcoin.

scriptSig of CTxIn

scriptSig: The signature script. This script contains the signature that proves the input of the transaction is legitimately used by the owner. The scriptSig is executed in combination with the scriptPubKey of the corresponding CTxOut to verify its validity. Note that SegWit uses a separately allocated field instead of scriptSig. Using that field excludes the size that should have been counted as scriptSig from the GetSerializesize of CTxIn, reducing transaction fees. Additionally, since the signature information is excluded from the transaction ID (txid) calculation, txid fluctuations are eliminated. These txid fluctuations were caused by the use of random numbers in the ECDSA signature process. In other words, while the signature is valid, the signature obtained from the same private key always changes due to the random number. Incidentally, this random number is very important and should not be known to third parties.

scriptPubKey of CTxOut

CTxOut contains a script to send coins to a specific address.

scriptPubKey: The public key script. This script specifies the address or conditions for receiving coins. This script is executed in combination with scriptSig when used as an input for a new transaction.

Combining Transactions

By combining CTxIn and CTxOut, a transaction is formed.

  • Input Reference: CTxIn refers to the output (UTXO: Unspent Transaction Output) of a previous transaction. This UTXO indicates the assets available to the current owner of the coins.
  • Signature Verification: The combination of scriptSig (within CTxIn) and scriptPubKey (within CTxOut) is executed with EvalScript to verify the validity of the transaction.

Transactions, Blockchain, and Synchronization

Such transactions are incorporated into the blockchain as data structures containing these CTxIn and CTxOut. Once incorporated, an inv is spread across the entire network to propagate the transaction to all nodes.

Upon receiving this inv, the node requests the transaction information from the sender of the inv. This mutual connection advances the synchronization of the blockchain. Because a node that has completed synchronization also spreads the inv to other nodes, attempting to propagate information, creating a chain reaction of information propagation.

This explanation lightly covers the key points. In summary, all transaction details are written into the blockchain and are publicly available to third parties.