Nobody has a bitcoin balance

COMMIT5c5dd91HEAD → main
PUBLISHEDApr 27, 20215y ago
UPDATEDJul 25, 20265d ago
READING14 min2,331 words
#bitcoin#cryptography·by santiago toscanini
Bitcoin·Part 2 of 4

Part one read the block: an 80-byte header, prev_block welding each block to the last, merkle_root standing in for a list of transactions we never opened. I promised we'd open one. This post opens all of them, and it starts by settling a debt: part one pointed at the genesis block's single transaction, the one carrying a newspaper headline, and said we'd open it properly here. So let's.

The question underneath is the one everyone gets wrong, including my own old course notes: where is your balance stored? The honest answer is nowhere. Bitcoin has no accounts, no balances, no field anywhere that says you have 2.3 coins. It has a pile of unspent outputs, each one a lump of value locked behind a little program, and "your balance" is just the sum of the lumps whose locks you can open. Spending is proving you can open one. That's the whole model, and by the end of this post you'll have watched a real one open, opcode by opcode.

Nothing here is a mockup. Every transaction below is real, fetched from blockstream.info: the coinbase db16…070c that minted 50 BTC in 2010, the transaction 2d5f…92f5 that spent it (mined in block 57055 — the same Pizza Day block whose Merkle tree we dissected in part one), and 9e74…1957, whose eighteenth input later redeemed those coins with a real signature. That signature gets re-verified in your browser, against a message recomputed from the raw bytes, by the same little TypeScript crypto engine the whole series runs on. No key here is one I own; these coins moved fifteen years ago.

01 · A coinbase points at nothing

Start with the genesis transaction, because it's the one exception that frames the rule. Here is its input's script, the actual bytes, decoded three ways — hex, opcodes, and ASCII:

$ curl -s https://blockstream.info/api/tx/<genesis-coinbase>/hex
  ... input scriptSig:
04ffff001d 0104 45 5468652054696d65732030332f4a616e2f...
└ push 4   └push└push 69 bytes ──────────────────────────┘
ASCII: "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"

That headline sits at byte 131 of the very first block, permanently. It's part timestamp — the paper proves the block couldn't have been made before that morning — and part mission statement. But look at where it lives: in the input's scriptSig, the slot that on every other transaction holds a proof of ownership. The genesis transaction gets away with putting a newspaper quote there for one reason: it's a coinbase, an input that points at nothing.

Every normal input names an earlier output it wants to spend. A coinbase input names all-zeros — there is no earlier output, because this is where coins are born. It's the first transaction of every block, it has no source to prove it owns, so its input script is unconstrained free space. Satoshi used that space for a message. Miners still use it today, mostly for pool tags and extra nonce room. The coins it creates — 50 BTC then, 3.125 BTC now — come out of thin air by protocol rule, and that's the only way new bitcoin ever enters existence.

One footnote about this particular coinbase before we move on: its 50 BTC can never be spent. Satoshi's client never entered the genesis block's transaction into its database, every implementation since has copied that behaviour, and so the output sits on the chain forever without ever being in the UTXO set. People still send coins to that address; those are burned too. The genesis block mints money that doesn't exist. Every other coinbase in this post is ordinary and spendable, which is why the coin we actually follow below is a different one.

So the coinbase is the source. Everything else is those coins moving. Which raises the question the rest of this post answers: once coins exist, what does it actually mean to own and move them?

02 · You do not have a balance

Here is a coin an ordinary coinbase minted — block 56934, May 2010, sixteen months after genesis — and here is what happens when it moves. There is no account being debited. There is one unspent output being destroyed and new ones being created. Drag the sliders to construct different spends of the same coin — and notice the coin is indivisible, so anything you don't send has to come back to you as change:

