// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "cashaddrenc.h" #include "cashaddr.h" #include "chainparams.h" #include "pubkey.h" #include "script/script.h" #include "utilstrencodings.h" #include #include namespace { // Convert the data part to a 5 bit representation. template std::vector PackAddrData(const T &id, uint8_t type) { uint8_t version_byte(type << 3); size_t 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; // Reserve the number of bytes required for a 5-bit packed version of a // hash, with version byte. Add half a byte(4) so integer math provides // the next multiple-of-5 that would fit all the data. converted.reserve(((size + 1) * 8 + 4) / 5); ConvertBits<8, 5, true>(converted, std::begin(data), std::end(data)); return converted; } // Implements encoding of CTxDestination using cashaddr. class CashAddrEncoder : public boost::static_visitor { public: CashAddrEncoder(const CChainParams &p) : params(p) {} std::string operator()(const CKeyID &id) const { 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); return cashaddr::Encode(params.CashAddrPrefix(), data); } std::string operator()(const CNoDestination &) const { return ""; } private: const CChainParams ¶ms; }; } // anon ns std::string EncodeCashAddr(const CTxDestination &dst, const CChainParams ¶ms) { return boost::apply_visitor(CashAddrEncoder(params), dst); } CTxDestination DecodeCashAddr(const std::string &addr, const CChainParams ¶ms) { CashAddrContent content = DecodeCashAddrContent(addr, params); if (content.hash.size() == 0) { return CNoDestination{}; } return DecodeCashAddrDestination(content); } CashAddrContent DecodeCashAddrContent(const std::string &addr, const CChainParams ¶ms) { std::string prefix; std::vector payload; std::tie(prefix, payload) = cashaddr::Decode(addr, params.CashAddrPrefix()); if (prefix != params.CashAddrPrefix()) { return {}; } if (payload.empty()) { return {}; } // Check that the padding is zero. size_t extrabits = payload.size() * 5 % 8; if (extrabits >= 5) { // We have more padding than allowed. return {}; } uint8_t last = payload.back(); uint8_t mask = (1 << extrabits) - 1; if (last & mask) { // We have non zero bits as padding. return {}; } std::vector data; data.reserve(payload.size() * 5 / 8); ConvertBits<5, 8, false>(data, begin(payload), end(payload)); // Decode type and size from the version. uint8_t version = data[0]; if (version & 0x80) { // First bit is reserved. return {}; } auto type = CashAddrType((version >> 3) & 0x1f); uint32_t hash_size = 20 + 4 * (version & 0x03); if (version & 0x04) { hash_size *= 2; } // Check that we decoded the exact number of bytes we expected. if (data.size() != hash_size + 1) { return {}; } // Pop the version. data.erase(data.begin()); return {type, std::move(data)}; } CTxDestination DecodeCashAddrDestination(const CashAddrContent &content) { if (content.hash.size() != 20) { // Only 20 bytes hash are supported now. return CNoDestination{}; } uint160 hash; std::copy(begin(content.hash), end(content.hash), hash.begin()); switch (content.type) { case PUBKEY_TYPE: return CKeyID(hash); case SCRIPT_TYPE: return CScriptID(hash); default: return CNoDestination{}; } } // PackCashAddrContent allows for testing PackAddrData in unittests due to // template definitions. std::vector PackCashAddrContent(const CashAddrContent &content) { return PackAddrData(content.hash, content.type); }