Every npub you've ever pasted into a Nostr client carried a hidden passenger: six characters of pure mathematics, silently verifying that the 57 characters before them arrived intact.
Most users never notice. The checksum validates, the client accepts the key, life continues. But occasionally something fails. You get an error message, paste again more carefully, and move on. In that moment, decades of error-correction theory just saved you from following a stranger, messaging nobody, or worse.
The encoding that makes this possible is called Bech32, and its error-detection properties aren't accidental. They're the result of a careful search through 159,605 candidate codes to find one optimized for catching the specific mistakes humans make when copying strings of letters and numbers.
The Problem with Human Fingers
Here's a hex public key:
3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459dTry reading that aloud to someone. Try typing it from a screenshot. Try writing it on paper and reading it back a week later. Every step introduces error: the lowercase 'b' that looks like a '6', the 'a' you swear was an '8', the character you accidentally skipped.
For most of computing history, we accepted this. Copy carefully. Double-check. Hope for the best.
Then Pieter Wuille and Greg Maxwell did something clever. They didn't just create a new encoding, they embedded a mathematical safety net directly into the format itself.
BCH Codes: Error Detection from the Space Age
The core technology behind Bech32 predates Bitcoin by decades. BCH codes (named after mathematicians Bose, Chaudhuri, and Hocquenghem) were developed in the 1960s for situations where errors are inevitable but must be caught: satellite communications, deep space probes, early computer memory.
The principle is elegant. Instead of just encoding your data, you append a checksum computed in a very specific way. Not a simple hash, but a polynomial computed over a finite field. This mathematical structure means errors don't just change the checksum randomly. They change it in predictable, detectable ways.
For Bech32 specifically, the math works over GF(32), a Galois field with 32 elements. Each character in a Bech32 string represents 5 bits, and the 6-character checksum at the end encodes 30 bits of error-detection information.
The guarantees are concrete:
- Any 1, 2, 3, or 4 character errors: 100% detection rate
- 5 or more errors in a 39-character address: less than 1 in a billion chance of going undetected
- As errors increase, detection probability converges to 1 in 2^30 (about 0.93 per billion)
The Character Set Is Not Random
Look at Bech32's alphabet:
qpzry9x8gf2tvdw0s3jn54khce6mua7lNotice what's missing: the letters 'b', 'i', 'o', and the number '1'. These were excluded because they're visually ambiguous. Is that a '1' or an 'l'? A '0' or an 'O'? A 'b' or a '6'?
But Wuille and Maxwell went further. They specifically ordered this alphabet so that commonly confused character pairs differ by only a single bit. When you mistake '5' for 'S', the binary representations (10100 vs 10000) differ in just one position.
Why does this matter? Because the BCH code they selected has distance 6 for single-bit errors. By making likely human mistakes map to single-bit differences, they maximized the code's ability to catch real-world typos.
This wasn't guesswork. They tested 159,605 possible BCH codes against models of human typing errors before selecting the one that performed best for addresses up to 89 characters.
Finding Where You Went Wrong
Detection is only half the story. Bech32 can also locate errors.
When the checksum fails, the mathematics can compute syndrome values, essentially fingerprints of what went wrong. From these syndromes, an algebraic decoder can determine:
- The position of up to 2 substitution errors
- The exact correction needed at each position
The algorithm uses GF(1024) arithmetic (the extension field GF(32²)) and precomputed logarithm tables to efficiently solve for error locations. Bitcoin Core's implementation includes over 1,000 lines of careful GF arithmetic specifically for this purpose.
But here's the crucial design decision: the reference implementations deliberately do not apply corrections automatically.
Why Silence Is Safety
Imagine you're sending Bitcoin to an exchange. You copy the address but introduce three typos. The Bech32 decoder detects an error, runs its location algorithm, and "corrects" to a valid address. You click send.
The problem: with three errors, the decoder's two-error correction found a mathematically valid solution, but not your address. Your funds went to a random valid address, and they're gone forever.
This is why Pieter Wuille's reference code reveals only error positions, not corrections. It highlights where you probably made mistakes, then forces you to go back to your original source and verify. The inconvenience is intentional. A few seconds of checking beats permanent loss.
As one Bitcoin Core developer put it: "An inattentive user may well just make the corrections suggested by the algorithm, and send funds into the void."
Nostr's Adoption of Bech32
NIP-19 brought Bech32 encoding to Nostr with a set of human-readable prefixes:
| Prefix | Purpose |
|---|---|
| npub | Public keys |
| nsec | Private keys |
| note | Event IDs |
| nprofile | Profiles with relay hints |
| nevent | Events with metadata |
| naddr | Addressable content |
The encoding serves the same purpose as in Bitcoin: make keys readable, copyable, and error-resistant. But Nostr made an interesting choice. The specification explicitly states "bech32-(not-m)" encoding.
The Bech32m Question
In 2019, developers discovered a weakness in Bech32. If a string ends with the character 'p', you can insert or delete 'q' characters before it without invalidating the checksum. For Bitcoin's variable-length Taproot addresses, this was a real vulnerability.
Bech32m fixed this by changing a single constant in the checksum calculation (from 1 to 0x2bc830a3). All other properties remain identical.
So why didn't Nostr adopt Bech32m?
The practical answer: Damus implemented Bech32 first, and breaking compatibility wasn't worth the theoretical benefit. The technical answer: Nostr keys are fixed at 32 bytes, so the length-extension attack doesn't apply. The pragmatic answer: Lightning Network invoices use original Bech32 too, and the ecosystem hasn't collapsed.
For Nostr's use case, the vulnerability is largely theoretical. You can't add random 'q' characters to an npub and get a valid key that corresponds to any real identity.
What Nostr Clients Should Do
The error detection properties of Bech32 are only useful if clients actually check them. A well-implemented Nostr client should:
On npub/nsec input:
- Validate the checksum before accepting
- Display a clear error on failure
- Optionally highlight likely error positions
- Never auto-correct
For nsec specifically:
- Be extra paranoid
- Don't reveal error positions that could help an attacker guess valid keys
- Consider the checksum failure itself as sensitive information
For display:
- Use lowercase consistently (uppercase is valid but must not be mixed)
- Consider grouping characters in fours for readability
- Make it easy to copy without accidentally selecting extra whitespace
The Numbers That Matter
| Property | Value |
|---|---|
| Guaranteed detection | 4 errors |
| Location capability | 2 errors |
| Checksum length | 6 characters (30 bits) |
| Failure probability | < 1 in 10^9 |
| Character set size | 32 |
| Nostr npub length | 63 characters |
| Field for location math | GF(1024) |
Conclusion
Vermeer's astronomer reached toward his celestial globe to understand the universe's patterns. The mathematicians behind BCH codes reached toward similar abstractions, patterns in finite fields that could catch errors before they cascade into disasters.
Every time you paste an npub and a Nostr client silently accepts it, there's a polynomial calculation happening in the background. Every time the checksum fails and you're asked to try again, that's 60 years of error-correction theory doing exactly what it was designed to do.
The best error correction is the kind you never notice. When it works, your message arrives. When it catches a mistake, you get another chance. The math protects you from yourself, quietly, reliably, one checksum at a time.