From 353afe46ae20a85b7fee6837cd3b5d6c692e3637 Mon Sep 17 00:00:00 2001 From: snider Date: Tue, 30 Dec 2025 20:08:36 +0000 Subject: [PATCH] feat: Add ETChash/Ethash algorithm support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add core crypto implementation (ETChash.cpp, ETCCache.cpp) - Implement ECIP-1099 epoch calculation for Ethereum Classic - Add Ethash support with standard 30000 block epochs - Integrate with OpenCL and CUDA backends for memory calculation - Register ETCHASH_ETC and ETHASH_ETH algorithm IDs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- miner/core/CMakeLists.txt | 2 + miner/core/cmake/etchash.cmake | 21 +++ miner/core/src/backend/cuda/CudaBackend.cpp | 15 ++ miner/core/src/backend/opencl/OclBackend.cpp | 15 ++ miner/core/src/base/crypto/Algorithm.cpp | 23 ++- miner/core/src/base/crypto/Algorithm.h | 12 +- miner/core/src/crypto/etchash/ETCCache.cpp | 134 +++++++++++++ miner/core/src/crypto/etchash/ETCCache.h | 78 ++++++++ miner/core/src/crypto/etchash/ETChash.cpp | 187 +++++++++++++++++++ miner/core/src/crypto/etchash/ETChash.h | 80 ++++++++ 10 files changed, 565 insertions(+), 2 deletions(-) create mode 100644 miner/core/cmake/etchash.cmake create mode 100644 miner/core/src/crypto/etchash/ETCCache.cpp create mode 100644 miner/core/src/crypto/etchash/ETCCache.h create mode 100644 miner/core/src/crypto/etchash/ETChash.cpp create mode 100644 miner/core/src/crypto/etchash/ETChash.h diff --git a/miner/core/CMakeLists.txt b/miner/core/CMakeLists.txt index d3caef0..a9cb787 100644 --- a/miner/core/CMakeLists.txt +++ b/miner/core/CMakeLists.txt @@ -9,6 +9,7 @@ option(WITH_CN_FEMTO "Enable CryptoNight-UPX2 algorithm" ON) 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_GHOSTRIDER "Enable GhostRider algorithm" ON) option(WITH_HTTP "Enable HTTP protocol support (client/server)" ON) option(WITH_DEBUG_LOG "Enable debug log output" OFF) @@ -201,6 +202,7 @@ include(cmake/flags.cmake) include(cmake/randomx.cmake) include(cmake/argon2.cmake) include(cmake/kawpow.cmake) +include(cmake/etchash.cmake) include(cmake/ghostrider.cmake) include(cmake/OpenSSL.cmake) include(cmake/asm.cmake) diff --git a/miner/core/cmake/etchash.cmake b/miner/core/cmake/etchash.cmake new file mode 100644 index 0000000..3300c86 --- /dev/null +++ b/miner/core/cmake/etchash.cmake @@ -0,0 +1,21 @@ +if (WITH_ETCHASH) + add_definitions(/DXMRIG_ALGO_ETCHASH) + + list(APPEND HEADERS_CRYPTO + src/crypto/etchash/ETChash.h + src/crypto/etchash/ETCCache.h + ) + + list(APPEND SOURCES_CRYPTO + src/crypto/etchash/ETChash.cpp + src/crypto/etchash/ETCCache.cpp + ) + + # ETChash uses the same libethash library as KawPow + if (NOT WITH_KAWPOW) + add_subdirectory(src/3rdparty/libethash) + set(ETHASH_LIBRARY ethash) + endif() +else() + remove_definitions(/DXMRIG_ALGO_ETCHASH) +endif() diff --git a/miner/core/src/backend/cuda/CudaBackend.cpp b/miner/core/src/backend/cuda/CudaBackend.cpp index 621563e..226132e 100644 --- a/miner/core/src/backend/cuda/CudaBackend.cpp +++ b/miner/core/src/backend/cuda/CudaBackend.cpp @@ -46,6 +46,12 @@ #endif +#ifdef XMRIG_ALGO_ETCHASH +# include "crypto/etchash/ETCCache.h" +# include "crypto/etchash/ETChash.h" +#endif + + #ifdef XMRIG_FEATURE_API # include "base/api/interfaces/IApiRequest.h" #endif @@ -226,6 +232,15 @@ public: } # endif +# ifdef XMRIG_ALGO_ETCHASH + if (algo.family() == Algorithm::ETCHASH) { + const uint32_t epoch = (algo == Algorithm::ETCHASH_ETC) + ? ETChash::epoch(job.height()) + : Ethash::epoch(job.height()); + mem_used = (ETCCache::dagSize(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 820c63d..aa26d45 100644 --- a/miner/core/src/backend/opencl/OclBackend.cpp +++ b/miner/core/src/backend/opencl/OclBackend.cpp @@ -47,6 +47,12 @@ #endif +#ifdef XMRIG_ALGO_ETCHASH +# include "crypto/etchash/ETCCache.h" +# include "crypto/etchash/ETChash.h" +#endif + + #ifdef XMRIG_FEATURE_API # include "base/api/interfaces/IApiRequest.h" #endif @@ -214,6 +220,15 @@ public: } # endif +# ifdef XMRIG_ALGO_ETCHASH + if (algo.family() == Algorithm::ETCHASH) { + const uint32_t epoch = (algo == Algorithm::ETCHASH_ETC) + ? ETChash::epoch(job.height()) + : Ethash::epoch(job.height()); + mem_used = (ETCCache::cacheSize(epoch) + ETCCache::dagSize(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 e7c4f55..da0675c 100644 --- a/miner/core/src/base/crypto/Algorithm.cpp +++ b/miner/core/src/base/crypto/Algorithm.cpp @@ -101,6 +101,13 @@ const char* Algorithm::kGHOSTRIDER = "ghostrider"; const char* Algorithm::kGHOSTRIDER_RTM = "ghostrider"; #endif +#ifdef XMRIG_ALGO_ETCHASH +const char *Algorithm::kETCHASH = "etchash"; +const char *Algorithm::kETCHASH_ETC = "etchash"; +const char *Algorithm::kETHASH = "ethash"; +const char *Algorithm::kETHASH_ETH = "ethash"; +#endif + #define ALGO_NAME(ALGO) { Algorithm::ALGO, Algorithm::k##ALGO } #define ALGO_ALIAS(ALGO, NAME) { NAME, Algorithm::ALGO } @@ -163,6 +170,11 @@ static const std::map kAlgorithmNames = { # ifdef XMRIG_ALGO_GHOSTRIDER ALGO_NAME(GHOSTRIDER_RTM), # endif + +# ifdef XMRIG_ALGO_ETCHASH + ALGO_NAME(ETCHASH_ETC), + ALGO_NAME(ETHASH_ETH), +# endif }; @@ -279,6 +291,14 @@ static const std::map kAlgorithmAlias ALGO_ALIAS_AUTO(GHOSTRIDER_RTM), ALGO_ALIAS(GHOSTRIDER_RTM, "ghostrider/rtm"), ALGO_ALIAS(GHOSTRIDER_RTM, "gr"), # endif + +# ifdef XMRIG_ALGO_ETCHASH + ALGO_ALIAS_AUTO(ETCHASH_ETC), ALGO_ALIAS(ETCHASH_ETC, "etchash/etc"), + ALGO_ALIAS(ETCHASH_ETC, "etc"), + ALGO_ALIAS_AUTO(ETHASH_ETH), ALGO_ALIAS(ETHASH_ETH, "ethash/eth"), + ALGO_ALIAS(ETHASH_ETH, "eth"), + ALGO_ALIAS(ETHASH_ETH, "daggerhashimoto"), +# endif }; @@ -353,7 +373,8 @@ std::vector xmrig::Algorithm::all(const std::function. + */ + + +#include + +#include "crypto/etchash/ETCCache.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 ETCCache::s_cacheMutex; +ETCCache ETCCache::s_etcCache; +ETCCache ETCCache::s_ethCache; + + +ETCCache::ETCCache() +{ +} + + +ETCCache::~ETCCache() +{ + delete m_memory; +} + + +bool ETCCache::init(uint32_t epoch, bool isETC) +{ + if (epoch >= sizeof(cache_sizes) / sizeof(cache_sizes[0])) { + return false; + } + + if (m_epoch == epoch && m_isETC == isETC) { + 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); + } + + // Calculate seed hash for this epoch + uint8_t seed[32]; + seedHash(epoch, seed); + + ethash_h256_t seedhash; + memcpy(seedhash.b, seed, 32); + + ethash_compute_cache_nodes(m_memory->raw(), size, &seedhash); + + m_size = size; + m_epoch = epoch; + m_isETC = isETC; + + const char* algoName = isETC ? "ETChash" : "Ethash"; + LOG_INFO("%s " YELLOW("%s") " light cache for epoch " WHITE_BOLD("%u") " calculated " BLACK_BOLD("(%" PRIu64 "ms)"), + Tags::miner(), algoName, epoch, Chrono::steadyMSecs() - start_ms); + + return true; +} + + +void* ETCCache::data() const +{ + return m_memory ? m_memory->raw() : nullptr; +} + + +uint64_t ETCCache::cacheSize(uint32_t epoch) +{ + if (epoch >= sizeof(cache_sizes) / sizeof(cache_sizes[0])) { + return 0; + } + + return cache_sizes[epoch]; +} + + +uint64_t ETCCache::dagSize(uint32_t epoch) +{ + if (epoch >= sizeof(dag_sizes) / sizeof(dag_sizes[0])) { + return 0; + } + + return dag_sizes[epoch]; +} + + +void ETCCache::seedHash(uint32_t epoch, uint8_t (&seed)[32]) +{ + // Seed hash starts as zeros + memset(seed, 0, 32); + + // Each epoch, seed = keccak256(previous_seed) + for (uint32_t i = 0; i < epoch; ++i) { + ethash_h256_t hash; + memcpy(hash.b, seed, 32); + hash = ethash_get_seedhash(i + 1); + memcpy(seed, hash.b, 32); + } + + // Actually just use libethash's function directly + ethash_h256_t hash = ethash_get_seedhash(epoch); + memcpy(seed, hash.b, 32); +} + + +} // namespace xmrig diff --git a/miner/core/src/crypto/etchash/ETCCache.h b/miner/core/src/crypto/etchash/ETCCache.h new file mode 100644 index 0000000..6355299 --- /dev/null +++ b/miner/core/src/crypto/etchash/ETCCache.h @@ -0,0 +1,78 @@ +/* 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_ETC_CACHE_H +#define XMRIG_ETC_CACHE_H + + +#include "base/tools/Object.h" +#include +#include +#include + + +namespace xmrig +{ + + +class VirtualMemory; + + +class ETCCache +{ +public: + // Ethash cache item size = 64 bytes (HASH_BYTES) + static constexpr size_t HASH_BYTES = 64; + + XMRIG_DISABLE_COPY_MOVE(ETCCache) + + ETCCache(); + ~ETCCache(); + + // Initialize cache for given epoch + bool init(uint32_t epoch, bool isETC = true); + + // Access cache data + void* data() const; + size_t size() const { return m_size; } + uint32_t epoch() const { return m_epoch; } + bool isETC() const { return m_isETC; } + + // Calculate cache and DAG sizes for epoch + static uint64_t cacheSize(uint32_t epoch); + static uint64_t dagSize(uint32_t epoch); + + // Get seed hash for epoch + static void seedHash(uint32_t epoch, uint8_t (&seed)[32]); + + // Singleton instances + static std::mutex s_cacheMutex; + static ETCCache s_etcCache; // For ETC (ETChash) + static ETCCache s_ethCache; // For ETH (Ethash) + +private: + VirtualMemory* m_memory = nullptr; + size_t m_size = 0; + uint32_t m_epoch = 0xFFFFFFFFUL; + bool m_isETC = true; +}; + + +} /* namespace xmrig */ + + +#endif /* XMRIG_ETC_CACHE_H */ diff --git a/miner/core/src/crypto/etchash/ETChash.cpp b/miner/core/src/crypto/etchash/ETChash.cpp new file mode 100644 index 0000000..967e9d7 --- /dev/null +++ b/miner/core/src/crypto/etchash/ETChash.cpp @@ -0,0 +1,187 @@ +/* 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 . + */ + + +#include "crypto/etchash/ETChash.h" +#include "crypto/etchash/ETCCache.h" +#include "3rdparty/libethash/ethash.h" +#include "3rdparty/libethash/ethash_internal.h" +#include "3rdparty/libethash/data_sizes.h" + + +namespace xmrig { + + +// ECIP-1099: Calculate epoch from block number for Ethereum Classic +// Before activation: epoch = block / 30000 +// After activation: epoch = 390 + (block - 11700000) / 60000 +uint32_t ETChash::epoch(uint64_t blockNumber) +{ + if (blockNumber < ECIP1099_ACTIVATION_BLOCK) { + return static_cast(blockNumber / EPOCH_LENGTH_OLD); + } + + // After ECIP-1099 activation, epoch increases every 60000 blocks + return ECIP1099_ACTIVATION_EPOCH + + static_cast((blockNumber - ECIP1099_ACTIVATION_BLOCK) / EPOCH_LENGTH_NEW); +} + + +uint64_t ETChash::epochStartBlock(uint32_t epoch) +{ + if (epoch < ECIP1099_ACTIVATION_EPOCH) { + return static_cast(epoch) * EPOCH_LENGTH_OLD; + } + + // After ECIP-1099 + return ECIP1099_ACTIVATION_BLOCK + + static_cast(epoch - ECIP1099_ACTIVATION_EPOCH) * EPOCH_LENGTH_NEW; +} + + +void ETChash::calculate(const ETCCache& cache, uint64_t blockNumber, + const uint8_t (&headerHash)[32], uint64_t nonce, + uint8_t (&output)[32], uint8_t (&mixHash)[32]) +{ + const uint32_t epochNum = cache.epoch(); + + // Get DAG size for this epoch + const uint64_t fullSize = dag_sizes[epochNum]; + + // Setup light cache structure for libethash + ethash_light lightCache; + lightCache.cache = cache.data(); + lightCache.cache_size = cache.size(); + lightCache.block_number = blockNumber; + + // Calculate fast mod data for optimized DAG item calculation + lightCache.num_parent_nodes = static_cast(cache.size() / sizeof(node)); + + // Calculate reciprocal, increment, shift for fast modulo + uint32_t divisor = lightCache.num_parent_nodes; + if ((divisor & (divisor - 1)) == 0) { + // Power of 2 + lightCache.reciprocal = 1; + lightCache.increment = 0; + uint32_t shift = 0; + uint32_t temp = divisor; + while (temp > 1) { + temp >>= 1; + shift++; + } + lightCache.shift = shift; + } else { + // Use fast division algorithm + uint32_t shift = 31; + uint32_t temp = divisor; + while (temp > 0) { + temp >>= 1; + if (temp > 0) shift++; + } + shift = 63 - (31 - shift); + + const uint64_t N = 1ULL << shift; + const uint64_t q = N / divisor; + const uint64_t r = N - q * divisor; + + if (r * 2 < divisor) { + lightCache.reciprocal = static_cast(q); + lightCache.increment = 1; + } else { + lightCache.reciprocal = static_cast(q + 1); + lightCache.increment = 0; + } + lightCache.shift = shift; + } + + // Convert header hash to libethash format + ethash_h256_t header; + memcpy(header.b, headerHash, 32); + + // Compute the Ethash using light client verification + ethash_return_value_t result = ethash_light_compute_internal(&lightCache, fullSize, header, nonce); + + // Copy results + memcpy(output, result.result.b, 32); + memcpy(mixHash, result.mix_hash.b, 32); +} + + +// Ethash (standard Ethereum) - uses fixed 30000 block epochs +void Ethash::calculate(const ETCCache& cache, uint64_t blockNumber, + const uint8_t (&headerHash)[32], uint64_t nonce, + uint8_t (&output)[32], uint8_t (&mixHash)[32]) +{ + const uint32_t epochNum = cache.epoch(); + + // Get DAG size for this epoch + const uint64_t fullSize = dag_sizes[epochNum]; + + // Setup light cache structure for libethash + ethash_light lightCache; + lightCache.cache = cache.data(); + lightCache.cache_size = cache.size(); + lightCache.block_number = blockNumber; + + // Calculate fast mod data + lightCache.num_parent_nodes = static_cast(cache.size() / sizeof(node)); + + uint32_t divisor = lightCache.num_parent_nodes; + if ((divisor & (divisor - 1)) == 0) { + lightCache.reciprocal = 1; + lightCache.increment = 0; + uint32_t shift = 0; + uint32_t temp = divisor; + while (temp > 1) { + temp >>= 1; + shift++; + } + lightCache.shift = shift; + } else { + uint32_t shift = 31; + uint32_t temp = divisor; + while (temp > 0) { + temp >>= 1; + if (temp > 0) shift++; + } + shift = 63 - (31 - shift); + + const uint64_t N = 1ULL << shift; + const uint64_t q = N / divisor; + const uint64_t r = N - q * divisor; + + if (r * 2 < divisor) { + lightCache.reciprocal = static_cast(q); + lightCache.increment = 1; + } else { + lightCache.reciprocal = static_cast(q + 1); + lightCache.increment = 0; + } + lightCache.shift = shift; + } + + ethash_h256_t header; + memcpy(header.b, headerHash, 32); + + ethash_return_value_t result = ethash_light_compute_internal(&lightCache, fullSize, header, nonce); + + memcpy(output, result.result.b, 32); + memcpy(mixHash, result.mix_hash.b, 32); +} + + +} // namespace xmrig diff --git a/miner/core/src/crypto/etchash/ETChash.h b/miner/core/src/crypto/etchash/ETChash.h new file mode 100644 index 0000000..d6e2025 --- /dev/null +++ b/miner/core/src/crypto/etchash/ETChash.h @@ -0,0 +1,80 @@ +/* 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_ETCHASH_H +#define XMRIG_ETCHASH_H + + +#include + + +namespace xmrig +{ + + +class ETCCache; + + +class ETChash +{ +public: + // ETChash constants + // ECIP-1099: After epoch 390, epoch length changes to 60000 blocks + static constexpr uint32_t EPOCH_LENGTH_OLD = 30000; // Before ECIP-1099 + static constexpr uint32_t EPOCH_LENGTH_NEW = 60000; // After ECIP-1099 + static constexpr uint32_t ECIP1099_ACTIVATION_EPOCH = 390; + static constexpr uint32_t ECIP1099_ACTIVATION_BLOCK = 11700000; // 390 * 30000 + + // Ethash core constants (shared with libethash) + static constexpr uint32_t MIX_BYTES = 128; + static constexpr uint32_t HASH_BYTES = 64; + static constexpr uint32_t DATASET_PARENTS = 256; + static constexpr uint32_t CACHE_ROUNDS = 3; + static constexpr uint32_t ACCESSES = 64; + + // Calculate epoch from block number (accounts for ECIP-1099) + static uint32_t epoch(uint64_t blockNumber); + + // Calculate block number at start of epoch + static uint64_t epochStartBlock(uint32_t epoch); + + // Calculate hash + static void calculate(const ETCCache& cache, uint64_t blockNumber, + const uint8_t (&headerHash)[32], uint64_t nonce, + uint8_t (&output)[32], uint8_t (&mixHash)[32]); +}; + + +// Ethash class - identical to ETChash but with standard epoch length +class Ethash +{ +public: + static constexpr uint32_t EPOCH_LENGTH = 30000; + + static uint32_t epoch(uint64_t blockNumber) { return static_cast(blockNumber / EPOCH_LENGTH); } + static uint64_t epochStartBlock(uint32_t epoch) { return static_cast(epoch) * EPOCH_LENGTH; } + + static void calculate(const ETCCache& cache, uint64_t blockNumber, + const uint8_t (&headerHash)[32], uint64_t nonce, + uint8_t (&output)[32], uint8_t (&mixHash)[32]); +}; + + +} // namespace xmrig + + +#endif // XMRIG_ETCHASH_H