The Big Idea
Bitcoin has two core primitives:
- A transaction moves value from one owner to another.
- A block bundles transactions into an ordered, tamper-proof batch.
Everything else in Bitcoin Core (validation, networking, wallets) exists to create, verify, relay, and store these two data structures. This chapter shows you exactly what they look like in C++.
Bitcoin doesn't track "balances." Instead, it tracks unspent transaction outputs (UTXOs). To spend bitcoin, you consume one or more existing UTXOs and create new ones. A transaction is simply a list of inputs (UTXOs being spent) and outputs (new UTXOs being created).
COutPoint: Pointing to a UTXO
Every input needs to reference which previous output it's spending. A COutPoint does exactly that: it's a (txid, index) pair:
The special value NULL_INDEX (0xFFFFFFFF) with a null hash marks a coinbase input, the first transaction in every block that creates new coins.
// Example: reference the 0th output of transaction abc123...
COutPoint prevout(Txid::FromHex("abc123..."), 0);
// Coinbase inputs have a null outpoint
assert(coinbase_tx.vin[0].prevout.IsNull()); // true
CTxIn: Transaction Inputs
A transaction input consumes an existing UTXO and provides proof (a signature) that the spender is authorized:
The Sequence Number
The nSequence field has evolved to serve multiple purposes:
0xFFFFFFFF(SEQUENCE_FINAL): Transaction is final. Disables RBF and relative locktime.- nSequence < 0xFFFFFFFE: Signals RBF (Replace-By-Fee) opt-in.
- Relative Locktime (BIP 68): Lower 16 bits encode a relative lock:
- If bit 22 is set โ time-based (units of 512 seconds)
- If bit 22 is clear โ block-based (number of blocks)
CTxOut: Transaction Outputs
An output is where new UTXOs are born. It specifies an amount and a spending condition:
Output Types
The scriptPubKey determines what kind of output it is:
- P2PKH:
OP_DUP OP_HASH160 <pubkeyHash> OP_EQUALVERIFY OP_CHECKSIG - P2SH:
OP_HASH160 <scriptHash> OP_EQUAL - P2WPKH:
OP_0 <20-byte pubkeyHash>(native SegWit) - P2WSH:
OP_0 <32-byte scriptHash> - P2TR:
OP_1 <32-byte pubkey>(Taproot) - OP_RETURN:
OP_RETURN <data>(provably unspendable, used for data)
Interactive: Transaction Anatomy
Click any part of the transaction below to see what it contains and how it's serialized:
CTransaction: The Complete Transaction
CTransaction is the immutable representation of a Bitcoin transaction. Once constructed, its fields cannot be changed.
Under the hood, CTransaction computes and caches two hashes:
- txid (
GetHash()): hash of the transaction without witness data. This is the canonical transaction ID. - wtxid (
GetWitnessHash()): hash including witness data. Used for witness commitment in blocks and for BIP 339 relay.
Before SegWit, a third party could change the signature (witness) data without invalidating the transaction. This changed the txid and broke chains of unconfirmed transactions. SegWit fixed this by moving witness data outside the txid computation. The txid is now "malleability-free."
CMutableTransaction
Need to build or modify a transaction? Use CMutableTransaction. It has the same fields as CTransaction but they're mutable. The typical workflow:
// 1. Create a mutable transaction
CMutableTransaction mtx;
mtx.version = 2;
// 2. Add inputs
mtx.vin.push_back(CTxIn(COutPoint(prev_txid, 0)));
// 3. Add outputs
mtx.vout.push_back(CTxOut(amount, scriptPubKey));
// 4. Sign inputs (fills in scriptSig/scriptWitness)
SignTransaction(mtx, keystore, ...);
// 5. Convert to immutable CTransaction
CTransactionRef tx = MakeTransactionRef(std::move(mtx));
CBlockHeader: 80 Bytes of Truth
The block header is only 80 bytes, yet it secures the entire block:
The hashPrevBlock field is what creates the chain - each block points back to its parent, forming an unbroken chain all the way to the genesis block.
CBlock: Header + Transactions
CBlock inherits from CBlockHeader and adds the transaction list:
The Merkle Root
The hashMerkleRoot in the header is computed by building a binary tree of transaction hashes:
- Hash each transaction (txid) to get leaf nodes.
- Pair adjacent hashes and hash them together.
- If there's an odd number, duplicate the last one.
- Repeat until one hash remains. That's the Merkle root.
This allows Merkle proofs, a compact proof that a transaction is included in a block, without downloading the entire block.
Serialization & SegWit
Transactions are serialized differently depending on whether they have witness data:
Legacy Format
[version][vin count][vin...][vout count][vout...][locktime]
SegWit Format (BIP 144)
[version][marker=0x00][flag=0x01][vin count][vin...][vout count][vout...][witness...][locktime]
The marker byte (0x00) signals that this is a SegWit transaction. The witness data follows the outputs and is not included in the txid hash (only in the wtxid).
SegWit introduced a new metric: weight. Non-witness data counts as 4 weight units per byte; witness data counts as 1. The block weight limit is 4,000,000 weight units (equivalent to ~1 MB of non-witness data, or up to ~4 MB with witness data). This effectively gives a "discount" to witness data.
Coinbase Transactions
The first transaction in every block is the coinbase transaction. It's special:
- Has exactly one input with
prevout.IsNull() == true - No previous output is being spent, new coins are created
- The
scriptSigcan contain up to 100 bytes of arbitrary data - Must start with the block height (BIP 34)
- Outputs contain the block reward (subsidy + fees)
The block subsidy started at 50 BTC and halves every 210,000 blocks (~4 years). After the 2024 halving, it's 3.125 BTC per block.
// Check if a transaction is a coinbase
bool CTransaction::IsCoinBase() const {
return (vin.size() == 1 && vin[0].prevout.IsNull());
}