Bech32 decoding logic

This commit is contained in:
Reese Russell 2025-02-23 08:51:49 +00:00
parent 6e7cf4c583
commit f974d465d7
3 changed files with 35 additions and 39 deletions

View file

@ -96,7 +96,6 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
auto check_base58 = [&]() { return DecodeBase58Check(str, data, MAX_BASE58_CHECK_CHARS); }; auto check_base58 = [&]() { return DecodeBase58Check(str, data, MAX_BASE58_CHECK_CHARS); };
if (!is_bech32 && check_base58()) { if (!is_bech32 && check_base58()) {
uint160 hash; uint160 hash;
// base58-encoded Bitcoin addresses. // base58-encoded Bitcoin addresses.
@ -160,61 +159,59 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return CNoDestination(); return CNoDestination();
} }
data.clear(); if (bech32_encoding == bech32::Encoding::BECH32 || bech32_encoding == bech32::Encoding::BECH32M) {
const auto dec = bech32::Decode(str); if (bech32_chars.empty()) {
if (dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) {
if (dec.data.empty()) {
error_str = "Empty Bech32 data section"; error_str = "Empty Bech32 data section";
return CNoDestination(); return CNoDestination();
} }
// Bech32 decoding // Bech32 decoding
if (dec.hrp != params.Bech32HRP()) { if (bech32_hrp != params.Bech32HRP()) {
error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) address (expected %s, got %s)", params.Bech32HRP(), dec.hrp); error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) %s address (expected %s, got %s)", params.GetChainTypeDisplayString(), params.Bech32HRP(), bech32_hrp);
return CNoDestination(); return CNoDestination();
} }
int version = dec.data[0]; // The first 5 bit symbol is the witness version (0-16) int version = bech32_chars[0]; // The first 5 bit symbol is the witness version (0-16)
if (version == 0 && dec.encoding != bech32::Encoding::BECH32) { if (version == 0 && bech32_encoding != bech32::Encoding::BECH32) {
error_str = "Version 0 witness address must use Bech32 checksum"; error_str = "Version 0 witness address must use Bech32 checksum";
return CNoDestination(); return CNoDestination();
} }
if (version != 0 && dec.encoding != bech32::Encoding::BECH32M) { if (version != 0 && bech32_encoding != bech32::Encoding::BECH32M) {
error_str = "Version 1+ witness address must use Bech32m checksum"; error_str = "Version 1+ witness address must use Bech32m checksum";
return CNoDestination(); return CNoDestination();
} }
// The rest of the symbols are converted witness program bytes. // The rest of the symbols are converted witness program bytes.
data.reserve(((dec.data.size() - 1) * 5) / 8); bech32_data.reserve(((bech32_chars.size() - 1) * 5) / 8);
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) { if (ConvertBits<5, 8, false>([&](unsigned char c) { bech32_data.push_back(c); }, bech32_chars.begin() + 1, bech32_chars.end())) {
std::string_view byte_str{data.size() == 1 ? "byte" : "bytes"}; std::string_view byte_str{bech32_data.size() == 1 ? "byte" : "bytes"};
if (version == 0) { if (version == 0) {
{ {
WitnessV0KeyHash keyid; WitnessV0KeyHash keyid;
if (data.size() == keyid.size()) { if (bech32_data.size() == keyid.size()) {
std::copy(data.begin(), data.end(), keyid.begin()); std::copy(bech32_data.begin(), bech32_data.end(), keyid.begin());
return keyid; return keyid;
} }
} }
{ {
WitnessV0ScriptHash scriptid; WitnessV0ScriptHash scriptid;
if (data.size() == scriptid.size()) { if (bech32_data.size() == scriptid.size()) {
std::copy(data.begin(), data.end(), scriptid.begin()); std::copy(bech32_data.begin(), bech32_data.end(), scriptid.begin());
return scriptid; return scriptid;
} }
} }
error_str = strprintf("Invalid Bech32 v0 address program size (%d %s), per BIP141", data.size(), byte_str); error_str = strprintf("Invalid SegWit v0 address program size (%d %s), per BIP141", bech32_data.size(), byte_str);
return CNoDestination(); return CNoDestination();
} }
if (version == 1 && data.size() == WITNESS_V1_TAPROOT_SIZE) { if (version == 1 && bech32_data.size() == WITNESS_V1_TAPROOT_SIZE) {
static_assert(WITNESS_V1_TAPROOT_SIZE == WitnessV1Taproot::size()); static_assert(WITNESS_V1_TAPROOT_SIZE == WitnessV1Taproot::size());
WitnessV1Taproot tap; WitnessV1Taproot tap;
std::copy(data.begin(), data.end(), tap.begin()); std::copy(bech32_data.begin(), bech32_data.end(), tap.begin());
return tap; return tap;
} }
if (CScript::IsPayToAnchor(version, data)) { if (CScript::IsPayToAnchor(version, bech32_data)) {
return PayToAnchor(); return PayToAnchor();
} }
@ -223,12 +220,12 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return CNoDestination(); return CNoDestination();
} }
if (data.size() < 2 || data.size() > BECH32_WITNESS_PROG_MAX_LEN) { if (bech32_data.size() < 2 || bech32_data.size() > BECH32_WITNESS_PROG_MAX_LEN) {
error_str = strprintf("Invalid Bech32 address program size (%d %s)", data.size(), byte_str); error_str = strprintf("Invalid Bech32 address program size (%d %s)", bech32_data.size(), byte_str);
return CNoDestination(); return CNoDestination();
} }
return WitnessUnknown{version, data}; return WitnessUnknown{version, bech32_data};
} else { } else {
error_str = strprintf("Invalid padding in Bech32 data section"); error_str = strprintf("Invalid padding in Bech32 data section");
return CNoDestination(); return CNoDestination();
@ -236,9 +233,8 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
} }
// Perform Bech32 error location // Perform Bech32 error location
auto res = bech32::LocateErrors(str); error_str = bech32_error;
error_str = res.first; if (error_locations) *error_locations = std::move(bech32_error_loc);
if (error_locations) *error_locations = std::move(res.second);
return CNoDestination(); return CNoDestination();
} }
} // namespace } // namespace

