diff --git a/src/Bech32.cpp b/src/Bech32.cpp index 6946132b9ec..f1c6699eba6 100644 --- a/src/Bech32.cpp +++ b/src/Bech32.cpp @@ -1,5 +1,5 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2021 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -10,6 +10,11 @@ #include +// Bech32 address encoding +// Bech32M variant also supported (BIP350) +// Max length of 90 constraint is extended here to 120 for other usages + +using namespace TW::Bech32; using namespace TW; namespace { @@ -26,11 +31,9 @@ constexpr std::array charset_rev = { 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; -/** Concatenate two byte arrays. */ -Data cat(Data x, const Data& y) { - x.insert(x.end(), y.begin(), y.end()); - return x; -} +const uint32_t BECH32_XOR_CONST = 0x01; +const uint32_t BECH32M_XOR_CONST = 0x2bc830a3; + /** Find the polynomial with value coefficients mod the generator as 30-bit. */ uint32_t polymod(const Data& values) { @@ -62,16 +65,35 @@ Data expand_hrp(const std::string& hrp) { return ret; } +inline uint32_t xorConstant(ChecksumVariant variant) { + if (variant == ChecksumVariant::Bech32) { + return BECH32_XOR_CONST; + } + // Bech32M + return BECH32M_XOR_CONST; +} + /** Verify a checksum. */ -bool verify_checksum(const std::string& hrp, const Data& values) { - return polymod(cat(expand_hrp(hrp), values)) == 1; +ChecksumVariant verify_checksum(const std::string& hrp, const Data& values) { + Data enc = expand_hrp(hrp); + append(enc, values); + auto poly = polymod(enc); + if (poly == BECH32_XOR_CONST) { + return ChecksumVariant::Bech32; + } + if (poly == BECH32M_XOR_CONST) { + return ChecksumVariant::Bech32M; + } + return ChecksumVariant::None; } /** Create a checksum. */ -Data create_checksum(const std::string& hrp, const Data& values) { - Data enc = cat(expand_hrp(hrp), values); +Data create_checksum(const std::string& hrp, const Data& values, ChecksumVariant variant) { + Data enc = expand_hrp(hrp); + append(enc, values); enc.resize(enc.size() + 6); - uint32_t mod = polymod(enc) ^ 1; + auto xorConst = xorConstant(variant); + uint32_t mod = polymod(enc) ^ xorConst; Data ret; ret.resize(6); for (size_t i = 0; i < 6; ++i) { @@ -83,9 +105,10 @@ Data create_checksum(const std::string& hrp, const Data& values) { } // namespace /** Encode a Bech32 string. */ -std::string Bech32::encode(const std::string& hrp, const Data& values) { - Data checksum = create_checksum(hrp, values); - Data combined = cat(values, checksum); +std::string Bech32::encode(const std::string& hrp, const Data& values, ChecksumVariant variant) { + Data checksum = create_checksum(hrp, values, variant); + Data combined = values; + append(combined, checksum); std::string ret = hrp + '1'; ret.reserve(ret.size() + combined.size()); for (const auto& value : combined) { @@ -95,7 +118,11 @@ std::string Bech32::encode(const std::string& hrp, const Data& values) { } /** Decode a Bech32 string. */ -std::pair Bech32::decode(const std::string& str) { +std::tuple Bech32::decode(const std::string& str) { + if (str.length() > 120 || str.length() < 2) { + // too long or too short + return std::make_tuple(std::string(), Data(), None); + } bool lower = false, upper = false; bool ok = true; for (size_t i = 0; ok && i < str.size(); ++i) { @@ -114,7 +141,7 @@ std::pair Bech32::decode(const std::string& str) { ok = false; } size_t pos = str.rfind('1'); - if (ok && str.size() <= 120 && pos != str.npos && pos >= 1 && pos + 7 <= str.size()) { + if (ok && pos != str.npos && pos >= 1 && pos + 7 <= str.size()) { Data values; values.resize(str.size() - 1 - pos); for (size_t i = 0; i < str.size() - 1 - pos; ++i) { @@ -129,10 +156,11 @@ std::pair Bech32::decode(const std::string& str) { for (size_t i = 0; i < pos; ++i) { hrp += lc(str[i]); } - if (verify_checksum(hrp, values)) { - return std::make_pair(hrp, Data(values.begin(), values.end() - 6)); + auto variant = verify_checksum(hrp, values); + if (variant != None) { + return std::make_tuple(hrp, Data(values.begin(), values.end() - 6), variant); } } } - return std::make_pair(std::string(), Data()); + return std::make_tuple(std::string(), Data(), None); } diff --git a/src/Bech32.h b/src/Bech32.h index 38840fdde94..60037fc5794 100644 --- a/src/Bech32.h +++ b/src/Bech32.h @@ -1,30 +1,42 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2021 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Data.h" + #include #include #include +#include namespace TW::Bech32 { +enum ChecksumVariant { + None = 0, + Bech32, + Bech32M, +}; + /// Encodes a Bech32 string. /// /// \returns the encoded string, or an empty string in case of failure. -std::string encode(const std::string& hrp, const std::vector& values); +std::string encode(const std::string& hrp, const Data& values, ChecksumVariant variant); /// Decodes a Bech32 string. /// -/// \returns a pair with the human-readable part and the data, or a pair or -/// empty collections on failure. -std::pair> decode(const std::string& str); +/// \returns a tuple with +/// - the human-readable part +/// - data, or a pair or +/// - ChecksumVariant used +/// or empty values on failure. +std::tuple decode(const std::string& str); /// Converts from one power-of-2 number base to another. template -inline bool convertBits(std::vector& out, const std::vector& in) { +inline bool convertBits(Data& out, const Data& in) { int acc = 0; int bits = 0; const int maxv = (1 << tobits) - 1; diff --git a/src/Bech32Address.cpp b/src/Bech32Address.cpp index 3df8b491ef7..d1c36c013b6 100644 --- a/src/Bech32Address.cpp +++ b/src/Bech32Address.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2021 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -18,15 +18,15 @@ bool Bech32Address::isValid(const std::string& addr) { bool Bech32Address::isValid(const std::string& addr, const std::string& hrp) { auto dec = Bech32::decode(addr); // check hrp prefix (if given) - if (hrp.length() > 0 && dec.first.compare(0, hrp.length(), hrp) != 0) { + if (hrp.length() > 0 && std::get<0>(dec).compare(0, hrp.length(), hrp) != 0) { return false; } - if (dec.second.empty()) { + if (std::get<1>(dec).empty()) { return false; } Data conv; - auto success = Bech32::convertBits<5, 8, false>(conv, dec.second); + auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(dec)); if (!success || conv.size() < 2 || conv.size() > 40) { return false; } @@ -37,20 +37,20 @@ bool Bech32Address::isValid(const std::string& addr, const std::string& hrp) { bool Bech32Address::decode(const std::string& addr, Bech32Address& obj_out, const std::string& hrp) { auto dec = Bech32::decode(addr); // check hrp prefix (if given) - if (hrp.length() > 0 && dec.first.compare(0, hrp.length(), hrp) != 0) { + if (hrp.length() > 0 && std::get<0>(dec).compare(0, hrp.length(), hrp) != 0) { return false; } - if (dec.second.empty()) { + if (std::get<1>(dec).empty()) { return false; } Data conv; - auto success = Bech32::convertBits<5, 8, false>(conv, dec.second); + auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(dec)); if (!success || conv.size() < 2 || conv.size() > 40) { return false; } - obj_out.setHrp(dec.first); + obj_out.setHrp(std::get<0>(dec)); obj_out.setKey(conv); return true; } @@ -94,7 +94,7 @@ std::string Bech32Address::string() const { if (!Bech32::convertBits<8, 5, true>(enc, keyHash)) { return ""; } - std::string result = Bech32::encode(hrp, enc); + std::string result = Bech32::encode(hrp, enc, Bech32::ChecksumVariant::Bech32); // check back Bech32Address obj; if (!decode(result, obj, hrp)) { diff --git a/src/Bitcoin/SegwitAddress.cpp b/src/Bitcoin/SegwitAddress.cpp index a8b7f6baa42..a658da281cd 100644 --- a/src/Bitcoin/SegwitAddress.cpp +++ b/src/Bitcoin/SegwitAddress.cpp @@ -24,7 +24,7 @@ bool SegwitAddress::isValid(const std::string& string, const std::string& hrp) { } // extra step to check hrp auto dec = Bech32::decode(string); - if (dec.first != hrp) { + if (std::get<0>(dec) != hrp) { return false; } @@ -44,35 +44,51 @@ SegwitAddress::SegwitAddress(const PublicKey& publicKey, int witver, std::string std::tuple SegwitAddress::decode(const std::string& addr) { auto resp = std::make_tuple(SegwitAddress(), "", false); auto dec = Bech32::decode(addr); - if (dec.second.empty()) { + auto& hrp = std::get<0>(dec); + auto& data = std::get<1>(dec); + auto& variant = std::get<2>(dec); + if (data.empty()) { // bech32 decode fails, or decoded data is empty return resp; } - assert(dec.second.size() >= 1); + assert(data.size() >= 1); // First byte is Segwit version - // Only version 0 is currently supported; BIP173 BIP350 - if (dec.second[0] != 0) { - return resp; + auto segwitVersion = data[0]; + if (segwitVersion == 0) { + // v0 uses Bech32 (not M) + if (variant != Bech32::ChecksumVariant::Bech32) { + return resp; + } + } else { // segwitVersion >= 1 + // v1 uses Bech32M, BIP350 + if (variant != Bech32::ChecksumVariant::Bech32M) { + return resp; + } } - auto raw = fromRaw(dec.first, dec.second); - return std::make_tuple(raw.first, dec.first, raw.second); + auto raw = fromRaw(hrp, data); + return std::make_tuple(raw.first, hrp, raw.second); } std::string SegwitAddress::string() const { Data enc; enc.push_back(static_cast(witnessVersion)); Bech32::convertBits<8, 5, true>(enc, witnessProgram); - std::string result = Bech32::encode(hrp, enc); + Bech32::ChecksumVariant variant = Bech32::ChecksumVariant::Bech32; + if (witnessVersion== 0) { + variant = Bech32::ChecksumVariant::Bech32; + } else if (witnessVersion >= 1) { + variant = Bech32::ChecksumVariant::Bech32M; + } + std::string result = Bech32::encode(hrp, enc, variant); if (!std::get<2>(decode(result))) { return {}; } return result; } -std::pair SegwitAddress::fromRaw(const std::string& hrp, - const std::vector& data) { +std::pair SegwitAddress::fromRaw(const std::string& hrp, const Data& data) { auto resp = std::make_pair(SegwitAddress(), false); if (data.size() == 0) { return resp; diff --git a/src/Bitcoin/SegwitAddress.h b/src/Bitcoin/SegwitAddress.h index 3317d075615..68ad227e05a 100644 --- a/src/Bitcoin/SegwitAddress.h +++ b/src/Bitcoin/SegwitAddress.h @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2021 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -7,6 +7,7 @@ #pragma once #include "../PublicKey.h" +#include "../Data.h" #include #include @@ -28,7 +29,7 @@ class SegwitAddress { int witnessVersion; /// Witness program. - std::vector witnessProgram; + Data witnessProgram; /// Determines whether a string makes a valid Bech32 address. static bool isValid(const std::string& string); @@ -39,7 +40,7 @@ class SegwitAddress { /// Initializes a Bech32 address with a human-readable part, a witness /// version, and a witness program. - SegwitAddress(std::string hrp, int witver, std::vector witprog) + SegwitAddress(std::string hrp, int witver, Data witprog) : hrp(std::move(hrp)), witnessVersion(witver), witnessProgram(std::move(witprog)) {} /// Initializes a Bech32 address with a public key and a HRP prefix. @@ -56,8 +57,7 @@ class SegwitAddress { std::string string() const; /// Initializes a Bech32 address with raw data. - static std::pair fromRaw(const std::string& hrp, - const std::vector& data); + static std::pair fromRaw(const std::string& hrp, const Data& data); bool operator==(const SegwitAddress& rhs) const { return hrp == rhs.hrp && witnessVersion == rhs.witnessVersion && diff --git a/src/Cardano/AddressV3.cpp b/src/Cardano/AddressV3.cpp index 70ea2d4768b..6382dfde779 100644 --- a/src/Cardano/AddressV3.cpp +++ b/src/Cardano/AddressV3.cpp @@ -23,13 +23,13 @@ using namespace std; bool AddressV3::parseAndCheckV3(const std::string& addr, Discrimination& discrimination, Kind& kind, Data& key1, Data& key2) { try { auto bech = Bech32::decode(addr); - if (bech.second.size() == 0) { + if (std::get<1>(bech).size() == 0) { // empty Bech data return false; } // Bech bits conversion Data conv; - auto success = Bech32::convertBits<5, 8, false>(conv, bech.second); + auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(bech)); if (!success) { return false; } @@ -213,7 +213,7 @@ string AddressV3::string(const std::string& hrp) const { if (!Bech32::convertBits<8, 5, true>(bech, keys)) { return ""; } - return Bech32::encode(hrp, bech); + return Bech32::encode(hrp, bech, Bech32::ChecksumVariant::Bech32); } string AddressV3::stringBase32() const { diff --git a/tests/Bech32Tests.cpp b/tests/Bech32Tests.cpp new file mode 100644 index 00000000000..adb569ec333 --- /dev/null +++ b/tests/Bech32Tests.cpp @@ -0,0 +1,120 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "Bech32.h" +#include "HexCoding.h" + +#include + +using namespace TW; + +struct DecodeTestData { + std::string encoded; + bool isValid; + bool isValidM; + std::string hrp; + std::string dataHex; +}; + +std::vector testData = { + /// valid + {"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", true, false, "bnb", "080301090f051414170f04160200111d0314131b1c1b1e041a080d091f1a1f06"}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true, false, "bc", "010e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e160e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16"}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", false, true, "bc", "010e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e160e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16"}, + /// invalid + {"bnb1grpf0955h0ykzq3ar6nmum7y6gdfl6lxfn46h2", false, false, "", ""}, // 1-char diff + + /// valid test vectors (BIP173) + {"A12UEL5L", true, false, "a", ""}, + {"a12uel5l", true, false, "a", ""}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true, false, "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio", ""}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true, false, "abcdef", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", true, false, "1", "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", true, false, "split", "18171918161c01100b1d0819171d130d10171d16191c01100b03191d1b1903031d130b190303190d181d01190303190d"}, + {"?1ezyfcl", true, false, "?", ""}, + + /// valid bech32m test vectors (BIP350) + {"A1LQFN3A", false, true, "a", ""}, + {"a1lqfn3a", false, true, "a", ""}, + {"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", false, true, "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber1", ""}, + {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", false, true, "abcdef", "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}, + {"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", false, true, "1", "1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f"}, + {"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", false, true, "split", "18171918161c01100b1d0819171d130d10171d16191c01100b03191d1b1903031d130b190303190d181d01190303190d"}, + {"?1v759aa", false, true, "?", ""}, + + /// invalid test vectors (BIP173) + {"\x20""1nwldj5", false, false, "", ""}, // HRP character out of range + {"\x7F""1axkwrx", false, false, "", ""}, // HRP character out of range + {"\x80""1eym55h", false, false, "", ""}, // HRP character out of range + {"pzry9x0s0muk", false, false, "", ""}, // No separator character + {"1pzry9x0s0muk", false, false, "", ""}, // Empty HRP + {"x1b4n0q5v", false, false, "", ""}, // Invalid data character + {"lt1igcx5c0", false, false, "", ""}, // Invalid data character + {"li1dgmt3", false, false, "", ""}, // Too short checksum + {"de1lg7wt""\xFF", false, false, "", ""}, // Invalid character in checksum + {"A1G7SGD8", false, false, "", ""}, // checksum calculated with uppercase form of HRP + {"10a06t8", false, false, "", ""}, // empty HRP + {"1qzzfhee", false, false, "", ""}, // empty HRP + + /// invalid bech32m test vectors (BIP350) + {"\x20""1xj0phk", false, false, "", ""}, // HRP character out of range + {"\x7F""1g6xzxy", false, false, "", ""}, // HRP character out of range + {"\x80""1vctc34", false, false, "", ""}, // HRP character out of range + {"qyrz8wqd2c9m", false, false, "", ""}, // No separator character + {"1qyrz8wqd2c9m", false, false, "", ""}, // Empty HRP + {"y1b0jsk6g", false, false, "", ""}, // Invalid data character + {"lt1igcx5c0", false, false, "", ""}, // Invalid data character + {"in1muywd", false, false, "", ""}, // Too short checksum + {"mm1crxm3i", false, false, "", ""}, // Invalid character in checksum + {"au1s5cgom", false, false, "", ""}, // Invalid character in checksum + {"M1VUXWEZ", false, false, "", ""}, // checksum calculated with uppercase form of HRP + {"16plkw9", false, false, "", ""}, // empty HRP + {"1p2gdwpf", false, false, "", ""}, // empty HRP +}; + +TEST(Bech32, decode) { + for (auto& td: testData) { + ASSERT_FALSE(td.isValid && td.isValidM); // a string cannot be valid under both + + auto res = Bech32::decode(td.encoded); + if (!td.isValid && !td.isValidM) { + EXPECT_EQ(std::get<0>(res), ""); + EXPECT_EQ(std::get<1>(res).size(), 0); + } else { + if (td.isValid) { + EXPECT_EQ(std::get<2>(res), Bech32::ChecksumVariant::Bech32); + } else if (td.isValidM) { + EXPECT_EQ(std::get<2>(res), Bech32::ChecksumVariant::Bech32M); + } + EXPECT_EQ(std::get<0>(res), td.hrp) << "Wrong hrp for " << td.encoded; + EXPECT_EQ(hex(std::get<1>(res)), td.dataHex) << "Wrong data for " << td.encoded; + } + } +} + +TEST(Bech32, encode) { + for (auto& td: testData) { + if (!td.isValid) { + continue; + } + auto res = Bech32::encode(td.hrp, parse_hex(td.dataHex), Bech32::ChecksumVariant::Bech32); + std::string encodedLow = td.encoded; + std::transform(encodedLow.begin(), encodedLow.end(), encodedLow.begin(), [](unsigned char c){ return std::tolower(c); }); + EXPECT_EQ(res, encodedLow); + } +} + +TEST(Bech32, encodeM) { + for (auto& td: testData) { + if (!td.isValidM) { + continue; + } + auto res = Bech32::encode(td.hrp, parse_hex(td.dataHex), Bech32::ChecksumVariant::Bech32M); + std::string encodedLow = td.encoded; + std::transform(encodedLow.begin(), encodedLow.end(), encodedLow.begin(), [](unsigned char c){ return std::tolower(c); }); + EXPECT_EQ(res, encodedLow); + } +} diff --git a/tests/Bitcoin/SegwitAddressTests.cpp b/tests/Bitcoin/SegwitAddressTests.cpp index a919cd66878..256f5b59981 100644 --- a/tests/Bitcoin/SegwitAddressTests.cpp +++ b/tests/Bitcoin/SegwitAddressTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2017-2020 Trust Wallet. +// Copyright © 2017-2021 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -37,8 +37,7 @@ static const std::string invalid_checksum[] = { struct valid_address_data { std::string address; - size_t scriptPubKeyLen; - uint8_t scriptPubKey[42]; + std::string scriptPubKeyHex; }; struct invalid_address_data { @@ -47,46 +46,56 @@ struct invalid_address_data { size_t program_length; }; -static const struct valid_address_data valid_address[] = { - { - "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", - 22, { - 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, - 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 - } - }, - { - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", - 34, { - 0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04, - 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78, - 0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, - 0x62 - } - }, - { - "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", - 34, { - 0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, - 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5, - 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, - 0x33 - } - } +static const std::vector valid_address = { + /// test vectors from BIP173 + {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6"}, + {"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"}, + //{"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, + //{"BC1SW50QA3JX3S", "6002751e"}, + //{"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323"}, + {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + + /// test vectors from BIP350 + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, + {"BC1SW50QGDZ25J", "6002751e"}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", "5210751e76e8199196d454941c45d1b3a323"}, + {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + {"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + {"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}, }; static const std::vector invalid_address = { - "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", - "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", - "bc1rw5uspcuh", - "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", - "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", - "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", - "bc1gmk9yu", // empty data, no version + /// test vectors from BIP73 + //"tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", // Invalid human-readable part + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", // Invalid checksum + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", // Invalid witness version + "bc1rw5uspcuh", // Invalid program length + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", // Invalid program length + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", // Invalid program length for witness version 0 (per BIP141) + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", // Mixed case + "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", // zero padding of more than 4 bits + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", // Non-zero padding in 8-to-5 conversion + "bc1gmk9yu", // Empty data section + + /// test vectors from BIP350 + //"tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut", // Invalid human-readable part + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd", // Invalid checksum (Bech32 instead of Bech32m) + "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf", // Invalid checksum (Bech32 instead of Bech32m) + "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL", // Invalid checksum (Bech32 instead of Bech32m) + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh", // Invalid checksum (Bech32m instead of Bech32) + "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47", // Invalid checksum (Bech32m instead of Bech32) + "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4", // Invalid character in checksum + "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R", // Invalid witness version + "bc1pw5dgrnzv", // Invalid program length (1 byte) + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", // Invalid program length (41 bytes) + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", // Invalid program length for witness version 0 (per BIP141) + "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", // Mixed case + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf", // zero padding of more than 4 bits + "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j", // Non-zero padding in 8-to-5 conversion + "bc1gmk9yu", // Empty data section + + "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", // overall max length exceeded "bc1q9zpgru", // 1 byte data (only version byte) - "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", // version = 1 "BC1SW50QA3JX3S", // version = 16 "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", // version = 2 }; @@ -99,34 +108,28 @@ static const invalid_address_data invalid_address_enc[] = { {"bc", 16, 41}, }; -static std::vector segwit_scriptpubkey(int witver, const std::vector& witprog) { - std::vector ret; - ret.push_back(witver ? (0x80 | witver) : 0); +static Data segwit_scriptpubkey(int witver, const Data& witprog) { + Data ret; + ret.push_back(witver ? (0x50 + (witver & 0x1f)) : 0); ret.push_back(witprog.size()); ret.insert(ret.end(), witprog.begin(), witprog.end()); return ret; } bool case_insensitive_equal(const std::string& s1, const std::string& s2) { - size_t i = 0; - if (s1.size() != s2.size()) return false; - while (i < s1.size() && i < s2.size()) { - char c1 = s1[i]; - char c2 = s2[i]; - if (c1 >= 'A' && c1 <= 'Z') c1 = (c1 - 'A') + 'a'; - if (c2 >= 'A' && c2 <= 'Z') c2 = (c2 - 'A') + 'a'; - if (c1 != c2) return false; - ++i; - } - return true; + std::string s1l = s1; + std::transform(s1l.begin(), s1l.end(), s1l.begin(), [](unsigned char c){ return std::tolower(c); }); + std::string s2l = s2; + std::transform(s2l.begin(), s2l.end(), s2l.begin(), [](unsigned char c){ return std::tolower(c); }); + return s1l == s2l; } TEST(SegwitAddress, ValidChecksum) { for (auto i = 0; i < sizeof(valid_checksum) / sizeof(valid_checksum[0]); ++i) { auto dec = Bech32::decode(valid_checksum[i]); - ASSERT_FALSE(dec.first.empty()); + ASSERT_FALSE(std::get<0>(dec).empty()); - auto recode = Bech32::encode(dec.first, dec.second); + auto recode = Bech32::encode(std::get<0>(dec), std::get<1>(dec), Bech32::ChecksumVariant::Bech32); ASSERT_FALSE(recode.empty()); ASSERT_TRUE(case_insensitive_equal(recode, valid_checksum[i])); @@ -136,23 +139,24 @@ TEST(SegwitAddress, ValidChecksum) { TEST(SegwitAddress, InvalidChecksum) { for (auto i = 0; i < sizeof(invalid_checksum) / sizeof(invalid_checksum[0]); ++i) { auto dec = Bech32::decode(invalid_checksum[i]); - EXPECT_TRUE(dec.first.empty() && dec.second.empty()); + EXPECT_TRUE(std::get<0>(dec).empty() && std::get<1>(dec).empty()); } } TEST(SegwitAddress, ValidAddress) { - for (auto i = 0; i < sizeof(valid_address) / sizeof(valid_address[0]); ++i) { - auto dec = SegwitAddress::decode(valid_address[i].address); - ASSERT_TRUE(std::get<2>(dec)) << "Valid address could not be decoded " << valid_address[i].address; - ASSERT_EQ(std::get<1>(dec).length(), 2); // hrp - - std::vector spk = segwit_scriptpubkey(std::get<0>(dec).witnessVersion, std::get<0>(dec).witnessProgram); - ASSERT_TRUE(spk.size() == valid_address[i].scriptPubKeyLen && std::memcmp(&spk[0], valid_address[i].scriptPubKey, spk.size()) == 0); + for (auto& td: valid_address) { + auto dec = SegwitAddress::decode(td.address); + EXPECT_TRUE(std::get<2>(dec)) << "Valid address could not be decoded " << td.address; + EXPECT_TRUE(std::get<0>(dec).witnessProgram.size() > 0) << "Empty decoded address data for " << td.address; + EXPECT_EQ(std::get<1>(dec).length(), 2); // hrp + // recode std::string recode = std::get<0>(dec).string(); - ASSERT_FALSE(recode.empty()); + EXPECT_FALSE(recode.empty()) << "Recode failed for " << td.address; + EXPECT_TRUE(case_insensitive_equal(td.address, recode)); - ASSERT_TRUE(case_insensitive_equal(valid_address[i].address, recode)); + Data spk = segwit_scriptpubkey(std::get<0>(dec).witnessVersion, std::get<0>(dec).witnessProgram); + EXPECT_EQ(hex(spk), td.scriptPubKeyHex); } } @@ -165,7 +169,7 @@ TEST(SegwitAddress, InvalidAddress) { TEST(SegwitAddress, InvalidAddressEncoding) { for (auto i = 0; i < sizeof(invalid_address_enc) / sizeof(invalid_address_enc[0]); ++i) { - auto address = SegwitAddress(invalid_address_enc[i].hrp, invalid_address_enc[i].version, std::vector(invalid_address_enc[i].program_length, 0)); + auto address = SegwitAddress(invalid_address_enc[i].hrp, invalid_address_enc[i].version, Data(invalid_address_enc[i].program_length, 0)); std::string code = address.string(); EXPECT_TRUE(code.empty()); }