feat: Add ProgPowZ algorithm support (Zano)

- Add core crypto implementation (ProgPowZHash.cpp, ProgPowZCache.cpp)
- ProgPowZ uses standard Ethash 30000 block epochs
- Period length 50 (vs 3 for KawPow), CNT_CACHE 12, CNT_MATH 20
- Integrate with OpenCL and CUDA backends for memory calculation
- Register PROGPOWZ_ZANO algorithm ID (0x70100000)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
snider 2025-12-30 20:19:54 +00:00
parent 353afe46ae
commit b7af288374
10 changed files with 746 additions and 2 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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,

View file

@ -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,

View file

@ -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<uint32_t, const char *> 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<const char *, Algorithm::Id, aliasCompare> 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> xmrig::Algorithm::all(const std::function<bool(con
AR2_CHUKWA, AR2_CHUKWA_V2, AR2_WRKZ,
KAWPOW_RVN,
GHOSTRIDER_RTM,
ETCHASH_ETC, ETHASH_ETH
ETCHASH_ETC, ETHASH_ETH,
PROGPOWZ_ZANO
};
Algorithms out;

View file

@ -84,6 +84,7 @@ public:
KAWPOW_RVN = 0x6b0f0000, // "kawpow/rvn" KawPow (RVN)
ETCHASH_ETC = 0x65100000, // "etchash" ETChash (ETC)
ETHASH_ETH = 0x65100001, // "ethash" Ethash (ETH)
PROGPOWZ_ZANO = 0x70100000, // "progpowz" ProgPowZ (ZANO)
};
enum Family : uint32_t {
@ -98,7 +99,8 @@ public:
ARGON2 = 0x61000000,
KAWPOW = 0x6b000000,
GHOSTRIDER = 0x6c000000,
ETCHASH = 0x65000000
ETCHASH = 0x65000000,
PROGPOWZ = 0x70000000
};
static const char *kINVALID;
@ -173,6 +175,11 @@ public:
static const char *kETHASH_ETH;
# endif
# ifdef XMRIG_ALGO_PROGPOWZ
static const char *kPROGPOWZ;
static const char *kPROGPOWZ_ZANO;
# endif
inline Algorithm() = default;
inline Algorithm(const char *algo) : m_id(parse(algo)) {}
inline Algorithm(Id id) : m_id(id) {}

View file

@ -0,0 +1,178 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include <cinttypes>
#include <algorithm>
#include <thread>
#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<std::thread> 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<uint32_t>(q);
increment = 1;
}
else
{
reciprocal = static_cast<uint32_t>(q + 1);
increment = 0;
}
}
}
} // namespace xmrig

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef XMRIG_PROGPOWZ_CACHE_H
#define XMRIG_PROGPOWZ_CACHE_H
#include "base/tools/Object.h"
#include "crypto/progpowz/ProgPowZHash.h"
#include <mutex>
#include <vector>
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<uint32_t> m_DAGCache;
};
} /* namespace xmrig */
#endif /* XMRIG_PROGPOWZ_CACHE_H */

View file

@ -0,0 +1,362 @@
/* Miner
* Copyright (c) 2025 Lethean
*
* Based on XMRig KawPow implementation
* Copyright 2010 Jeff Garzik <jgarzik@pobox.com>
* Copyright 2012-2014 pooler <pooler@litecoinpool.org>
* Copyright 2014 Lucas Jones <https://github.com/lucasjones>
* Copyright 2014-2016 Wolf9466 <https://github.com/OhGodAPet>
* Copyright 2016 Jay D Dee <jayddee246@gmail.com>
* Copyright 2017-2019 XMR-Stak <https://github.com/fireice-uk>, <https://github.com/psychocrypt>
* Copyright 2018 Lee Clagett <https://github.com/vtnerd>
* Copyright 2018-2019 tevador <tevador@gmail.com>
* Copyright 2018-2023 SChernykh <https://github.com/SChernykh>
* Copyright 2016-2023 XMRig <https://github.com/xmrig>, <support@xmrig.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <intrin.h>
#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<uint32_t>(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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef XMRIG_PROGPOWZ_HASH_H
#define XMRIG_PROGPOWZ_HASH_H
#include <cstdint>
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