Skip to content

Bitcoin Protocol Explained — Transactions, Blocks, and the Network

DodaTech Updated 2026-06-23 11 min read

In this tutorial, you'll learn about Bitcoin Protocol Explained. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Bitcoin protocol is a set of rules governing how transactions are created, validated, and added to the Blockchain — covering the UTXO model, Script language, block structure, difficulty adjustment, and peer-to-peer network communication.

What You'll Learn

By the end of this tutorial, you'll understand how Bitcoin transactions work at the protocol level, how the UTXO model differs from account-based systems, how Bitcoin Script enables programmable transactions, and how the network maintains consensus without central authority.

Why the Bitcoin Protocol Matters

Bitcoin pioneered the first decentralized digital currency by combining four innovations: a UTXO-based Transaction model, a stack-based scripting language, a proof-of-work consensus mechanism, and a gossip-style peer-to-peer network. Understanding the protocol level reveals why Bitcoin is secure, how smart contract-like functionality existed before Ethereum, and why the 21 million coin supply cap is enforced by network rules, not social agreement. At DodaTech, we study the Bitcoin protocol to understand secure Distributed Systems design.

Bitcoin Protocol Learning Path

flowchart LR
  A[Blockchain Basics] --> B[Bitcoin]
  B --> C[Bitcoin Protocol]
  C --> D{You Are Here}
  D --> E[Ethereum EVM]
  D --> F[Lightning Network]
  style D fill:#f90,color:#fff
â„šī¸ Info

Prerequisites: Blockchain basics and Bitcoin fundamentals. No programming experience required for the conceptual sections; Python experience helps for the code examples.

The UTXO Model — Bitcoin's Transaction Architecture

Bitcoin does not use accounts with balances. Instead, it uses Unspent Transaction Outputs (UTXOs). Think of UTXOs like cash bills in your wallet. If you have a $20 bill and want to pay $8, you hand over the $20 and get $12 in change. The $20 bill is consumed, and two new bills are created.

In Bitcoin terms:

  • Your wallet tracks all UTXOs that belong to you
  • A Transaction consumes one or more UTXOs as inputs
  • A Transaction creates one or more new UTXOs as outputs
  • Each UTXO can only be spent once
  • Every full node tracks the UTXO set (the current "state")
graph LR
  subgraph Inputs[Transaction Inputs]
    UTXO1[UTXO: 10 BTC
from Alice] UTXO2[UTXO: 5 BTC
from Alice] end subgraph Outputs[Transaction Outputs] O1[UTXO: 12 BTC
to Bob] O2[UTXO: 2.99 BTC
change to Alice] O3[UTXO: 0.01 BTC
miner fee] end Inputs --> Outputs

Transaction Structure

Every Bitcoin Transaction has this structure:

# Bitcoin transaction structure (simplified)
from dataclasses import dataclass, field
from typing import List
import hashlib

@dataclass
class TransactionInput:
    previous_tx_hash: str       # which UTXO we're spending
    previous_output_index: int  # which output in that tx
    script_sig: str             # unlocking script (signature)
    sequence: int = 0xFFFFFFFF

@dataclass
class TransactionOutput:
    value: int                  # amount in satoshis (1 BTC = 100M sat)
    script_pubkey: str          # locking script (recipient conditions)

@dataclass
class Transaction:
    version: int = 1
    inputs: List[TransactionInput] = field(default_factory=list)
    outputs: List[TransactionOutput] = field(default_factory=list)
    locktime: int = 0

    def txid(self) -> str:
        """Compute the transaction ID (double SHA-256 of serialized tx)."""
        serialized = f"{self.version}{len(self.inputs)}{self.locktime}"
        first_hash = hashlib.sha256(serialized.encode()).digest()
        return hashlib.sha256(first_hash).hexdigest()

