Skip to content

Commit eeee55f

Browse files
author
MarcoFalke
committed
rpc: Fix invalid bech32 handling
1 parent 4567014 commit eeee55f

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)