Both bc1q… and bc1p… addresses look like the same species, but they use two different encodings: bech32 for SegWit v0, and bech32m for Taproot and beyond. The difference is a single constant in the checksum, and the story of why it exists is a small classic of protocol engineering.
Why bech32 replaced Base58 in the first place
Bitcoin's original address format, Base58Check, was fine but dated: mixed-case (hostile to error-prone typing and inefficient in QR codes), and its 4-byte truncated-hash checksum can tell you that an address is wrong but nothing about where.
Bech32 (BIP-173, 2017) fixed all of that for SegWit addresses. Lowercase-only, a compact character set that excludes lookalikes (no 1, b, i, o), QR-friendly, and a BCH error-detecting code as its checksum (the crown jewel): mathematically guaranteed to detect any error affecting up to four characters, and able to point at the likely error position rather than just failing.
The flaw nobody expected
In 2019, a mundane bug report turned into a cryptographic surprise. If a bech32 string happens to end in the character p, you can insert or delete q characters immediately before that final p and the checksum still verifies. The culprit was the checksum's final XOR constant, the value 1, which interacts badly with the algebra of trailing zeros (the character q encodes zero).
For SegWit v0 this was survivable, because v0 witness programs must be exactly 20 or 32 bytes, a length check that catches the mutation independently. But future witness versions were allowed variable lengths, and Taproot (v1) was on approach. Shipping Taproot on unmodified bech32 would have meant addresses where an accidental (or malicious) length change could pass validation and burn funds.
The fix: change one constant
BIP-350 defines bech32m: the identical algorithm with the final checksum constant changed from 1 to 0x2bc830a3. That single change restores the intended error-detection guarantees for arbitrary lengths. Everything else (character set, human-readable prefix, structure) is untouched.
The rule since then is split by witness version: version 0 keeps bech32 (changing it would have invalidated every existing bc1q address), version 1 and up use bech32m. A correct validator must check both directions: a v0 program encoded with bech32m is invalid, and a v1 program encoded with old bech32 is invalid too. This cross-check is exactly what our validator enforces.
Beyond Bitcoin
The design travels well. Litecoin uses the same scheme with the ltc prefix (and, notably, defines ltc1p Taproot-style addresses even though Taproot is not active on Litecoin mainnet, a trap our Litecoin validator flags). Cosmos-ecosystem chains adopted bech32 wholesale for account addresses, and Lightning invoices use a bech32 variant too.
The deeper lesson: checksums are cryptography-adjacent engineering, and "adjacent" is where surprises live. The bech32 flaw survived four years of expert review and was found essentially by accident. The response, a minimal, surgical, one-constant fix with a clean version boundary, is about as good as protocol remediation gets.