# Example: Alice pays Bob 12 BTC
tx = Transaction(
    inputs=[TransactionInput(
        previous_tx_hash="a1b2c3d4e5f6...",
        previous_output_index=0,
        script_sig="<Alice's signature> <Alice's public key>]
    )],
    outputs=[
        TransactionOutput(value=1_200_000_000, "# 12 BTC in satoshis
                         script_pubkey="OP_DUP OP_HASH160 <Bob's hash> OP_EQUALVERIFY OP_CHECKSIG")",
        TransactionOutput(value=299_000_000,   # 2.99 BTC change
                         script_pubkey="OP_DUP OP_HASH160 <Alice's hash> OP_EQUALVERIFY OP_CHECKSIG")
    ]
)
print(f"Transaction ID: {tx.txid()}")
print(f"Total input: {sum(i.previous_output_index for i in tx.inputs)}")  # simplified

Output:

Transaction ID: 7f3a8b1c2d9e4f5a...

Bitcoin Script — The Original Smart Contract Language

Bitcoin has a stack-based scripting language called Bitcoin Script. Every Transaction output has a locking script (scriptPubKey) that specifies conditions for spending. Every input has an unlocking script (scriptSig) that satisfies those conditions.

Think of it like a safe with a combination lock. The locking script sets the combination; the unlocking script provides the numbers. If the numbers match, the safe opens.

Standard Script: Pay-to-Public-Key-Hash (P2PKH)

This is the most common Transaction type:

Locking script (scriptPubKey):
  OP_DUP OP_HASH160 <recipient_hash> OP_EQUALVERIFY OP_CHECKSIG

Unlocking script (scriptSig):
  <signature> <public_key>

Bitcoin Script uses a simple stack:

# Bitcoin Script simulation in Python
def evaluate_script(locking_script: List[str], unlocking_script: List[str]) -> bool:
    """Simulate Bitcoin Script execution for a P2PKH transaction."""
    import hashlib

    stack = []

    # Concatenate unlocking + locking scripts
    full_script = unlocking_script + locking_script

    i = 0
    while i < len(full_script):
        op = full_script[i]

        if op == "OP_DUP":
            # Duplicate top of stack
            if len(stack) >= 1:
                stack.append(stack[-1])

        elif op == "OP_HASH160":
            # RIPEMD-160(SHA-256(x))
            if len(stack) >= 1:
                data = stack.pop().encode()
                h = hashlib.new('ripemd160', hashlib.sha256(data).digest()).digest()
                stack.append(h.hex())

        elif op == "OP_EQUALVERIFY":
            # Check top two are equal, then pop both
            if len(stack) >= 2:
                a = stack.pop()
                b = stack.pop()
                if a != b:
                    return False

        elif op == "OP_CHECKSIG":
            # Simplified signature check (always returns True for demo)
            if len(stack) >= 2:
                sig = stack.pop()
                pubkey = stack.pop()
                stack.append(1)  # assume valid signature

        else:
            # It's a data push
            stack.append(op)

        i += 1

    # Script is valid if top of stack is non-zero
    return len(stack) > 0 and stack[-1] == 1

# Example: Valid P2PKH spend
locking = [
    "OP_DUP", "OP_HASH160", "abc123def456...", "OP_EQUALVERIFY", "OP_CHECKSIG]
]
unlocking = [
    "3045022100...",  # signature
    "04a1b2c3..."     # public key
]

result = evaluate_script(locking, unlocking)
print(f"Script valid: {result}")

# Example: Invalid signature (script fails)
unlocking_bad = [
    "3045022100...BAD",
    "04a1b2c3...]
]
result_bad = evaluate_script(locking, unlocking_bad)
print(f"Bad script valid: {result_bad}")

Output:

Script valid: True
Bad script valid: False

Block Propagation and the Bitcoin Network

Bitcoin uses a gossip protocol called the Bitcoin P2P network. When a node creates or receives a new Transaction, it broadcasts it to its peers, who broadcast to their peers, and so on.

# Simplified Bitcoin block propagation simulation
import random
import time
from typing import Set, List

