Skip to content

Commit a0ed479

Browse files
committed
address: reject v2-v16 segwit addresses encoded with bech32
BIP-350 requires that segregated witness outputs of version 1 through 16 use the bech32m checksum, while only version 0 uses bech32. decodeSegWitAddress only special-cased versions 0 and 1, so a witness program with version 2-16 encoded using the legacy bech32 checksum decoded successfully, in violation of the spec and contrary to the BIP-350 reference decoder (which rejects any non-zero witness version that is not bech32m). Generalize the version 1 check to cover all versions >= 1, matching the reference decode() function. Add the relevant BIP-350 INVALID_ADDRESS vectors (v2 and v16 encoded with bech32) as a regression test; they decoded successfully before this change and are now rejected. Signed-off-by: Lrifton92 <Lrifton92@users.noreply.github.com>
1 parent 1966c38 commit a0ed479

2 files changed

Lines changed: 68 additions & 5 deletions

File tree

address/address.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,16 +275,19 @@ func decodeSegWitAddress(address string) (byte, []byte, error) {
275275
"version 0: %v", len(regrouped))
276276
}
277277

278-
// For witness version 0, the bech32 encoding must be used.
278+
// Per BIP-350, witness version 0 must use the bech32 encoding, while
279+
// witness versions 1 through 16 must use the bech32m encoding. Previously
280+
// only versions 0 and 1 were checked, which let a v2-v16 program encoded
281+
// with the legacy bech32 checksum decode successfully in violation of the
282+
// spec.
279283
if version == 0 && bech32version != bech32.Version0 {
280284
return 0, nil, fmt.Errorf("invalid checksum expected bech32 " +
281285
"encoding for address with witness version 0")
282286
}
283287

284-
// For witness version 1, the bech32m encoding must be used.
285-
if version == 1 && bech32version != bech32.VersionM {
286-
return 0, nil, fmt.Errorf("invalid checksum expected bech32m " +
287-
"encoding for address with witness version 1")
288+
if version >= 1 && bech32version != bech32.VersionM {
289+
return 0, nil, fmt.Errorf("invalid checksum expected bech32m "+
290+
"encoding for address with witness version %d", version)
288291
}
289292

290293
return version, regrouped, nil

address/bip350_diff_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package address
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// TestSegWitAddressBIP350Vectors runs a subset of the official BIP-173/BIP-350
8+
// reference segwit address vectors against decodeSegWitAddress, which implements
9+
// the reference decode() function from those BIPs. It specifically guards the
10+
// rule that witness versions 1 through 16 MUST use the bech32m encoding (BIP-350
11+
// line "Addresses for segregated witness outputs version 1 through 16 use
12+
// Bech32m"), not just version 1.
13+
func TestSegWitAddressBIP350Vectors(t *testing.T) {
14+
// Valid Bech32m vectors (BIP-350). These MUST decode without error. The HRP
15+
// is validated at a higher layer; here we only validate segwit-encoding
16+
// rules, so the HRP value is irrelevant.
17+
valid := []string{
18+
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
19+
"BC1SW50QGDZ25J",
20+
"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
21+
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
22+
// Valid Bech32 v0 vectors.
23+
"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
24+
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
25+
}
26+
for _, addr := range valid {
27+
if _, _, err := decodeSegWitAddress(addr); err != nil {
28+
t.Errorf("BIP350 valid vector rejected: %q -> %v", addr, err)
29+
}
30+
}
31+
32+
// Invalid vectors from the BIP-350 INVALID_ADDRESS list whose defect lies in
33+
// the segwit-encoding rules (not merely the HRP). Each MUST be rejected.
34+
invalid := []struct {
35+
name string
36+
addr string
37+
}{
38+
// Witness version 1, Bech32 instead of Bech32m.
39+
{"v1-bech32-not-bech32m", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd"},
40+
// Witness version 2, Bech32 instead of Bech32m. This is the vector that
41+
// previously decoded successfully because only v0/v1 were checked.
42+
{"v2-bech32-not-bech32m", "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf"},
43+
// Witness version 16, Bech32 instead of Bech32m.
44+
{"v16-bech32-not-bech32m", "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL"},
45+
// Witness version 0, Bech32m instead of Bech32.
46+
{"v0-bech32m-not-bech32", "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh"},
47+
// Invalid program length (1 byte).
48+
{"program-length-1", "bc1pw5dgrnzv"},
49+
// Invalid program length (41 bytes).
50+
{"program-length-41", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav"},
51+
// Invalid program length for witness version 0 (per BIP-141).
52+
{"v0-program-length-16", "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"},
53+
}
54+
for _, tc := range invalid {
55+
if _, _, err := decodeSegWitAddress(tc.addr); err == nil {
56+
t.Errorf("BIP350 invalid vector %s accepted: %q (expected error)",
57+
tc.name, tc.addr)
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)