diff --git a/src/crypto/common.h b/src/crypto/common.h --- a/src/crypto/common.h +++ b/src/crypto/common.h @@ -47,6 +47,12 @@ memcpy(ptr, (char *)&v, 8); } +uint16_t static inline ReadBE16(const uint8_t *ptr) { + uint16_t x; + memcpy((char *)&x, ptr, 2); + return be16toh(x); +} + static inline uint32_t ReadBE32(const uint8_t *ptr) { uint32_t x; memcpy((char *)&x, ptr, 4); diff --git a/src/netaddress.h b/src/netaddress.h --- a/src/netaddress.h +++ b/src/netaddress.h @@ -51,9 +51,15 @@ /// IPv6 NET_IPV6, - /// TORv2 + /// TOR (v2 or v3) NET_ONION, + /// I2P + NET_I2P, + + /// CJDNS + NET_CJDNS, + /// A set of addresses that represent the hash of a string or FQDN. We use /// them in CAddrMan to keep track of which DNS seeds were used. NET_INTERNAL, @@ -92,6 +98,16 @@ /// Size of TORv2 address (in bytes). static constexpr size_t ADDR_TORV2_SIZE = 10; +/// Size of TORv3 address (in bytes). This is the length of just the address +/// as used in BIP155, without the checksum and the version byte. +static constexpr size_t ADDR_TORV3_SIZE = 32; + +/// Size of I2P address (in bytes). +static constexpr size_t ADDR_I2P_SIZE = 32; + +/// Size of CJDNS address (in bytes). +static constexpr size_t ADDR_CJDNS_SIZE = 16; + /// Size of "internal" (NET_INTERNAL) address (in bytes). static constexpr size_t ADDR_INTERNAL_SIZE = 10; @@ -170,6 +186,8 @@ // IPv6 Hurricane Electric - https://he.net (2001:0470::/36) bool IsHeNet() const; bool IsTor() const; + bool IsI2P() const; + bool IsCJDNS() const; bool IsLocal() const; bool IsRoutable() const; bool IsInternal() const; @@ -238,6 +256,9 @@ IPV4 = 1, IPV6 = 2, TORV2 = 3, + TORV3 = 4, + I2P = 5, + CJDNS = 6, }; /** @@ -266,7 +287,7 @@ * @retval false not recognised (from future?) and should be silently * ignored * @throws std::ios_base::failure if the network is one of the BIP155 - * founding networks recognized by this software (id 1..3) and has wrong + * founding networks recognized by this software (id 1..6) with wrong * address size. */ bool SetNetFromBIP155Network(uint8_t possible_bip155_net, @@ -274,7 +295,6 @@ /** * Serialize in pre-ADDRv2/BIP155 format to an array. - * Some addresses (e.g. TORv3) cannot be serialized in pre-BIP155 format. */ void SerializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE]) const { size_t prefix_size; @@ -291,6 +311,9 @@ memcpy(arr + prefix_size, m_addr.data(), m_addr.size()); return; case NET_ONION: + if (m_addr.size() == ADDR_TORV3_SIZE) { + break; + } prefix_size = sizeof(TORV2_IN_IPV6_PREFIX); assert(prefix_size + m_addr.size() == sizeof(arr)); memcpy(arr, TORV2_IN_IPV6_PREFIX.data(), prefix_size); @@ -302,17 +325,21 @@ memcpy(arr, INTERNAL_IN_IPV6_PREFIX.data(), prefix_size); memcpy(arr + prefix_size, m_addr.data(), m_addr.size()); return; + case NET_I2P: + break; + case NET_CJDNS: + break; case NET_UNROUTABLE: case NET_MAX: assert(false); } // no default case, so the compiler can warn about missing cases - assert(false); + // Serialize TORv3, I2P and CJDNS as all-zeros. + memset(arr, 0x0, V1_SERIALIZATION_SIZE); } /** * Serialize in pre-ADDRv2/BIP155 format to a stream. - * Some addresses (e.g. TORv3) cannot be serialized in pre-BIP155 format. */ template void SerializeV1Stream(Stream &s) const { uint8_t serialized[V1_SERIALIZATION_SIZE]; diff --git a/src/netaddress.cpp b/src/netaddress.cpp --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -5,7 +5,10 @@ #include +#include +#include #include +#include #include #include #include @@ -29,7 +32,18 @@ case NET_IPV6: return BIP155Network::IPV6; case NET_ONION: - return BIP155Network::TORV2; + switch (m_addr.size()) { + case ADDR_TORV2_SIZE: + return BIP155Network::TORV2; + case ADDR_TORV3_SIZE: + return BIP155Network::TORV3; + default: + assert(false); + } + case NET_I2P: + return BIP155Network::I2P; + case NET_CJDNS: + return BIP155Network::CJDNS; case NET_INTERNAL: // should have been handled before calling this function case NET_UNROUTABLE: @@ -69,6 +83,30 @@ throw std::ios_base::failure( strprintf("BIP155 TORv2 address with length %u (should be %u)", address_size, ADDR_TORV2_SIZE)); + case BIP155Network::TORV3: + if (address_size == ADDR_TORV3_SIZE) { + m_net = NET_ONION; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 TORv3 address with length %u (should be %u)", + address_size, ADDR_TORV3_SIZE)); + case BIP155Network::I2P: + if (address_size == ADDR_I2P_SIZE) { + m_net = NET_I2P; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 I2P address with length %u (should be %u)", + address_size, ADDR_I2P_SIZE)); + case BIP155Network::CJDNS: + if (address_size == ADDR_CJDNS_SIZE) { + m_net = NET_CJDNS; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 CJDNS address with length %u (should be %u)", + address_size, ADDR_CJDNS_SIZE)); } // Don't throw on addresses with unknown network ids (maybe from the @@ -94,7 +132,14 @@ assert(ipIn.m_addr.size() == ADDR_IPV6_SIZE); break; case NET_ONION: - assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE); + assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE || + ipIn.m_addr.size() == ADDR_TORV3_SIZE); + break; + case NET_I2P: + assert(ipIn.m_addr.size() == ADDR_I2P_SIZE); + break; + case NET_CJDNS: + assert(ipIn.m_addr.size() == ADDR_CJDNS_SIZE); break; case NET_INTERNAL: assert(ipIn.m_addr.size() == ADDR_INTERNAL_SIZE); @@ -151,25 +196,85 @@ return true; } +namespace torv3 { +// https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2135 +static constexpr size_t CHECKSUM_LEN = 2; +static const uint8_t VERSION[] = {3}; +static constexpr size_t TOTAL_LEN = + ADDR_TORV3_SIZE + CHECKSUM_LEN + sizeof(VERSION); + +static void Checksum(Span addr_pubkey, + uint8_t (&checksum)[CHECKSUM_LEN]) { + // TORv3 CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2] + static const uint8_t prefix[] = ".onion checksum"; + static constexpr size_t prefix_len = 15; + + SHA3_256 hasher; + + hasher.Write(MakeSpan(prefix).first(prefix_len)); + hasher.Write(addr_pubkey); + hasher.Write(VERSION); + + uint8_t checksum_full[SHA3_256::OUTPUT_SIZE]; + + hasher.Finalize(checksum_full); + + memcpy(checksum, checksum_full, sizeof(checksum)); +} + +}; // namespace torv3 + /** - * Parse a TORv2 address and set this object to it. + * Parse a TOR address and set this object to it. * * @returns Whether or not the operation was successful. * * @see CNetAddr::IsTor() */ -bool CNetAddr::SetSpecial(const std::string &strName) { - if (strName.size() > 6 && - strName.substr(strName.size() - 6, 6) == ".onion") { - std::vector vchAddr = - DecodeBase32(strName.substr(0, strName.size() - 6).c_str()); - if (vchAddr.size() != ADDR_TORV2_SIZE) { - return false; +bool CNetAddr::SetSpecial(const std::string &str) { + static const char *suffix{".onion"}; + static constexpr size_t suffix_len{6}; + + if (!ValidAsCString(str) || str.size() <= suffix_len || + str.substr(str.size() - suffix_len) != suffix) { + return false; + } + + bool invalid; + const auto &input = + DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid); + + if (invalid) { + return false; + } + + switch (input.size()) { + case ADDR_TORV2_SIZE: + m_net = NET_ONION; + m_addr.assign(input.begin(), input.end()); + return true; + case torv3::TOTAL_LEN: { + Span input_pubkey{input.data(), ADDR_TORV3_SIZE}; + Span input_checksum{input.data() + ADDR_TORV3_SIZE, + torv3::CHECKSUM_LEN}; + Span input_version{input.data() + ADDR_TORV3_SIZE + + torv3::CHECKSUM_LEN, + sizeof(torv3::VERSION)}; + + uint8_t calculated_checksum[torv3::CHECKSUM_LEN]; + torv3::Checksum(input_pubkey, calculated_checksum); + + if (input_checksum != calculated_checksum || + input_version != torv3::VERSION) { + return false; + } + + m_net = NET_ONION; + m_addr.assign(input_pubkey.begin(), input_pubkey.end()); + return true; } - m_net = NET_ONION; - m_addr.assign(vchAddr.begin(), vchAddr.end()); - return true; } + return false; } @@ -282,8 +387,7 @@ } /** - * @returns Whether or not this is a dummy address that maps an onion address - * into IPv6. + * Check whether this object represents a TOR address. * * @see CNetAddr::SetSpecial(const std::string &) */ @@ -291,6 +395,20 @@ return m_net == NET_ONION; } +/** + * Check whether this object represents an I2P address. + */ +bool CNetAddr::IsI2P() const { + return m_net == NET_I2P; +} + +/** + * Check whether this object represents a CJDNS address. + */ +bool CNetAddr::IsCJDNS() const { + return m_net == NET_CJDNS; +} + bool CNetAddr::IsLocal() const { // IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8) if (IsIPv4() && (m_addr[0] == 127 || m_addr[0] == 0)) { @@ -391,33 +509,79 @@ return m_net; } +static std::string IPv6ToString(Span a) { + assert(a.size() == ADDR_IPV6_SIZE); + // clang-format off + return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", + ReadBE16(&a[0]), + ReadBE16(&a[2]), + ReadBE16(&a[4]), + ReadBE16(&a[6]), + ReadBE16(&a[8]), + ReadBE16(&a[10]), + ReadBE16(&a[12]), + ReadBE16(&a[14])); + // clang-format on +} + std::string CNetAddr::ToStringIP() const { - if (IsTor()) { - return EncodeBase32(m_addr) + ".onion"; - } - if (IsInternal()) { - return EncodeBase32(m_addr) + ".internal"; - } - CService serv(*this, 0); - struct sockaddr_storage sockaddr; - socklen_t socklen = sizeof(sockaddr); - if (serv.GetSockAddr((struct sockaddr *)&sockaddr, &socklen)) { - char name[1025] = ""; - if (!getnameinfo((const struct sockaddr *)&sockaddr, socklen, name, - sizeof(name), nullptr, 0, NI_NUMERICHOST)) { - return std::string(name); + switch (m_net) { + case NET_IPV4: + case NET_IPV6: { + CService serv(*this, 0); + struct sockaddr_storage sockaddr; + socklen_t socklen = sizeof(sockaddr); + if (serv.GetSockAddr((struct sockaddr *)&sockaddr, &socklen)) { + char name[1025] = ""; + if (!getnameinfo((const struct sockaddr *)&sockaddr, socklen, + name, sizeof(name), nullptr, 0, + NI_NUMERICHOST)) { + return std::string(name); + } + } + if (m_net == NET_IPV4) { + return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], + m_addr[3]); + } + return IPv6ToString(m_addr); } - } - if (IsIPv4()) { - return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], - m_addr[3]); - } - assert(IsIPv6()); - return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", m_addr[0] << 8 | m_addr[1], - m_addr[2] << 8 | m_addr[3], m_addr[4] << 8 | m_addr[5], - m_addr[6] << 8 | m_addr[7], m_addr[8] << 8 | m_addr[9], - m_addr[10] << 8 | m_addr[11], m_addr[12] << 8 | m_addr[13], - m_addr[14] << 8 | m_addr[15]); + case NET_ONION: + switch (m_addr.size()) { + case ADDR_TORV2_SIZE: + return EncodeBase32(m_addr) + ".onion"; + case ADDR_TORV3_SIZE: { + uint8_t checksum[torv3::CHECKSUM_LEN]; + torv3::Checksum(m_addr, checksum); + + // TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + // + ".onion" + prevector address{m_addr.begin(), + m_addr.end()}; + address.insert(address.end(), checksum, + checksum + torv3::CHECKSUM_LEN); + address.insert(address.end(), torv3::VERSION, + torv3::VERSION + sizeof(torv3::VERSION)); + + return EncodeBase32(address) + ".onion"; + } + default: + assert(false); + } + case NET_I2P: + return EncodeBase32(m_addr, false /* don't pad with = */) + + ".b32.i2p"; + case NET_CJDNS: + return IPv6ToString(m_addr); + case NET_INTERNAL: + return EncodeBase32(m_addr) + ".internal"; + case NET_UNROUTABLE: + // m_net is never and should not be set to NET_UNROUTABLE + case NET_MAX: + // m_net is never and should not be set to NET_MAX + assert(false); + } // no default case, so the compiler can warn about missing cases + + assert(false); } std::string CNetAddr::ToString() const { @@ -494,20 +658,21 @@ } uint32_t CNetAddr::GetNetClass() const { - uint32_t net_class = NET_IPV6; - if (IsLocal()) { - net_class = 255; - } + // Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers + // expect that. + + // Check for "internal" first because such addresses are also !IsRoutable() + // and we don't want to return NET_UNROUTABLE in that case. if (IsInternal()) { - net_class = NET_INTERNAL; - } else if (!IsRoutable()) { - net_class = NET_UNROUTABLE; - } else if (HasLinkedIPv4()) { - net_class = NET_IPV4; - } else if (IsTor()) { - net_class = NET_ONION; + return NET_INTERNAL; + } + if (!IsRoutable()) { + return NET_UNROUTABLE; } - return net_class; + if (HasLinkedIPv4()) { + return NET_IPV4; + } + return m_net; } uint32_t CNetAddr::GetMappedAS(const std::vector &asmap) const { @@ -586,7 +751,7 @@ vchRet.push_back((ipv4 >> 24) & 0xFF); vchRet.push_back((ipv4 >> 16) & 0xFF); return vchRet; - } else if (IsTor()) { + } else if (IsTor() || IsI2P() || IsCJDNS()) { nBits = 4; } else if (IsHeNet()) { // for he.net, use /36 groups @@ -832,7 +997,7 @@ } std::string CService::ToStringIPPort() const { - if (IsIPv4() || IsTor() || IsInternal()) { + if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) { return ToStringIP() + ":" + ToStringPort(); } else { return "[" + ToStringIP() + "]:" + ToStringPort(); diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -16,9 +16,13 @@ static const std::string vstrOut[] = { "", "my======", "mzxq====", "mzxw6===", "mzxw6yq=", "mzxw6ytb", "mzxw6ytboi======"}; + static const std::string vstrOutNoPadding[] = { + "", "my", "mzxq", "mzxw6", "mzxw6yq", "mzxw6ytb", "mzxw6ytboi"}; for (unsigned int i = 0; i < sizeof(vstrIn) / sizeof(vstrIn[0]); i++) { std::string strEnc = EncodeBase32(vstrIn[i]); BOOST_CHECK_EQUAL(strEnc, vstrOut[i]); + strEnc = EncodeBase32(vstrIn[i], false); + BOOST_CHECK_EQUAL(strEnc, vstrOutNoPadding[i]); std::string strDec = DecodeBase32(vstrOut[i]); BOOST_CHECK_EQUAL(strDec, vstrIn[i]); } diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -346,13 +346,42 @@ "1122:3344:5566:7788:9900:aabb:ccdd:eeff"); // TORv2 - addr.SetSpecial("6hzph5hv6337r6p2.onion"); + BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion")); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); + // TORv3 + const char *torv3_addr = + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"; + BOOST_REQUIRE(addr.SetSpecial(torv3_addr)); + BOOST_REQUIRE(addr.IsValid()); + BOOST_REQUIRE(addr.IsTor()); + + BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); + + // TORv3, broken, with wrong checksum + BOOST_CHECK(!addr.SetSpecial( + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.onion")); + + // TORv3, broken, with wrong version + BOOST_CHECK(!addr.SetSpecial( + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscrye.onion")); + + // TORv3, malicious + BOOST_CHECK(!addr.SetSpecial(std::string{ + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd\0wtf.onion", + 66})); + + // TOR, bogus length + BOOST_CHECK(!addr.SetSpecial(std::string{"mfrggzak.onion"})); + + // TOR, invalid base32 + BOOST_CHECK(!addr.SetSpecial(std::string{"mf*g zak.onion"})); + // Internal addr.SetInternal("esffpp"); // "internal" is considered invalid @@ -361,6 +390,9 @@ BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal"); + + // Totally bogus + BOOST_CHECK(!addr.SetSpecial("totally bogus")); } BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v1) { @@ -387,6 +419,12 @@ BOOST_CHECK_EQUAL(HexStr(s), "fd87d87eeb43f1f2f3f4f5f6f7f8f9fa"); s.clear(); + BOOST_REQUIRE(addr.SetSpecial( + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion")); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000"); + s.clear(); + addr.SetInternal("a"); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "fd6b88c08724ca978112ca1bbdcafac2"); @@ -420,6 +458,14 @@ BOOST_CHECK_EQUAL(HexStr(s), "030af1f2f3f4f5f6f7f8f9fa"); s.clear(); + BOOST_REQUIRE(addr.SetSpecial( + "kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion")); + s << addr; + BOOST_CHECK_EQUAL( + HexStr(s), + "042053cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88"); + s.clear(); + BOOST_REQUIRE(addr.SetInternal("a")); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "0210fd6b88c08724ca978112ca1bbdcafac2"); @@ -539,6 +585,74 @@ BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); + // Valid TORv3. + s << MakeSpan(ParseHex("04" // network type (TORv3) + "20" // address length + "79bcc625184b05194975c28b66b66b04" // address + "69f7f6556fb1ac3189a79b40dda32f1f")); + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK(addr.IsTor()); + BOOST_CHECK_EQUAL( + addr.ToString(), + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"); + BOOST_REQUIRE(s.empty()); + + // Invalid TORv3, with bogus length. + s << MakeSpan(ParseHex("04" // network type (TORv3) + "00" // address length + "00" // address + )); + BOOST_CHECK_EXCEPTION( + s >> addr, std::ios_base::failure, + HasReason("BIP155 TORv3 address with length 0 (should be 32)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Valid I2P. + s << MakeSpan(ParseHex("05" // network type (I2P) + "20" // address length + "a2894dabaec08c0051a481a6dac88b64" // address + "f98232ae42d4b6fd2fa81952dfe36a87")); + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK_EQUAL( + addr.ToString(), + "ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p"); + BOOST_REQUIRE(s.empty()); + + // Invalid I2P, with bogus length. + s << MakeSpan(ParseHex("05" // network type (I2P) + "03" // address length + "00" // address + )); + BOOST_CHECK_EXCEPTION( + s >> addr, std::ios_base::failure, + HasReason("BIP155 I2P address with length 3 (should be 32)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Valid CJDNS. + s << MakeSpan(ParseHex("06" // network type (CJDNS) + "10" // address length + "fc000001000200030004000500060007" // address + )); + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK_EQUAL(addr.ToString(), "fc00:1:2:3:4:5:6:7"); + BOOST_REQUIRE(s.empty()); + + // Invalid CJDNS, with bogus length. + s << MakeSpan(ParseHex("06" // network type (CJDNS) + "01" // address length + "00" // address + )); + BOOST_CHECK_EXCEPTION( + s >> addr, std::ios_base::failure, + HasReason("BIP155 CJDNS address with length 1 (should be 16)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + // Unknown, with extreme length. s << MakeSpan( ParseHex("aa" // network type (unknown) diff --git a/src/util/strencodings.h b/src/util/strencodings.h --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -59,8 +59,20 @@ std::string EncodeBase64(const std::string &str); std::vector DecodeBase32(const char *p, bool *pf_invalid = nullptr); std::string DecodeBase32(const std::string &str, bool *pf_invalid = nullptr); -std::string EncodeBase32(Span input); -std::string EncodeBase32(const std::string &str); + +/** + * Base32 encode. + * If `pad` is true, then the output will be padded with '=' so that its length + * is a multiple of 8. + */ +std::string EncodeBase32(Span input, bool pad = true); + +/** + * Base32 encode. + * If `pad` is true, then the output will be padded with '=' so that its length + * is a multiple of 8. + */ +std::string EncodeBase32(const std::string &str, bool pad = true); void SplitHostPort(std::string in, int &portOut, std::string &hostOut); int64_t atoi64(const std::string &str); diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -214,21 +214,23 @@ return std::string((const char *)vchRet.data(), vchRet.size()); } -std::string EncodeBase32(Span input) { +std::string EncodeBase32(Span input, bool pad) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; std::string str; str.reserve(((input.size() + 4) / 5) * 8); ConvertBits<8, 5, true>([&](int v) { str += pbase32[v]; }, input.begin(), input.end()); - while (str.size() % 8) { - str += '='; + if (pad) { + while (str.size() % 8) { + str += '='; + } } return str; } -std::string EncodeBase32(const std::string &str) { - return EncodeBase32(MakeUCharSpan(str)); +std::string EncodeBase32(const std::string &str, bool pad) { + return EncodeBase32(MakeUCharSpan(str), pad); } std::vector DecodeBase32(const char *p, bool *pf_invalid) {