diff --git a/src/cashaddrenc.h b/src/cashaddrenc.h --- a/src/cashaddrenc.h +++ b/src/cashaddrenc.h @@ -11,10 +11,12 @@ class CChainParams; +enum AddrType : uint8_t { PUBKEY_TYPE = 0, SCRIPT_TYPE = 1 }; + std::string EncodeCashAddr(const CTxDestination &, const CChainParams &); struct CashAddrContent { - uint8_t type; + AddrType type; std::vector hash; }; diff --git a/src/cashaddrenc.cpp b/src/cashaddrenc.cpp --- a/src/cashaddrenc.cpp +++ b/src/cashaddrenc.cpp @@ -12,30 +12,54 @@ #include -const uint8_t PUBKEY_TYPE = 0; -const uint8_t SCRIPT_TYPE = 1; - // Size of data-part in a pubkey/script cash address. // Consists of: 8 bits version + 160 bits hash. -const size_t CASHADDR_GROUPED_SIZE = 34; /* 5 bit representation */ -const size_t CASHADDR_BYTES = 21; /* 8 bit representation */ +const size_t CASHADDR_BYTES = 21; /* 8 bit representation */ namespace { // Convert the data part to a 5 bit representation. template -std::vector PackAddrData(const T &id, uint8_t type, - size_t expectedSize) { - std::vector data = {uint8_t(type << 3)}; - data.insert(data.end(), id.begin(), id.end()); +std::vector PackAddrData(const T &id, uint8_t type) { + uint8_t version_byte = uint8_t(type << 3); + auto size = id.size(); + uint8_t encoded_size = 0; + switch (size * 8) { + case 160: + encoded_size = 0; + break; + case 192: + encoded_size = 1; + break; + case 224: + encoded_size = 2; + break; + case 256: + encoded_size = 3; + break; + case 320: + encoded_size = 4; + break; + case 384: + encoded_size = 5; + break; + case 448: + encoded_size = 6; + break; + case 512: + encoded_size = 7; + break; + default: + throw std::runtime_error( + "Error packing cashaddr: invalid address length"); + } + version_byte |= encoded_size; + std::vector data = {version_byte}; + data.insert(data.end(), std::begin(id), std::end(id)); std::vector converted; - converted.reserve(expectedSize); - ConvertBits<8, 5, true>(converted, begin(data), end(data)); - - if (converted.size() != expectedSize) { - throw std::runtime_error("Error packing cashaddr"); - } + converted.reserve(((size + 1) * 8 + 4) / 5); + ConvertBits<8, 5, true>(converted, std::begin(data), std::end(data)); return converted; } @@ -46,14 +70,12 @@ CashAddrEncoder(const CChainParams &p) : params(p) {} std::string operator()(const CKeyID &id) const { - std::vector data = - PackAddrData(id, PUBKEY_TYPE, CASHADDR_GROUPED_SIZE); + std::vector data = PackAddrData(id, PUBKEY_TYPE); return cashaddr::Encode(params.CashAddrPrefix(), data); } std::string operator()(const CScriptID &id) const { - std::vector data = - PackAddrData(id, SCRIPT_TYPE, CASHADDR_GROUPED_SIZE); + std::vector data = PackAddrData(id, SCRIPT_TYPE); return cashaddr::Encode(params.CashAddrPrefix(), data); } @@ -133,7 +155,7 @@ // Pop the version. data.erase(data.begin()); - return {type, std::move(data)}; + return {AddrType(type), std::move(data)}; } CTxDestination DecodeCashAddrDestination(const CashAddrContent &content) { @@ -154,3 +176,9 @@ return CNoDestination{}; } } + +// PackCashAddrContent allows for testing PackAddrData in unittests due to +// template definitinos. +std::vector PackCashAddrContent(const CashAddrContent &content) { + return PackAddrData(content.hash, content.type); +} diff --git a/src/test/cashaddrenc_tests.cpp b/src/test/cashaddrenc_tests.cpp --- a/src/test/cashaddrenc_tests.cpp +++ b/src/test/cashaddrenc_tests.cpp @@ -11,6 +11,9 @@ #include +// Declare here so we can use it in tests. Defined in cashaddrenc.cpp +std::vector PackCashAddrContent(const CashAddrContent &content); + namespace { std::vector GetNetworks() { @@ -26,6 +29,17 @@ return n; } +std::vector insecure_GetRandomByteArray(FastRandomContext &rand, + size_t n) { + std::vector out; + out.reserve(n); + + for (size_t i = 0; i < n; i++) { + out.push_back(static_cast(rand.rand32())); + } + return out; +} + class DstTypeChecker : public boost::static_visitor { public: void operator()(const CKeyID &id) { isKey = true; } @@ -49,22 +63,25 @@ bool isKey; bool isScript; }; - } // anon ns BOOST_FIXTURE_TEST_SUITE(cashaddrenc_tests, BasicTestingSetup) -BOOST_AUTO_TEST_CASE(encode_decode) { - std::vector toTest = {CNoDestination{}, - CKeyID(uint160S("badf00d")), - CScriptID(uint160S("f00dbad"))}; +BOOST_AUTO_TEST_CASE(encode_decode_all_sizes) { + FastRandomContext rand(true); + const std::array valid_sizes = { + {160, 192, 224, 256, 320, 384, 448, 512}}; + const CChainParams ¶ms = Params(CBaseChainParams::MAIN); - for (auto dst : toTest) { - for (auto net : GetNetworks()) { - std::string encoded = EncodeCashAddr(dst, Params(net)); - CTxDestination decoded = DecodeCashAddr(encoded, Params(net)); - BOOST_CHECK(dst == decoded); - } + for (auto bits : valid_sizes) { + auto data = insecure_GetRandomByteArray(rand, bits / 8); + CashAddrContent content = {PUBKEY_TYPE, data}; + auto packed_data = PackCashAddrContent(content); + auto address = cashaddr::Encode(params.CashAddrPrefix(), packed_data); + CashAddrContent decoded = DecodeCashAddrContent(address, params); + BOOST_CHECK_EQUAL_COLLECTIONS( + std::begin(content.hash), std::end(content.hash), + std::begin(decoded.hash), std::end(decoded.hash)); } } @@ -126,7 +143,7 @@ data.push_back(1); } - BOOST_CHECK_EQUAL(data.size(), 34); + BOOST_CHECK_EQUAL(data.size(), 34UL); const CTxDestination nodst = CNoDestination{}; const CChainParams params = Params(CBaseChainParams::MAIN); @@ -161,14 +178,14 @@ auto content = DecodeCashAddrContent( cashaddr::Encode(params.CashAddrPrefix(), data), params); BOOST_CHECK_EQUAL(content.type, v); - BOOST_CHECK_EQUAL(content.hash.size(), 20); + BOOST_CHECK_EQUAL(content.hash.size(), 20UL); // Check that using the reserved bit result in a failure. data[0] |= 0x10; content = DecodeCashAddrContent( cashaddr::Encode(params.CashAddrPrefix(), data), params); BOOST_CHECK_EQUAL(content.type, 0); - BOOST_CHECK_EQUAL(content.hash.size(), 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); } } @@ -209,7 +226,7 @@ cashaddr::Encode(params.CashAddrPrefix(), data), params); BOOST_CHECK_EQUAL(content.type, 0); - BOOST_CHECK_EQUAL(content.hash.size(), 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); data.pop_back(); data.pop_back(); @@ -217,7 +234,7 @@ cashaddr::Encode(params.CashAddrPrefix(), data), params); BOOST_CHECK_EQUAL(content.type, 0); - BOOST_CHECK_EQUAL(content.hash.size(), 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); } }