Skip to content

Commit 79f02d5

Browse files
committed
Merge bitcoin/bitcoin#30623: test: Fuzz the human-readable part of bech32 as well
9b7023d Fuzz HRP of bech32 as well (Lőrinc) c1a5d5c Split out bech32 separator char to header (Lőrinc) Pull request description: Instead of the static "bc" human-readable part, it's now randomly generated based on https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki and the extra restrictions in the code: > The human-readable part, which is intended to convey the type of data, or anything else that is relevant to the reader. This part MUST contain 1 to 83 US-ASCII characters, with each character having a value in the range [33-126]. HRP validity may be further restricted by specific applications. Since `bech32::Encode` rejects uppercase letters, we're actually generating values in the `[33-126] - ['A'-'Z']` range. Split out of https://github.com/bitcoin/bitcoin/pull/30596/files#r1706957219 ACKs for top commit: sipa: ACK 9b7023d achow101: ACK 9b7023d marcofleon: Code review ACK 9b7023d. The separation into two targets and the new `GenerateRandomHRP` seem fine to me. brunoerg: code review ACK 9b7023d Tree-SHA512: 22a261b8e7b5516e98f4e7990811954454595438a49a10191ed4ca42b5c71c5054fcc73f2d94e23b498ea833c7f1d5adb225f537ef1a24d15b428259450cdf98
2 parents ff3171f + 9b7023d commit 79f02d5

File tree

3 files changed

+51
-26
lines changed

3 files changed

+51
-26
lines changed