Things worth watching for:

  • An input carries no amount. It's a pointer: a previous transaction's id plus an output index, and nothing else. The 50 BTC isn't copied into the input — every node looks it up from the output being pointed at. This is why you can't lie about how much you're spending: the value is fixed by an output you don't control anymore.
  • You can't spend half a coin. An output is atomic. To pay someone 30 BTC out of a 50 BTC coin, you consume the whole 50 and create two outputs: 30 to them, ~20 back to yourself. That second output is "change," and it confused me for years — it's not a refund, it's just the leftover coin, re-locked to a fresh address of your own.
  • The fee has no field. Add up the inputs, add up the outputs, and whatever's missing is the fee. There is no fee: line anywhere in a transaction; it's implicit, the satoshis you simply chose not to account for, and the miner sweeps them into their coinbase. The real 2010 spend here paid zero fee — blocks were empty, and you could.

"Your balance," then, is a view your wallet computes by scanning the whole UTXO set for outputs it can unlock and adding them up. The number lives in your wallet's head, never on the chain. The chain only knows about coins and their locks.

03 · A transaction, byte by byte

That spend of the coin is 159 bytes. Every one of them belongs to exactly one field, and there are fewer fields than you'd guess: a version, a list of inputs (each a pointer plus an unlocking script), a list of outputs (each a value plus a locking script), and a locktime. Hover any byte to light up its region and decode it:

Things worth watching for:

  • The prevout is the pointer from section two, made of bytes. 32 bytes of previous-transaction id, then a 4-byte output index. Hover it and you'll see it names output #0 of the coinbase — byte-reversed, the same explorer-order flip from part one, because a txid is just sha256d of the whole transaction printed backwards. The txid at the bottom of the figure is computed live from these 159 bytes; it matches what any explorer shows.
  • The value is eight little-endian bytes. 00f2052a01000000 is not gibberish; read low byte first and it's 5,000,000,000 — satoshis, so 50.00000000 BTC. Bitcoin has no decimal point anywhere; it counts in hundred-millionths and lets wallets draw the dot.
  • The scriptPubKey is the lock. Twenty-five bytes here: OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG. That's a Pay-to-Public-Key-Hash lock, and those twenty bytes in the middle are the whole address, which is our next stop.

04 · An address is a fingerprint of a key

An address is not an account and it's not a public key. It's a hash of a public key — a 20-byte fingerprint, dressed up with a version byte and a checksum so you can't fat-finger it. And a private key is nothing but a very large random number. Everything between the two is a one-way street. Type a key, roll a random one, or use 1 (the smallest legal key) and watch every intermediate byte get computed:

The pipeline is worth naming stage by stage, because each stage is a real thing people conflate:

  • Private key → public key is one multiplication on an elliptic curve: the point d·G, where d is your number and G is a fixed base point everyone shares. Easy to compute forward, believed impossible to reverse. That single asymmetry is the entire foundation — lose the private key and the coins are gone; leak it and they're stolen. I'm asking you to take the asymmetry on faith here; if you'd rather not, the curve gets opened properly — the group law, a curve small enough to see every point of, and this exact multiplication run bit by bit.
  • Public key → hash160 is ripemd160(sha256(pubkey)), and most explanations skip this part: the lock in section three didn't store your public key, it stored this hash of it. Your public key stays secret until the moment you spend, when you finally reveal it — a small, real privacy win.
  • hash160 → address just bolts on a version byte (0x00, the reason legacy addresses start with 1) and a four-byte checksum, then writes the result in Base58. Same twenty bytes, wearing a coat.

Two honest caveats here. First: signing is not encrypting. Bitcoin encrypts nothing — every address, script, and public key on the chain is in the clear. A signature proves you know a private key without revealing it; it hides nothing and locks nothing away. If you take one idea from this section, take that one, because almost every popular explanation gets it backwards.

Second: the 1… address I've been teaching is legacy, and in 2026 it's a museum piece — depending on whose dashboard you read, somewhere around 10–15% of spent inputs still come from one, and the number only goes down. It's the pedagogical spine because it's the simplest complete lock, but the coin you actually receive today lands at a bc1q… address (native segwit, the clear majority at roughly 60–70% of inputs) or a bc1p… one (taproot, roughly 15–20%, down from a peak above 40% during the 2024 inscriptions wave). Treat all three as weather, not climate: the ratios move, the encodings don't. The widget shows the bc1q form of the very same twenty bytes next to the legacy one; it's a different, stronger encoding of an identical fingerprint. The lock behind it is cheaper to spend but conceptually the same story.

