When Math Catches Your Typos

npub1klkk3vrzme455yh9rl2jshq7rc8dpegj3ndf82c3ks2sk40dxt7qulx3vt
hex
84c6c916b19140d8c3b15bd7918a989d51d9feb78eaceabf436b2cfe6a82816fnevent
nevent1qqsgf3kfz6cezsxccwc4h4u332vf65wel6mcat82hapkkt87d2pgzmcprpmhxue69uhhyetvv9ujuem4d36kwatvw5hx6mm9qgst0mtgkp3du662ztj3l4fgts0purksu5fgek5n4vgmg9gt2hkn9lqfxz5t3naddr
naddr1qqgx2dtrxcmxgvrp89jnyer9vymrsqgcwaehxw309aex2mrp0yhxwatvw4nh2mr49ekk7egzyzm7669svt0xkjsju50a22zurc0qa589z2xd4yatzx6p2z64a5e0cqcyqqq823c48qkvsKind-30023 (Article)
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.
The checksum validates silently as the client accepts the key and life continues. But occasionally something fails. An error message appears. You paste more carefully and the second attempt succeeds. In that moment, decades of error-correction theory caught the mistake before it could matter.
The encoding that makes this possible is called Bech32, and its error-detection properties reflect a deliberate 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:
3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d
Try 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, the best available strategy was to copy carefully, check twice, and trust that nothing had slipped through.
Then Pieter Wuille and Greg Maxwell did something clever. 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 (Bose-Chaudhuri-Hocquenghem) were developed in the 1960s for situations where errors are inevitable but must be caught: satellite communications and deep space probes, where a corrupted bit could mean mission failure.
The principle is elegant. You append a checksum computed over a finite field as a polynomial, not a simple hash. This mathematical structure means errors change the checksum 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 deliberate
Look at Bech32's alphabet:
qpzry9x8gf2tvdw0s3jn54khce6mua7l
Notice what's missing: the letters 'b', 'i', 'o', and the number '1'. These were excluded for visual ambiguity, the kind where '1' and 'l' look identical in most fonts, '0' and 'O' blur together, and 'b' reads as '6' under any stress.
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.
The selection process tested 159,605 possible BCH codes against models of human typing errors before choosing 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, 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.
The key design decision: the reference implementations deliberately withhold automatic corrections.
Why silence is safety
Imagine you're sending Bitcoin to an exchange. You copy the address but introduce three typos. The Bech32 decoder detects the error and runs its location algorithm, "correcting" 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.
Pieter Wuille's reference code reveals only error positions, leaving the correction to the user. 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 and error-resistant for humans copying them. 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?
Damus implemented Bech32 first, and breaking compatibility with deployed clients outweighed any theoretical benefit. Nostr keys are fixed at 32 bytes, so the length-extension attack has no surface to exploit. Lightning Network invoices use original Bech32 as well, and the broader set of tools has held up fine.
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 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
| 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.
Raw JSON
{
"kind": 30023,
"id": "84c6c916b19140d8c3b15bd7918a989d51d9feb78eaceabf436b2cfe6a82816f",
"pubkey": "b7ed68b062de6b4a12e51fd5285c1e1e0ed0e5128cda93ab11b4150b55ed32fc",
"created_at": 1777543606,
"tags": [
[
"d",
"e5c66d0a9e2dea68"
],
[
"image",
"https://image.nostr.build/f80cbfad1073737470acc7a897922d3b97ad693a8747e18dcc312ab039560499.jpg"
],
[
"title",
"When Math Catches Your Typos"
],
[
"summary",
"Bech32's BCH code detects up to 4 typos with 100% certainty, locates up to 2 error positions, but deliberately refuses to auto-correct to prevent catastrophic mistakes."
],
[
"published_at",
"1765044681"
],
[
"t",
"nostr"
],
[
"t",
"cryptography"
],
[
"t",
"bitcoin"
],
[
"t",
"decentralization"
],
[
"t",
"austrian-economics"
],
[
"t",
"freedom-tech"
],
[
"t",
"bech32"
],
[
"t",
"typo"
]
],
"content": "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.\n\nThe checksum validates silently as the client accepts the key and life continues. But occasionally something fails. An error message appears. You paste more carefully and the second attempt succeeds. In that moment, decades of error-correction theory caught the mistake before it could matter.\n\nThe encoding that makes this possible is called Bech32, and its error-detection properties reflect a deliberate search through 159,605 candidate codes to find one optimized for catching the specific mistakes humans make when copying strings of letters and numbers.\n\n## The problem with human fingers\n\nHere's a hex public key:\n\n```\n3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d\n```\n\nTry 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.\n\nFor most of computing history, the best available strategy was to copy carefully, check twice, and trust that nothing had slipped through.\n\nThen Pieter Wuille and Greg Maxwell did something clever. They embedded a mathematical safety net directly into the format itself.\n\n## BCH codes: error detection from the space age\n\nThe core technology behind Bech32 predates Bitcoin by decades. BCH codes (Bose-Chaudhuri-Hocquenghem) were developed in the 1960s for situations where errors are inevitable but must be caught: satellite communications and deep space probes, where a corrupted bit could mean mission failure.\n\nThe principle is elegant. You append a checksum computed over a finite field as a polynomial, not a simple hash. This mathematical structure means errors change the checksum in predictable, detectable ways.\n\nFor 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.\n\nThe guarantees are concrete:\n\n- Any 1, 2, 3, or 4 character errors: 100% detection rate\n- 5 or more errors in a 39-character address: less than 1 in a billion chance of going undetected\n- As errors increase, detection probability converges to 1 in 2^30 (about 0.93 per billion)\n\n## The character set is deliberate\n\nLook at Bech32's alphabet:\n\n```\nqpzry9x8gf2tvdw0s3jn54khce6mua7l\n```\n\nNotice what's missing: the letters 'b', 'i', 'o', and the number '1'. These were excluded for visual ambiguity, the kind where '1' and 'l' look identical in most fonts, '0' and 'O' blur together, and 'b' reads as '6' under any stress.\n\nBut 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.\n\nWhy 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.\n\nThe selection process tested 159,605 possible BCH codes against models of human typing errors before choosing the one that performed best for addresses up to 89 characters.\n\n## Finding where you went wrong\n\nDetection is only half the story. Bech32 can also locate errors.\n\nWhen the checksum fails, the mathematics can compute syndrome values, fingerprints of what went wrong. From these syndromes, an algebraic decoder can determine:\n\n- The position of up to 2 substitution errors\n- The exact correction needed at each position\n\nThe 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.\n\nThe key design decision: *the reference implementations deliberately withhold automatic corrections*.\n\n## Why silence is safety\n\nImagine you're sending Bitcoin to an exchange. You copy the address but introduce three typos. The Bech32 decoder detects the error and runs its location algorithm, \"correcting\" to a valid address. You click send.\n\nThe 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.\n\nPieter Wuille's reference code reveals only error positions, leaving the correction to the user. 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.\n\nAs 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.\"\n\n## Nostr's adoption of Bech32\n\nNIP-19 brought Bech32 encoding to Nostr with a set of human-readable prefixes:\n\n| Prefix | Purpose |\n|--------|---------|\n| npub | Public keys |\n| nsec | Private keys |\n| note | Event IDs |\n| nprofile | Profiles with relay hints |\n| nevent | Events with metadata |\n| naddr | Addressable content |\n\nThe encoding serves the same purpose as in Bitcoin: make keys readable and error-resistant for humans copying them. But Nostr made an interesting choice. The specification explicitly states \"bech32-(not-m)\" encoding.\n\n## The Bech32m question\n\nIn 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.\n\nBech32m fixed this by changing a single constant in the checksum calculation (from 1 to 0x2bc830a3). All other properties remain identical.\n\nSo why didn't Nostr adopt Bech32m?\n\nDamus implemented Bech32 first, and breaking compatibility with deployed clients outweighed any theoretical benefit. Nostr keys are fixed at 32 bytes, so the length-extension attack has no surface to exploit. Lightning Network invoices use original Bech32 as well, and the broader set of tools has held up fine.\n\nFor 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.\n\n## What Nostr clients should do\n\nThe error detection properties of Bech32 are only useful if clients check them. A well-implemented Nostr client should:\n\n**On npub/nsec input:**\n1. Validate the checksum before accepting\n2. Display a clear error on failure\n3. Optionally highlight likely error positions\n4. Never auto-correct\n\n**For nsec specifically:**\n- Be extra paranoid\n- Don't reveal error positions that could help an attacker guess valid keys\n- Consider the checksum failure itself as sensitive information\n\n**For display:**\n- Use lowercase consistently (uppercase is valid but must not be mixed)\n- Consider grouping characters in fours for readability\n- Make it easy to copy without accidentally selecting extra whitespace\n\n## The numbers\n\n| Property | Value |\n|----------|-------|\n| Guaranteed detection | 4 errors |\n| Location capability | 2 errors |\n| Checksum length | 6 characters (30 bits) |\n| Failure probability | \u003c 1 in 10^9 |\n| Character set size | 32 |\n| Nostr npub length | 63 characters |\n| Field for location math | GF(1024) |\n\n## Conclusion\n\nVermeer'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.\n\nEvery 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.\n\nThe 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.\n",
"sig": "b2e758038a2b121c7e67b79a0d5558938290f37f0567940fbe037688bd9367fbea1878abd805edb4c99ae767d8db8406ba6cac271071f38f672077a01426d7ac"
}