Skip to content

Commit a13f374

Browse files
committed
Merge bitcoin/bitcoin#27727: rpc: Fix invalid bech32 address handling
eeee55f rpc: Fix invalid bech32 handling (MarcoFalke) Pull request description: Currently the handling of invalid bech32(m) addresses over RPC has many issues: * No error for invalid addresses is reported, leading to internal bugs via `CHECK_NONFATAL`, see bitcoin/bitcoin#27723 * The error messages use "data size" (the meaning of which is unclear to the user, because the witness program data and bech32 section data are related but different) when they mean "program size" Fix all issues. Also, use the BIP 173 and BIP 350 test vectors. ACKs for top commit: achow101: ACK eeee55f brunoerg: crACK eeee55f Tree-SHA512: c8639ee49e2a54b740b72d66bc4a40352dd553a6e3220dea9f94e48e33124f21f597a2817cb405d0a4c88d21df1013c0a4877a01370a2d326aa2cff1f9c381a8
2 parents 51c0507 + eeee55f commit a13f374

File tree

4 files changed

+217
-6
lines changed

4 files changed

+217
-6
lines changed

src/key_io.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
124124

125125
data.clear();
126126
const auto dec = bech32::Decode(str);
127-
if ((dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) && dec.data.size() > 0) {
127+
if (dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) {
128+
if (dec.data.empty()) {
129+
error_str = "Empty Bech32 data section";
130+
return CNoDestination();
131+
}
128132
// Bech32 decoding
129133
if (dec.hrp != params.Bech32HRP()) {
130134
error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) address (expected %s, got %s).", params.Bech32HRP(), dec.hrp);
@@ -158,7 +162,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
158162
}
159163
}
160164

161-
error_str = "Invalid Bech32 v0 address data size";
165+
error_str = strprintf("Invalid Bech32 v0 address program size (%s byte), per BIP141", data.size());
162166
return CNoDestination();
163167
}
164168

@@ -175,7 +179,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
175179
}
176180

177181
if (data.size() < 2 || data.size() > BECH32_WITNESS_PROG_MAX_LEN) {
178-
error_str = "Invalid Bech32 address data size";
182+
error_str = strprintf("Invalid Bech32 address program size (%s byte)", data.size());
179183
return CNoDestination();
180184
}
181185

@@ -184,6 +188,9 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
184188
std::copy(data.begin(), data.end(), unk.program);
185189
unk.length = data.size();
186190
return unk;
191+
} else {
192+
error_str = strprintf("Invalid padding in Bech32 data section");
193+
return CNoDestination();
187194
}
188195
}
189196

test/functional/rpc_invalid_address_message.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ def check_invalid(self, addr, error_str, error_locations=None):
6363

6464
def test_validateaddress(self):
6565
# Invalid Bech32
66-
self.check_invalid(BECH32_INVALID_SIZE, 'Invalid Bech32 address data size')
66+
self.check_invalid(BECH32_INVALID_SIZE, "Invalid Bech32 address program size (41 byte)")
6767
self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid or unsupported Segwit (Bech32) or Base58 encoding.')
6868
self.check_invalid(BECH32_INVALID_BECH32, 'Version 1+ witness address must use Bech32m checksum')
6969
self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum')
7070
self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version')
71-
self.check_invalid(BECH32_INVALID_V0_SIZE, 'Invalid Bech32 v0 address data size')
71+
self.check_invalid(BECH32_INVALID_V0_SIZE, "Invalid Bech32 v0 address program size (21 byte), per BIP141")
7272
self.check_invalid(BECH32_TOO_LONG, 'Bech32 string too long', list(range(90, 108)))
7373
self.check_invalid(BECH32_ONE_ERROR, 'Invalid Bech32 checksum', [9])
7474
self.check_invalid(BECH32_TWO_ERRORS, 'Invalid Bech32 checksum', [22, 43])
@@ -105,7 +105,7 @@ def test_validateaddress(self):
105105
def test_getaddressinfo(self):
106106
node = self.nodes[0]
107107

