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:
parent
353afe46ae
commit
b7af288374
10 changed files with 746 additions and 2 deletions
|
|
@ -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)
|
||||
|
|
|
|||
21
miner/core/cmake/progpowz.cmake
Normal file
21
miner/core/cmake/progpowz.cmake
Normal 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()
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
178
miner/core/src/crypto/progpowz/ProgPowZCache.cpp
Normal file
178
miner/core/src/crypto/progpowz/ProgPowZCache.cpp
Normal 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
|
||||
75
miner/core/src/crypto/progpowz/ProgPowZCache.h
Normal file
75
miner/core/src/crypto/progpowz/ProgPowZCache.h
Normal 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 */
|
||||
362
miner/core/src/crypto/progpowz/ProgPowZHash.cpp
Normal file
362
miner/core/src/crypto/progpowz/ProgPowZHash.cpp
Normal 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
|
||||
58
miner/core/src/crypto/progpowz/ProgPowZHash.h
Normal file
58
miner/core/src/crypto/progpowz/ProgPowZHash.h
Normal 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
|
||||
Loading…
Add table
Reference in a new issue