/* * This file is part of the Monero P2Pool * Copyright (c) 2021-2025 SChernykh * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "util.h" #include "uv_util.h" #include "keccak.h" extern "C" { #include "crypto-ops.h" } #include #include #include #include #if !defined(_WIN32) && defined(HAVE_SCHED) #include #endif #ifdef WITH_UPNP #include "miniupnpc.h" #include "upnpcommands.h" #endif #ifdef _WIN32 #include #elif defined(HAVE_RES_QUERY) #include #include #include #include #endif #ifndef P2POOL_UNIT_TESTS #include #endif #include #ifdef HAVE_GLIBC #include #endif #ifdef WITH_GRPC #include #endif #include LOG_CATEGORY(Util) namespace p2pool { #if defined(P2POOL_VERSION_PATCH) && (P2POOL_VERSION_PATCH > 0) #define P2POOL_VERSION_PATCH_STR "." STR2(P2POOL_VERSION_PATCH) #else #define P2POOL_VERSION_PATCH_STR "" #endif const char* VERSION = "v" STR2(P2POOL_VERSION_MAJOR) "." STR2(P2POOL_VERSION_MINOR) P2POOL_VERSION_PATCH_STR " (built" #if defined(__clang__) " with clang/" __clang_version__ #elif defined(__GNUC__) " with GCC/" STR2(__GNUC__) "." STR2(__GNUC_MINOR__) "." STR2(__GNUC_PATCHLEVEL__) #elif defined(_MSC_VER) " with MSVC/" STR2(_MSC_VER) #endif #ifdef GIT_COMMIT " from " GIT_COMMIT #endif " on " __DATE__ ")"; std::string p2pool_version() { #ifndef P2POOL_UNIT_TESTS const curl_version_info_data* curl_version_data = curl_version_info(CURLVERSION_NOW); #endif int zmq_major, zmq_minor, zmq_patch; zmq_version(&zmq_major, &zmq_minor, &zmq_patch); char buf[384] = {}; log::Stream s(buf); s << "P2Pool " << VERSION << '\n' << "\nDependencies:\n" #ifdef HAVE_GLIBC << " - glibc " << gnu_get_libc_version() << '-' << gnu_get_libc_release() << '\n' #endif #ifdef WITH_GRPC << " - grpc " << GRPC_CPP_VERSION_STRING << '\n' #endif #ifndef P2POOL_UNIT_TESTS << " - libcurl " << (curl_version_data ? curl_version_data->version : "unknown") << '\n' #endif << " - libuv " << uv_version_string() << '\n' << " - libzmq " << zmq_major << '.' << zmq_minor << '.' << zmq_patch << '\n' #ifdef WITH_UPNP << " - miniupnpc " << MINIUPNPC_VERSION << '\n' #endif << " - rapidjson " << RAPIDJSON_VERSION_STRING << '\n' ; return std::string(buf, s.m_pos); } void fixup_path(std::string& path) { if (!path.empty() && (path.back() != '/') #ifdef _WIN32 && (path.back() != '\\') #endif ) { path.append(1, '/'); } } // Tell the compiler to not optimize secure_zero_memory and hope that it listens #ifdef _MSC_VER #pragma optimize("", off) #endif void #if defined(__clang__) __attribute__((optnone)) #elif defined(__GNUC__) __attribute__((optimize("O0"))) #endif secure_zero_memory(volatile void* data, size_t size) { volatile uint8_t* p = reinterpret_cast(data); // cppcheck-suppress constVariablePointer volatile uint8_t* e = reinterpret_cast(data) + size; while (p < e) { *(p++) = 0; } } #ifdef _MSC_VER #pragma optimize("", on) #endif const uint8_t ED25519_MASTER_PUBLIC_KEY[32] = {51,175,37,73,203,241,188,115,195,255,123,53,218,120,90,74,186,240,82,178,67,139,124,91,180,106,188,181,187,51,236,10}; SoftwareID get_software_id(uint32_t value) { switch (value) { case static_cast(SoftwareID::P2Pool): return SoftwareID::P2Pool; case static_cast(SoftwareID::GoObserver): return SoftwareID::GoObserver; default: return SoftwareID::Unknown; } } const raw_ip raw_ip::localhost_ipv4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01 }; const raw_ip raw_ip::localhost_ipv6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; alignas(8) const uint8_t raw_ip::ipv4_prefix[12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; MinerCallbackHandler::~MinerCallbackHandler() {} void panic_stop(const char* message) { #ifdef DEV_TRACK_MEMORY // Give it 1 minute to shut down, otherwise save a minidump minidump_and_crash(60 * 1000); #endif fprintf(stderr, "P2Pool can't continue execution: panic at %s\n", message); p2pool::log::stop(); do { #ifdef _WIN32 if (IsDebuggerPresent()) { __debugbreak(); } #endif abort(); } while (true); } void make_thread_background() { #ifdef _WIN32 SetThreadPriorityBoost(GetCurrentThread(), true); SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE); #elif defined(HAVE_SCHED) sched_param param; param.sched_priority = 0; if (sched_setscheduler(0, SCHED_IDLE, ¶m) != 0) { sched_setscheduler(0, SCHED_BATCH, ¶m); } #endif } NOINLINE difficulty_type& difficulty_type::operator/=(difficulty_type b) { if (*this < b) { lo = 0; hi = 0; return *this; } if (*this - b < b) { lo = 1; hi = 0; return *this; } if (b.hi == 0) { return operator/=(b.lo); } const uint64_t shift = bsr(b.hi) + 1; const uint64_t divisor = shiftleft128(b.lo, b.hi, 64 - shift); uint64_t t; if (hi < divisor) { uint64_t r; t = udiv128(hi, lo, divisor, &r) >> shift; } else { uint64_t r; t = shiftright128(udiv128(hi - divisor, lo, divisor, &r), 1, shift); } difficulty_type product; product.lo = umul128(b.lo, t, &product.hi); uint64_t t1, t2; t1 = umul128(b.hi, t, &t2); product.hi += t1; if (t2 || (product.hi < t1) || (*this < product)) { --t; } lo = t; hi = 0; return *this; } NOINLINE bool difficulty_type::check_pow(const hash& pow_hash) const { const uint64_t* a = pow_hash.u64(); uint64_t result[6] = {}; uint64_t product[6] = {}; if (LIKELY(hi == 0)) { for (int i = 3; i >= 0; --i) { product[0] = umul128(a[i], lo, &product[1]); uint64_t carry = 0; for (int k = i, l = 0; k < 5; ++k, ++l) { uint64_t t = result[k] + product[l]; const uint64_t next_carry = static_cast(t < result[k]); t += carry; carry = next_carry | static_cast(t < result[k]); result[k] = t; } if (result[4]) { return false; } } } else { const uint64_t* b = reinterpret_cast(this); for (int i = 3; i >= 0; --i) { for (int j = 1; j >= 0; --j) { product[0] = umul128(a[i], b[j], &product[1]); uint64_t carry = 0; for (int k = i + j, l = 0; k < 6; ++k, ++l) { uint64_t t = result[k] + product[l]; const uint64_t next_carry = static_cast(t < result[k]); t += carry; carry = next_carry | static_cast(t < result[k]); result[k] = t; } if (result[4] || result[5]) { return false; } } } } return true; } std::ostream& operator<<(std::ostream& s, const difficulty_type& d) { char buf[log::Stream::BUF_SIZE + 1]; // cppcheck-suppress uninitvar log::Stream s1(buf); s1 << d << '\0'; s << buf; return s; } std::istream& operator>>(std::istream& s, difficulty_type& diff) { diff.lo = 0; diff.hi = 0; bool found_number = false; char c; while (s.good() && !s.eof()) { s.read(&c, 1); if (!s.good() || s.eof()) { break; } if ('0' <= c && c <= '9') { found_number = true; const uint32_t digit = static_cast(c - '0'); uint64_t hi; diff.lo = umul128(diff.lo, 10, &hi) + digit; if (diff.lo < digit) { ++hi; } diff.hi = diff.hi * 10 + hi; } else if (found_number) { return s; } } return s; } std::ostream& operator<<(std::ostream& s, const hash& h) { char buf[log::Stream::BUF_SIZE + 1]; // cppcheck-suppress uninitvar log::Stream s1(buf); s1 << h << '\0'; s << buf; return s; } std::istream& operator>>(std::istream& s, hash& h) { memset(h.h, 0, HASH_SIZE); bool found_number = false; uint32_t index = 0; char c; while (s.good() && !s.eof()) { s.read(&c, 1); if (!s.good() || s.eof()) { break; } uint8_t digit; if (from_hex(c, digit)) { found_number = true; h.h[index >> 1] = (h.h[index >> 1] << 4) | digit; ++index; if (index >= HASH_SIZE * 2) { return s; } } else if (found_number) { return s; } } return s; } void uv_cond_init_checked(uv_cond_t* cond) { const int result = uv_cond_init(cond); if (result) { LOGERR(1, "failed to create conditional variable, error " << uv_err_name(result)); PANIC_STOP(); } } void uv_mutex_init_checked(uv_mutex_t* mutex) { const int result = uv_mutex_init(mutex); if (result) { LOGERR(1, "failed to create mutex, error " << uv_err_name(result)); PANIC_STOP(); } } void uv_rwlock_init_checked(uv_rwlock_t* lock) { const int result = uv_rwlock_init(lock); if (result) { LOGERR(1, "failed to create rwlock, error " << uv_err_name(result)); PANIC_STOP(); } } void uv_async_init_checked(uv_loop_t* loop, uv_async_t* async, uv_async_cb async_cb) { const int err = uv_async_init(loop, async, async_cb); if (err) { LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); PANIC_STOP(); } } uv_loop_t* uv_default_loop_checked() { if (!is_main_thread()) { LOGERR(1, "uv_default_loop() can only be used by the main thread. Fix the code!"); #ifdef _WIN32 if (IsDebuggerPresent()) { __debugbreak(); } #endif } return uv_default_loop(); } struct BackgroundJobTracker::Impl { Impl() { uv_mutex_init_checked(&m_lock); } ~Impl() { uv_mutex_destroy(&m_lock); } void start(const char* name) { MutexLock lock(m_lock); auto it = m_jobs.emplace(name, 1); if (!it.second) { const int32_t n = ++it.first->second; // Print the warning only once as the number goes past 20 if (n == 20) { LOGWARN(3, "Performance warning: there are " << n << " instances of " << name << " running in the background. This shouldn't be normally happening - check logs for other warnings and errors."); } } } void stop(const char* name) { MutexLock lock(m_lock); auto it = m_jobs.find(name); if (it == m_jobs.end()) { LOGWARN(1, "background job " << name << " is not running, but stop() was called"); return; } const int32_t n = --it->second; if (n <= 0) { m_jobs.erase(it); } } std::vector> get_jobs() { std::vector> result; { MutexLock lock(m_lock); result.reserve(m_jobs.size()); for (const auto& job : m_jobs) { result.emplace_back(job.first.data(), job.second); } } return result; } void print_status() { MutexLock lock(m_lock); if (m_jobs.empty()) { LOGINFO(0, "no background jobs running"); return; } char buf[log::Stream::BUF_SIZE + 1]; // cppcheck-suppress uninitvar log::Stream s(buf); for (const auto& job : m_jobs) { s << '\n' << job.first << " (" << job.second << ')'; } LOGINFO(0, "background jobs running:" << log::const_buf(buf, s.m_pos)); } uv_mutex_t m_lock; std::map m_jobs; }; BackgroundJobTracker::BackgroundJobTracker() : m_impl(new Impl()) { } BackgroundJobTracker::~BackgroundJobTracker() { delete m_impl; } void BackgroundJobTracker::start_internal(const char* name) { m_impl->start(name); } void BackgroundJobTracker::stop_internal(const char* name) { m_impl->stop(name); } std::vector> BackgroundJobTracker::get_jobs() { return m_impl->get_jobs(); } void BackgroundJobTracker::print_status() { m_impl->print_status(); } BackgroundJobTracker* bkg_jobs_tracker = nullptr; static thread_local bool main_thread = false; void set_main_thread() { main_thread = true; } bool is_main_thread() { return main_thread; } bool disable_resolve_host = false; bool resolve_host(std::string& host, bool& is_v6) { if (disable_resolve_host) { LOGERR(1, "resolve_host was called with DNS disabled for host " << host); return false; } addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_ADDRCONFIG; addrinfo* r = nullptr; int err = getaddrinfo(host.c_str(), nullptr, &hints, &r); if (err) { LOGWARN(4, "getaddrinfo failed for " << host << ": " << gai_strerror(err) << ", retrying with IPv4 only"); hints.ai_family = AF_INET; err = getaddrinfo(host.c_str(), nullptr, &hints, &r); } if ((err == 0) && r) { const char* addr_str = nullptr; char addr_str_buf[64]; void* addr; if (r->ai_family == AF_INET6) { addr = &reinterpret_cast(r->ai_addr)->sin6_addr; is_v6 = true; } else { addr = &reinterpret_cast(r->ai_addr)->sin_addr; is_v6 = false; } addr_str = inet_ntop(r->ai_family, addr, addr_str_buf, sizeof(addr_str_buf)); if (addr_str) { LOGINFO(5, log::LightCyan() << host << log::NoColor() << " resolved to " << log::Gray() << addr_str); host = addr_str; } freeaddrinfo(r); } else { LOGWARN(3, "getaddrinfo failed for " << host << ": " << gai_strerror(err)); return false; } return true; } bool get_dns_txt_records_base(const std::string& host, const Callback::Base& callback) { if (disable_resolve_host) { LOGERR(1, "get_dns_txt_records was called with DNS disabled for host " << host); return false; } #ifdef _WIN32 PDNS_RECORD pQueryResults; if (DnsQuery(host.c_str(), DNS_TYPE_TEXT, DNS_QUERY_STANDARD, NULL, &pQueryResults, NULL) != 0) { return false; } for (PDNS_RECORD p = pQueryResults; p; p = p->pNext) { if (p->wType != DNS_TYPE_TEXT) { continue; } for (size_t j = 0; j < p->Data.TXT.dwStringCount; ++j) { const char* s = p->Data.TXT.pStringArray[j]; if (s) { const size_t n = strlen(s); if (n > 0) { callback(s, n); } } } } DnsRecordListFree(pQueryResults, DnsFreeRecordList); return true; #elif defined(HAVE_RES_QUERY) static const int res_init_result = res_init(); if (res_init_result != 0) { return false; } uint8_t answer[4096] = {}; const int anslen = res_query(host.c_str(), ns_c_in, ns_t_txt, answer, sizeof(answer)); if ((anslen <= 0) || (anslen > static_cast(sizeof(answer)))) { return false; } ns_msg handle{}; if (ns_initparse(answer, anslen, &handle) != 0) { return false; } for (int rrnum = 0, n = ns_msg_count(handle, ns_s_an); rrnum < n; ++rrnum) { ns_rr rr{}; if ((ns_parserr(&handle, ns_s_an, rrnum, &rr) == 0) && (ns_rr_type(rr) == ns_t_txt)) { for (const uint8_t* data = ns_rr_rdata(rr), *e = data + ns_rr_rdlen(rr); data < e;) { const size_t k = *(data++); if (k && (data + k <= e)) { callback(reinterpret_cast(data), k); } data += k; } } } return true; #else (void)host; (void)callback; return false; #endif } RandomDeviceSeed RandomDeviceSeed::instance; struct BSR8 { uint8_t data[256]; static constexpr BSR8 init() { BSR8 result = { 55 }; for (int i = 1; i < 256; ++i) { int x = i; result.data[i] = 63; while (x < 0x80) { --result.data[i]; x <<= 1; } } return result; } }; static constexpr BSR8 bsr8_table = BSR8::init(); NOINLINE uint64_t bsr_reference(uint64_t x) { uint32_t y = static_cast(x); uint64_t n0 = (x == y) ? 0 : 32; y = static_cast(x >> n0); n0 ^= 32; const uint64_t n1 = (y & 0xFFFF0000UL) ? 0 : 16; y <<= n1; const uint64_t n2 = (y & 0xFF000000UL) ? 0 : 8; y <<= n2; return bsr8_table.data[y >> 24] - n0 - n1 - n2; } bool str_to_ip(bool is_v6, const char* ip, raw_ip& result) { sockaddr_storage addr; if (is_v6) { sockaddr_in6* addr6 = reinterpret_cast(&addr); const int err = uv_ip6_addr(ip, 0, addr6); if (err) { LOGERR(1, "failed to parse IPv6 address " << ip << ", error " << uv_err_name(err)); return false; } memcpy(result.data, &addr6->sin6_addr, sizeof(in6_addr)); } else { sockaddr_in* addr4 = reinterpret_cast(&addr); const int err = uv_ip4_addr(ip, 0, addr4); if (err) { LOGERR(1, "failed to parse IPv4 address " << ip << ", error " << uv_err_name(err)); return false; } memcpy(result.data, raw_ip::ipv4_prefix, sizeof(raw_ip::ipv4_prefix)); memcpy(result.data + sizeof(raw_ip::ipv4_prefix), &addr4->sin_addr, sizeof(in_addr)); } return true; } bool is_localhost(const std::string& host) { if (host.empty()) { return false; } if (host.compare("localhost") == 0) { return true; } if (host.find_first_not_of("0123456789.:") != std::string::npos) { return false; } raw_ip addr; if (!str_to_ip(host.find(':') != std::string::npos, host.c_str(), addr)) { return false; } return addr.is_localhost(); } UV_LoopUserData* GetLoopUserData(uv_loop_t* loop, bool create) { static_assert(sizeof(std::atomic) <= sizeof(void*), "loop->data size mismatch"); static_assert(alignof(std::atomic) <= alignof(void*), "loop->data alignment mismatch"); std::atomic& data = reinterpret_cast&>(loop->data); UV_LoopUserData* result = data.load(); if (!result && create) { UV_LoopUserData* new_data = new UV_LoopUserData(loop); if (data.compare_exchange_strong(result, new_data)) { result = new_data; } else { delete new_data; } } return result; } void DeleteLoopUserData(uv_loop_t* loop) { std::atomic& data = reinterpret_cast&>(loop->data); delete data.exchange(nullptr); } #ifdef WITH_UPNP static struct UPnP_Discover { uv_mutex_t lock; int error; UPNPDev* devlist; } upnp_discover; void init_upnp() { uv_mutex_init_checked(&upnp_discover.lock); uv_work_t* req = new uv_work_t{}; const int err = uv_queue_work(uv_default_loop_checked(), req, [](uv_work_t* /*req*/) { BACKGROUND_JOB_START(init_upnp); LOGINFO(1, "UPnP: Started scanning for UPnP IGD devices"); { MutexLock lock(upnp_discover.lock); upnp_discover.devlist = upnpDiscover(1000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &upnp_discover.error); } LOGINFO(1, "UPnP: Finished scanning for UPnP IGD devices"); }, [](uv_work_t* req, int /*status*/) { delete req; BACKGROUND_JOB_STOP(init_upnp); } ); if (err) { LOGERR(0, "init_upnp: uv_queue_work failed, error " << uv_err_name(err)); delete req; } } void destroy_upnp() { { MutexLock lock(upnp_discover.lock); freeUPNPDevlist(upnp_discover.devlist); upnp_discover.devlist = nullptr; } uv_mutex_destroy(&upnp_discover.lock); } int add_portmapping(int external_port, int internal_port) { LOGINFO(1, "UPnP: trying to map WAN:" << external_port << " to LAN:" << internal_port); MutexLock lock(upnp_discover.lock); if (!upnp_discover.devlist) { LOGWARN(1, "upnpDiscover: no UPnP IGD devices found, error " << upnp_discover.error); return 0; } UPNPUrls urls; IGDdatas data; char local_addr[64] = {}; char wan_addr[64] = {}; int result = UPNP_GetValidIGD(upnp_discover.devlist, &urls, &data, local_addr, sizeof(local_addr), wan_addr, sizeof(wan_addr)); if (result != 1) { LOGWARN(1, "UPNP_GetValidIGD returned " << result << ", no valid UPnP IGD devices found"); return 0; } LOGINFO(1, "UPnP: LAN IP address " << log::Gray() << static_cast(local_addr) << log::NoColor() << ", WAN IP address " << log::Gray() << static_cast(wan_addr)); char eport[16] = {}; do { log::Stream s(eport); s << external_port; } while (0); char iport[16] = {}; do { log::Stream s(iport); s << internal_port; } while (0); result = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, eport, iport, local_addr, "P2Pool", "TCP", nullptr, nullptr); // ConflictInMappingEntry: try to delete the old record and then add the new one again if (result == 718) { LOGWARN(1, "UPNP_AddPortMapping failed: ConflictInMappingEntry"); result = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, eport, "TCP", nullptr); if (result) { LOGWARN(1, "UPNP_DeletePortMapping returned error " << result); return 0; } LOGINFO(1, "UPnP: Deleted mapping for external port " << external_port); result = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, eport, iport, local_addr, "P2Pool", "TCP", nullptr, nullptr); } if (result) { LOGWARN(1, "UPNP_AddPortMapping returned error " << result); return 0; } LOGINFO(1, "UPnP: Mapped " << log::Gray() << static_cast(wan_addr) << ':' << external_port << log::NoColor() << " to " << log::Gray() << static_cast(local_addr) << ':' << internal_port); return external_port; } void remove_portmapping(int external_port) { LOGINFO(1, "UPnP: trying to delete mapping for external port " << external_port); MutexLock lock(upnp_discover.lock); if (!upnp_discover.devlist) { LOGWARN(1, "upnpDiscover: no UPnP IGD devices found, error " << upnp_discover.error); return; } UPNPUrls urls; IGDdatas data; char local_addr[64] = {}; int result = UPNP_GetValidIGD(upnp_discover.devlist, &urls, &data, local_addr, sizeof(local_addr), nullptr, 0); if (result != 1) { LOGWARN(1, "UPNP_GetValidIGD returned " << result << ", no valid UPnP IGD devices found"); return; } char eport[16] = {}; do { log::Stream s(eport); s << external_port; } while (0); result = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, eport, "TCP", nullptr); if (result) { LOGWARN(1, "UPNP_DeletePortMapping returned error " << result); } else { LOGINFO(1, "UPnP: Deleted mapping for external port " << external_port); } } #endif NOINLINE PerfTimer::~PerfTimer() { using namespace std::chrono; const duration dt = high_resolution_clock::now() - m_start; LOGINFO(m_level, m_name << " took " << dt.count() << " ms"); } void set_thread_name(const char* name) { #if (UV_VERSION_MAJOR > 1) || ((UV_VERSION_MAJOR == 1) && (UV_VERSION_MINOR >= 50)) const int err = uv_thread_setname(name); if (err) { LOGERR(1, "uv_thread_setname failed for " << name << ", error " << uv_err_name(err)); } #elif defined(HAVE_PTHREAD_SETNAME_NP) const int err = pthread_setname_np(pthread_self(), name); if (err) { LOGERR(1, "pthread_setname_np failed for " << name << ", error " << err); } #else (void)name; #endif } std::string to_onion_v3(const hash& pubkey) { static constexpr uint8_t prefix[] = ".onion checksum"; static constexpr uint8_t version = 3; hash h; keccak_custom([&pubkey](int offset) -> uint8_t { size_t k = static_cast(offset); if (k < sizeof(prefix) - 1) { return prefix[k]; } k -= sizeof(prefix) - 1; return (k < HASH_SIZE) ? pubkey.h[k] : version; }, sizeof(prefix) + HASH_SIZE, h.h, HASH_SIZE, true); // pubkey (32 bytes), checksum (2 bytes), version (1 byte), and a zero byte for padding uint8_t buf[HASH_SIZE + 4]; memcpy(buf, pubkey.h, HASH_SIZE); memcpy(buf + HASH_SIZE, h.h, 2); buf[HASH_SIZE + 2] = version; buf[HASH_SIZE + 3] = 0; std::string result; result.reserve(62); uint64_t data = 0; uint64_t bit_size = 0; for (size_t i = 0; i < HASH_SIZE + 3; ++i) { data = (data << 8) | buf[i]; bit_size += 8; while (bit_size >= 5) { bit_size -= 5; result += "abcdefghijklmnopqrstuvwxyz234567"[(data >> bit_size) & 31]; } } result.append(".onion"); return result; } hash from_onion_v3(const std::string& address) { if ((address.length() < 6) || (address.find(".onion") != address.length() - 6)) { LOGWARN(3, "Invalid onion address \"" << address << "\": doesn't end with \".onion\""); return {}; } if (address.length() != 62) { LOGWARN(3, "Invalid onion address \"" << address << "\": expected length 62, got " << address.length() ); return {}; } const hash result = from_onion_v3_const(address.c_str()); if (result.empty()) { LOGWARN(3, "Invalid onion address \"" << address << "\": has invalid character(s)"); return {}; } // Convert address to lowercase std::string s = address; std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); // Checksum validation if (to_onion_v3(result) != s) { LOGWARN(3, "Invalid onion address \"" << address << "\": checksum failed"); return {}; } // Pubkey validation ge_p3 point; if (ge_frombytes_vartime(&point, result.h) != 0) { LOGWARN(3, "Invalid onion address \"" << address << "\": invalid ed25519 pubkey"); return {}; } return result; } std::vector> parse_config(const std::string& file_name) { std::ifstream f(file_name); if (!f.is_open() || !f.good()) { throw std::exception(); } std::vector> args; auto is_alpha = [](char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); }; auto is_num = [](char c) { return ('0' <= c && c <= '9'); }; std::string s; while (std::getline(f, s).good()) { if (s.empty()) { continue; } bool quoted = false; enum { BEFORE_NAME, COMMENT, NAME, AFTER_NAME, VALUE, VALUE_ESCAPE_CHAR, } state = BEFORE_NAME; for (const char c : s) { switch (state) { case BEFORE_NAME: if (is_alpha(c)) { state = NAME; args.emplace_back(std::vector(1, std::string(1, c))); } else if (c == '#') { state = COMMENT; } break; case COMMENT: break; case NAME: if (is_alpha(c) || is_num(c) || (c == '-')) { args.back()[0] += c; } else if (c == '#') { state = COMMENT; } else { state = AFTER_NAME; } break; case AFTER_NAME: if (c == '#') { state = COMMENT; } else if ((c != '\t') && (c != ' ') && (c != '=')) { state = VALUE; quoted = (c == '"'); if (quoted) { args.back().emplace_back(std::string()); } else { args.back().emplace_back(std::string(1, c)); } } break; case VALUE: if (quoted) { if (c == '"') { state = AFTER_NAME; } else if (c == '\\') { state = VALUE_ESCAPE_CHAR; } else { args.back().back() += c; } } else { if (c == ' ') { state = AFTER_NAME; } else if (c == '#') { state = COMMENT; } else { args.back().back() += c; } } break; case VALUE_ESCAPE_CHAR: args.back().back() += c; state = VALUE; break; } } } return args; } } // namespace p2pool