05 · The machine that decides who spends

Now the payoff. When a transaction spends an output, the network runs a tiny program to decide whether it's allowed. The unlocking script (scriptSig: your signature, then your public key) runs first, leaving items on a stack; then the locking script (scriptPubKey) runs on that same stack. If the stack ends with a single TRUE, the spend is valid. Here is a real P2PKH spend — input #17 of 9e74…1957, counting from zero, so the eighteenth of its twenty, redeeming the coin from section three — executed one opcode at a time. Step through it, then tamper with a byte and watch it die:

What you're actually watching:

  • OP_DUP OP_HASH160 … OP_EQUALVERIFY checks the key fits the lock. Your public key gets duplicated, hashed to a hash160, and compared against the twenty bytes baked into the output. If they differ, OP_EQUALVERIFY kills the script on the spot — that's what "you presented the wrong key" looks like. Flip a public-key byte and you'll see it fail here, before any signature is even checked.
  • OP_CHECKSIG checks the key actually signed. This is the real ECDSA verification, run in your browser: it takes your signature, the public key, and a message called the sighash — a sha256d digest of the transaction with the input scripts blanked, recomputed live from the raw bytes — and confirms the signature was made by the matching private key. Flip a signature byte and the key still fits the lock, but this step ends in FALSE. Two different tampers, two different failure points; that's the machine doing its job.
  • It ends on a single TRUE. No total, no balance update, no message to anyone. Just: this input was authorized. The output is now spent and leaves the UTXO set forever.

The honest caveats, because this is where the toy meets the real thing. The sighash — what a signature actually signs — is genuinely fiddly, and I compute the legacy (pre-segwit) version live for these 2010 transactions; modern segwit signatures sign a different, saner preimage (BIP143) that I'm not building here. And the signature is ECDSA, which is what these coins used; since 2021, taproot spends use Schnorr signatures instead, which are smaller and composable — I name them but don't implement them. Nothing on this page is a live network; these are confirmed transactions from 2010, replayed.

One design choice deserves the last word. Bitcoin Script has no loops. You cannot write while in it; it is deliberately not Turing-complete. That looks like a limitation until you realize every node on Earth has to run every script to verify every transaction — an infinite loop in a lock would be an infinite loop for the entire network. Bitcoin refused Turing-completeness on purpose, trading expressiveness for the guarantee that validation always halts. Ethereum makes the opposite bet — it is Turing-complete, and it pays for the risk with a meter called gas. That's a story for the second half of this series.

06 · What's next

The mental model, compressed: there are no accounts, only coins — unspent outputs, each a value plus a locking script. Owning a coin means holding a private key whose public key hashes to the fingerprint in that lock. Spending means writing an input that points at the coin and an unlocking script that satisfies it — a public key that fits, and a signature that proves you made it — and the network runs a small, halting stack machine to check. Your balance is a fiction your wallet maintains by adding up the coins it can open. All of it is bytes you can read, and every signature is algebra you can re-run.

Honesty about the toy's edges, gathered in one place: I teach legacy P2PKH because it's the simplest complete lock, not because it's common; the live sighash is the pre-segwit one; the signatures are ECDSA, not the newer Schnorr; and there's no mempool, no network, no fee market — just confirmed 2010 transactions replayed from their bytes.

The series keeps following the pointers:

  • Consensus: the hard problem, with a real miner in your browser — the bits and nonce padlock we still haven't opened
  • Storage: what a full node keeps on disk, and how to verify a payment with twelve hashes

Same promise as always: it's bytes all the way down, no magic, and you can read every one of them.

$git log --oneline public/posts/bitcoin-ownership/
5c5dd91Elliptic curves: the cryptography the crypto series kept deferringtoday
536605dNeural networks from scratch: a third post on pixels and vectorisationtoday
© 2026 · v2.0 · santiago toscanini