From 931e3e73d8cddd0f98552b9331a449e08dffebca Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Tue, 25 May 2021 11:00:04 +0530 Subject: [PATCH 1/7] partial #8149: import OverrideStream from "BIP144: Serialization, hashes, relay (sender side)" --- src/streams.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/streams.h b/src/streams.h index 21db4ba6dd53..f8b760b2d506 100644 --- a/src/streams.h +++ b/src/streams.h @@ -168,6 +168,39 @@ class VectorReader } } }; +template +class OverrideStream +{ + Stream* stream; +public: + const int nType; + const int nVersion; + + OverrideStream(Stream* stream_, int nType_, int nVersion_) : stream(stream_), nType(nType_), nVersion(nVersion_) {} + + template + OverrideStream& operator<<(const T& obj) + { + // Serialize to this stream + ::Serialize(*this->stream, obj); + return (*this); + } + + template + OverrideStream& operator>>(T& obj) + { + // Unserialize from this stream + ::Unserialize(*this->stream, obj); + return (*this); + } +}; + +template +OverrideStream WithOrVersion(S* s, int nVersionFlag) +{ + return OverrideStream(s, s->GetType(), s->GetVersion() | nVersionFlag); +} + /** Double ended buffer combining vector and stream-like interfaces. * From c6b8561c66a961e0e2ad352e86eb6a178eff37a3 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Tue, 25 May 2021 19:13:32 +0530 Subject: [PATCH 2/7] partial #16670: Add Join helper to join a list of strings --- src/Makefile.am | 2 ++ src/test/util_tests.cpp | 8 ++++++++ src/utilstring.cpp | 5 +++++ src/utilstring.h | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 src/utilstring.cpp create mode 100644 src/utilstring.h diff --git a/src/Makefile.am b/src/Makefile.am index c85d1b5c2b90..c284f9800dcb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -262,6 +262,7 @@ BITCOIN_CORE_H = \ utilasmap.h \ utilmemory.h \ utilmoneystr.h \ + utilstring.h \ utiltime.h \ validation.h \ validationinterface.h \ @@ -587,6 +588,7 @@ libdash_util_a_SOURCES = \ utilmoneystr.cpp \ utilstrencodings.cpp \ utiltime.cpp \ + utilstring.cpp \ $(BITCOIN_CORE_H) if GLIBC_BACK_COMPAT diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index f30dc17bd0e1..84ba262f9b9f 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -95,6 +96,13 @@ BOOST_AUTO_TEST_CASE(util_HexStr) ); } +BOOST_AUTO_TEST_CASE(util_Join) +{ + // Normal version + BOOST_CHECK_EQUAL(Join({}, ", "), ""); + BOOST_CHECK_EQUAL(Join({"foo"}, ", "), "foo"); + BOOST_CHECK_EQUAL(Join({"foo", "bar"}, ", "), "foo, bar"); +} BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime) { diff --git a/src/utilstring.cpp b/src/utilstring.cpp new file mode 100644 index 000000000000..28b95ff75b42 --- /dev/null +++ b/src/utilstring.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include diff --git a/src/utilstring.h b/src/utilstring.h new file mode 100644 index 000000000000..6d0edcbcf915 --- /dev/null +++ b/src/utilstring.h @@ -0,0 +1,35 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTILSTRING_H +#define BITCOIN_UTILSTRING_H + +#include +#include +#include + +/** + * Join a list of items + * + * @param list The list to join + * @param separator The separator + * @param unary_op Apply this operator to each item in the list + */ +template +std::string Join(const std::vector& list, const std::string& separator, UnaryOp unary_op) +{ + std::string ret; + for (size_t i = 0; i < list.size(); ++i) { + if (i > 0) ret += separator; + ret += unary_op(list.at(i)); + } + return ret; +} + +inline std::string Join(const std::vector& list, const std::string& separator) +{ + return Join(list, separator, [](const std::string& i) { return i; }); +} + +#endif // BITCOIN_UTILSTRING_H From c8e14a3e41534450a3ed8105ae3f13d41f3232c5 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Wed, 11 Dec 2019 11:00:52 +0000 Subject: [PATCH 3/7] merge #17721: Don't allow Base58 decoding of non-Base58 strings. Add Base58 tests. --- src/base58.cpp | 8 ++++++++ src/test/base58_tests.cpp | 13 +++++++++++++ src/utilstrencodings.cpp | 3 ++- src/utilstring.h | 12 +++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index a05917aedff5..f2f0f1373ea2 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include @@ -127,6 +129,9 @@ std::string EncodeBase58(const std::vector& vch) bool DecodeBase58(const std::string& str, std::vector& vchRet) { + if (!ValidAsCString(str)) { + return false; + } return DecodeBase58(str.c_str(), vchRet); } @@ -158,6 +163,9 @@ bool DecodeBase58Check(const char* psz, std::vector& vchRet) bool DecodeBase58Check(const std::string& str, std::vector& vchRet) { + if (!ValidAsCString(str)) { + return false; + } return DecodeBase58Check(str.c_str(), vchRet); } diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 2a713a3fff54..55f5d8702525 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -58,12 +58,25 @@ BOOST_AUTO_TEST_CASE(base58_DecodeBase58) } BOOST_CHECK(!DecodeBase58("invalid", result)); + BOOST_CHECK(!DecodeBase58("invalid", result)); + BOOST_CHECK(!DecodeBase58(std::string("invalid"), result)); + BOOST_CHECK(!DecodeBase58(std::string("\0invalid", 8), result)); + + BOOST_CHECK(DecodeBase58(std::string("good", 4), result)); + BOOST_CHECK(!DecodeBase58(std::string("bad0IOl", 7), result)); + BOOST_CHECK(!DecodeBase58(std::string("goodbad0IOl", 11), result)); + BOOST_CHECK(!DecodeBase58(std::string("good\0bad0IOl", 12), result)); // check that DecodeBase58 skips whitespace, but still fails with unexpected non-whitespace at the end. BOOST_CHECK(!DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t a", result)); BOOST_CHECK( DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t ", result)); std::vector expected = ParseHex("971a55"); BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); + + BOOST_CHECK(DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oh", 21), result)); + BOOST_CHECK(!DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oi", 21), result)); + BOOST_CHECK(!DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oh0IOl", 25), result)); + BOOST_CHECK(!DecodeBase58Check(std::string("3vQB7B6MrGQZaxCuFg4oh\00IOl", 26), result)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utilstrencodings.cpp b/src/utilstrencodings.cpp index e2c5e5913ada..c8d75130d41e 100644 --- a/src/utilstrencodings.cpp +++ b/src/utilstrencodings.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include @@ -267,7 +268,7 @@ NODISCARD static bool ParsePrechecks(const std::string& str) return false; if (str.size() >= 1 && (isspace(str[0]) || isspace(str[str.size()-1]))) // No padding allowed return false; - if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed + if (!ValidAsCString(str)) // No embedded NUL characters allowed return false; return true; } diff --git a/src/utilstring.h b/src/utilstring.h index 6d0edcbcf915..0c8a4f44100f 100644 --- a/src/utilstring.h +++ b/src/utilstring.h @@ -5,7 +5,9 @@ #ifndef BITCOIN_UTILSTRING_H #define BITCOIN_UTILSTRING_H -#include +#include + +#include #include #include @@ -32,4 +34,12 @@ inline std::string Join(const std::vector& list, const std::string& return Join(list, separator, [](const std::string& i) { return i; }); } +/** + * Check if a string does not contain any embedded NUL (\0) characters + */ +NODISCARD inline bool ValidAsCString(const std::string& str) noexcept +{ + return str.size() == strlen(str.c_str()); +} + #endif // BITCOIN_UTILSTRING_H From 894d29bf9c44329b13aba1396b7a030b6883d392 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Wed, 11 Dec 2019 09:55:00 +0000 Subject: [PATCH 4/7] partial #17229: util: Add TrimString(...). Introduce default pattern, NODISCARD --- src/utilstring.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utilstring.h b/src/utilstring.h index 0c8a4f44100f..9b8230f492a6 100644 --- a/src/utilstring.h +++ b/src/utilstring.h @@ -11,6 +11,16 @@ #include #include +NODISCARD inline std::string TrimString(const std::string& str, const std::string& pattern = " \f\n\r\t\v") +{ + std::string::size_type front = str.find_first_not_of(pattern); + if (front == std::string::npos) { + return std::string(); + } + std::string::size_type end = str.find_last_not_of(pattern); + return str.substr(front, end - front + 1); +} + /** * Join a list of items * From f154f0ddb7c182ac370f72ed76623a896a5dbf20 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Sat, 9 May 2020 14:45:47 +0300 Subject: [PATCH 5/7] partial #18922: Enhance Join() --- src/utilstring.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/utilstring.h b/src/utilstring.h index 9b8230f492a6..8d9fff6c9def 100644 --- a/src/utilstring.h +++ b/src/utilstring.h @@ -28,10 +28,11 @@ NODISCARD inline std::string TrimString(const std::string& str, const std::strin * @param separator The separator * @param unary_op Apply this operator to each item in the list */ -template -std::string Join(const std::vector& list, const std::string& separator, UnaryOp unary_op) +template +auto Join(const std::vector& list, const BaseType& separator, UnaryOp unary_op) + -> decltype(unary_op(list.at(0))) { - std::string ret; + decltype(unary_op(list.at(0))) ret; for (size_t i = 0; i < list.size(); ++i) { if (i > 0) ret += separator; ret += unary_op(list.at(i)); @@ -39,9 +40,16 @@ std::string Join(const std::vector& list, const std::string& separator, Unary return ret; } +template +T Join(const std::vector& list, const T& separator) +{ + return Join(list, separator, [](const T& i) { return i; }); +} + +// Explicit overload needed for c_str arguments, which would otherwise cause a substitution failure in the template above. inline std::string Join(const std::vector& list, const std::string& separator) { - return Join(list, separator, [](const std::string& i) { return i; }); + return Join(list, separator); } /** From 777034184109ef65638b230d32b1d72622d9e809 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Tue, 25 May 2021 19:13:13 +0530 Subject: [PATCH 6/7] merge #19845: add support to (un)serialize as ADDRv2 --- src/crypto/common.h | 7 + src/netaddress.cpp | 304 ++++++++++++++++++++++++++++++----- src/netaddress.h | 169 ++++++++++++++++++- src/primitives/transaction.h | 1 + src/test/base32_tests.cpp | 3 + src/test/miner_tests.cpp | 11 -- src/test/net_tests.cpp | 298 +++++++++++++++++++++++++++++++++- src/test/test_dash.h | 11 ++ src/utilstrencodings.cpp | 12 +- src/utilstrencodings.h | 16 +- src/utilstring.h | 17 +- src/version.h | 3 + 12 files changed, 783 insertions(+), 69 deletions(-) diff --git a/src/crypto/common.h b/src/crypto/common.h index a4879b4d6c71..f8440aab68ab 100644 --- a/src/crypto/common.h +++ b/src/crypto/common.h @@ -53,6 +53,13 @@ void static inline WriteLE64(unsigned char* ptr, uint64_t x) memcpy(ptr, (char*)&v, 8); } +uint16_t static inline ReadBE16(const unsigned char* ptr) +{ + uint16_t x; + memcpy((char*)&x, ptr, 2); + return be16toh(x); +} + uint32_t static inline ReadBE32(const unsigned char* ptr) { uint32_t x; diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 12e423852cd8..9a262a7308fc 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -5,18 +5,112 @@ #include #include +#include +#include #include +#include +#include #include #include -#include +#include #include #include #include +#include #include #include constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE; +constexpr size_t CNetAddr::MAX_ADDRV2_SIZE; + +CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const +{ + switch (m_net) { + case NET_IPV4: + return BIP155Network::IPV4; + case NET_IPV6: + return BIP155Network::IPV6; + case NET_ONION: + 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: // 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); +} + +bool CNetAddr::SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t address_size) +{ + switch (possible_bip155_net) { + case BIP155Network::IPV4: + if (address_size == ADDR_IPV4_SIZE) { + m_net = NET_IPV4; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 IPv4 address with length %u (should be %u)", address_size, + ADDR_IPV4_SIZE)); + case BIP155Network::IPV6: + if (address_size == ADDR_IPV6_SIZE) { + m_net = NET_IPV6; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 IPv6 address with length %u (should be %u)", address_size, + ADDR_IPV6_SIZE)); + case BIP155Network::TORV2: + if (address_size == ADDR_TORV2_SIZE) { + m_net = NET_ONION; + return true; + } + 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 future). + // Instead silently drop them and have the unserialization code consume + // subsequent ones which may be known to us. + return false; +} bool fAllowPrivateNet = DEFAULT_ALLOWPRIVATENET; @@ -38,7 +132,13 @@ void CNetAddr::SetIP(const CNetAddr& ipIn) 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); @@ -52,13 +152,6 @@ void CNetAddr::SetIP(const CNetAddr& ipIn) m_addr = ipIn.m_addr; } -template -inline bool HasPrefix(const T1& obj, const std::array& prefix) -{ - return obj.size() >= PREFIX_LEN && - std::equal(std::begin(prefix), std::end(prefix), std::begin(obj)); -} - void CNetAddr::SetLegacyIPv6(Span ipv6) { assert(ipv6.size() == ADDR_IPV6_SIZE); @@ -103,24 +196,80 @@ bool CNetAddr::SetInternal(const std::string &name) 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 unsigned char 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 unsigned char 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) +bool CNetAddr::SetSpecial(const std::string& str) { - 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) { + 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(vchAddr.begin(), vchAddr.end()); + m_addr.assign(input_pubkey.begin(), input_pubkey.end()); return true; } + } + return false; } @@ -231,13 +380,27 @@ bool CNetAddr::IsRFC7343() const (m_addr[3] & 0xF0) == 0x20; } -bool CNetAddr::IsTor() const { return m_net == NET_ONION; } - bool CNetAddr::IsHeNet() const { return IsIPv6() && HasPrefix(m_addr, std::array{0x20, 0x01, 0x04, 0x70}); } +/** + * Check whether this object represents a TOR address. + * @see CNetAddr::SetSpecial(const std::string &) + */ +bool CNetAddr::IsTor() const { 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) @@ -320,11 +483,67 @@ enum Network CNetAddr::GetNetwork() const 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(bool fUseGetnameinfo) const { - if (IsTor()) - return EncodeBase32(m_addr) + ".onion"; - if (IsInternal()) + switch (m_net) { + case NET_IPV4: + case NET_IPV6: { + if (fUseGetnameinfo) { + 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); + } + 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"; if (fUseGetnameinfo) { @@ -338,14 +557,12 @@ std::string CNetAddr::ToStringIP(bool fUseGetnameinfo) const } } } - 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_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 @@ -404,21 +621,22 @@ uint32_t CNetAddr::GetLinkedIPv4() const assert(false); } -uint32_t CNetAddr::GetNetClass() const { - uint32_t net_class = NET_IPV6; - if (IsLocal()) { - net_class = 255; - } +uint32_t CNetAddr::GetNetClass() const +{ + // 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 { @@ -493,7 +711,7 @@ std::vector CNetAddr::GetGroup(const std::vector &asmap) co 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 @@ -703,7 +921,7 @@ std::string CService::ToStringPort() const std::string CService::ToStringIPPort(bool fUseGetnameinfo) const { - if (IsIPv4() || IsTor() || IsInternal()) { + if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) { return ToStringIP(fUseGetnameinfo) + ":" + ToStringPort(); } else { return "[" + ToStringIP(fUseGetnameinfo) + "]:" + ToStringPort(); diff --git a/src/netaddress.h b/src/netaddress.h index c1af100d12fb..53a2c9a37735 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -14,14 +14,26 @@ #include #include #include +#include +#include +#include #include #include +#include #include #include extern bool fAllowPrivateNet; +/** + * A flag that is ORed into the protocol version to designate that addresses + * should be serialized in (unserialized from) v2 format (BIP155). + * Make sure that this does not collide with any of the values in `version.h` + * or with `SERIALIZE_TRANSACTION_NO_WITNESS`. + */ +static const int ADDRV2_FORMAT = 0x20000000; + /** * A network type. * @note An address may belong to more than one network, for example `10.0.0.1` @@ -42,9 +54,15 @@ enum Network /// 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, @@ -85,6 +103,16 @@ static constexpr size_t ADDR_IPV6_SIZE = 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; @@ -154,6 +182,8 @@ class CNetAddr bool IsRFC6145() const; // IPv6 IPv4-translated address (::FFFF:0:0:0/96) (actually defined in RFC2765) bool IsHeNet() const; // IPv6 Hurricane Electric - https://he.net (2001:0470::/36) bool IsTor() const; + bool IsI2P() const; + bool IsCJDNS() const; bool IsLocal() const; bool IsRoutable() const; bool IsInternal() const; @@ -191,7 +221,11 @@ class CNetAddr template void Serialize(Stream& s) const { - SerializeV1Stream(s); + if (s.GetVersion() & ADDRV2_FORMAT) { + SerializeV2Stream(s); + } else { + SerializeV1Stream(s); + } } /** @@ -200,20 +234,58 @@ class CNetAddr template void Unserialize(Stream& s) { - UnserializeV1Stream(s); + if (s.GetVersion() & ADDRV2_FORMAT) { + UnserializeV2Stream(s); + } else { + UnserializeV1Stream(s); + } } friend class CSubNet; private: + /** + * BIP155 network ids recognized by this software. + */ + enum BIP155Network : uint8_t { + IPV4 = 1, + IPV6 = 2, + TORV2 = 3, + TORV3 = 4, + I2P = 5, + CJDNS = 6, + }; + /** * Size of CNetAddr when serialized as ADDRv1 (pre-BIP155) (in bytes). */ static constexpr size_t V1_SERIALIZATION_SIZE = ADDR_IPV6_SIZE; + /** + * Maximum size of an address as defined in BIP155 (in bytes). + * This is only the size of the address, not the entire CNetAddr object + * when serialized. + */ + static constexpr size_t MAX_ADDRV2_SIZE = 512; + + /** + * Get the BIP155 network id of this address. + * Must not be called for IsInternal() objects. + * @returns BIP155 network id + */ + BIP155Network GetBIP155Network() const; + + /** + * Set `m_net` from the provided BIP155 network id and size after validation. + * @retval true the network was recognized, is valid and `m_net` was set + * @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 (id 1..6) with wrong address size. + */ + bool SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t address_size); + /** * 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 { @@ -231,6 +303,9 @@ class CNetAddr 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); @@ -242,18 +317,22 @@ class CNetAddr 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); } public: /** * 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 @@ -265,6 +344,25 @@ class CNetAddr s << serialized; } + /** + * Serialize as ADDRv2 / BIP155. + */ + template + void SerializeV2Stream(Stream& s) const + { + if (IsInternal()) { + // Serialize NET_INTERNAL as embedded in IPv6. We need to + // serialize such addresses from addrman. + s << static_cast(BIP155Network::IPV6); + s << COMPACTSIZE(ADDR_IPV6_SIZE); + SerializeV1Stream(s); + return; + } + + s << static_cast(GetBIP155Network()); + s << m_addr; + } + /** * Unserialize from a pre-ADDRv2/BIP155 format from an array. */ @@ -287,6 +385,65 @@ class CNetAddr UnserializeV1Array(serialized); } + + /** + * Unserialize from a ADDRv2 / BIP155 format. + */ + template + void UnserializeV2Stream(Stream& s) + { + uint8_t bip155_net; + s >> bip155_net; + + size_t address_size; + s >> COMPACTSIZE(address_size); + + if (address_size > MAX_ADDRV2_SIZE) { + throw std::ios_base::failure(strprintf( + "Address too long: %u > %u", address_size, MAX_ADDRV2_SIZE)); + } + + scopeId = 0; + + if (SetNetFromBIP155Network(bip155_net, address_size)) { + m_addr.resize(address_size); + s >> MakeSpan(m_addr); + + if (m_net != NET_IPV6) { + return; + } + + // Do some special checks on IPv6 addresses. + + // Recognize NET_INTERNAL embedded in IPv6, such addresses are not + // gossiped but could be coming from addrman, when unserializing from + // disk. + if (HasPrefix(m_addr, INTERNAL_IN_IPV6_PREFIX)) { + m_net = NET_INTERNAL; + memmove(m_addr.data(), m_addr.data() + INTERNAL_IN_IPV6_PREFIX.size(), + ADDR_INTERNAL_SIZE); + m_addr.resize(ADDR_INTERNAL_SIZE); + return; + } + + if (!HasPrefix(m_addr, IPV4_IN_IPV6_PREFIX) && + !HasPrefix(m_addr, TORV2_IN_IPV6_PREFIX)) { + return; + } + + // IPv4 and TORv2 are not supposed to be embedded in IPv6 (like in V1 + // encoding). Unserialize as !IsValid(), thus ignoring them. + } else { + // If we receive an unknown BIP155 network id (from the future?) then + // ignore the address - unserialize as !IsValid(). + s.ignore(address_size); + } + + // Mimic a default-constructed CNetAddr object which is !IsValid() and thus + // will not be gossiped, but continue reading next addresses from the stream. + m_net = NET_IPV6; + m_addr.assign(ADDR_IPV6_SIZE, 0x0); + } }; class CSubNet diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index a5ab6f033807..8284a262a7ab 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -10,6 +10,7 @@ #include