src/bech32.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ std::string Encode(Encoding encoding, const std::string& hrp, const data& values
364364
std::string ret;
365365
ret.reserve(hrp.size() + 1 + values.size() + CHECKSUM_SIZE);
366366
ret += hrp;
367-
ret += '1';
367+
ret += SEPARATOR;
368368
for (const uint8_t& i : values) ret += CHARSET[i];
369369
for (const uint8_t& i : CreateChecksum(encoding, hrp, values)) ret += CHARSET[i];
370370
return ret;
@@ -374,7 +374,7 @@ std::string Encode(Encoding encoding, const std::string& hrp, const data& values
374374
DecodeResult Decode(const std::string& str, CharLimit limit) {
375375
std::vector<int> errors;
376376
if (!CheckCharacters(str, errors)) return {};
377-
size_t pos = str.rfind('1');
377+
size_t pos = str.rfind(SEPARATOR);
378378
if (str.size() > limit) return {};
379379
if (pos == str.npos || pos == 0 || pos + CHECKSUM_SIZE >= str.size()) {
380380
return {};
@@ -413,7 +413,7 @@ std::pair<std::string, std::vector<int>> LocateErrors(const std::string& str, Ch
413413
return std::make_pair("Invalid character or mixed case", std::move(error_locations));
414414
}
415415

416-
size_t pos = str.rfind('1');
416+
size_t pos = str.rfind(SEPARATOR);
417417
if (pos == str.npos) {
418418
return std::make_pair("Missing separator", std::vector<int>{});
419419
}

src/bech32.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
namespace bech32
2222
{
2323

24-
/** The Bech32 and Bech32m checksum size */
25-
constexpr size_t CHECKSUM_SIZE = 6;
24+
static constexpr size_t CHECKSUM_SIZE = 6;
25+
static constexpr char SEPARATOR = '1';
2626

2727
enum class Encoding {
2828
INVALID, //!< Failed decoding

src/test/fuzz/bech32.cpp

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,66 @@
44

55
#include <bech32.h>
66
#include <test/fuzz/fuzz.h>
7+
#include <test/fuzz/FuzzedDataProvider.h>
78
#include <test/util/str.h>
89
#include <util/strencodings.h>
910

1011
#include <cassert>
1112
#include <cstdint>
1213
#include <string>
13-
#include <utility>
1414
#include <vector>
1515

16-
FUZZ_TARGET(bech32)
16+
FUZZ_TARGET(bech32_random_decode)
1717
{
18-
const std::string random_string(buffer.begin(), buffer.end());
19-
const auto r1 = bech32::Decode(random_string);
20-
if (r1.hrp.empty()) {
21-
assert(r1.encoding == bech32::Encoding::INVALID);
22-
assert(r1.data.empty());
18+
auto limit = bech32::CharLimit::BECH32;
19+
FuzzedDataProvider fdp(buffer.data(), buffer.size());
20+
auto random_string = fdp.ConsumeRandomLengthString(limit + 1);
21+
auto decoded = bech32::Decode(random_string, limit);
22+
23+
if (decoded.hrp.empty()) {
24+
assert(decoded.encoding == bech32::Encoding::INVALID);
25+
assert(decoded.data.empty());
2326
} else {
24-
assert(r1.encoding != bech32::Encoding::INVALID);
25-
const std::string reencoded = bech32::Encode(r1.encoding, r1.hrp, r1.data);
27+
assert(decoded.encoding != bech32::Encoding::INVALID);
28+
auto reencoded = bech32::Encode(decoded.encoding, decoded.hrp, decoded.data);
2629
assert(CaseInsensitiveEqual(random_string, reencoded));
2730
}
31+
}
32+
33+
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki and https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
34+
std::string GenerateRandomHRP(FuzzedDataProvider& fdp)
35+
{
36+
std::string hrp;
37+
size_t length = fdp.ConsumeIntegralInRange<size_t>(1, 83);
38+
for (size_t i = 0; i < length; ++i) {
39+
// Generate lowercase ASCII characters in ([33-126] - ['A'-'Z']) range
40+
char c = fdp.ConsumeBool()
41+
? fdp.ConsumeIntegralInRange<char>(33, 'A' - 1)
42+
: fdp.ConsumeIntegralInRange<char>('Z' + 1, 126);
43+
hrp += c;
44+
}
45+
return hrp;
46+
}
47+
48+
FUZZ_TARGET(bech32_roundtrip)
49+
{
50+
FuzzedDataProvider fdp(buffer.data(), buffer.size());
51+
auto hrp = GenerateRandomHRP(fdp);
2852

29-
std::vector<unsigned char> input;
30-
ConvertBits<8, 5, true>([&](unsigned char c) { input.push_back(c); }, buffer.begin(), buffer.end());
53+
auto input_chars = fdp.ConsumeBytes<unsigned char>(fdp.ConsumeIntegralInRange<size_t>(0, 82));
54+
std::vector<uint8_t> converted_input;
55+
ConvertBits<8, 5, true>([&](auto c) { converted_input.push_back(c); }, input_chars.begin(), input_chars.end());
3156

32-
// Input data part + 3 characters for the HRP and separator (bc1) + the checksum characters
33-
if (input.size() + 3 + bech32::CHECKSUM_SIZE <= bech32::CharLimit::BECH32) {
34-
// If it's possible to encode input in Bech32(m) without exceeding the bech32-character limit:
35-
for (auto encoding : {bech32::Encoding::BECH32, bech32::Encoding::BECH32M}) {
36-
const std::string encoded = bech32::Encode(encoding, "bc", input);
57+
auto size = converted_input.size() + hrp.length() + std::string({bech32::SEPARATOR}).size() + bech32::CHECKSUM_SIZE;
58+
if (size <= bech32::CharLimit::BECH32) {
59+
for (auto encoding: {bech32::Encoding::BECH32, bech32::Encoding::BECH32M}) {
60+
auto encoded = bech32::Encode(encoding, hrp, converted_input);
3761
assert(!encoded.empty());
38-
const auto r2 = bech32::Decode(encoded);
39-
assert(r2.encoding == encoding);
40-
assert(r2.hrp == "bc");
41-
assert(r2.data == input);
62+
63+
const auto decoded = bech32::Decode(encoded);
64+
assert(decoded.encoding == encoding);
65+
assert(decoded.hrp == hrp);
66+
assert(decoded.data == converted_input);
4267
}
4368
}
44-
}
69+
}

0 commit comments

Comments
 (0)