Open any Bitcoin block explorer — blockstream.info, mempool.space, blockchain.com — and click on a recent transaction. You will see something like this:
Learning Objectives
- Trace a Bitcoin transaction from creation through validation using the UTXO model
- Decode a raw Bitcoin block header and identify each field's purpose
- Explain the UTXO model's advantages over the account model for transaction validation
- Implement a simplified blockchain data structure in Python with block creation, linking, and validation
- Parse a real Bitcoin transaction from raw hexadecimal format into human-readable fields
In This Chapter
- Every Byte Tells a Story
- 6.1 The UTXO Model: Bitcoin's Accounting System
- 6.2 Bitcoin Transaction Anatomy
- 6.3 Locking and Unlocking Scripts: Bitcoin's Scripting Language
- 6.4 The Coinbase Transaction: Where New Bitcoin Is Born
- 6.5 Block Structure
- 6.6 SegWit: Segregated Witness
- 6.7 Reading a Raw Bitcoin Transaction
- 6.8 Building Our Blockchain in Python
- 6.9 UTXO Model vs. Account Model
- 6.10 Chapter Summary and Bridge
- Key Equations and Formulas
Chapter 6: Bitcoin Protocol Deep Dive: Blocks, Transactions, and the UTXO Model
Every Byte Tells a Story
Open any Bitcoin block explorer — blockstream.info, mempool.space, blockchain.com — and click on a recent transaction. You will see something like this:
Transaction: 7a2f5c3e8b...
Status: Confirmed (3 confirmations)
Size: 225 bytes
Weight: 573 WU
Fee: 0.00012340 BTC (54.84 sat/vB)
Inputs (1):
bc1q3k2m8... 0.05000000 BTC
Outputs (2):
bc1qr7y9f... 0.03200000 BTC
bc1q3k2m8... 0.01787660 BTC
Right now, this might look like a reasonably clear summary: someone sent 0.032 BTC somewhere, with some change left over. But beneath this polished display sits a raw hexadecimal blob — a precisely structured sequence of bytes that encodes version numbers, input references, locking scripts, output amounts, and witness data. Every single byte has a purpose. Every field follows rules that thousands of nodes enforce independently, without any central authority telling them what to do.
By the end of this chapter, you will understand every one of those bytes. You will know why a transaction has inputs and outputs instead of simple "from" and "to" fields. You will understand what a UTXO is and why this model was chosen over the seemingly more intuitive account-based model. You will be able to decode a block header, explain what the coinbase transaction is, trace how SegWit changed the transaction format, and — critically — you will build your own simplified blockchain in Python that creates blocks, links them via hashes, and validates the chain's integrity.
This chapter is the bridge between conceptual understanding and implementation-level knowledge. After Chapter 5's overview of Bitcoin's design, we now open the hood and examine the engine.
6.1 The UTXO Model: Bitcoin's Accounting System
Why Bitcoin Doesn't Have "Balances"
If you have used a traditional bank, you are accustomed to the account model: your bank maintains a ledger with your name and a balance. When you spend money, the bank subtracts from your balance. When you receive money, the bank adds to your balance. Simple, intuitive, familiar.
Bitcoin does not work this way. There is no master ledger of accounts and balances. When a block explorer shows that address bc1q3k2m8... "has" 0.05 BTC, it is not reading a balance field stored somewhere. Instead, it is scanning every transaction ever recorded on the blockchain, identifying all the outputs that were sent to that address and have not yet been spent, and summing them up. The "balance" is computed, not stored.
This design decision — using Unspent Transaction Outputs (UTXOs) instead of account balances — is one of Bitcoin's most fundamental architectural choices, and understanding it is essential to understanding everything else in this chapter.
What Is a UTXO?
A UTXO — an Unspent Transaction Output — is exactly what the name says: an output from a previous transaction that has not yet been used as an input to a new transaction. Think of UTXOs as individual coins or bills in a physical wallet. If someone hands you a $20 bill and a $10 bill, you do not have a "balance of $30" — you have two distinct pieces of money, each with its own denomination.
In Bitcoin, every time you receive BTC, a new UTXO is created. That UTXO sits in the global UTXO set — the collection of all unspent outputs across the entire blockchain — until you spend it. When you spend it, that UTXO is consumed (destroyed), and new UTXOs are created as outputs of the spending transaction.
Here is the critical mental model:
Transactions do not move Bitcoin from one account to another. Transactions consume existing UTXOs and create new UTXOs.
The Lifecycle of a UTXO
Let us trace a complete example. Alice wants to send 0.5 BTC to Bob.
Step 1: Alice's wallet scans the UTXO set. Alice's wallet software looks for UTXOs that are locked to Alice's address. Suppose it finds three:
| UTXO | Source Transaction | Amount |
|---|---|---|
| UTXO-A | tx_abc...output #0 | 0.3 BTC |
| UTXO-B | tx_def...output #1 | 0.15 BTC |
| UTXO-C | tx_ghi...output #0 | 0.2 BTC |
Alice's "balance" is 0.3 + 0.15 + 0.2 = 0.65 BTC. But again, this balance is not stored anywhere — it is the sum of her unspent outputs.
Step 2: Alice's wallet selects UTXOs to spend. To send 0.5 BTC to Bob, the wallet needs to gather enough UTXOs to cover the amount plus the transaction fee. Suppose the fee is 0.001 BTC. The wallet selects UTXO-A (0.3 BTC) and UTXO-C (0.2 BTC), totaling 0.5 BTC. But wait — the total input is 0.5 BTC and the required output is 0.5 BTC (to Bob) plus 0.001 BTC (fee). The wallet does not have enough with just those two UTXOs for both the payment and fee. So it also includes UTXO-B (0.15 BTC), bringing the total input to 0.65 BTC.
Step 3: The transaction creates new outputs. The transaction now has:
- Inputs: UTXO-A (0.3), UTXO-B (0.15), UTXO-C (0.2) = 0.65 BTC total
- Output 1: 0.5 BTC locked to Bob's address (the payment)
- Output 2: 0.149 BTC locked to Alice's own address (the change)
- Implicit fee: 0.65 - 0.5 - 0.149 = 0.001 BTC (goes to the miner)
Step 4: Old UTXOs are destroyed, new UTXOs are created. Once this transaction is confirmed in a block: - UTXO-A, UTXO-B, and UTXO-C are removed from the UTXO set (they are now "spent") - Two new UTXOs are added: one for Bob (0.5 BTC) and one for Alice (0.149 BTC, the change)
Notice that the transaction fee is never explicitly stated. It is the difference between total inputs and total outputs. This is by design — it makes fee manipulation impossible because any "missing" value is automatically claimed by the miner.
Change Outputs: Why You Pay Yourself
The change output concept confuses many newcomers. In the physical cash analogy: if you buy a $7 coffee with a $10 bill, the cashier gives you $3 back. You do not tear the $10 bill in half. The entire bill is consumed, and change is returned.
Similarly, UTXOs cannot be partially spent. If you have a UTXO worth 1.0 BTC and you want to send 0.3 BTC, you must consume the entire 1.0 BTC UTXO and create two outputs: 0.3 BTC to the recipient and approximately 0.7 BTC back to yourself (minus the fee). The output back to yourself is the change output.
💡 Key Insight: Every UTXO must be spent in its entirety. There is no way to "take part" of a UTXO. This is why transactions almost always have at least two outputs — the payment and the change — even when sending to a single recipient.
Dust: When UTXOs Are Too Small
This leads to an important practical concept: dust. If a UTXO is so small that the fee required to spend it exceeds or approaches its value, that UTXO is effectively unspendable. It costs more to use it than it is worth.
Bitcoin Core defines a "dust threshold" — currently 546 satoshis for P2PKH outputs and 294 satoshis for SegWit outputs. Any output below this threshold is considered "dust" and will be rejected by most nodes' default relay policies. The dust threshold is not a consensus rule (the blockchain itself would accept such outputs), but nodes refuse to relay transactions that create them because those tiny UTXOs would bloat the UTXO set without practical utility.
As of early 2025, the UTXO set contains roughly 170 million entries. Every full node must store this set in memory (or at least fast-access storage) for efficient transaction validation. Dust UTXOs impose a cost on every node operator while providing no real value to anyone.
The UTXO Set as Global State
The UTXO set is Bitcoin's global state — the complete snapshot of "who owns what" at any given moment. To validate a new transaction, a node only needs to check:
- Do the referenced input UTXOs actually exist in the current UTXO set?
- Does the transaction provide valid unlocking scripts for each input?
- Is the total input value greater than or equal to the total output value?
- Are the outputs above the dust threshold?
If all checks pass, the transaction is valid. The node removes the consumed UTXOs from its local set and adds the new ones. This is enormously efficient — you never need to scan the entire blockchain history, only the current UTXO set.
⚠️ Common Misconception: "Bitcoin transactions send coins from one address to another." This is incorrect. Bitcoin transactions consume UTXOs (which happen to be associated with addresses via their locking scripts) and create new UTXOs (which are locked to new addresses via new locking scripts). The address is part of the locking mechanism, not a fundamental identity.
6.2 Bitcoin Transaction Anatomy
Now that you understand the UTXO model conceptually, let us examine the actual data structure of a Bitcoin transaction. We will walk through each field in the order it appears in the raw serialized format.
Transaction Structure Overview
A legacy (pre-SegWit) Bitcoin transaction consists of the following fields, serialized in this exact order:
| Field | Size | Description |
|---|---|---|
| Version | 4 bytes | Transaction version number |
| Input Count | 1-9 bytes | Number of inputs (varint) |
| Inputs | Variable | Array of transaction inputs |
| Output Count | 1-9 bytes | Number of outputs (varint) |
| Outputs | Variable | Array of transaction outputs |
| Locktime | 4 bytes | Earliest time/block for inclusion |
Version (4 bytes)
The version field is a 32-bit little-endian integer. As of 2025, two version numbers are in common use:
- Version 1 (
01000000in little-endian hex): The original transaction format. - Version 2 (
02000000in little-endian hex): Introduced by BIP 68 to enable relative time locks via the sequence field.
The version field allows the protocol to introduce new transaction features without breaking old nodes. An old node encountering a version-2 transaction will still parse it correctly — it simply will not enforce the additional rules that version 2 enables.
Variable-Length Integers (VarInts)
Before examining inputs and outputs, we need to understand VarInts — Bitcoin's compact integer encoding scheme. A VarInt can represent any integer from 0 to 2^64-1, using the minimum number of bytes necessary:
| Value Range | Size | Encoding |
|---|---|---|
| 0 - 252 | 1 byte | The value directly |
| 253 - 65535 | 3 bytes | 0xfd + 2 bytes (little-endian) |
| 65536 - 4294967295 | 5 bytes | 0xfe + 4 bytes (little-endian) |
| 4294967296+ | 9 bytes | 0xff + 8 bytes (little-endian) |
For most transactions, the input count and output count are small numbers (1-10), so they fit in a single byte.
Transaction Inputs
Each input in the inputs array references a specific output from a previous transaction and provides proof that the spender has the right to consume it:
| Field | Size | Description |
|---|---|---|
| Previous TX Hash | 32 bytes | Hash of the transaction containing the UTXO to spend |
| Output Index | 4 bytes | Which output of that transaction (0-indexed) |
| ScriptSig Size | 1-9 bytes | Length of the unlocking script (varint) |
| ScriptSig | Variable | The unlocking script (proof of authorization) |
| Sequence | 4 bytes | Originally for transaction replacement; now used for relative time locks and signaling |
The previous transaction hash and output index together form a unique pointer to a specific UTXO. This pair is sometimes called an "outpoint." If a transaction has three inputs, there are three of these structures, each pointing to a different UTXO.
The ScriptSig (also called the "unlocking script" or "witness" in common parlance, though technically the witness is different in SegWit) contains the cryptographic proof that the spender is authorized to consume this UTXO. For the most common transaction type (P2PKH), the ScriptSig contains the spender's digital signature and their public key.
The Sequence field was originally intended by Satoshi Nakamoto to enable transaction replacement within the mempool — a mechanism where a higher-sequence-number version of a transaction would replace a lower one. This mechanism was disabled early in Bitcoin's history due to denial-of-service concerns. The sequence field was later repurposed by BIP 68 (version 2 transactions) to enable relative time locks — conditions that prevent a UTXO from being spent until a certain number of blocks or seconds have elapsed since it was created.
Transaction Outputs
Each output specifies an amount and the conditions under which that amount can be spent:
| Field | Size | Description |
|---|---|---|
| Value | 8 bytes | Amount in satoshis (little-endian) |
| ScriptPubKey Size | 1-9 bytes | Length of the locking script (varint) |
| ScriptPubKey | Variable | The locking script (spending conditions) |
The value is expressed in satoshis — the smallest unit of Bitcoin, where 1 BTC = 100,000,000 satoshis. The 8-byte (64-bit) value field can represent amounts up to approximately 21 x 10^14 satoshis, far more than the 2.1 x 10^15 satoshi total supply cap.
The ScriptPubKey (also called the "locking script" or "output script") defines the conditions that must be met to spend this output. For a standard P2PKH output, this script essentially says: "To spend this output, provide a signature that corresponds to the public key whose hash matches this hash." The actual scripting mechanism is described in detail in the next section.
Locktime (4 bytes)
The locktime field specifies the earliest time at which a transaction can be included in a block:
- 0: No restriction — the transaction can be included immediately.
- 1 to 499,999,999: Interpreted as a block height — the transaction cannot be included until the blockchain reaches this height.
- 500,000,000 and above: Interpreted as a Unix timestamp — the transaction cannot be included until this time has passed.
Locktime enables time-locked transactions — for example, creating a transaction today that cannot be confirmed until next month. This feature is foundational to many of Bitcoin's second-layer protocols, including the Lightning Network.
📊 By the Numbers: The average Bitcoin transaction has approximately 1-2 inputs and 2 outputs (payment + change). A typical P2PKH transaction with one input and two outputs is approximately 226 bytes. A typical P2WPKH (native SegWit) transaction of the same structure is approximately 141 virtual bytes (vbytes).
6.3 Locking and Unlocking Scripts: Bitcoin's Scripting Language
Script: A Stack-Based Language
Bitcoin includes a simple but powerful scripting language called Script. It is a stack-based, Forth-like language that is intentionally not Turing-complete — it has no loops, no recursion, and no ability to access external state. This limited design is deliberate: every Bitcoin node must execute every script, so the language must be guaranteed to terminate and must be deterministic.
Script operates on a stack — a last-in, first-out data structure. Operations either push data onto the stack or pop data off the stack and operate on it. When a transaction is being validated, the node concatenates the unlocking script (ScriptSig, from the spending transaction's input) with the locking script (ScriptPubKey, from the UTXO being spent) and executes the combined script. If execution completes successfully with a "true" value on top of the stack, the spend is authorized.
P2PKH: Pay-to-Public-Key-Hash
The most common traditional transaction type is Pay-to-Public-Key-Hash (P2PKH). Here is how the scripts work:
Locking Script (ScriptPubKey) — stored in the UTXO:
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
This script says: "To spend this output, you must provide a public key that hashes to this specific hash, and a valid signature made with that key's corresponding private key."
Unlocking Script (ScriptSig) — provided by the spender:
<signature> <publicKey>
Combined execution (the unlocking script runs first, then the locking script):
| Step | Operation | Stack (top on right) |
|---|---|---|
| 1 | Push <signature> |
<sig> |
| 2 | Push <publicKey> |
<sig> <pubKey> |
| 3 | OP_DUP — duplicate top item |
<sig> <pubKey> <pubKey> |
| 4 | OP_HASH160 — hash the top item |
<sig> <pubKey> <pubKeyHash_computed> |
| 5 | Push <pubKeyHash> from script |
<sig> <pubKey> <pubKeyHash_computed> <pubKeyHash> |
| 6 | OP_EQUALVERIFY — verify top two are equal, fail if not |
<sig> <pubKey> |
| 7 | OP_CHECKSIG — verify signature against public key |
TRUE |
If the stack ends with TRUE, the transaction is valid. If any step fails — the hashes do not match, the signature is invalid, or the stack ends with FALSE — the transaction is rejected.
P2SH: Pay-to-Script-Hash
Pay-to-Script-Hash (P2SH), introduced by BIP 16 in 2012, allows the sender to lock funds to the hash of an arbitrary script, rather than to a specific public key hash. The actual spending conditions are revealed only when the funds are spent.
Locking Script (ScriptPubKey):
OP_HASH160 <scriptHash> OP_EQUAL
Unlocking Script (ScriptSig):
<signatures and data> <redeemScript>
The node first verifies that the hash of the provided redeemScript matches the scriptHash in the locking script. If it does, the node then executes the redeemScript with the provided signatures and data.
P2SH enables multi-signature transactions, time-locked contracts, and other advanced spending conditions without requiring the sender to know the details of the locking conditions. The sender just sends to a P2SH address (which starts with "3" on mainnet), and the recipient deals with the complexity of satisfying the script when they want to spend.
Multi-Signature Transactions
Multi-signature (multisig) is one of the most important applications of Bitcoin's scripting system. A multisig script requires M signatures out of N possible public keys. The standard multisig redeem script looks like:
OP_2 <pubKey1> <pubKey2> <pubKey3> OP_3 OP_CHECKMULTISIG
This is a "2-of-3" multisig: any two of the three keys can authorize a spend. Common use cases include:
- Corporate treasury management: 2-of-3 among the CEO, CFO, and a board member.
- Escrow services: 2-of-3 among buyer, seller, and arbiter.
- Personal security: 2-of-3 among a hardware wallet, a mobile wallet, and a backup key stored in a safe deposit box.
The OP_CHECKMULTISIG opcode has a well-known bug: it pops one extra item from the stack beyond what it needs. This is why multisig unlocking scripts must include an extra dummy OP_0 at the beginning. Fixing this bug would be a consensus change, so it has been left in place since Bitcoin's creation.
🔗 Cross-Reference: Multi-signature schemes are foundational to the voting dApp we will build in our progressive project. In Chapter 16, when we implement on-chain governance voting, the contract will use multi-signature patterns to authorize election administration actions.
Script Opcodes: A Sampler
Bitcoin Script includes approximately 100 opcodes. Here are the most commonly used:
| Opcode | Hex | Function |
|---|---|---|
| OP_0 | 0x00 | Push empty byte array (false) |
| OP_DUP | 0x76 | Duplicate top stack item |
| OP_HASH160 | 0xa9 | SHA-256 then RIPEMD-160 hash |
| OP_EQUAL | 0x87 | Check if top two items are equal |
| OP_EQUALVERIFY | 0x88 | OP_EQUAL + OP_VERIFY (fail if not equal) |
| OP_CHECKSIG | 0xac | Verify a signature against a public key |
| OP_CHECKMULTISIG | 0xae | Verify M-of-N signatures |
| OP_RETURN | 0x6a | Mark output as provably unspendable (used for data embedding) |
| OP_CHECKLOCKTIMEVERIFY | 0xb1 | Verify locktime has passed |
| OP_CHECKSEQUENCEVERIFY | 0xb2 | Verify relative locktime |
Several opcodes were disabled early in Bitcoin's history due to security concerns — notably OP_CAT (concatenate two strings) and OP_MUL (multiply two numbers). Some of these, particularly OP_CAT, have been the subject of recent proposals to re-enable them for enabling more expressive smart contracts on Bitcoin.
⚠️ Security Note: Bitcoin Script is intentionally limited. It cannot access the internet. It cannot read other transactions. It cannot loop. These limitations are features, not bugs. Every script must be deterministic and must terminate in bounded time. A Turing-complete scripting language on a blockchain would create denial-of-service vulnerabilities — which is why Ethereum's Turing-complete Solidity requires a gas mechanism to prevent infinite loops.
6.4 The Coinbase Transaction: Where New Bitcoin Is Born
A Special Transaction
Every block contains a special first transaction called the coinbase transaction. It is unique in several ways:
-
It has no real inputs. The coinbase transaction's single input does not reference a previous transaction output. Instead, the "previous transaction hash" is set to all zeros (32 bytes of 0x00), and the "output index" is set to 0xffffffff.
-
It creates new Bitcoin. The outputs of the coinbase transaction may have a total value up to the current block reward plus the sum of all transaction fees in the block. As of 2025, after the April 2024 halving, the block reward is 3.125 BTC. So if the block contains 0.5 BTC in total fees, the coinbase outputs may total up to 3.625 BTC.
-
Its "ScriptSig" is arbitrary data. Since there is no real input to unlock, the coinbase ScriptSig can contain almost anything — up to 100 bytes. The only requirement (since BIP 34) is that the first bytes must encode the block height.
-
Its outputs cannot be spent for 100 blocks. This maturity rule protects against blockchain reorganizations. If a block is orphaned (replaced by a competing chain), its coinbase reward disappears. Requiring 100 confirmations before the coinbase reward is spendable ensures that reorganizations do not create confusion about whether those coins are valid.
The Genesis Block Message
The most famous coinbase data in Bitcoin's history is in block 0 — the genesis block, mined by Satoshi Nakamoto on January 3, 2009. The coinbase contains the following text:
The Times 03/Jan/2009 Chancellor on brink of second bailout for banks
This was the headline of that day's edition of The Times of London. Satoshi embedded it for two purposes: as a timestamp proof (this block could not have been mined before January 3, 2009) and as a statement about the monetary system that Bitcoin was designed to challenge.
Coinbase Data in Practice
Miners use the coinbase data field for several purposes:
- Block height (required since BIP 34): The first bytes encode the block number, ensuring each coinbase transaction is unique.
- Miner identification: Many mining pools include their name or website (e.g., "/Foundry USA Pool/", "/AntPool/").
- Extra nonce: When the 4-byte nonce in the block header is exhausted without finding a valid hash, miners increment the "extra nonce" in the coinbase data, which changes the Merkle root and provides a fresh set of 2^32 nonce values to try.
- Signaling: Miners can use coinbase data to signal support for proposed protocol upgrades.
- Messages: Occasionally, miners embed messages — proposals, memorials, or political statements.
The Block Reward Schedule
The coinbase transaction's maximum output amount follows Bitcoin's predetermined issuance schedule:
| Block Range | Reward | Approximate Dates |
|---|---|---|
| 0 - 209,999 | 50 BTC | 2009-2012 |
| 210,000 - 419,999 | 25 BTC | 2012-2016 |
| 420,000 - 629,999 | 12.5 BTC | 2016-2020 |
| 630,000 - 839,999 | 6.25 BTC | 2020-2024 |
| 840,000 - 1,049,999 | 3.125 BTC | 2024-2028 |
Every 210,000 blocks (approximately four years), the reward halves. This geometric series converges to a total supply of approximately 20,999,999.9769 BTC — commonly rounded to 21 million. The last satoshi will be mined around the year 2140.
After the last halving, miners will be compensated entirely through transaction fees. Whether the fee market alone will provide sufficient incentive to maintain the network's security is one of Bitcoin's most debated long-term questions.
6.5 Block Structure
The Block as a Container
A Bitcoin block is a container for transactions. It consists of two parts: a compact block header (exactly 80 bytes) and a block body (the list of transactions). The header is what miners hash repeatedly when searching for a valid proof of work; the body contains the actual transaction data.
Block Header Fields
The 80-byte block header contains six fields:
| Field | Size | Description |
|---|---|---|
| Version | 4 bytes | Block version for signaling protocol upgrades |
| Previous Block Hash | 32 bytes | SHA-256d hash of the previous block's header |
| Merkle Root | 32 bytes | Root hash of the Merkle tree of all transactions in the block |
| Timestamp | 4 bytes | Unix timestamp (seconds since epoch) |
| Bits | 4 bytes | Compact encoding of the current difficulty target |
| Nonce | 4 bytes | Counter for proof-of-work mining |
Let us examine each field in detail.
Version (4 bytes). The version field was originally meant to track protocol versions (1, 2, 3...). Since BIP 9, it has been repurposed for "version bits" signaling, where individual bits indicate miner support for specific proposed changes. Bits 0-28 can each signal for a different proposal. This allows multiple proposals to be signaled simultaneously.
Previous Block Hash (32 bytes). This is the SHA-256d (double SHA-256) hash of the previous block's 80-byte header. This field is what chains blocks together — each block points back to its parent. The genesis block's previous block hash is all zeros.
The chain of previous-block-hash pointers is what gives the blockchain its immutability. Changing any transaction in block N changes that block's Merkle root, which changes block N's header hash, which invalidates block N+1's previous-block-hash pointer, and so on for every subsequent block. Modifying an old block requires re-mining every block after it.
Merkle Root (32 bytes). The Merkle root is the top hash of a binary hash tree (Merkle tree) built from all the transaction hashes in the block. We covered Merkle trees in Chapter 2. This field commits to every transaction in the block — changing, adding, or removing any transaction would produce a different Merkle root.
The Merkle tree also enables Simplified Payment Verification (SPV), where lightweight clients can verify that a specific transaction is included in a block by downloading only a small "Merkle proof" (logarithmic in the number of transactions) rather than the entire block.
Timestamp (4 bytes). A Unix timestamp set by the miner. The Bitcoin protocol allows timestamps to be somewhat inaccurate — a block's timestamp must be greater than the median of the previous 11 blocks and less than the network time plus 2 hours. This flexibility is necessary because miners are distributed around the world and do not have perfectly synchronized clocks.
Bits (4 bytes). A compact representation of the 256-bit difficulty target. The target is the threshold that a block's header hash must be below to be considered valid. The bits field encodes this large number in a compact format: the first byte is the exponent, and the remaining three bytes are the coefficient.
For example, the bits value 0x1903a30c decodes as:
- Exponent: 0x19 = 25
- Coefficient: 0x03a30c
- Target: 0x03a30c * 2^(8 * (25 - 3)) = a very large 256-bit number
The difficulty adjusts every 2,016 blocks (approximately two weeks) to maintain an average block time of 10 minutes, as we discussed in Chapter 5.
Nonce (4 bytes). A 32-bit counter that miners increment when searching for a valid proof of work. The miner hashes the 80-byte header (with different nonce values) until the resulting hash is below the target. With a 4-byte nonce, there are only 2^32 (approximately 4.3 billion) possible values. Modern ASIC miners can exhaust this space in under a second, which is why the "extra nonce" in the coinbase transaction is also modified (changing the Merkle root) to provide additional search space.
Block Body
The block body is simply an ordered list of transactions, starting with the coinbase transaction. Transactions are serialized one after another, preceded by a VarInt indicating the total number of transactions in the block.
A block's maximum size is determined by its weight, which we will discuss in the SegWit section. Under the current rules, the effective maximum block size is approximately 4 MB of raw data, though the practical maximum for a block filled with typical transactions is around 2 MB.
📊 By the Numbers (2025): A typical Bitcoin block contains 2,000-4,000 transactions, is approximately 1.5-2 MB in size, and takes about 10 minutes to mine on average. The longest chain has over 890,000 blocks, containing over 1 billion transactions. The total blockchain size exceeds 600 GB.
6.6 SegWit: Segregated Witness
The Problem SegWit Solved
Segregated Witness (SegWit) was activated on the Bitcoin network on August 24, 2017, via a soft fork. It was one of the most significant protocol upgrades in Bitcoin's history, and understanding it is essential for understanding modern Bitcoin transactions.
SegWit addressed two critical problems:
Problem 1: Transaction Malleability. Before SegWit, the signature data (ScriptSig) was included in the transaction data that gets hashed to produce the transaction ID (txid). This meant that anyone — not just the transaction creator — could subtly modify the signature data (in a way that kept the signature valid) and change the transaction's hash. The transaction would still be valid and would still do the same thing, but it would have a different txid.
This might sound harmless, but it was devastating for protocols built on top of Bitcoin. If you create a transaction that spends the output of an unconfirmed transaction (by referencing its txid), and someone malleates that parent transaction (changing its txid), your child transaction becomes invalid because it references a txid that no longer exists. The Lightning Network, which requires chains of unconfirmed transactions, was effectively impossible to build securely without fixing transaction malleability.
Problem 2: Block Capacity. The original 1 MB block size limit constrained Bitcoin's transaction throughput. There was intense debate in the community about how to increase capacity — some wanted a simple block size increase (a hard fork), while others preferred SegWit's approach (a soft fork that effectively increased capacity without changing the nominal block size limit).
How SegWit Works
SegWit's key insight is that the witness data (signatures and public keys) can be separated — "segregated" — from the main transaction data. In a SegWit transaction:
- The ScriptSig is empty (0 bytes). The transaction inputs contain no signature data.
- A new "witness" section is appended to the transaction, containing the signature and public key data for each input.
- The witness data is not included when computing the txid. This eliminates transaction malleability for SegWit transactions.
- A new "wtxid" (witness transaction ID) is computed over the full transaction including witness data. Blocks commit to both the txid-based Merkle tree and a wtxid-based Merkle tree (via the coinbase transaction's witness commitment).
SegWit Transaction Format
A SegWit transaction has a slightly different serialization:
| Field | Size | Description |
|---|---|---|
| Version | 4 bytes | Same as legacy |
| Marker | 1 byte | Always 0x00 (signals SegWit format) |
| Flag | 1 byte | Always 0x01 |
| Input Count | Varint | Same as legacy |
| Inputs | Variable | ScriptSig is empty for SegWit inputs |
| Output Count | Varint | Same as legacy |
| Outputs | Variable | Same as legacy |
| Witness | Variable | One witness stack per input |
| Locktime | 4 bytes | Same as legacy |
The marker byte (0x00) is what allows old nodes to remain compatible. An old node seeing 0x00 where it expects the input count would interpret the transaction as having zero inputs — which is invalid. But SegWit-aware nodes recognize the 0x00-0x01 marker-flag pair and parse the transaction correctly.
Weight Units and Virtual Bytes
SegWit introduced a new way to measure transaction size: weight units (WU). The formula is:
weight = (non-witness bytes * 4) + (witness bytes * 1)
The maximum block weight is 4,000,000 WU (4 MWU). For a block filled entirely with legacy transactions (no witness data), this is equivalent to the old 1 MB limit (1,000,000 bytes * 4 = 4,000,000 WU). For blocks with SegWit transactions, the effective block size can exceed 1 MB because witness data is "discounted" by a factor of 4.
Virtual bytes (vbytes) are used for fee calculation:
vbytes = weight / 4
A SegWit transaction pays fees based on its virtual size, not its actual size. Since the witness data has a 75% discount, SegWit transactions are cheaper to send than equivalent legacy transactions.
SegWit Address Formats
SegWit introduced new address types:
- P2WPKH (Pay-to-Witness-Public-Key-Hash): The SegWit equivalent of P2PKH. Addresses start with "bc1q" (Bech32 encoding). Example:
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 - P2WSH (Pay-to-Witness-Script-Hash): The SegWit equivalent of P2SH. Also starts with "bc1q" but is longer (62 characters vs. 42 for P2WPKH).
- P2SH-P2WPKH: A backward-compatible "wrapped" format that looks like a regular P2SH address (starting with "3") but internally uses SegWit. This was common during the SegWit transition period.
- P2TR (Pay-to-Taproot): Introduced in the 2021 Taproot upgrade, uses Bech32m encoding, and addresses start with "bc1p".
As of early 2025, SegWit adoption is approximately 85-90% of all transactions, with native SegWit (bc1q) being the most common format.
💡 Key Insight: SegWit was deployed as a soft fork — old nodes can still validate SegWit transactions, they just cannot fully verify the witness data. From an old node's perspective, SegWit outputs look like "anyone can spend" outputs, but the new rules enforced by SegWit-upgraded nodes prevent unauthorized spending. This backward compatibility is what made SegWit possible without requiring every node to upgrade simultaneously.
6.7 Reading a Raw Bitcoin Transaction
Now let us put all of this knowledge together and parse an actual raw Bitcoin transaction, byte by byte. We will use a real transaction from the Bitcoin blockchain.
A Real Transaction in Hex
Here is a simplified SegWit transaction (P2WPKH, one input, two outputs):
02000000 -- Version (2, little-endian)
00 -- SegWit marker
01 -- SegWit flag
01 -- Input count (1)
7b1eabe0209b1fe794124575ef807057
c77ada2138ae4fa8d6c4de0398a14f3f -- Previous TX hash (32 bytes)
00000000 -- Output index (0)
00 -- ScriptSig length (0 = empty, SegWit)
feffffff -- Sequence
02 -- Output count (2)
a086010000000000 -- Output 0 value (100,000 sat = 0.001 BTC)
160014d85c2b71d0060b09c9886aeb815e
50991dda124d -- Output 0 ScriptPubKey
e8030000000000000 -- Output 1 value (1,000 sat)
160014863c404a93840868e6a25e19c649
b5a32ef6c3e9 -- Output 1 ScriptPubKey
02 -- Witness items for input 0
4730440220... -- Witness item 0: signature (71 bytes)
2103... -- Witness item 1: public key (33 bytes)
00000000 -- Locktime (0)
Let us walk through this step by step.
Step-by-Step Parsing
Bytes 1-4: Version. 02000000 in little-endian is version 2. This transaction uses BIP 68 relative lock time semantics for the sequence field.
Bytes 5-6: SegWit Marker and Flag. 0001 — the marker byte 0x00 and flag byte 0x01 indicate this is a SegWit transaction.
Byte 7: Input Count. 01 — one input.
Bytes 8-39: Previous Transaction Hash. 32 bytes identifying which transaction's output we are spending. Note that this hash is stored in "internal byte order" (reversed from what you would see on a block explorer).
Bytes 40-43: Output Index. 00000000 — we are spending output index 0 (the first output) of the referenced transaction.
Byte 44: ScriptSig Length. 00 — the ScriptSig is empty, as expected for a native SegWit input. The actual "unlocking" data is in the witness section.
Bytes 45-48: Sequence. feffffff — this is 0xfffffffe, indicating that the transaction opts in to BIP 125 replace-by-fee (RBF). A sequence of 0xffffffff would mean final (no RBF).
Byte 49: Output Count. 02 — two outputs (payment + change).
Output 0: Value. a086010000000000 in little-endian is 0x000000000186a0 = 100,000 satoshis = 0.001 BTC.
Output 0: ScriptPubKey. 160014d85c2b71... — the first byte 16 (22 in decimal) is the script length. 0014 is the pattern for a P2WPKH output (OP_0 followed by a 20-byte push). The remaining 20 bytes are the witness program (the HASH160 of the recipient's public key).
Output 1: Value and ScriptPubKey. Similarly parsed — this is the change output, sending 1,000 satoshis back to the sender.
Witness Data. The witness section contains one stack per input. For our single input, there are 2 items: the signature (typically 71-72 bytes, DER-encoded ECDSA signature) and the compressed public key (33 bytes).
Bytes (final 4): Locktime. 00000000 — no time lock. The transaction can be included immediately.
Converting Addresses
The 20-byte witness program in a P2WPKH output is the HASH160 of the recipient's public key. To display this as a human-readable address, it is encoded using Bech32 format with the "bc1q" prefix for mainnet. Your code would use a Bech32 encoding library to produce the familiar bc1q... address string from the raw bytes.
Understanding raw transaction parsing is not merely academic. Wallet developers, block explorer developers, forensic analysts, and protocol researchers all need to work with raw transaction data. The Python parser we build in this chapter's code exercises will give you a concrete tool for this task.
6.8 Building Our Blockchain in Python
Progressive Project: A Simplified Blockchain
It is time to move from reading about data structures to building them. In this section, we construct a simplified blockchain in Python that captures the essential mechanics of the Bitcoin protocol: blocks containing transactions, blocks linked via hashes, and chain validation.
This is a milestone in our progressive project. In Chapter 2, we implemented hash functions and digital signatures. Now we use those primitives to build the chain itself. In later chapters, we will add networking, consensus mechanisms, and eventually a decentralized voting application on top of this foundation.
Design Overview
Our simplified blockchain will consist of:
- A
Transactionclass that represents a simplified transaction with inputs, outputs, and a unique ID. - A
Blockclass that contains a header (with the previous hash, Merkle root, timestamp, and nonce) and a body (list of transactions). - A
Blockchainclass that manages the chain, creates new blocks, and validates the entire chain.
We will use SHA-256 for hashing (from Python's hashlib library), and our mining will use a simple proof-of-work scheme where the block hash must start with a configurable number of leading zeros.
The Transaction Class
import hashlib
import json
import time
from typing import List, Dict, Optional
class Transaction:
"""A simplified Bitcoin-like transaction."""
def __init__(self, sender: str, recipient: str, amount: float,
tx_inputs: Optional[List[Dict]] = None):
self.sender = sender
self.recipient = recipient
self.amount = amount
self.timestamp = time.time()
self.tx_inputs = tx_inputs or []
self.tx_id = self.calculate_hash()
def calculate_hash(self) -> str:
"""Calculate the transaction ID (hash of transaction data)."""
tx_data = json.dumps({
'sender': self.sender,
'recipient': self.recipient,
'amount': self.amount,
'timestamp': self.timestamp,
'tx_inputs': self.tx_inputs
}, sort_keys=True)
return hashlib.sha256(tx_data.encode()).hexdigest()
def to_dict(self) -> Dict:
return {
'tx_id': self.tx_id,
'sender': self.sender,
'recipient': self.recipient,
'amount': self.amount,
'timestamp': self.timestamp,
'tx_inputs': self.tx_inputs
}
This is a simplification — real Bitcoin transactions do not have "sender" and "recipient" fields. They have inputs (UTXO references) and outputs (locking scripts). But this captures the essential idea: a transaction is a data structure with a unique identifier derived from its contents.
The Block Class
class Block:
"""A block in our simplified blockchain."""
def __init__(self, index: int, transactions: List[Transaction],
previous_hash: str, difficulty: int = 4):
self.index = index
self.timestamp = time.time()
self.transactions = transactions
self.previous_hash = previous_hash
self.difficulty = difficulty
self.nonce = 0
self.merkle_root = self.calculate_merkle_root()
self.hash = self.mine_block()
def calculate_merkle_root(self) -> str:
"""Calculate the Merkle root of the block's transactions."""
if not self.transactions:
return hashlib.sha256(b'').hexdigest()
tx_hashes = [tx.tx_id for tx in self.transactions]
while len(tx_hashes) > 1:
if len(tx_hashes) % 2 == 1:
tx_hashes.append(tx_hashes[-1]) # Duplicate last if odd
new_level = []
for i in range(0, len(tx_hashes), 2):
combined = tx_hashes[i] + tx_hashes[i + 1]
new_hash = hashlib.sha256(combined.encode()).hexdigest()
new_level.append(new_hash)
tx_hashes = new_level
return tx_hashes[0]
def calculate_hash(self) -> str:
"""Calculate the block header hash."""
header = json.dumps({
'index': self.index,
'timestamp': self.timestamp,
'merkle_root': self.merkle_root,
'previous_hash': self.previous_hash,
'nonce': self.nonce
}, sort_keys=True)
return hashlib.sha256(header.encode()).hexdigest()
def mine_block(self) -> str:
"""Simple proof of work: find a hash with leading zeros."""
target = '0' * self.difficulty
while True:
block_hash = self.calculate_hash()
if block_hash[:self.difficulty] == target:
return block_hash
self.nonce += 1
Notice how the block structure mirrors what we studied: the header contains the previous hash, Merkle root, timestamp, and nonce. The mining process increments the nonce until the hash meets the difficulty target.
The Blockchain Class
class Blockchain:
"""A simplified blockchain with validation."""
def __init__(self, difficulty: int = 4):
self.difficulty = difficulty
self.chain: List[Block] = []
self.pending_transactions: List[Transaction] = []
self.create_genesis_block()
def create_genesis_block(self):
"""Create the first block in the chain."""
genesis_tx = Transaction("network", "genesis", 0)
genesis_block = Block(0, [genesis_tx], "0" * 64, self.difficulty)
self.chain.append(genesis_block)
def get_latest_block(self) -> Block:
return self.chain[-1]
def add_transaction(self, transaction: Transaction):
"""Add a transaction to the pending pool."""
self.pending_transactions.append(transaction)
def mine_pending_transactions(self, miner_address: str) -> Block:
"""Create a new block with pending transactions."""
# Create coinbase transaction (block reward)
coinbase = Transaction("network", miner_address, 6.25)
block_transactions = [coinbase] + self.pending_transactions
new_block = Block(
index=len(self.chain),
transactions=block_transactions,
previous_hash=self.get_latest_block().hash,
difficulty=self.difficulty
)
self.chain.append(new_block)
self.pending_transactions = []
return new_block
def validate_chain(self) -> bool:
"""Validate the entire blockchain."""
for i in range(1, len(self.chain)):
current = self.chain[i]
previous = self.chain[i - 1]
# Verify the stored hash matches recalculation
if current.hash != current.calculate_hash():
print(f"Block {i}: Hash mismatch")
return False
# Verify the previous hash pointer
if current.previous_hash != previous.hash:
print(f"Block {i}: Previous hash mismatch")
return False
# Verify proof of work
if not current.hash.startswith('0' * current.difficulty):
print(f"Block {i}: Invalid proof of work")
return False
# Verify Merkle root
if current.merkle_root != current.calculate_merkle_root():
print(f"Block {i}: Merkle root mismatch")
return False
return True
Running the Blockchain
if __name__ == "__main__":
# Create blockchain with difficulty 4 (hash must start with "0000")
blockchain = Blockchain(difficulty=4)
# Add some transactions
blockchain.add_transaction(Transaction("Alice", "Bob", 1.5))
blockchain.add_transaction(Transaction("Bob", "Charlie", 0.7))
# Mine a block
print("Mining block 1...")
block1 = blockchain.mine_pending_transactions("Miner1")
print(f"Block 1 mined! Hash: {block1.hash}")
print(f" Nonce: {block1.nonce}")
print(f" Transactions: {len(block1.transactions)}")
# Add more transactions and mine another block
blockchain.add_transaction(Transaction("Charlie", "Alice", 0.3))
print("\nMining block 2...")
block2 = blockchain.mine_pending_transactions("Miner1")
print(f"Block 2 mined! Hash: {block2.hash}")
# Validate the chain
print(f"\nChain valid: {blockchain.validate_chain()}")
print(f"Chain length: {len(blockchain.chain)}")
This code is fully functional. You can run it right now and see blocks being mined, each linking to the previous one via its hash. The validate_chain method checks every link, every hash, every proof of work, and every Merkle root. If you modify any transaction in any block, validation will fail — demonstrating the immutability property we discussed in Chapter 3.
💡 Progressive Project Connection: This
Blockchainclass is the backbone of everything we build in this course. In Chapter 7, we will add a peer-to-peer networking layer. In Chapter 10, we will evolve this into an Ethereum-style state machine. By Chapter 16, this will support the smart contracts that power our decentralized voting dApp. Save this code — you will build on it.
6.9 UTXO Model vs. Account Model
We have now examined the UTXO model in depth. Before closing, let us compare it to the account model used by Ethereum and many other blockchains. This comparison is not about which is "better" — both designs make deliberate tradeoffs.
The Account Model (Ethereum's Approach)
In the account model, the blockchain maintains a global state that maps addresses to account objects. Each account has:
- A balance (how much ETH the account holds)
- A nonce (a counter of how many transactions the account has sent, used to prevent replay attacks)
- Storage (for smart contract accounts, an arbitrary key-value store)
- Code (for smart contract accounts, the compiled bytecode)
When Alice sends 1 ETH to Bob, the protocol directly modifies the state: Alice's balance decreases by 1, Bob's balance increases by 1. There are no UTXOs to consume, no change outputs to create.
Comparison
| Feature | UTXO Model (Bitcoin) | Account Model (Ethereum) |
|---|---|---|
| State representation | Set of unspent outputs | Map of addresses to balances |
| Transaction structure | Inputs consume UTXOs, outputs create UTXOs | From-address, to-address, value |
| Parallelism | Excellent — transactions spending different UTXOs can be validated in parallel | Harder — transactions from the same account must be ordered by nonce |
| Privacy | Better — each transaction can use a fresh change address | Worse — account addresses tend to be reused |
| Simplicity | More complex for wallets to manage | Simpler mental model for developers |
| Smart contracts | Limited (Script is not Turing-complete) | Rich (Solidity is Turing-complete with gas) |
| Double-spend detection | Simple — check if UTXO exists in the set | Requires nonce ordering |
| Proof size | Can prove UTXO existence without full state | Proving balance requires accessing global state tree |
UTXO Model Advantages
Parallel validation. Because UTXOs are independent objects, transactions that spend different UTXOs can be validated simultaneously on different CPU cores. This is a significant scalability advantage.
Better privacy. The UTXO model encourages address non-reuse. Each change output naturally goes to a fresh address, making it harder to link transactions to a single user. In the account model, users tend to reuse the same address, creating a clear transaction trail.
Simpler double-spend detection. To verify that a UTXO has not been double-spent, you only need to check whether it exists in the UTXO set. Either it is there (unspent) or it is not (already spent). There is no ambiguity.
Stateless verification. With the right data structures (like utreexo), it is possible to verify UTXO transactions with very compact proofs, without the validator maintaining the full UTXO set.
Account Model Advantages
Developer simplicity. The account model is much easier to reason about for smart contract development. You can read balances, check nonces, and update state in a straightforward way.
Smaller transaction size. Account-model transactions do not need to reference previous outputs or provide change — a simple transfer is just (from, to, value, nonce, signature).
Better for smart contracts. Complex state machines, token balances, and contract-to-contract interactions are far more natural in the account model.
Fungibility. All ETH in an account is identical. In the UTXO model, each UTXO has a distinct history, and some services may "taint" UTXOs based on their transaction history (e.g., UTXOs that passed through a mixer).
Why Bitcoin Chose the UTXO Model
Satoshi Nakamoto's choice of the UTXO model reflects Bitcoin's design priorities: security, privacy, and simplicity of the consensus rules. The UTXO model's "statelessness" — the fact that each transaction explicitly declares its inputs and outputs — makes it very easy for nodes to verify transactions independently. There is no "global account state" to synchronize; there is only the question of whether specific UTXOs exist and whether the spending conditions are met.
For Bitcoin's specific use case — a digital currency with simple transfer semantics — the UTXO model is arguably the superior choice. For Ethereum's use case — a general-purpose smart contract platform — the account model is arguably the superior choice. The blockchain ecosystem has room for both.
🔗 Cross-Reference: We will examine Ethereum's account model in much greater depth in Chapter 10 when we study the Ethereum Virtual Machine. Understanding both models is essential for our progressive project, where we will implement a voting dApp that leverages smart contracts (account model) while understanding the UTXO-based value transfer that underpins the broader ecosystem.
6.10 Chapter Summary and Bridge
What We Learned
This chapter took you from the conceptual understanding of "Bitcoin is a blockchain" to the implementation-level understanding of "Bitcoin is a specific data structure with specific rules." We covered:
The UTXO Model. Bitcoin does not maintain balances. Instead, it maintains a set of unspent transaction outputs. Transactions consume UTXOs (inputs) and create new UTXOs (outputs). Change outputs return excess value to the sender. The UTXO set is the global state.
Transaction Anatomy. A Bitcoin transaction contains a version, inputs (each referencing a previous UTXO and providing an unlocking script), outputs (each specifying a value and a locking script), and a locktime. Every field has a specific purpose and a specific encoding.
Locking and Unlocking Scripts. Bitcoin Script is a stack-based language that defines the conditions for spending UTXOs. P2PKH (pay to a public key hash) is the traditional format; P2SH (pay to a script hash) enables advanced features like multi-signature. The Script language is intentionally limited — no loops, no external state — for security.
The Coinbase Transaction. Every block's first transaction creates new Bitcoin according to the halving schedule. The coinbase has no real inputs, includes an arbitrary data field (famously used by Satoshi for a newspaper headline), and its outputs require 100 confirmations before they can be spent.
Block Structure. The 80-byte block header contains the version, previous block hash, Merkle root, timestamp, difficulty target, and nonce. The body contains the transaction list. The chain of previous-hash pointers is what creates the immutable ledger.
SegWit. Segregated Witness separates signature data from the transaction, fixing transaction malleability and increasing effective block capacity. SegWit introduced weight units, virtual bytes, and new address formats (bc1q for P2WPKH, bc1p for Taproot).
Raw Transaction Parsing. Every byte in a serialized Bitcoin transaction has a meaning and a purpose. We walked through a real transaction field by field, from version to locktime.
Our Python Blockchain. We built Transaction, Block, and Blockchain classes that create blocks with transactions, link them via hashes, mine with proof of work, and validate the chain's integrity.
Bridge to Chapter 7
We now understand how Bitcoin transactions and blocks work at the implementation level. But a transaction sitting on one computer is useless — it needs to be broadcast to the network, validated by thousands of nodes, and included in a block by a miner.
In Chapter 7, we examine Bitcoin's peer-to-peer networking layer: how nodes discover each other, how transactions propagate through the mempool, how blocks are relayed, and how the network reaches consensus without any central coordinator. We will add a networking layer to our Python blockchain, enabling multiple nodes to synchronize their chains.
The protocol details you learned in this chapter are the payload. The network is the delivery mechanism. Together, they form the complete Bitcoin system.
Key Equations and Formulas
Transaction fee (implicit): $$\text{fee} = \sum \text{input values} - \sum \text{output values}$$
SegWit weight: $$\text{weight} = (\text{non-witness bytes} \times 4) + (\text{witness bytes} \times 1)$$
Virtual bytes: $$\text{vbytes} = \frac{\text{weight}}{4}$$
Fee rate: $$\text{fee rate (sat/vB)} = \frac{\text{fee (satoshis)}}{\text{vbytes}}$$
Block reward at height h: $$\text{reward}(h) = \frac{50 \times 10^8}{2^{\lfloor h / 210000 \rfloor}} \text{ satoshis}$$
Total Bitcoin supply (geometric series): $$\text{total} = 210000 \times 50 \times \sum_{k=0}^{32} \frac{1}{2^k} \approx 20{,}999{,}999.9769 \text{ BTC}$$