Proof of Work is a Sybil‑resistance and leader‑election mechanism where participants prove they’ve performed a computationally expensive search. In blockchains, miners iterate nonces to find a block header whose cryptographic hash is below a network‑wide target. The first valid proof earns the right to propose the next block (and a reward), while everyone else can verify the proof instantly.
Intuition: PoW is a lottery where each hash is a ticket. The lower the target, the fewer tickets are winners → the harder it is to find one.
H
(e.g., SHA‑256).hdr
(version, prev_hash, merkle_root, time, nBits, nonce).hash = H(H(hdr))
(Bitcoin’s double‑SHA) or a single hash.hash
and target T
as big‑endian integers; require hash ≤ T
.E[trials] ≈ 2^n / (T+1)
for an n
‑bit hash. With leading‑zero difficulty d
, E ≈ 2^d
.Finality: PoW provides probabilistic finality: deeper confirmations → exponentially lower reorg risk.
Below is a minimal, production‑unsafe PoW you can run locally. It implements a Hashcash‑style challenge using SHA‑256 with a leading‑zero bits difficulty. Then we extend it toward a tiny “block header” flow.
# pow_hashcash.py — minimal PoW demo (Python 3.8+)
import os, time, struct, hashlib, binascii
from typing import Tuple
# --- utilities ---------------------------------------------------------------
def sha256(b: bytes) -> bytes:
return hashlib.sha256(b).digest()
def count_leading_zero_bits(b: bytes) -> int:
# Bit‑precise leading‑zero count
n = 0
for byte in b:
if byte == 0:
n += 8
continue
# First non‑zero byte: count its leading zeros
for i in range(7, -1, -1):
if (byte >> i) & 1:
return n
n += 1
return n
# --- prover: find nonce so that hash has >= difficulty leading zero bits -----
def solve_pow(payload: bytes, difficulty_bits: int, max_nonce: int = 10_000_000) -> Tuple[int, bytes, float]:
start = time.time()
for nonce in range(max_nonce):
h = sha256(payload + nonce.to_bytes(8, 'big'))
if count_leading_zero_bits(h) >= difficulty_bits:
return nonce, h, time.time() - start
raise RuntimeError("No solution found within max_nonce")
# --- verifier ---------------------------------------------------------------
def verify_pow(payload: bytes, nonce: int, difficulty_bits: int) -> bool:
h = sha256(payload + nonce.to_bytes(8, 'big'))
return count_leading_zero_bits(h) >= difficulty_bits
if __name__ == "__main__":
data = b"hello, cryptodocs!"
difficulty = 20 # ~1 in 2^20 ≈ 1,048,576 trials on average
nonce, h, dt = solve_pow(data, difficulty)
print("nonce=", nonce)
print("hash= ", binascii.hexlify(h).decode())
print(f"time= {dt:.3f}s; verified= {verify_pow(data, nonce, difficulty)}")
Notes
d
means “at least d
leading zero bits” → expected trials ≈ 2^d
.# pow_blocklike.py — tiny block‑style PoW with target threshold
import time, json, hashlib, binascii
from typing import Tuple
UINT256_MAX = 2**256 - 1
def sha256d(b: bytes) -> bytes:
return hashlib.sha256(hashlib.sha256(b).digest()).digest()
# Represent difficulty via an explicit integer target (small target = hard)
# e.g., target = UINT256_MAX >> difficulty_bits
def header_bytes(prev_hash_hex: str, merkle_root_hex: str, timestamp: int, nbits: int, nonce: int) -> bytes:
# Simple JSON serialization for demo purposes (not stable like Bitcoin’s)
hdr = {
"prev": prev_hash_hex,
"merkle": merkle_root_hex,
"time": int(timestamp),
"nbits": int(nbits),
"nonce": int(nonce),
}
return json.dumps(hdr, separators=(",", ":"), sort_keys=True).encode()
def target_from_bits(difficulty_bits: int) -> int:
return UINT256_MAX >> difficulty_bits
def mine_block(prev, merkle, difficulty_bits: int, max_nonce=50_000_000) -> Tuple[int, str, int]:
target = target_from_bits(difficulty_bits)
ts = int(time.time())
for nonce in range(max_nonce):
hdr = header_bytes(prev, merkle, ts, difficulty_bits, nonce)
h = sha256d(hdr)
hv = int.from_bytes(h, 'big')
if hv <= target:
return nonce, binascii.hexlify(h).decode(), ts
raise RuntimeError("exhausted nonce search")
def verify_block(prev, merkle, difficulty_bits: int, nonce: int, ts: int) -> bool:
target = target_from_bits(difficulty_bits)
hdr = header_bytes(prev, merkle, ts, difficulty_bits, nonce)
hv = int.from_bytes(sha256d(hdr), 'big')
return hv <= target
if __name__ == "__main__":
prev = "00"*32
merkle = "aa"*32
d = 22
n, hhex, ts = mine_block(prev, merkle, d)
print("nonce=", n, "hash=", hhex)
print("verify=", verify_block(prev, merkle, d, n, ts))
This demo:
UINT256_MAX >> d
).Exercise: Replace JSON with a binary struct and add a block height and version field.
Goal: maintain a target block interval T_sec
by shifting difficulty up/down based on the last N
blocks’ timestamps.
# retarget.py — naive moving retarget example
from typing import List
UINT256_MAX = 2**256 - 1
def target_from_bits(bits: int) -> int: return UINT256_MAX >> bits
def bits_from_target(target: int) -> int:
# Clamp to [0, 255]
if target <= 0: return 0
for b in range(256):
if (UINT256_MAX >> b) <= target:
return b
return 255
def retarget(prev_bits: int, timestamps: List[int], target_spacing_sec: int = 600) -> int:
if len(timestamps) < 2:
return prev_bits
span = max(1, timestamps[-1] - timestamps[0])
expected = target_spacing_sec * (len(timestamps) - 1)
# Scale factor (bounded to avoid swings)
scale = max(0.25, min(4.0, expected / span))
prev_target = target_from_bits(prev_bits)
new_target = int(prev_target * scale)
# Prevent absurdly easy targets
new_target = min(UINT256_MAX, max(1, new_target))
return bits_from_target(new_target)
Real networks use clamped, median‑time, and windowed retargets (e.g., Bitcoin retargets every 2016 blocks with explicit bounds). Avoid time‑warp pitfalls.
prev_hash
and a Merkle (or hash) of all transactions; add version/height for upgrade room.nBits
).All code above is educational; do not deploy as‑is in production networks.