View file

@ -64,12 +64,12 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
def test_validateaddress(self): def test_validateaddress(self):
# Invalid Bech32 # Invalid Bech32
self.check_invalid(BECH32_INVALID_SIZE, "Invalid Bech32 address program size (41 bytes)") self.check_invalid(BECH32_INVALID_SIZE, 'Invalid Bech32 address program size (41 bytes)')
self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid or unsupported prefix for Segwit (Bech32) address (expected bcrt, got bc)') self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid or unsupported prefix for Segwit (Bech32) regtest address (expected bcrt, got bc)')
self.check_invalid(BECH32_INVALID_BECH32, 'Version 1+ witness address must use Bech32m checksum') self.check_invalid(BECH32_INVALID_BECH32, 'Version 1+ witness address must use Bech32m checksum')
self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum') self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum')
self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version') self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version')
self.check_invalid(BECH32_INVALID_V0_SIZE, "Invalid Bech32 v0 address program size (21 bytes), per BIP141") self.check_invalid(BECH32_INVALID_V0_SIZE, 'Invalid SegWit v0 address program size (21 bytes), per BIP141')
self.check_invalid(BECH32_TOO_LONG, 'Bech32(m) address decoded with error: Bech32 string too long', list(range(90, 108))) self.check_invalid(BECH32_TOO_LONG, 'Bech32(m) address decoded with error: Bech32 string too long', list(range(90, 108)))
self.check_invalid(BECH32_ONE_ERROR, 'Bech32(m) address decoded with error: Invalid Bech32 checksum', [9]) self.check_invalid(BECH32_ONE_ERROR, 'Bech32(m) address decoded with error: Invalid Bech32 checksum', [9])
self.check_invalid(BECH32_TWO_ERRORS, 'Bech32(m) address decoded with error: Invalid Bech32 checksum', [22, 43]) self.check_invalid(BECH32_TWO_ERRORS, 'Bech32(m) address decoded with error: Invalid Bech32 checksum', [22, 43])
@ -107,10 +107,10 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
def test_getaddressinfo(self): def test_getaddressinfo(self):
node = self.nodes[0] node = self.nodes[0]
assert_raises_rpc_error(-5, "Invalid Bech32 address program size (41 bytes)", node.getaddressinfo, BECH32_INVALID_SIZE) assert_raises_rpc_error(-5, 'Invalid Bech32 address program size (41 bytes)', node.getaddressinfo, BECH32_INVALID_SIZE)
assert_raises_rpc_error(-5, "Invalid or unsupported prefix for Segwit (Bech32) address (expected bcrt, got bc)", node.getaddressinfo, BECH32_INVALID_PREFIX) assert_raises_rpc_error(-5, 'Invalid or unsupported prefix for Segwit (Bech32) regtest address (expected bcrt, got bc)', node.getaddressinfo, BECH32_INVALID_PREFIX)
assert_raises_rpc_error(-5, "Invalid Base58 regtest address. Expected prefix 2, m, or n", node.getaddressinfo, BASE58_INVALID_PREFIX) assert_raises_rpc_error(-5, 'Invalid Base58 regtest address. Expected prefix 2, m, or n', node.getaddressinfo, BASE58_INVALID_PREFIX)
assert_raises_rpc_error(-5, "Bech32(m) address decoded with error: Invalid separator position", node.getaddressinfo, INVALID_ADDRESS) assert_raises_rpc_error(-5, 'Bech32(m) address decoded with error: Invalid separator position', node.getaddressinfo, INVALID_ADDRESS)
assert "isscript" not in node.getaddressinfo(BECH32_VALID_UNKNOWN_WITNESS) assert "isscript" not in node.getaddressinfo(BECH32_VALID_UNKNOWN_WITNESS)
def run_test(self): def run_test(self):

View file

@ -12,7 +12,7 @@ INVALID_DATA = [
# BIP 173 # BIP 173
( (
"tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
"Invalid or unsupported prefix for Segwit (Bech32) address (expected bc, got tc)", # Invalid hrp "Invalid or unsupported prefix for Segwit (Bech32) Bitcoin address (expected bc, got tc)", # Invalid hrp
[], [],
), ),
("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "Bech32(m) address decoded with error: Invalid Bech32 checksum", [41]), ("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "Bech32(m) address decoded with error: Invalid Bech32 checksum", [41]),
@ -33,7 +33,7 @@ INVALID_DATA = [
), ),
( (
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
"Invalid Bech32 v0 address program size (16 bytes), per BIP141", "Invalid SegWit v0 address program size (16 bytes), per BIP141",
[], [],
), ),
( (
@ -60,7 +60,7 @@ INVALID_DATA = [
# BIP 350 # BIP 350
( (
"tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut", "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
"Invalid or unsupported prefix for Segwit (Bech32) address (expected bc, got tc)", # Invalid human-readable part "Invalid or unsupported prefix for Segwit (Bech32) Bitcoin address (expected bc, got tc)", # Invalid human-readable part
[], [],
), ),
( (
@ -96,7 +96,7 @@ INVALID_DATA = [
), ),
( (
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
"Invalid Bech32 v0 address program size (16 bytes), per BIP141", "Invalid SegWit v0 address program size (16 bytes), per BIP141",
[], [],
), ),
( (