Skip to content

Addresses, WIF, Base58Check & Bech32

A public key is a point on a curve; a private key is a 256-bit number. Neither is something you’d want to read aloud, type, or paste into a payment field. Encodings turn these raw values into compact, copy-pasteable strings — and, critically, bake in a checksum so a single mistyped character is rejected instead of silently sending your coins somewhere unspendable. This page is about that translation layer and why it matters more than it looks.

An address is not your public key, and definitely not your private key. It is a short representation of a spending condition — the rule a future spender must satisfy. For the common case, that rule is “provide a public key that hashes to this value, plus a valid signature.” So the pipeline is:

public key ──hash──► hash160 (or other commitment) ──encode──► ADDRESS you share

Hashing the public key before publishing it adds a layer: the world sees only a fingerprint until you actually spend, when the real public key is revealed. (See hash functions for why these fingerprints are one-way.) What kind of script the address commits to — legacy, P2SH-wrapped, SegWit, Taproot — is covered in the anatomy of a transaction; here we focus on the encoding that wraps it.

Bitcoin has no “undo” and no customer-support desk. If you send to a well-formed but unintended address, the coins land in a UTXO whose key nobody holds — gone forever. Humans transcribe strings wrong constantly: a swapped character, a dropped digit, an O read as 0. So every Bitcoin encoding embeds a checksum: extra characters derived from the data itself.

[ payload ] ──► hash/derive ──► [ checksum ]
concatenate: [ payload ][ checksum ] ──► encode ──► final string
wallet decodes ► recomputes checksum from payload ► compares
match → accept mismatch → REJECT (don't broadcast)

The wallet recomputes the checksum from what you typed and refuses to proceed unless it matches. A one-character typo changes the payload, so the recomputed checksum won’t match, and the address is rejected before any coins move. The checksum’s whole purpose is to convert silent catastrophe into a loud, harmless error.

The original Bitcoin encoding is Base58Check. Two ideas in the name:

  • Base58 — a 58-character alphabet that deliberately omits the visually ambiguous characters 0 (zero), O (capital o), I (capital i), and l (lowercase L). Fewer look-alikes means fewer transcription errors.
  • Check — a 4-byte checksum computed as the first 4 bytes of SHA256(SHA256(payload)), appended before encoding.

The structure is:

[ version byte ][ payload (e.g. hash160) ][ 4-byte checksum ] ──Base58──► string

The version byte is what makes the result start with a recognizable prefix. A mainnet legacy address (version 0x00) renders starting with 1; a P2SH address (version 0x05) starts with 3. So the leading character is not cosmetic — it encodes what type of thing the string is.

The same Base58Check machinery encodes private keys for backup and import, in a format called WIF (Wallet Import Format). It’s a private key wrapped with a version byte, a checksum, and an optional “compressed” flag:

[ 0x80 version ][ 32-byte private key ][ 0x01 if compressed ][ 4-byte checksum ] ──Base58──► WIF

Mainnet WIF keys start with 5 (uncompressed) or K/L (compressed). The point is the same as for addresses: a typo in a WIF string fails the checksum rather than importing a different, wrong key.

SegWit introduced a better encoding: Bech32 (BIP173), and Taproot refined it to Bech32m (BIP350). You recognize these by the human-readable prefix and a separator: mainnet uses bc1.

[ "bc" HRP ][ "1" separator ][ data + 6-char BCH checksum ] all-lowercase
native SegWit v0 ► bc1q…
Taproot v1 ► bc1p…

Why bother replacing Base58Check? Bech32 is strictly better at its one job:

  • All-lowercase, so it’s unambiguous to read aloud and QR-encodes more compactly.
  • A far stronger BCH error-detection code that doesn’t just catch errors but can pinpoint which characters are wrong — and is mathematically guaranteed to catch any few-character mistake.
  • Built-in error location is why some wallets can suggest corrections.
Starts withEncodingType
1…Base58Check (v0x00)Legacy P2PKH
3…Base58Check (v0x05)P2SH (incl. wrapped SegWit)
bc1q…Bech32Native SegWit v0
bc1p…Bech32mTaproot (SegWit v1)
5…, K…, L…Base58Check (WIF)Private key — keep secret

(All strings above are illustrative prefixes, not real addresses.)

How does this help untrusting strangers agree on one ledger? The ledger is global and unforgiving: once you broadcast, anyone in the world will faithfully execute exactly what you signed. Encodings with checksums are the safety rail at that cliff edge — they let a stranger hand you a string, and let your wallet independently verify the string is well-formed before committing irreversible value to it. No trusted intermediary catches your typo; the math built into the address does. That keeps the shared ledger usable by ordinary humans who fat-finger keys.

  1. What does an address actually encode — and why isn’t it simply your public key?
  2. Walk through how a checksum turns a single mistyped character into a rejection instead of lost coins.
  3. Why does Base58 omit 0, O, I, and l?
  4. What is WIF, and why is it as dangerous to expose as a seed phrase?
  5. Give two concrete reasons Bech32 improves on Base58Check, and explain why Bech32m was introduced.