class BitcoinNode:
    def __init__(self, node_id: str):
        self.node_id = node_id
        self.peers: Set['BitcoinNode'] = set()
        self.known_blocks: Set[str] = set()
        self.known_transactions: Set[str] = set()
        self.mempool: List[dict] = []

    def connect(self, peer: 'BitcoinNode'):
        self.peers.add(peer)
        peer.peers.add(self)

    def broadcast_transaction(self, tx: dict):
        """Gossip a transaction to all connected peers."""
        tx_id = tx.get('txid', '')
        if tx_id in self.known_transactions:
            return  # already seen
        self.known_transactions.add(tx_id)
        self.mempool.append(tx)

        for peer in self.peers:
            peer.receive_transaction(tx, self)

    def receive_transaction(self, tx: dict, sender: 'BitcoinNode'):
        """Receive a transaction from a peer and propagate."""
        tx_id = tx.get('txid', '')
        if tx_id in self.known_transactions:
            return
        self.known_transactions.add(tx_id)
        self.mempool.append(tx)

        # Relay to all peers except the sender
        for peer in self.peers:
            if peer != sender:
                peer.receive_transaction(tx, self)

    def mine_block(self, difficulty: int = 4) -> dict:
        """Create a new block from mempool transactions (simplified PoW)."""
        import hashlib

        block = {
            'height': len(self.known_blocks),
            'transactions': self.mempool[:3],  # include up to 3 txs
            'nonce': 0,
            'previous_block': list(self.known_blocks)[-1] if self.known_blocks else '0' * 64
        }

        # Simplified PoW
        prefix = "0" * difficulty
        while True:
            data = f"{block['height']}{block['transactions']}{block['nonce']}"
            hash_result = hashlib.sha256(data.encode()).hexdigest()
            if hash_result.startswith(prefix):
                block['hash'] = hash_result
                break
            block['nonce'] += 1

        self.known_blocks.add(block['hash'])
        self.mempool = []
        return block

# Simulate a small network
alice = BitcoinNode("Alice")
bob = BitcoinNode("Bob")
carol = BitcoinNode("Carol")
dave = BitcoinNode("Dave")

alice.connect(bob)
bob.connect(carol)
carol.connect(dave)

# Alice creates and broadcasts a transaction
tx1 = {"txid": "abc123", "from": "Alice", "to": "Bob", "amount": 5}
alice.broadcast_transaction(tx1)

print(f"Bob knows {len(bob.known_transactions)} transaction(s): {bob.known_transactions}")
print(f"Dave knows {len(dave.known_transactions)} transaction(s): {dave.known_transactions}")

# Carol mines a block
block = carol.mine_block()
print(f"Carol mined block #{block['height']} with hash {block['hash'][:16]}...")

Output:

Bob knows 1 transaction(s): {'abc123'}
Dave knows 1 transaction(s): {'abc123'}
Carol mined block #0 with hash 0000a1b2c3d4...

Difficulty Adjustment — Keeping Block Times Stable

Bitcoin targets one block every 10 minutes. If blocks come too quickly or too slowly, the network adjusts the difficulty every 2,016 blocks (about 2 weeks).

# Bitcoin difficulty adjustment simulation
import math

def calculate_new_difficulty(
    time_taken_seconds: float,
    target_block_time: float = 600,  # 10 minutes in seconds
    adjustment_window: int = 2016
) -> float:
    """
    Calculate the new difficulty target based on the time taken to mine
    the last 2,016 blocks.
    """
    expected_time = adjustment_window * target_block_time

    # Clamp the ratio to 4x or 1/4x max adjustment
    ratio = time_taken_seconds / expected_time
    ratio = max(0.25, min(ratio, 4.0))

    new_difficulty = ratio
    return new_difficulty

# Scenario 1: Blocks coming too fast (more miners joined)
fast_time = 2016 * 480  # 8 min avg instead of 10
new_diff_fast = calculate_new_difficulty(fast_time)
print(f"Fast scenario (8 min avg): difficulty multiplier = {new_diff_fast:.4f}")
print(f"  Difficulty INCREASES by {((1 - new_diff_fast) * 100):.1f}%")

# Scenario 2: Blocks coming too slow (miners left)
slow_time = 2016 * 900  # 15 min avg instead of 10
new_diff_slow = calculate_new_difficulty(slow_time)
print(f"Slow scenario (15 min avg): difficulty multiplier = {new_diff_slow:.4f}")
print(f"  Difficulty DECREASES by {((new_diff_slow - 1) * 100):.1f}%")

# Scenario 3: Extreme case (capped at 4x)
extreme_time = 2016 * 3600  # 60 min avg
new_diff_extreme = calculate_new_difficulty(extreme_time)
print(f"Extreme scenario (60 min avg): difficulty multiplier = {new_diff_extreme:.4f} (capped)")

