You could have invented Ethereum
One page, one continuous build. Start with a single shared spreadsheet, animate the one thing wrong with it, fix exactly that, and repeat — until a programmable, staked world computer falls out as the only thing it could have become.
- curiosity
Most explanations of Ethereum hand you the finished machine and name its parts. This one builds it. We start with the dumbest design that almost works, watch the single thing that breaks it, and fix exactly that — nothing more. Do it nine times and the famous pieces stop being trivia to memorize. Each one is just the answer to “what broke in the last step?”
Here is the analogy we’ll hold the whole way down. Forget coins and miners for a moment. Picture one spreadsheet that the entire world shares — a single sheet of who-owns-what that anyone can read and anyone can propose changes to. By the end, that spreadsheet will be able to run programs, no one will own it, and lying to it will cost you a fortune. That spreadsheet is Ethereum.
And it isn’t only animation. The moment a step invents a piece, you’ll drive the real one right there — the actual secp256k1 signer, a working EVM, a byte-exact Merkle-Patricia trie, the live EIP-1559 controller, a reorg simulator you can attack — and pick up the spec detail the real protocol had to pin down. Same incremental thread, intuition straight through to the running machine.
One spreadsheet for the whole world
Throw away the bank. In its place keep a single public spreadsheet: one row per account, one column for its balance. To pay someone, you submit an edit — “subtract 5 from my row, add 5 to Bob’s” — and everyone applies the same edit to their view. There is no statement to request and no teller to ask; the sheet is the truth, and the truth is just a pile of numbers that edits move around.
That’s the whole mental model of Ethereum’s state: not a history of payments, but the current value in every row, right now. It’s clean, it’s shared — and it’s wide open.
→ Step 2: make every edit something only its owner could have written.
A seal only you can stamp
Give every account a secret. Not a password you show to log in — a private key A secret 256-bit number. Its address is derived from it one-way; only the key can produce a valid signature, but anyone can check one. that never leaves your hands. With it you press a seal onto each edit: a signature. The magic of the seal is asymmetry — producing it requires the secret, but checking it requires only your public address. Everyone can confirm the seal is real; no one can counterfeit it.
Tamper with so much as a single digit of a signed edit and the seal stops matching the contents. The signature isn’t glued near the message; it’s computed from it. Your identity on the sheet isn’t a name anyone can type — it’s simply the ability to produce this stamp.
Verifying: accept the true, reject the tampered
Verifying is the mirror of signing. Anyone takes the message, the signature, and your public address and checks they agree — and there is no middle ground. The untouched message passes; the same message with a single byte flipped fails, every time. That binary outcome is the whole point: the seal turns “trust me” into “check it yourself.”
From key to address: a one-way hash
Where does that “public address” come from? You run your public key through a hash — a one-way fingerprint function, and the first foundational tool we’ll lean on again and again. Feed it any input and it returns a fixed 32 bytes; change a single character and almost every output byte flips; and there is no way to run it backward. Your address is just the last 20 bytes of the hash of your public key. Meet the machine here — you’ll see this exact avalanche again when we fingerprint the whole state and link blocks together.
keccak256 — the folding machine
The real seal: secp256k1
Now the whole pipeline at once, for real. The seal is an ECDSA signature over the secp256k1 curve, the exact scheme Ethereum uses. Roll a key and watch private key → public key → address (that last arrow is the hash above), then sign a message. You’ll notice the signer can be recovered straight from the signature — park that; it’s the whole trick of step 3.
Keypair & signature lab
- 1 a random 256-bit secret — the only thing you keep
- 2 secp256k1 scalar-multiplies it onto the curve (one-way)
- 3 the last 20 bytes of keccak256(public key)
One clarification, because it follows straight from “the message is public” and almost everyone gets it backwards: signing is not encryption. The same keypair could encrypt, but with the roles flipped — and Ethereum L1 doesn’t hide your edits at all.
| Signing (what we just built) | Encryption (what it isn’t) | |
|---|---|---|
| Goal | authenticity + integrity | confidentiality |
| Private key | signs | decrypts |
| Public key | verifies / recovers | encrypts |
| The message is | public | hidden |
Your edit is plaintext on the sheet forever; the seal proves who, and unchanged — never hidden.
And one subtle hole the real protocol had to plug. A raw signature is just over bytes, so a dapp could hand you an innocent-looking “message” that is secretly a valid transaction — and your seal would authorize it. The fix is to never stamp raw bytes, but bytes inside a labelled context:
→ Step 3: read the author straight out of the seal.
The signature already says who you are
Here’s the trick that lets Ethereum drop the “from” field entirely. From a signature and the message it covers, you can run the math backwards and recover the exact address that must have produced it. The author isn’t a claim attached to the edit — it’s something you compute from the seal itself. Lie about whose row to touch and the recovered address is a stranger with no money to move.
So an Ethereum transaction genuinely has no “from” field. The network takes your signed edit, recovers the address, and that is the only row it’s allowed to debit. Authorship and authority collapse into one unforgeable stamp — but authorship alone is not yet safety.
A signature is reusable — so the nonce
Here’s the flaw the seal doesn’t fix: it never expires, and the message it covers is public. The moment you hand Bob a signed “subtract 5 from me, add 5 to Bob,” Bob holds a perfectly valid edit he can broadcast again — and again. Every copy verifies; every copy moves another 5. One payment becomes a slow leak you never authorized.
The fix is one counter folded into the signed message: a nonce, “this is my 7th transaction.” The network remembers how many each account has spent and applies only the next number in line. Your nonce-7 edit lands exactly once; every replayed copy now names a 7 the account has already passed, so it bounces. Ordering comes free — an account’s edits can only take effect in nonce order, so no one can reshuffle them either.
Replayable across worlds too — so the chainId
One subtler copy remains. The same software runs on testnets and other EVM chains that hold the very same addresses. A signed nonce-7 transfer on a test network would verify just as well replayed onto the real one. So the signed message also commits to a chainId — making the edit valid on exactly one chain and inert on every other.
A bare transfer is finally watertight, and look at why each field is there: a recipient and amount to say what to do, a nonce so it can’t be replayed here, a chainId so it can’t be replayed elsewhere, and a signature that is the sender. Pull any one out and a specific door swings open.
Build the real transaction
That list — sender-by-recovery, nonce, chainId, recipient, amount — is an Ethereum transaction. Build and sign one for real: edit the fields, sign, and watch the sender get recovered from the signature, appearing nowhere in the bytes. Tamper with a field after signing and recovery collapses to a stranger.
Transaction signer
Recovery — the move that lets us drop the “from” field — is just the signing math run backward:
And the bytes are laid out as a typed envelope — a type byte, then an RLP payload — so the protocol can add new transaction shapes without ambiguity:
That one byte turned “the transaction format” into a versioned family — same core (signed, nonce-ordered, gas-metered), new fields bolted on as needs appeared:
0x00Legacy — the original, pre-typed format.0x01EIP-2930 — adds an access list (pre-declared storage, for gas).0x02EIP-1559 — today’s default: base fee + priority tip (the gas fields we reach in step 9).0x03EIP-4844 — blob-carrying transactions, the heart of L2 data availability.0x04EIP-7702 — lets an EOA temporarily act as a smart-contract account.
→ Step 4: let no one hold the master copy.
No master copy — everyone runs the sheet
So nobody hosts the spreadsheet. Everybody does. Every participant keeps the full sheet and the rules for updating it, and independently re-checks every signed edit before applying it. There’s no privileged server to subpoena, bribe, or switch off. You don’t trust the network — you keep your own copy and verify it from the very first row.
This is the real jump from “a database” to “a blockchain”: the data stops living somewhere and starts living everywhere, with the rules baked into every copy.
→ Step 5: fold the whole world into one number.
One fingerprint for the entire world
The blunt fix uses the hash from step 2: run the entire sheet through the folding machine and compare the one number that falls out. It half-works, and breaks two ways: changing a single balance forces you to re-hash the whole sheet, and to prove your balance to someone you’d have to hand them the entire sheet so they could re-hash it.
Fix both with a tree of hashes. Hash accounts in groups, then hash those hashes, level by level, up to a single root. Now editing one account only re-hashes the short path from its leaf to the root — not the whole sheet — and you can prove one account to someone holding only the root by sending just the sibling hashes along that path. That’s a Merkle tree.
But state isn’t a tidy fixed array; it’s a sparse, ever-changing map keyed by 20-byte addresses. A plain tree doesn’t encode where an account lives. So make the key be the path: read the address as a string of hex digits — nibbles — and walk them down the tree to find the account.
Walking that path efficiently forces exactly three kinds of node, and each earns its place. A branch node is a 16-way fork — one slot per possible next nibble — used wherever paths diverge. A leaf holds the final stretch of path and the account’s value. And an extension node compresses a long run of nibbles that everything below happens to share, so the tree doesn’t waste a whole 16-way branch on a stretch where nothing forks. Hash every node from its children — keccak of its encoding — up to one root, and you have Ethereum’s Merkle-Patricia trie: the Patricia part makes keyed updates cheap, the Merkle part makes the whole thing one verifiable fingerprint.
Take the real trie apart
This is the genuine article — the same RLP, hex-prefix node tags, and keccak256 a live client uses; the stateRoot it prints is byte-for-byte what go-ethereum or reth would compute. Insert an account and watch the path restructure and pulse to a new root; click any node to read its encoding.
Merkle-Patricia Trie visualizer
Two encoding rules make every client agree to the byte. RLP serializes a node before it’s hashed; hex-prefix encoding packs a node’s nibble path plus a flag (leaf vs. extension, odd vs. even) into bytes. And one rule trips up every hand-rolled trie:
What sits in a leaf isn’t just a balance — it’s the account’s four fields, and two of them only come alive once accounts can hold code (step 8):
That storage_root is the tell: once contracts exist, each one holds another MPT for its storage, whose root lives here in the account leaf — so a single storage write ripples a new storage root into the leaf, which ripples a new state root, the same wave nested one level deeper. (One real-world detail: keys are actually keccak256(address), not the raw address — the “secure trie” — which scatters accounts so prefixes rarely collide.)
Why the shape is still changing
The hexary trie has a bill: to prove one account to someone holding only the root — what a stateless client A node that verifies blocks using witnesses (proofs) supplied with the block, instead of storing the full state itself. needs — you must reveal, at every level, the sibling hashes you didn’t descend into. A 16-way branch means up to 15 siblings per level; times depth, witnesses balloon into megabytes. That’s the roadblock to statelessness, and it’s why the commitment is being redesigned.
Hexary MPT → Verkle → Binary
For years the fix was Verkle trees (vector commitments: one short proof for all 256 children, siblings stop mattering). Through 2024–2025 the roadmap pivoted toward a binary Merkle tree (EIP-7864): arity 2, ordinary hashing, no trusted setup, and friendly to the SNARK provers Systems that produce succinct proofs that a computation was done correctly. Simple hash-based trees are far cheaper to prove inside a SNARK than vector-commitment schemes. that will eventually verify Ethereum’s state transitions.
→ Step 6: freeze the history into a chain.
Chain the batches so the past can’t move
Stop treating edits as loose slips of paper. Gather them into a numbered block and reduce it to a small header: its number, the parentHash (the hash of the block before it), a txsRoot fingerprinting the edits inside, and the stateRoot we just built — the fingerprint of the whole sheet after this block. The block’s own hash is the hash of that entire header. Because the folding machine from step 2 turns any one-bit change into a totally different output, the header binds the block to all of history before it and to the exact state it produced.
Now watch what one tampered balance does. Change Alice 14 → 9 deep in block #1’s state and the effect doesn’t stay local. Her account’s leaf changes, which re-hashes up the trie to a new stateRoot; the stateRoot sits in block #1’s header, so the header changes; the header’s hash changes; and now block #2’s parentHash points at a hash that no longer exists — so block #2 is invalid, which invalidates block #3, and so on down the line. One edited number, and the entire chain after it falls apart. To rewrite a single past balance you’d have to silently rebuild every block since. The past is frozen not by a guard but by the sheer weight of everything stacked on top. That linked, batched, state-committed history is the blockchain.
→ Step 7: agree on one chain, and make lying self-destructive.
Agree on one history — and price the lie
We can’t appoint a referee, so the network has to converge on one chain by itself. The obvious idea — let everyone vote, one node one vote — dies instantly. Identities are free: I spin up a million fake nodes (a Sybil attack) and outvote the honest world before breakfast.
So a vote must cost something unforgeable. There are two famous prices. Proof of work charges electricity: to extend the chain you burn real computation, and the chain with the most work behind it wins, because faking it means out-burning the entire honest network. Proof of stake — Ethereum’s choice — charges bonded money: validators lock up a deposit to propose and attest to blocks. It’s cheaper and greener, but the real prize is something work can’t offer: because every voter is a known, bonded deposit, a liar can be punished, not merely out-competed.
On that base, two mechanisms settle the chain. Fork choice keeps the network on one history moment to moment: follow the fork with the most attesting stake behind it, so a minority can never make their branch the heaviest. Finality makes old history permanent: periodically validators vote to finalize a checkpoint, and once two-thirds of all stake has, reversing it would require a third of all stake to sign two contradictory checkpoints — a provable crime the protocol catches and punishes by slashing, destroying their deposits.
So the attacker’s own deposit is the deterrent. The double-spend isn’t stopped by a guard at the door — it’s stopped because pulling it off at scale sets fire to a fortune you posted in advance, while honest validators simply keep the heaviest, finalized chain.
Play the attacker
Don’t take that on faith — try to break it. You control a slice of the validators: pay a merchant, take the goods, then reveal a secret fork where the payment never happened, and watch fork choice and finality decide your fate (and the bill).
Reorg / double-spend simulator
The two mechanisms we just invented have real names; together they’re called Gasper:
And that same accounting prices out the whole family the double-spend belongs to: 51% (stake cost + slashing), long-range rewrites of ancient history (defended by weak subjectivity A new or long-offline node trusts a recent finalized checkpoint as its starting point, rather than the longest chain from genesis — neutralizing rewrites of distant history. ), Sybil itself (influence is proportional to staked ETH, not identity count), eclipse (diverse peer selection at the networking layer), and censorship (mitigated by proposer-builder separation and inclusion lists). The pattern never changes: find what the attacker must spend, and make it cost more than the attack can earn.
→ Step 8: let the cells hold programs.
Cells that run programs
Upgrade the sheet one more time: let a row hold not just a balance but code. Now an edit can call that code, which runs against the shared state — reading rows, writing rows, refusing if its rules aren’t met. Because every node runs the identical program over the identical state, they all reach the identical result. The spreadsheet has quietly become a world computer, and the little programs living in its cells are smart contracts. (This is the code_hash and storage_root we saw waiting in the account leaf back in step 5, finally put to use.)
This is the leap that makes Ethereum Ethereum rather than a faster bank. A contract is just an account whose behavior is fixed in code that anyone can read and no one can secretly override — an escrow that releases when conditions are met, a token whose supply rules are public, a vote no clerk can miscount.
And it forces one more field onto the transaction. To call a contract, an edit has to say which function and with what arguments — that payload is the data field. A plain transfer leaves it empty; calling code fills it with the encoded call. (If a transaction has no recipient at all, the data field is read as the code for a brand-new contract — which is exactly how contracts get deployed.)
Step the real machine
Here is that virtual machine for real — a stack A last-in-first-out list of 256-bit words, max depth 1024. Almost every opcode pops its inputs off the top and pushes its result back. for operands, scratch memory, persistent storage, and a meter. Load a program and step it one instruction at a time; watch values push and pop and the meter tick down (we price that meter properly in the next step).
EVM stepper
Every word on that stack is 256 bits — sized to hold a keccak256 hash or an address, with arithmetic wrapping modulo 2²⁵⁶ (try 0 - 1 for the max uint256). And the Solidity you might write compiles straight down to this: a mapping is a keccak256(key . slot) storage address; a function call dispatches on the first 4 bytes of calldata (the function selector The first 4 bytes of keccak256 of the function signature. The contract's dispatcher compares calldata's first 4 bytes against these to pick which function runs. ); revert undoes state and refunds the gas left.
→ Step 9: put a meter on every instruction.
A meter so nothing runs forever
Charge for computation, one instruction at a time. Every operation has a price in gas; each transaction prepays a gas budget; the meter ticks down as the program runs, and the instant it hits zero, execution halts and the changes revert. An infinite loop no longer freezes the world — it simply runs out of fuel and dies, with the attacker having paid for every step. Pricing is the halting solution.
That prepaid budget is itself a transaction field — the gasLimit — and it quietly does a second job: since execution can never burn more gas than the limit, it caps your worst-case loss when you call a contract whose behavior you don’t fully trust. The price you pay needs fields too. Block-to-block the base fee swings, so you don’t name an exact price; you name a ceiling, maxFeePerGas (the most you’ll tolerate), and a maxPriorityFeePerGas — a tip on top of the burned base fee to give the proposer a reason to include you. The base fee itself isn’t a blind auction; it’s a controller that drifts up when blocks are full and down when they’re empty, targeting half-full blocks, and it’s burned rather than pocketed.
Drive the controller
Before it lands in a block, a signed transaction waits in the mempool — a loosely-shared, per-node waiting room, gossiped peer to peer — priced by exactly that controller. Set how full each block is and watch the base fee chase the half-full target: up by at most ⅛ per block when demand runs hot, down when it cools.
EIP-1559 base-fee simulator
The whole controller is a few lines of integer math on the parent block:
A transaction then pays min(maxFeePerGas, baseFee + maxPriorityFeePerGas) per gas; if the base fee ever climbs above your maxFeePerGas, it simply waits rather than overpaying. And one consequence worth naming: the base fee buys inclusion, but the tip buys position — and position has value (a liquidation to capture, an arbitrage to front-run). That value is MEV Maximal Extractable Value — the profit a builder can extract by choosing which transactions to include and in what order. It turned block-building into a competitive market. , and under proposer-builder separation specialized builders now assemble the most valuable block they can and bid for the proposer to publish it.
With that, the transaction is complete — and so is the machine. Lay the transaction out and not one field is decoration; each is the answer to a specific “what breaks without it?”