Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Bitcoin is pseudonymous, not anonymous. Every transaction is public; chain analysis firms cluster addresses into wallets, link wallets to exchanges, and trace flows. Understanding what is and isn't private — and the modern tools that improve privacy (CoinJoin, Lightning, silent payments, Taproot) — is essential for any builder shipping consumer Bitcoin software.
Address clustering.
Use these three in order. Each builds on the one before.
In one paragraph, explain Bitcoin pseudonymity vs anonymity.
Walk me through common-input ownership clustering.
Design a wallet that maximizes on-chain privacy. Which tools combine, and what's the user-experience cost?
# Chain-analysis firms cluster addresses with these heuristics:
# 1. Common-input ownership heuristic (CIOH):
# If two addresses appear as inputs in the same tx, they're likely owned
# by the same wallet (because spending requires the corresponding private key).
# 2. Change-output heuristic:
# Identify which output is change vs payment.
# Often the change output is the "round number off" one, e.g., 0.5 BTC payment + 0.32847291 change.
# 3. Reuse heuristic:
# Reused addresses are bound to a single owner over time.
# Counter-strategies:
# - CoinJoin: many users combine inputs in one tx, breaking CIOH
# - Avoid address reuse: each receive uses a new address (BIP-32 HD wallets)
# - Lightning: tx is off-chain; only open/close hits the chain
# - Silent payments (BIP-352): static reusable address; receiver-derived per-tx address
# Example clustering pseudocode:
clusters = {} # addr -> cluster_id
next_cluster_id = 0
def add_tx(tx):
global next_cluster_id
if len(tx.inputs) > 1:
in_addrs = [tx.input_addr(i) for i in tx.inputs]
# Find existing cluster for any input addr, or create new
existing = next((clusters.get(a) for a in in_addrs if a in clusters), None)
if existing is None:
existing = next_cluster_id
next_cluster_id += 1
# Merge all input addrs into existing cluster
for a in in_addrs:
clusters[a] = existing
# Heuristic: identify change output (smallest output is often change for round payments)
# ... etc
# Real implementations: BlockSci, WalletExplorer, Chainalysis Reactor, Elliptic