A fully functional blockchain implementation in pure Python, featuring Proof of Work mining, RSA cryptographic wallets, transaction signing & verification, balance tracking, tamper detection, an interactive CLI, and a full unit test suite.
- Features
- Project Structure
- How a Blockchain Works
- Quick Start
- Running the Demo
- Interactive CLI
- Running Tests
- Core Concepts Explained
- API Reference
- Configuration
| Feature | Details |
|---|---|
| SHA-256 Hashing | Every block is cryptographically sealed with SHA-256 |
| Proof of Work | Adjustable mining difficulty (default: 4 leading zeros) |
| RSA Wallets | 2048-bit RSA key-pairs; address derived from public key |
| Transaction Signing | RSA-PSS signatures prevent forgery |
| Balance Tracking | Computed by replaying the entire chain β no central DB |
| Tamper Detection | Any modification to a mined block breaks all subsequent hashes |
| Chain Persistence | Chain saved to / loaded from JSON automatically |
| Interactive CLI | Full REPL: create wallets, send coins, mine, inspect chain |
| 38 Unit Tests | Full coverage of Block, Blockchain, and Wallet classes |
pychain/
βββ blockchain/
β βββ __init__.py # Package exports
β βββ block.py # Block class β hashing, serialisation
β βββ blockchain.py # Blockchain class β PoW, transactions, validation
βββ wallet/
β βββ __init__.py # Package exports
β βββ wallet.py # RSA wallet β key-gen, sign, verify, save/load
βββ cli/
β βββ cli.py # Interactive command-line interface
βββ tests/
β βββ test_blockchain.py # 38 unit tests (pytest)
βββ demo.py # End-to-end scripted demonstration
βββ requirements.txt # Python dependencies
βββ README.md # This file
Genesis Block Block #1 Block #2
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β index: 0 β β index: 1 β β index: 2 β
β prev: 0000β¦ ββββββββββ prev: a3f9β¦ ββββββββββ prev: 0000β¦ β
β txns: [] β β txns: [...] β β txns: [...] β
β nonce: 0 β β nonce: 132k β β nonce: 33k β
β hash: a3f9β¦ β β hash: 0000β¦ β β hash: 0000β¦ β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β² β²
Must start with Must start with
"0000" (PoW) "0000" (PoW)
Why is it secure?
- Changing Block #1's data changes its hash β Block #2's
previous_hashno longer matches β the chain is broken. - An attacker would need to re-mine Block #1 and every block after it, faster than the honest network β practically impossible.
pip install -r requirements.txtpython demo.pypython cli/cli.pydemo.py walks through the complete lifecycle automatically:
βΏ PyChain β Full Blockchain Demo
1. INITIALISING BLOCKCHAIN
Chain initialised with 1 block(s) (genesis only).
2. CREATING WALLETS
Alice : 6d16867d45f3fbf00f811fcb69d29d29344c001b
Bob : 77d45d388a254db85a12bf3f4762eebe7f8c0134
3. FUNDING ALICE (COINBASE + MINE)
β Mining block #1 (difficulty=4)β¦
β
Block #1 mined! Nonce=132790, Hash=0000af21c08af563β¦
4. BALANCES AFTER FUNDING
Alice : 200.0000 coins
Bob : 50.0000 coins
5. ALICE SENDS 30 COINS TO BOB
Signature valid: True
...
12. TAMPER DETECTION DEMO
β Attempting to tamper with Block #1β¦
β Block #1: stored hash doesn't match recomputed hash.
π‘ TAMPER DETECTED!
Start the REPL:
python cli/cli.py| Command | Description |
|---|---|
new_wallet |
Generate a new RSA wallet |
load_wallet |
Load a wallet from a .json file |
balance |
Check the confirmed balance of any address |
send |
Queue a signed transaction from the active wallet |
mine |
Mine all pending transactions into a new block |
chain |
Print all blocks in the chain |
validate |
Verify the entire chain's cryptographic integrity |
history |
Show transaction history for an address |
balances |
Show all confirmed account balances |
pending |
List transactions in the mempool |
help |
Show the command menu |
exit |
Quit PyChain |
PyChain [no wallet] > new_wallet
β
New wallet created!
Address : 6d16867d45f3fbf00f811fcb69d29d2
PyChain [6d16867dβ¦] > mine
β Mining block #1 (difficulty=4)β¦
β
Block #1 mined! Nonce=132790
PyChain [6d16867dβ¦] > balance
βΊ Wallet address: 6d16867d45f3fbf00f811fcb69d29d2
Balance : 10.00000000 coins
# Run all 38 tests with verbose output
python -m pytest tests/test_blockchain.py -v
# Run a specific test class
python -m pytest tests/test_blockchain.py::TestWallet -v
# Run a single test
python -m pytest tests/test_blockchain.py::TestBlockchain::test_tampered_transaction_fails_validation -vTest coverage:
TestBlock (4 tests) β hash correctness, serialisation, repr
TestBlockchain (24 tests) β genesis, PoW, transactions, mining,
balances, validation, persistence, history
TestWallet (10 tests) β key generation, signing, verification,
save/load, PEM export
All 38 tests pass in ~25 seconds (mining is intentionally slow by design).
import hashlib, json
data = {"index": 1, "nonce": 42, "transactions": [...]}
digest = hashlib.sha256(
json.dumps(data, sort_keys=True).encode("utf-8")
).hexdigest()
# β "0000af21c08af563c531e52b..."- Same input always produces the same hash (deterministic).
- Changing even 1 character changes the entire hash (avalanche effect).
- You cannot reverse a hash to get the original data (one-way function).
target = "0000" # difficulty = 4
nonce = 0
while not block.compute_hash().startswith(target):
nonce += 1 # Try billions of nonces until hash starts with "0000"
# On average this takes ~65,536 attempts for difficulty=4Mining is hard (CPU-intensive), but verification is instant β anyone can check that hash.startswith("0000") in one step.
# Sender signs (proves ownership of the private key)
signature = private_key.sign(transaction_bytes, PSS_padding, SHA256)
# Anyone can verify using only the public key
public_key.verify(signature, transaction_bytes, PSS_padding, SHA256)
# β Raises InvalidSignature if tamperedOnly the true owner of the private key can produce a valid signature. This prevents anyone from spending coins from an address they don't control.
Balances are not stored β they are computed on demand by replaying every transaction in the chain from the beginning:
balance = 0.0
for block in chain[1:]: # Skip genesis
for tx in block.transactions:
if tx["recipient"] == address:
balance += tx["amount"]
if tx["sender"] == address:
balance -= tx["amount"] + tx["fee"]This is the same approach Bitcoin uses (UTXO model is a more efficient variant).
| Method | Returns | Description |
|---|---|---|
compute_hash() |
str |
SHA-256 hash of all block fields |
to_dict() |
dict |
Serialisable plain dictionary |
| Method | Returns | Description |
|---|---|---|
add_transaction(sender, recipient, amount, fee, note) |
dict |
Validate and add to mempool |
mine_pending_transactions(miner_address) |
Block or None |
Run PoW and append new block |
get_balance(address) |
float |
Replay chain to compute balance |
get_all_balances() |
dict |
All address β balance pairs |
is_chain_valid() |
bool |
Verify hash integrity across chain |
get_transaction_history(address) |
list |
All confirmed txns for address |
save_chain() |
β | Persist chain to JSON file |
print_chain() |
β | Pretty-print all blocks |
| Method | Returns | Description |
|---|---|---|
sign_transaction(tx_dict) |
bytes |
RSA-PSS signature |
verify_signature(pub_pem, tx, sig) |
bool |
Static method β verify signature |
get_public_key_pem() |
str |
PEM-encoded public key |
get_private_key_pem(password) |
str |
PEM-encoded private key |
save_to_file(path, password) |
β | Save wallet as JSON |
load_from_file(path, password) |
Wallet |
Class method β restore wallet |
Edit constants in blockchain/blockchain.py:
MINING_DIFFICULTY = 4 # Number of leading zeros required in block hash
# Higher = harder mining, more security
# Typical values: 3 (fast) β 6 (slow/secure)
MINING_REWARD = 10.0 # Coins awarded to the miner per mined blockPyChain is an educational project demonstrating blockchain fundamentals. It is a single-node implementation. It does not include peer-to-peer networking, a distributed consensus protocol, or production-grade security hardening. Do not use it to store or transfer real assets.