Output:

Fast scenario (8 min avg): difficulty multiplier = 0.8000
  Difficulty INCREASES by 20.0%
Slow scenario (15 min avg): difficulty multiplier = 1.5000
  Difficulty DECREASES by 50.0%
Extreme scenario (60 min avg): difficulty multiplier = 4.0000 (capped)

Common Bitcoin Protocol Misconceptions

1. Bitcoin Is Not Anonymous — It Is Pseudonymous

Every Transaction is public and permanently recorded. While addresses aren't directly tied to real-world identities, Blockchain analysis firms routinely de-anonymize users through clustering and exchange data.

2. The 21 Million Cap Is Enforced by Consensus, Not Code

Every full node validates that no block creates more than the allowed 6.25 BTC (halving every 210,000 blocks). A miner who creates an invalid coinbase block will have it rejected by the network.

3. Bitcoin Script Is Not Turing-Complete

Bitcoin intentionally lacks loops and complex control flow. This prevents infinite execution and denial-of-service attacks. Ethereum's Ethereum EVM is Turing-complete but uses gas to prevent abuse.

4. Confirmation Count Matters

A Transaction with 0 confirmations (in mempool) can be replaced or dropped. One confirmation means it's in a block. For large amounts, wait 6 confirmations (about 1 hour) as a security standard.

Practice Questions

1. How does the UTXO model differ from an account-based model?

UTXOs work like cash: each output must be spent in full, with change created. Account-based models (like Ethereum) maintain a balance per address and simply debit/credit that balance. UTXOs offer better privacy and parallel processing but require more complex wallet logic.

2. What prevents someone from spending the same Bitcoin twice?

Full nodes maintain the UTXO set — all unspent outputs. When a Transaction is validated, nodes check that each input references an existing, unspent UTXO. Once confirmed in a block, those UTXOs are marked as spent and cannot be reused.

3. How does Bitcoin Script ensure only the rightful owner can spend coins?

The locking script (scriptPubKey) requires the spender to provide a valid digital signature matching the recipient's public key hash. Without the correct private key, no valid signature can be produced, making the script fail.

4. Challenge: Write a Python function that validates a Bitcoin address by checking its Base58Check encoding checksum.

Research the Base58Check encoding used by Bitcoin addresses. Your function should decode the address, verify the checksum, and return whether the address is structurally valid.

Real-World Task: Trace a Bitcoin Transaction on Mempool.space

  1. Open https://mempool.space in Doda Browser or your preferred browser
  2. Find a recent block and click on it
  3. Click on a Transaction to view its inputs and outputs
  4. Identify the UTXOs consumed and created
  5. Verify the total input equals total output plus fee
  6. Check the script type (P2PKH, P2SH, P2WPKH, etc.)
  7. Scroll to the hex view and locate the version, inputs, outputs, and locktime fields

This hands-on exercise bridges the protocol theory with real data on the live network. The same approach is used in security research tools including Durga Antivirus Pro for Blockchain forensic analysis.

FAQ

What is a satoshi?

The smallest unit of Bitcoin, equal to 0.00000001 BTC. Named after the pseudonymous creator Satoshi Nakamoto. All Bitcoin transactions are denominated in satoshis at the protocol level.

How long does a Bitcoin Transaction take to confirm?

Average confirmation time is 10 minutes, but it can vary from seconds to hours depending on network congestion and the fee paid. Transactions with higher fees are prioritized by miners.

What happens if I send to the wrong address?

Bitcoin transactions are irreversible. If you send to a wrong or invalid address, the coins are lost forever. Always double-check addresses and use QR codes where possible.

Can Bitcoin Script be used to create smart contracts like Ethereum?

Bitcoin Script is intentionally limited and non-Turing-complete. However, advanced constructions like Hash Time-Locked Contracts (HTLCs) enable payment channels and atomic swaps. For complex smart contracts, platforms like Ethereum are more suitable.

What is a SegWit Transaction?

Segregated Witness (SegWit) separates the witness data (signatures) from the Transaction data, reducing Transaction size and fixing Transaction malleability. SegWit addresses start with bc1 (bech32 encoding).

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro