
When you hear the word “blockchain,” what comes to mind? Bitcoin? Ethereum? Complex cryptographic puzzles and million-dollar mining rigs? For most people, blockchain technology feels like a black box—powerful, revolutionary, but inaccessible to the average programmer.
Here is the secret that the hype machine doesn’t tell you: The core concept of a blockchain is surprisingly simple. At its heart, a blockchain is just a linked list—a chain of blocks—with a few clever cryptographic tricks to ensure that once data is written, it cannot be changed without everyone noticing.
In this guide, we will strip away the noise and build a fully functional blockchain from scratch using Python in fewer than 200 lines of code. You will learn about hashing, proof-of-work, and immutability by actually building it. Whether you are a budding developer, a crypto enthusiast, or just curious, by the end of this tutorial, you will have a working prototype that can mine blocks and validate its own integrity.
Let’s code.
What Exactly Is a Blockchain? (The 30-Second Refresher)
Before we open our code editor, let’s align on the vocabulary. A blockchain is a distributed digital ledger. It consists of a list of records (called blocks) linked together using cryptography.
Each block contains three essential ingredients:
- Data: The actual information (e.g., “Alice sent Bob 5 coins”).
- Hash: A unique digital fingerprint of the current block (like a thumbprint).
- Previous Hash: The fingerprint of the block that came before it.
This “previous hash” is the chain. If someone changes Block #2, its hash changes. Block #3 will immediately notice because it stores the old hash. The chain breaks.
To make this secure against tampering, blockchains often use a Proof-of-Work mechanism—a mathematical puzzle that makes creating a new block intentionally slow (about 10 minutes for Bitcoin). We will implement a simplified version of this puzzle.
Setting Up Your Python Environment
You don’t need any exotic libraries for this project. We will use only the Python standard library. This ensures that your code runs anywhere without installation headaches.
Requirements
- Python 3.6 or higher: If you don’t have it, download it from python.org.
- A text editor: VS Code, PyCharm, or even Notepad.
Importing the Necessary Modules
Create a new file called simple_blockchain.py. We need three imports:
python
import hashlib import json import time
- hashlib: For creating SHA-256 hashes (the fingerprints).
- json: To convert our block data into a string format that can be hashed.
- time: To add timestamps to our blocks.
H2: Building the Block Class
Every chain needs blocks. We will define a Python class that represents a single block. This is the blueprint for every link in our chain.
The Block Structure
A block in our blockchain will have the following attributes:
index: The position of the block (0 for Genesis, 1, 2…).timestamp: When the block was created.data: The transactions or information.previous_hash: The hash of the preceding block (a string).nonce: A number used for the proof-of-work puzzle.hash: The current block’s own fingerprint.
Here is the __init__ method:
python
class Block:
def __init__(self, index, data, previous_hash):
self.index = index
self.timestamp = time.time()
self.data = data
self.previous_hash = previous_hash
self.nonce = 0 # Starts at zero, we will increment this for mining
self.hash = self.compute_hash()
The Compute Hash Method
The compute_hash method takes all the block’s data and scrambles it into a 64-character hexadecimal string.
Important: The data must be deterministic. If we hash the same data twice, we get the same hash. If we change even one comma, the hash changes completely.
python
def compute_hash(self):
# Convert the block contents to a JSON string
block_string = json.dumps({
"index": self.index,
"timestamp": self.timestamp,
"data": self.data,
"previous_hash": self.previous_hash,
"nonce": self.nonce
}, sort_keys=True).encode()
# Return the SHA-256 hash
return hashlib.sha256(block_string).hexdigest()
Notice how we include the nonce in the hash. This is how we will solve the puzzle later.
Implementing Proof-of-Work (Mining)
In a real blockchain like Bitcoin, mining is hard. The network requires that the block’s hash starts with a certain number of zeros (e.g., 0000a3f5...).
We will implement a simple puzzle: Find a hash that starts with four zeros ("0000") .
The Proof-of-Work Logic
We will add a mine method to our Block class. It will try nonce values (0, 1, 2, 3…) until it finds a hash that meets the difficulty requirement.
python
def mine(self, difficulty):
# Create a target string of zeros (e.g., "0000")
target = "0" * difficulty
# Keep looping until the hash starts with the target
while self.hash[:difficulty] != target:
self.nonce += 1
self.hash = self.compute_hash()
print(f"Block mined! Nonce: {self.nonce}, Hash: {self.hash}")
- Why this works: Because the
nonceis part of the hash calculation, changing it changes the hash. The computer brute-forces numbers until it gets “luck” and finds a hash starting with zeros. - Difficulty: In production, difficulty adjusts. For our tutorial, we will use
difficulty = 4.
Building the Blockchain Class
Now we need a class to manage the chain. This class will handle creating new blocks, validating the chain, and storing the list.
Initializing the Chain
Python
class Blockchain:
def __init__(self):
self.chain = []
self.difficulty = 4 # Number of leading zeros required
self.create_genesis_block()
def create_genesis_block(self):
# The first block has no previous hash
genesis_block = Block(0, "Genesis Block", "0")
genesis_block.mine(self.difficulty)
self.chain.append(genesis_block)
The Genesis Block is the first block. Its previous_hash is just a placeholder string ("0").
Adding New Blocks
To add a new block, we get the hash of the current last block, create a new block pointing to it, mine it, and append it.
python
def add_block(self, data):
previous_block = self.chain[-1]
new_block = Block(len(self.chain), data, previous_block.hash)
new_block.mine(self.difficulty)
self.chain.append(new_block)
Validating the Chain (Immutability Check)
This is the most important function. It ensures that no one has tampered with the data.
python
def is_chain_valid(self):
# Start from block 1 (skip genesis)
for i in range(1, len(self.chain)):
current = self.chain[i]
previous = self.chain[i-1]
# 1. Check if the current block's hash is correct
if current.hash != current.compute_hash():
print(f"Invalid hash at block {current.index}")
return False
# 2. Check if the current block points to the previous block's hash
if current.previous_hash != previous.hash:
print(f"Broken link at block {current.index}")
return False
# 3. Check if the proof-of-work is valid (starts with zeros)
if current.hash[:self.difficulty] != "0" * self.difficulty:
print(f"Invalid proof-of-work at block {current.index}")
return False
return True
Putting It All Together (The Main Function)
Let’s run our blockchain! In the same file, add the following code outside the classes:
python
if __name__ == "__main__":
# Create the blockchain
my_chain = Blockchain()
print("Mining block 1...")
my_chain.add_block("Alice sent Bob 10 BTC")
print("\nMining block 2...")
my_chain.add_block("Bob sent Charlie 5 BTC")
# Print the entire chain
print("\n--- Blockchain Contents ---")
for block in my_chain.chain:
print(f"Index: {block.index}")
print(f"Data: {block.data}")
print(f"Hash: {block.hash}")
print(f"Prev: {block.previous_hash}")
print("-" * 40)
# Validate the chain
print(f"\nIs chain valid? {my_chain.is_chain_valid()}")
# Attempt to tamper with the data
print("\n--- Attempting Tampering ---")
my_chain.chain[1].data = "Alice sent Bob 1000 BTC"
# Recompute hash (the attacker forgets to do this in reality)
# But even if they do, the next block's previous hash breaks.
my_chain.chain[1].hash = my_chain.chain[1].compute_hash()
print(f"Is chain valid after tampering? {my_chain.is_chain_valid()}")
Expected Output
When you run python simple_blockchain.py, you will see the mining process (a slight delay as the computer searches for nonces). The final tampering test should return False, proving the security of the chain.
Advanced Concepts (Where to Go Next)
You have just built the skeleton of Bitcoin! However, a production blockchain needs a few more features. Here is how to extend your project:
Networking and Consensus
Currently, our blockchain lives in one file. Real blockchains run on thousands of computers (nodes). To make your blockchain a “network”:
- P2P Communication: Use
socketorweb3.pyto broadcast new blocks to peers. - Consensus Algorithm: If two miners mine a block at the same time, you get a “fork.” The network follows the Longest Chain Rule (the chain with the most proof-of-work).
Transactions Instead of “Data”
Replace the data string with a list of Transaction objects.
python
class Transaction:
def __init__(self, sender, receiver, amount):
self.sender = sender
self.receiver = receiver
self.amount = amount
Then, collect pending transactions into a Mempool before mining.
Digital Signatures (ECDSA)
To prove that Alice authorized the transaction, you need cryptography.
- Use the
cryptographylibrary orecdsato generate public/private keys. - Sign the transaction hash with the private key.
- Nodes verify the signature using the public key.
Flask API
Turn your blockchain into a REST API using Flask. This allows you to send transactions via HTTP POST requests and view the chain via HTTP GET.
python
from flask import Flask, jsonify, request # ... (integrate your blockchain class)
Conclusion
In less than 200 lines of Python, you have built a tamper-proof, self-validating blockchain. You learned that:
- Hashing creates unique fingerprints.
- Proof-of-Work prevents spam and makes tampering expensive.
- The Chain links blocks via
previous_hashreferences, ensuring integrity.
This is not just a coding exercise; this is the foundation of a technological revolution. Whether you want to build a supply chain tracker, a voting system, or a cryptocurrency, you now understand the engine under the hood.
Your challenge: Run the code. Try changing the difficulty from 4 to 5. Watch how much longer it takes to mine. That is the economics of blockchain in action.
Now, go build something revolutionary.
Full Source Code (Summary)
Here is the complete script (approx 130 lines) for your reference:
python
import hashlib
import json
import time
class Block:
def __init__(self, index, data, previous_hash):
self.index = index
self.timestamp = time.time()
self.data = data
self.previous_hash = previous_hash
self.nonce = 0
self.hash = self.compute_hash()
def compute_hash(self):
block_string = json.dumps({
"index": self.index,
"timestamp": self.timestamp,
"data": self.data,
"previous_hash": self.previous_hash,
"nonce": self.nonce
}, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
def mine(self, difficulty):
target = "0" * difficulty
while self.hash[:difficulty] != target:
self.nonce += 1
self.hash = self.compute_hash()
print(f"Mined Block {self.index}: Nonce={self.nonce} Hash={self.hash}")
class Blockchain:
def __init__(self):
self.chain = []
self.difficulty = 4
self.create_genesis_block()
def create_genesis_block(self):
genesis = Block(0, "Genesis Block", "0")
genesis.mine(self.difficulty)
self.chain.append(genesis)
def add_block(self, data):
prev = self.chain[-1]
new_block = Block(len(self.chain), data, prev.hash)
new_block.mine(self.difficulty)
self.chain.append(new_block)
def is_chain_valid(self):
for i in range(1, len(self.chain)):
curr, prev = self.chain[i], self.chain[i-1]
if curr.hash != curr.compute_hash():
return False
if curr.previous_hash != prev.hash:
return False
if curr.hash[:self.difficulty] != "0" * self.difficulty:
return False
return True
# Run it
if __name__ == "__main__":
bc = Blockchain()
bc.add_block("Send 10 BTC")
bc.add_block("Send 5 BTC")
print("Chain valid?", bc.is_chain_valid())

