Skip to content
Double-Spend Defense
step 1/5

Reading — step 1 of 5

Read

~3 min readTransactions & UTXO

Double-Spend Defense

Digital cash has one hard problem, and it isn't cryptography — it's copying. A coin that's just data can be paid to two merchants at once:

I own UTXO u.
Tx_A: u -> merchant_A        Tx_B: u -> merchant_B
broadcast both simultaneously. Which one is money?

Signatures don't help — both are validly signed, by me. Before Bitcoin, the only known fix was a central server deciding order. Nakamoto's insight: let the chain decide. Whichever transaction lands in the canonical chain wins; the other becomes invalid automatically, because its input is already consumed. Double-spend defense is the consensus problem — ordering, not secrecy.

Confirmations

Temporary forks complicate this. Two miners can find blocks at the same height — one containing Tx_A, the other Tx_B. Both are valid chains for a while. The fork resolves when the next block extends one side; the other side's block is orphaned and its transactions return to the mempool (where Tx_B now bounces off the spent input).

So "in a block" is not "final." Finality is probabilistic and deepens with confirmations: a transaction at height H with the tip at height T has T − H + 1 confirmations — the block containing it counts as the first. Each additional confirmation is another block an attacker would have to out-mine. The whitepaper's own math: an attacker's catch-up chance shrinks exponentially with depth as long as they hold under half the hashrate (at ~10% of hashrate, six blocks deep is already worse than 1-in-1000); over half, and they eventually win no matter the depth — confirmations only buy time against a majority. Hence the folklore policies:

PaymentWaitRationale
Coffee0-confloss < attack cost; RBF makes even this risky
E-commerce1 conf (~10 min)survives simple races
Large transfer6 conf (~1 hour)the classic "final enough"
Exchange depositup to 100paranoia scales with amount

Faster chains (Litecoin 2.5 min, Solana sub-second) shrink the wait but not the principle: fewer independent extensions of history = weaker guarantee per confirmation. Lightning moves small payments off-chain entirely and settles disputes on-chain.

Reorgs, concretely

When the canonical tip switches branches, blocks above the fork point are disconnected. Any transaction that only existed in a disconnected block loses all its confirmations at once — it's unconfirmed again, pending re-mining. This is why exchanges track confirmations continuously rather than flagging "confirmed" once: the count can go down.

Your exercise

Build the tracker: BLOCK <height> <txs>, TX_CONFIRMATIONS <tx>, TIP_HEIGHT, and REORG <h> — which, despite the name reading like a rewind to h, keeps blocks at heights ≤ h and removes every block above h (that's what the tests encode: after REORG 1, a tx in block 1 still prints 1). Two graded mistakes: the off-by-one — a tx in block 1 with tip 3 has 3 confirmations (3−1+1), and printing 2 fails the very first test — and reorg direction: after blocks at 5, 6, 7 and REORG 6, the tx in block 7 prints UNCONFIRMED while the one in block 6 prints 1. Unknown transactions and reorged-out transactions both print exactly UNCONFIRMED; empty chain prints tip 0.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…