From f974d465d738090d7391e98ce2e5b0cdecba1376 Mon Sep 17 00:00:00 2001 From: Reese Russell Date: Sun, 23 Feb 2025 08:51:49 +0000 Subject: [PATCH] Bech32 decoding logic --- src/key_io.cpp | 52 +++++++++---------- .../functional/rpc_invalid_address_message.py | 14 ++--- test/functional/rpc_validateaddress.py | 8 +-- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/key_io.cpp b/src/key_io.cpp index 444f8065b72..bc65ff9badf 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -95,7 +95,6 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par bool is_bech32 = bech32_encoding != bech32::Encoding::INVALID; auto check_base58 = [&]() { return DecodeBase58Check(str, data, MAX_BASE58_CHECK_CHARS); }; - if (!is_bech32 && check_base58()) { uint160 hash; @@ -124,7 +123,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par } else { std::vector encoded_prefixes; const std::vector& pubkey_prefixes = params.Base58EncodedPrefix(CChainParams::PUBKEY_ADDRESS); - const std::vector& script_prefixes = params.Base58EncodedPrefix(CChainParams::SCRIPT_ADDRESS); + const std::vector& script_prefixes = params.Base58EncodedPrefix(CChainParams::SCRIPT_ADDRESS); encoded_prefixes.insert(encoded_prefixes.end(), script_prefixes.begin(), script_prefixes.end()); encoded_prefixes.insert(encoded_prefixes.end(), pubkey_prefixes.begin(), pubkey_prefixes.end()); @@ -160,61 +159,59 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par return CNoDestination(); } - data.clear(); - const auto dec = bech32::Decode(str); - if (dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) { - if (dec.data.empty()) { + if (bech32_encoding == bech32::Encoding::BECH32 || bech32_encoding == bech32::Encoding::BECH32M) { + if (bech32_chars.empty()) { error_str = "Empty Bech32 data section"; return CNoDestination(); } // Bech32 decoding - if (dec.hrp != params.Bech32HRP()) { - error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) address (expected %s, got %s)", params.Bech32HRP(), dec.hrp); + if (bech32_hrp != params.Bech32HRP()) { + error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) %s address (expected %s, got %s)", params.GetChainTypeDisplayString(), params.Bech32HRP(), bech32_hrp); return CNoDestination(); } - int version = dec.data[0]; // The first 5 bit symbol is the witness version (0-16) - if (version == 0 && dec.encoding != bech32::Encoding::BECH32) { + int version = bech32_chars[0]; // The first 5 bit symbol is the witness version (0-16) + if (version == 0 && bech32_encoding != bech32::Encoding::BECH32) { error_str = "Version 0 witness address must use Bech32 checksum"; 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"; return CNoDestination(); } // The rest of the symbols are converted witness program bytes. - data.reserve(((dec.data.size() - 1) * 5) / 8); - if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) { + bech32_data.reserve(((bech32_chars.size() - 1) * 5) / 8); + 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) { { WitnessV0KeyHash keyid; - if (data.size() == keyid.size()) { - std::copy(data.begin(), data.end(), keyid.begin()); + if (bech32_data.size() == keyid.size()) { + std::copy(bech32_data.begin(), bech32_data.end(), keyid.begin()); return keyid; } } { WitnessV0ScriptHash scriptid; - if (data.size() == scriptid.size()) { - std::copy(data.begin(), data.end(), scriptid.begin()); + if (bech32_data.size() == scriptid.size()) { + std::copy(bech32_data.begin(), bech32_data.end(), scriptid.begin()); 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(); } - 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()); WitnessV1Taproot tap; - std::copy(data.begin(), data.end(), tap.begin()); + std::copy(bech32_data.begin(), bech32_data.end(), tap.begin()); return tap; } - if (CScript::IsPayToAnchor(version, data)) { + if (CScript::IsPayToAnchor(version, bech32_data)) { return PayToAnchor(); } @@ -223,12 +220,12 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par return CNoDestination(); } - if (data.size() < 2 || data.size() > BECH32_WITNESS_PROG_MAX_LEN) { - error_str = strprintf("Invalid Bech32 address program size (%d %s)", data.size(), byte_str); + if (bech32_data.size() < 2 || bech32_data.size() > BECH32_WITNESS_PROG_MAX_LEN) { + error_str = strprintf("Invalid Bech32 address program size (%d %s)", bech32_data.size(), byte_str); return CNoDestination(); } - return WitnessUnknown{version, data}; + return WitnessUnknown{version, bech32_data}; } else { error_str = strprintf("Invalid padding in Bech32 data section"); return CNoDestination(); @@ -236,9 +233,8 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par } // Perform Bech32 error location - auto res = bech32::LocateErrors(str); - error_str = res.first; - if (error_locations) *error_locations = std::move(res.second); + error_str = bech32_error; + if (error_locations) *error_locations = std::move(bech32_error_loc); return CNoDestination(); } } // namespace diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py index f9924862f94..8b9f818bda0 100755 --- a/test/functional/rpc_invalid_address_message.py +++ b/test/functional/rpc_invalid_address_message.py @@ -64,12 +64,12 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework): def test_validateaddress(self): # Invalid Bech32 - 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_SIZE, 'Invalid Bech32 address program size (41 bytes)') + 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_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_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_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]) @@ -107,10 +107,10 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework): def test_getaddressinfo(self): 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 or unsupported prefix for Segwit (Bech32) 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, "Bech32(m) address decoded with error: Invalid separator position", node.getaddressinfo, INVALID_ADDRESS) + 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) 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, 'Bech32(m) address decoded with error: Invalid separator position', node.getaddressinfo, INVALID_ADDRESS) assert "isscript" not in node.getaddressinfo(BECH32_VALID_UNKNOWN_WITNESS) def run_test(self): diff --git a/test/functional/rpc_validateaddress.py b/test/functional/rpc_validateaddress.py index 11b5c0212e8..17d0d1171a1 100755 --- a/test/functional/rpc_validateaddress.py +++ b/test/functional/rpc_validateaddress.py @@ -12,7 +12,7 @@ INVALID_DATA = [ # BIP 173 ( "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]), @@ -33,7 +33,7 @@ INVALID_DATA = [ ), ( "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 ( "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", - "Invalid Bech32 v0 address program size (16 bytes), per BIP141", + "Invalid SegWit v0 address program size (16 bytes), per BIP141", [], ), (