diff --git a/miner/core/CMakeLists.txt b/miner/core/CMakeLists.txt index a9cb787..2fbd480 100644 --- a/miner/core/CMakeLists.txt +++ b/miner/core/CMakeLists.txt @@ -10,6 +10,7 @@ option(WITH_RANDOMX "Enable RandomX algorithms family" ON) option(WITH_ARGON2 "Enable Argon2 algorithms family" ON) option(WITH_KAWPOW "Enable KawPow algorithms family" ON) option(WITH_ETCHASH "Enable ETChash/Ethash algorithms family" ON) +option(WITH_PROGPOWZ "Enable ProgPowZ algorithm (Zano)" ON) option(WITH_GHOSTRIDER "Enable GhostRider algorithm" ON) option(WITH_HTTP "Enable HTTP protocol support (client/server)" ON) option(WITH_DEBUG_LOG "Enable debug log output" OFF) @@ -203,6 +204,7 @@ include(cmake/randomx.cmake) include(cmake/argon2.cmake) include(cmake/kawpow.cmake) include(cmake/etchash.cmake) +include(cmake/progpowz.cmake) include(cmake/ghostrider.cmake) include(cmake/OpenSSL.cmake) include(cmake/asm.cmake) diff --git a/miner/core/cmake/progpowz.cmake b/miner/core/cmake/progpowz.cmake new file mode 100644 index 0000000..c1063c9 --- /dev/null +++ b/miner/core/cmake/progpowz.cmake @@ -0,0 +1,21 @@ +if (WITH_PROGPOWZ) + add_definitions(/DXMRIG_ALGO_PROGPOWZ) + + list(APPEND HEADERS_CRYPTO + src/crypto/progpowz/ProgPowZHash.h + src/crypto/progpowz/ProgPowZCache.h + ) + + list(APPEND SOURCES_CRYPTO + src/crypto/progpowz/ProgPowZHash.cpp + src/crypto/progpowz/ProgPowZCache.cpp + ) + + # ProgPowZ uses the same libethash library as KawPow and ETChash + if (NOT WITH_KAWPOW AND NOT WITH_ETCHASH) + add_subdirectory(src/3rdparty/libethash) + set(ETHASH_LIBRARY ethash) + endif() +else() + remove_definitions(/DXMRIG_ALGO_PROGPOWZ) +endif() diff --git a/miner/core/src/backend/cuda/CudaBackend.cpp b/miner/core/src/backend/cuda/CudaBackend.cpp index 226132e..23e1cb3 100644 --- a/miner/core/src/backend/cuda/CudaBackend.cpp +++ b/miner/core/src/backend/cuda/CudaBackend.cpp @@ -52,6 +52,12 @@ #endif +#ifdef XMRIG_ALGO_PROGPOWZ +# include "crypto/progpowz/ProgPowZCache.h" +# include "crypto/progpowz/ProgPowZHash.h" +#endif + + #ifdef XMRIG_FEATURE_API # include "base/api/interfaces/IApiRequest.h" #endif @@ -241,6 +247,13 @@ public: } # endif +# ifdef XMRIG_ALGO_PROGPOWZ + if (algo.family() == Algorithm::PROGPOWZ) { + const uint32_t epoch = job.height() / ProgPowZHash::EPOCH_LENGTH; + mem_used = (ProgPowZCache::dag_size(epoch) + oneMiB - 1) / oneMiB; + } +# endif + Log::print("|" CYAN_BOLD("%3zu") " |" CYAN_BOLD("%4u") " |" YELLOW(" %7s") " |" CYAN_BOLD("%10d") " |" CYAN_BOLD("%8d") " |" CYAN_BOLD("%7d") " |" CYAN_BOLD("%3d") " |" CYAN_BOLD("%4d") " |" CYAN("%7zu") " | " GREEN_BOLD("%s"), i, diff --git a/miner/core/src/backend/opencl/OclBackend.cpp b/miner/core/src/backend/opencl/OclBackend.cpp index aa26d45..c51dd17 100644 --- a/miner/core/src/backend/opencl/OclBackend.cpp +++ b/miner/core/src/backend/opencl/OclBackend.cpp @@ -53,6 +53,12 @@ #endif +#ifdef XMRIG_ALGO_PROGPOWZ +# include "crypto/progpowz/ProgPowZCache.h" +# include "crypto/progpowz/ProgPowZHash.h" +#endif + + #ifdef XMRIG_FEATURE_API # include "base/api/interfaces/IApiRequest.h" #endif @@ -229,6 +235,13 @@ public: } # endif +# ifdef XMRIG_ALGO_PROGPOWZ + if (algo.family() == Algorithm::PROGPOWZ) { + const uint32_t epoch = job.height() / ProgPowZHash::EPOCH_LENGTH; + mem_used = (ProgPowZCache::cache_size(epoch) + ProgPowZCache::dag_size(epoch)) / oneMiB; + } +# endif + Log::print("|" CYAN_BOLD("%3zu") " |" CYAN_BOLD("%4u") " |" YELLOW(" %7s") " |" CYAN_BOLD("%10u") " |" CYAN_BOLD("%6u") " |" CYAN("%7zu") " | %s", i, diff --git a/miner/core/src/base/crypto/Algorithm.cpp b/miner/core/src/base/crypto/Algorithm.cpp index da0675c..543434f 100644 --- a/miner/core/src/base/crypto/Algorithm.cpp +++ b/miner/core/src/base/crypto/Algorithm.cpp @@ -108,6 +108,11 @@ const char *Algorithm::kETHASH = "ethash"; const char *Algorithm::kETHASH_ETH = "ethash"; #endif +#ifdef XMRIG_ALGO_PROGPOWZ +const char *Algorithm::kPROGPOWZ = "progpowz"; +const char *Algorithm::kPROGPOWZ_ZANO = "progpowz"; +#endif + #define ALGO_NAME(ALGO) { Algorithm::ALGO, Algorithm::k##ALGO } #define ALGO_ALIAS(ALGO, NAME) { NAME, Algorithm::ALGO } @@ -175,6 +180,10 @@ static const std::map kAlgorithmNames = { ALGO_NAME(ETCHASH_ETC), ALGO_NAME(ETHASH_ETH), # endif + +# ifdef XMRIG_ALGO_PROGPOWZ + ALGO_NAME(PROGPOWZ_ZANO), +# endif }; @@ -299,6 +308,11 @@ static const std::map kAlgorithmAlias ALGO_ALIAS(ETHASH_ETH, "eth"), ALGO_ALIAS(ETHASH_ETH, "daggerhashimoto"), # endif + +# ifdef XMRIG_ALGO_PROGPOWZ + ALGO_ALIAS_AUTO(PROGPOWZ_ZANO), ALGO_ALIAS(PROGPOWZ_ZANO, "progpowz/zano"), + ALGO_ALIAS(PROGPOWZ_ZANO, "zano"), +# endif }; @@ -374,7 +388,8 @@ std::vector xmrig::Algorithm::all(const std::function. + */ + + +#include +#include +#include + +#include "crypto/progpowz/ProgPowZCache.h" +#include "3rdparty/libethash/data_sizes.h" +#include "3rdparty/libethash/ethash_internal.h" +#include "3rdparty/libethash/ethash.h" +#include "base/io/log/Log.h" +#include "base/io/log/Tags.h" +#include "base/tools/Chrono.h" +#include "crypto/common/VirtualMemory.h" + + +namespace xmrig { + + +std::mutex ProgPowZCache::s_cacheMutex; +ProgPowZCache ProgPowZCache::s_cache; + + +ProgPowZCache::ProgPowZCache() +{ +} + + +ProgPowZCache::~ProgPowZCache() +{ + delete m_memory; +} + + +bool ProgPowZCache::init(uint32_t epoch) +{ + if (epoch >= sizeof(cache_sizes) / sizeof(cache_sizes[0])) { + return false; + } + + if (m_epoch == epoch) { + return true; + } + + const uint64_t start_ms = Chrono::steadyMSecs(); + + const size_t size = cache_sizes[epoch]; + if (!m_memory || m_memory->size() < size) { + delete m_memory; + m_memory = new VirtualMemory(size, false, false, false); + } + + const ethash_h256_t seedhash = ethash_get_seedhash(epoch); + ethash_compute_cache_nodes(m_memory->raw(), size, &seedhash); + + ethash_light cache; + cache.cache = m_memory->raw(); + cache.cache_size = size; + + cache.num_parent_nodes = cache.cache_size / sizeof(node); + calculate_fast_mod_data(cache.num_parent_nodes, cache.reciprocal, cache.increment, cache.shift); + + const uint64_t cache_nodes = (size + sizeof(node) * 4 - 1) / sizeof(node); + m_DAGCache.resize(cache_nodes * (sizeof(node) / sizeof(uint32_t))); + + // Init DAG cache + { + const uint64_t n = std::max(std::thread::hardware_concurrency(), 1U); + + std::vector threads; + threads.reserve(n); + + for (uint64_t i = 0; i < n; ++i) { + const uint32_t a = (cache_nodes * i) / n; + const uint32_t b = (cache_nodes * (i + 1)) / n; + + threads.emplace_back([this, a, b, &cache]() { + uint32_t j = a; + for (; j + 4 <= b; j += 4) ethash_calculate_dag_item4_opt(((node*)m_DAGCache.data()) + j, j, num_dataset_parents, &cache); + for (; j < b; ++j) ethash_calculate_dag_item_opt(((node*)m_DAGCache.data()) + j, j, num_dataset_parents, &cache); + }); + } + + for (auto& t : threads) { + t.join(); + } + } + + m_size = size; + m_epoch = epoch; + + LOG_INFO("%s " YELLOW("ProgPowZ") " light cache for epoch " WHITE_BOLD("%u") " calculated " BLACK_BOLD("(%" PRIu64 "ms)"), Tags::miner(), epoch, Chrono::steadyMSecs() - start_ms); + + return true; +} + + +void* ProgPowZCache::data() const +{ + return m_memory ? m_memory->raw() : nullptr; +} + + +static inline uint32_t clz(uint32_t a) +{ +#ifdef _MSC_VER + unsigned long index; + _BitScanReverse(&index, a); + return 31 - index; +#else + return __builtin_clz(a); +#endif +} + + +uint64_t ProgPowZCache::cache_size(uint32_t epoch) +{ + if (epoch >= sizeof(cache_sizes) / sizeof(cache_sizes[0])) { + return 0; + } + + return cache_sizes[epoch]; +} + + +uint64_t ProgPowZCache::dag_size(uint32_t epoch) +{ + if (epoch >= sizeof(dag_sizes) / sizeof(dag_sizes[0])) { + return 0; + } + + return dag_sizes[epoch]; +} + + +void ProgPowZCache::calculate_fast_mod_data(uint32_t divisor, uint32_t& reciprocal, uint32_t& increment, uint32_t& shift) +{ + if ((divisor & (divisor - 1)) == 0) { + reciprocal = 1; + increment = 0; + shift = 31U - clz(divisor); + } + else { + shift = 63U - clz(divisor); + const uint64_t N = 1ULL << shift; + const uint64_t q = N / divisor; + const uint64_t r = N - q * divisor; + if (r * 2 < divisor) + { + reciprocal = static_cast(q); + increment = 1; + } + else + { + reciprocal = static_cast(q + 1); + increment = 0; + } + } +} + + +} // namespace xmrig diff --git a/miner/core/src/crypto/progpowz/ProgPowZCache.h b/miner/core/src/crypto/progpowz/ProgPowZCache.h new file mode 100644 index 0000000..550082b --- /dev/null +++ b/miner/core/src/crypto/progpowz/ProgPowZCache.h @@ -0,0 +1,75 @@ +/* Miner + * Copyright (c) 2025 Lethean + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + */ + +#ifndef XMRIG_PROGPOWZ_CACHE_H +#define XMRIG_PROGPOWZ_CACHE_H + + +#include "base/tools/Object.h" +#include "crypto/progpowz/ProgPowZHash.h" +#include +#include + + +namespace xmrig +{ + + +class VirtualMemory; + + +class ProgPowZCache +{ +public: + // L1 cache size for ProgPowZ + static constexpr size_t l1_cache_size = ProgPowZHash::CACHE_BYTES; + static constexpr size_t l1_cache_num_items = l1_cache_size / sizeof(uint32_t); + static constexpr uint32_t num_dataset_parents = 512; + + XMRIG_DISABLE_COPY_MOVE(ProgPowZCache) + + ProgPowZCache(); + ~ProgPowZCache(); + + bool init(uint32_t epoch); + + void* data() const; + size_t size() const { return m_size; } + uint32_t epoch() const { return m_epoch; } + + const uint32_t* l1_cache() const { return m_DAGCache.data(); } + + static uint64_t cache_size(uint32_t epoch); + static uint64_t dag_size(uint32_t epoch); + + static void calculate_fast_mod_data(uint32_t divisor, uint32_t &reciprocal, uint32_t &increment, uint32_t& shift); + + static std::mutex s_cacheMutex; + static ProgPowZCache s_cache; + +private: + VirtualMemory* m_memory = nullptr; + size_t m_size = 0; + uint32_t m_epoch = 0xFFFFFFFFUL; + std::vector m_DAGCache; +}; + + +} /* namespace xmrig */ + + +#endif /* XMRIG_PROGPOWZ_CACHE_H */ diff --git a/miner/core/src/crypto/progpowz/ProgPowZHash.cpp b/miner/core/src/crypto/progpowz/ProgPowZHash.cpp new file mode 100644 index 0000000..d22528a --- /dev/null +++ b/miner/core/src/crypto/progpowz/ProgPowZHash.cpp @@ -0,0 +1,362 @@ +/* Miner + * Copyright (c) 2025 Lethean + * + * Based on XMRig KawPow implementation + * Copyright 2010 Jeff Garzik + * Copyright 2012-2014 pooler + * Copyright 2014 Lucas Jones + * Copyright 2014-2016 Wolf9466 + * Copyright 2016 Jay D Dee + * Copyright 2017-2019 XMR-Stak , + * Copyright 2018 Lee Clagett + * Copyright 2018-2019 tevador + * Copyright 2018-2023 SChernykh + * Copyright 2016-2023 XMRig , + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 "backend/cpu/Cpu.h" +#include "crypto/progpowz/ProgPowZHash.h" +#include "crypto/progpowz/ProgPowZCache.h" +#include "3rdparty/libethash/ethash.h" +#include "3rdparty/libethash/ethash_internal.h" +#include "3rdparty/libethash/data_sizes.h" + +#ifdef _MSC_VER +#include +#endif + +namespace xmrig { + + +// ProgPowZ uses empty/null padding instead of a coin identifier +static const uint32_t progpowz_padding[15] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + + +static const uint32_t fnv_prime = 0x01000193; +static const uint32_t fnv_offset_basis = 0x811c9dc5; + + +static inline uint32_t fnv1a(uint32_t u, uint32_t v) +{ + return (u ^ v) * fnv_prime; +} + + +static inline uint32_t kiss99(uint32_t& z, uint32_t& w, uint32_t& jsr, uint32_t& jcong) +{ + z = 36969 * (z & 0xffff) + (z >> 16); + w = 18000 * (w & 0xffff) + (w >> 16); + + jcong = 69069 * jcong + 1234567; + + jsr ^= (jsr << 17); + jsr ^= (jsr >> 13); + jsr ^= (jsr << 5); + + return (((z << 16) + w) ^ jcong) + jsr; +} + + +static inline uint32_t rotl(uint32_t n, uint32_t c) +{ +#ifdef _MSC_VER + return _rotl(n, c); +#else + c &= 31; + uint32_t neg_c = (uint32_t)(-(int32_t)c); + return (n << c) | (n >> (neg_c & 31)); +#endif +} + + +static inline uint32_t rotr(uint32_t n, uint32_t c) +{ +#ifdef _MSC_VER + return _rotr(n, c); +#else + c &= 31; + uint32_t neg_c = (uint32_t)(-(int32_t)c); + return (n >> c) | (n << (neg_c & 31)); +#endif +} + + +static inline void random_merge(uint32_t& a, uint32_t b, uint32_t selector) +{ + const uint32_t x = (selector >> 16) % 31 + 1; + switch (selector % 4) + { + case 0: + a = (a * 33) + b; + break; + case 1: + a = (a ^ b) * 33; + break; + case 2: + a = rotl(a, x) ^ b; + break; + case 3: + a = rotr(a, x) ^ b; + break; + default: +#ifdef _MSC_VER + __assume(false); +#else + __builtin_unreachable(); +#endif + break; + } +} + + +static inline uint32_t clz(uint32_t a) +{ +#ifdef _MSC_VER + unsigned long index; + _BitScanReverse(&index, a); + return a ? (31 - index) : 32; +#else + return a ? (uint32_t)__builtin_clz(a) : 32; +#endif +} + + +static inline uint32_t popcount(uint32_t a) +{ +#ifdef _MSC_VER + return __popcnt(a); +#else + return __builtin_popcount(a); +#endif +} + + +static inline uint32_t popcount_soft(uint64_t x) +{ + constexpr uint64_t m1 = 0x5555555555555555ull; + constexpr uint64_t m2 = 0x3333333333333333ull; + constexpr uint64_t m4 = 0x0f0f0f0f0f0f0f0full; + constexpr uint64_t h01 = 0x0101010101010101ull; + + x -= (x >> 1) & m1; + x = (x & m2) + ((x >> 2) & m2); + x = (x + (x >> 4)) & m4; + return (x * h01) >> 56; +} + + +static inline uint32_t random_math(uint32_t a, uint32_t b, uint32_t selector, bool has_popcnt) +{ + switch (selector % 11) + { + case 0: + return a + b; + case 1: + return a * b; + case 2: + return (uint64_t(a) * b) >> 32; + case 3: + return (a < b) ? a : b; + case 4: + return rotl(a, b); + case 5: + return rotr(a, b); + case 6: + return a & b; + case 7: + return a | b; + case 8: + return a ^ b; + case 9: + return clz(a) + clz(b); + case 10: + if (has_popcnt) + return popcount(a) + popcount(b); + else + return popcount_soft(a) + popcount_soft(b); + default: +#ifdef _MSC_VER + __assume(false); +#else + __builtin_unreachable(); +#endif + break; + } +} + + +void ProgPowZHash::calculate(const ProgPowZCache& light_cache, uint32_t block_height, const uint8_t (&header_hash)[32], uint64_t nonce, uint32_t (&output)[8], uint32_t (&mix_hash)[8]) +{ + uint32_t keccak_state[25]; + uint32_t mix[LANES][REGS]; + + memcpy(keccak_state, header_hash, sizeof(header_hash)); + memcpy(keccak_state + 8, &nonce, sizeof(nonce)); + memcpy(keccak_state + 10, progpowz_padding, sizeof(progpowz_padding)); + + ethash_keccakf800(keccak_state); + + uint32_t z = fnv1a(fnv_offset_basis, keccak_state[0]); + uint32_t w = fnv1a(z, keccak_state[1]); + uint32_t jsr, jcong; + + for (uint32_t l = 0; l < LANES; ++l) { + uint32_t z1 = z; + uint32_t w1 = w; + jsr = fnv1a(w, l); + jcong = fnv1a(jsr, l); + + for (uint32_t r = 0; r < REGS; ++r) { + mix[l][r] = kiss99(z1, w1, jsr, jcong); + } + } + + const uint32_t prog_number = block_height / PERIOD_LENGTH; + + uint32_t dst_seq[REGS]; + uint32_t src_seq[REGS]; + + z = fnv1a(fnv_offset_basis, prog_number); + w = fnv1a(z, 0); + jsr = fnv1a(w, prog_number); + jcong = fnv1a(jsr, 0); + + for (uint32_t i = 0; i < REGS; ++i) + { + dst_seq[i] = i; + src_seq[i] = i; + } + + for (uint32_t i = REGS; i > 1; --i) + { + std::swap(dst_seq[i - 1], dst_seq[kiss99(z, w, jsr, jcong) % i]); + std::swap(src_seq[i - 1], src_seq[kiss99(z, w, jsr, jcong) % i]); + } + + const uint32_t epoch = light_cache.epoch(); + const uint32_t num_items = static_cast(dag_sizes[epoch] / ETHASH_MIX_BYTES / 2); + + constexpr size_t num_words_per_lane = 256 / (sizeof(uint32_t) * LANES); + constexpr int max_operations = (CNT_CACHE > CNT_MATH) ? CNT_CACHE : CNT_MATH; + + ethash_light cache; + cache.cache = light_cache.data(); + cache.cache_size = light_cache.size(); + cache.block_number = block_height; + + cache.num_parent_nodes = cache.cache_size / sizeof(node); + ProgPowZCache::calculate_fast_mod_data(cache.num_parent_nodes, cache.reciprocal, cache.increment, cache.shift); + + uint32_t z0 = z; + uint32_t w0 = w; + uint32_t jsr0 = jsr; + uint32_t jcong0 = jcong; + + const bool has_popcnt = Cpu::info()->has(ICpuInfo::FLAG_POPCNT); + + for (uint32_t r = 0; r < ETHASH_ACCESSES; ++r) { + uint32_t item_index = (mix[r % LANES][0] % num_items) * 4; + + node item[4]; + ethash_calculate_dag_item4_opt(item, item_index, ProgPowZCache::num_dataset_parents, &cache); + + uint32_t dst_counter = 0; + uint32_t src_counter = 0; + + z = z0; + w = w0; + jsr = jsr0; + jcong = jcong0; + + for (uint32_t i = 0; i < max_operations; ++i) { + if (i < CNT_CACHE) { + const uint32_t src = src_seq[(src_counter++) % REGS]; + const uint32_t dst = dst_seq[(dst_counter++) % REGS]; + const uint32_t sel = kiss99(z, w, jsr, jcong); + for (uint32_t j = 0; j < LANES; ++j) { + random_merge(mix[j][dst], light_cache.l1_cache()[mix[j][src] % ProgPowZCache::l1_cache_num_items], sel); + } + } + + if (i < CNT_MATH) + { + const uint32_t src_rnd = kiss99(z, w, jsr, jcong) % (REGS * (REGS - 1)); + const uint32_t src1 = src_rnd % REGS; + uint32_t src2 = src_rnd / REGS; + if (src2 >= src1) { + ++src2; + } + + const uint32_t sel1 = kiss99(z, w, jsr, jcong); + const uint32_t dst = dst_seq[(dst_counter++) % REGS]; + const uint32_t sel2 = kiss99(z, w, jsr, jcong); + + for (size_t l = 0; l < LANES; ++l) + { + const uint32_t data = random_math(mix[l][src1], mix[l][src2], sel1, has_popcnt); + random_merge(mix[l][dst], data, sel2); + } + } + } + + uint32_t dsts[num_words_per_lane]; + uint32_t sels[num_words_per_lane]; + for (uint32_t i = 0; i < num_words_per_lane; ++i) { + dsts[i] = (i == 0) ? 0 : dst_seq[(dst_counter++) % REGS]; + sels[i] = kiss99(z, w, jsr, jcong); + } + + for (uint32_t l = 0; l < LANES; ++l) { + const uint32_t offset = ((l ^ r) % LANES) * num_words_per_lane; + for (size_t i = 0; i < num_words_per_lane; ++i) { + random_merge(mix[l][dsts[i]], ((uint32_t*)item)[offset + i], sels[i]); + } + } + } + + uint32_t lane_hash[LANES]; + for (uint32_t l = 0; l < LANES; ++l) + { + lane_hash[l] = fnv_offset_basis; + for (uint32_t i = 0; i < REGS; ++i) { + lane_hash[l] = fnv1a(lane_hash[l], mix[l][i]); + } + } + + constexpr uint32_t num_words = 8; + + for (uint32_t i = 0; i < num_words; ++i) { + mix_hash[i] = fnv_offset_basis; + } + + for (uint32_t l = 0; l < LANES; ++l) + mix_hash[l % num_words] = fnv1a(mix_hash[l % num_words], lane_hash[l]); + + memcpy(keccak_state + 8, mix_hash, sizeof(mix_hash)); + memcpy(keccak_state + 16, progpowz_padding, sizeof(uint32_t) * 9); + + ethash_keccakf800(keccak_state); + + memcpy(output, keccak_state, sizeof(output)); +} + + +} // namespace xmrig diff --git a/miner/core/src/crypto/progpowz/ProgPowZHash.h b/miner/core/src/crypto/progpowz/ProgPowZHash.h new file mode 100644 index 0000000..5149ff3 --- /dev/null +++ b/miner/core/src/crypto/progpowz/ProgPowZHash.h @@ -0,0 +1,58 @@ +/* Miner + * Copyright (c) 2025 Lethean + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + */ + +#ifndef XMRIG_PROGPOWZ_HASH_H +#define XMRIG_PROGPOWZ_HASH_H + + +#include + + +namespace xmrig +{ + + +class ProgPowZCache; + + +class ProgPowZHash +{ +public: + // ProgPowZ uses standard Ethash epoch length (30000 blocks) + static constexpr uint32_t EPOCH_LENGTH = 30000; + + // ProgPowZ period - blocks before changing the random program + // Zano uses 50 (vs 3 for KawPow) + static constexpr uint32_t PERIOD_LENGTH = 50; + + // ProgPowZ algorithm parameters + static constexpr int CNT_CACHE = 12; // vs 11 for KawPow + static constexpr int CNT_MATH = 20; // vs 18 for KawPow + static constexpr uint32_t REGS = 32; + static constexpr uint32_t LANES = 16; + static constexpr uint32_t DAG_LOADS = 4; + static constexpr uint32_t CNT_DAG = 64; + static constexpr size_t CACHE_BYTES = 16384; + + static void calculate(const ProgPowZCache& light_cache, uint32_t block_height, const uint8_t (&header_hash)[32], uint64_t nonce, uint32_t (&output)[8], uint32_t (&mix_hash)[8]); +}; + + +} // namespace xmrig + + +#endif // XMRIG_PROGPOWZ_HASH_H