108-
assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE)
108+
assert_raises_rpc_error(-5, "Invalid Bech32 address program size (41 byte)", node.getaddressinfo, BECH32_INVALID_SIZE)
109109
assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, BECH32_INVALID_PREFIX)
110110
assert_raises_rpc_error(-5, "Invalid or unsupported Base58-encoded address.", node.getaddressinfo, BASE58_INVALID_PREFIX)
111111
assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, INVALID_ADDRESS)
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2023 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test validateaddress for main chain"""
6+
7+
from test_framework.test_framework import BitcoinTestFramework
8+
9+
from test_framework.util import assert_equal
10+
11+
INVALID_DATA = [
12+
# BIP 173
13+
(
14+
"tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
15+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # Invalid hrp
16+
[],
17+
),
18+
("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "Invalid Bech32 checksum", [41]),
19+
(
20+
"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
21+
"Version 1+ witness address must use Bech32m checksum",
22+
[],
23+
),
24+
(
25+
"bc1rw5uspcuh",
26+
"Version 1+ witness address must use Bech32m checksum", # Invalid program length
27+
[],
28+
),
29+
(
30+
"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
31+
"Version 1+ witness address must use Bech32m checksum", # Invalid program length
32+
[],
33+
),
34+
(
35+
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
36+
"Invalid Bech32 v0 address program size (16 byte), per BIP141",
37+
[],
38+
),
39+
(
40+
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
41+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Mixed case
42+
[],
43+
),
44+
(
45+
"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3t4",
46+
"Invalid character or mixed case", # bc1, Mixed case, not in BIP 173 test vectors
47+
[40],
48+
),
49+
(
50+
"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
51+
"Version 1+ witness address must use Bech32m checksum", # Wrong padding
52+
[],
53+
),
54+
(
55+
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
56+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Non-zero padding in 8-to-5 conversion
57+
[],
58+
),
59+
("bc1gmk9yu", "Empty Bech32 data section", []),
60+
# BIP 350
61+
(
62+
"tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
63+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # Invalid human-readable part
64+
[],
65+
),
66+
(
67+
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
68+
"Version 1+ witness address must use Bech32m checksum", # Invalid checksum (Bech32 instead of Bech32m)
69+
[],
70+
),
71+
(
72+
"tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
73+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Invalid checksum (Bech32 instead of Bech32m)
74+
[],
75+
),
76+
(
77+
"BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
78+
"Version 1+ witness address must use Bech32m checksum", # Invalid checksum (Bech32 instead of Bech32m)
79+
[],
80+
),
81+
(
82+
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
83+
"Version 0 witness address must use Bech32 checksum", # Invalid checksum (Bech32m instead of Bech32)
84+
[],
85+
),
86+
(
87+
"tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
88+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Invalid checksum (Bech32m instead of Bech32)
89+
[],
90+
),
91+
(
92+
"bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
93+
"Invalid Base 32 character", # Invalid character in checksum
94+
[59],
95+
),
96+
(
97+
"BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
98+
"Invalid Bech32 address witness version",
99+
[],
100+
),
101+
("bc1pw5dgrnzv", "Invalid Bech32 address program size (1 byte)", []),
102+
(
103+
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
104+
"Invalid Bech32 address program size (41 byte)",
105+
[],
106+
),
107+
(
108+
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
109+
"Invalid Bech32 v0 address program size (16 byte), per BIP141",
110+
[],
111+
),
112+
(
113+
"tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
114+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Mixed case
115+
[],
116+
),
117+
(
118+
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
119+
"Invalid padding in Bech32 data section", # zero padding of more than 4 bits
120+
[],
121+
),
122+
(
123+
"tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",
124+
"Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Non-zero padding in 8-to-5 conversion
125+
[],
126+
),
127+
("bc1gmk9yu", "Empty Bech32 data section", []),
128+
]
129+
VALID_DATA = [
130+
# BIP 350
131+
(
132+
"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
133+
"0014751e76e8199196d454941c45d1b3a323f1433bd6",
134+
),
135+
# (
136+
# "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
137+
# "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
138+
# ),
139+
(
140+
"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3",
141+
"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
142+
),
143+
(
144+
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
145+
"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
146+
),
147+
("BC1SW50QGDZ25J", "6002751e"),
148+
("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", "5210751e76e8199196d454941c45d1b3a323"),
149+
# (
150+
# "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
151+
# "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
152+
# ),
153+
(
154+
"bc1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvses5wp4dt",
155+
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
156+
),
157+
# (
158+
# "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
159+
# "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
160+
# ),
161+
(
162+
"bc1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvses7epu4h",
163+
"5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
164+
),
165+
(
166+
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
167+
"512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
168+
),
169+
]
170+
171+
172+
class ValidateAddressMainTest(BitcoinTestFramework):
173+
def set_test_params(self):
174+
self.setup_clean_chain = True
175+
self.chain = "" # main
176+
self.num_nodes = 1
177+
self.extra_args = [["-prune=899"]] * self.num_nodes
178+
179+
def check_valid(self, addr, spk):
180+
info = self.nodes[0].validateaddress(addr)
181+
assert_equal(info["isvalid"], True)
182+
assert_equal(info["scriptPubKey"], spk)
183+
assert "error" not in info
184+
assert "error_locations" not in info
185+
186+
def check_invalid(self, addr, error_str, error_locations):
187+
res = self.nodes[0].validateaddress(addr)
188+
assert_equal(res["isvalid"], False)
189+
assert_equal(res["error"], error_str)
190+
assert_equal(res["error_locations"], error_locations)
191+
192+
def test_validateaddress(self):
193+
for (addr, error, locs) in INVALID_DATA:
194+
self.check_invalid(addr, error, locs)
195+
for (addr, spk) in VALID_DATA:
196+
self.check_valid(addr, spk)
197+
198+
def run_test(self):
199+
self.test_validateaddress()
200+
201+
202+
if __name__ == "__main__":
203+
ValidateAddressMainTest().main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
'wallet_fast_rescan.py --descriptors',
172172
'interface_zmq.py',
173173
'rpc_invalid_address_message.py',
174+
'rpc_validateaddress.py',
174175
'interface_bitcoin_cli.py --legacy-wallet',
175176
'interface_bitcoin_cli.py --descriptors',
176177
'feature_bind_extra.py',

0 commit comments

Comments
 (0)