feat: Add ETChash/Ethash algorithm support

- 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 <noreply@anthropic.com>
This commit is contained in:
snider 2025-12-30 20:08:36 +00:00
parent 327a4968e1
commit 353afe46ae
10 changed files with 565 additions and 2 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -82,6 +82,8 @@ public:
AR2_CHUKWA_V2 = 0x61140000, // "argon2/chukwav2" Argon2id (Chukwa v2).
AR2_WRKZ = 0x61120000, // "argon2/wrkz" Argon2id (WRKZ)
KAWPOW_RVN = 0x6b0f0000, // "kawpow/rvn" KawPow (RVN)
ETCHASH_ETC = 0x65100000, // "etchash" ETChash (ETC)
ETHASH_ETH = 0x65100001, // "ethash" Ethash (ETH)
};
enum Family : uint32_t {
@ -95,7 +97,8 @@ public:
RANDOM_X = 0x72000000,
ARGON2 = 0x61000000,
KAWPOW = 0x6b000000,
GHOSTRIDER = 0x6c000000
GHOSTRIDER = 0x6c000000,
ETCHASH = 0x65000000
};
static const char *kINVALID;
@ -163,6 +166,13 @@ public:
static const char* kGHOSTRIDER_RTM;
# endif
# ifdef XMRIG_ALGO_ETCHASH
static const char *kETCHASH;
static const char *kETCHASH_ETC;
static const char *kETHASH;
static const char *kETHASH_ETH;
# 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,134 @@
/* 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 "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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef XMRIG_ETC_CACHE_H
#define XMRIG_ETC_CACHE_H
#include "base/tools/Object.h"
#include <mutex>
#include <vector>
#include <cstdint>
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 */

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<uint32_t>(blockNumber / EPOCH_LENGTH_OLD);
}
// After ECIP-1099 activation, epoch increases every 60000 blocks
return ECIP1099_ACTIVATION_EPOCH +
static_cast<uint32_t>((blockNumber - ECIP1099_ACTIVATION_BLOCK) / EPOCH_LENGTH_NEW);
}
uint64_t ETChash::epochStartBlock(uint32_t epoch)
{
if (epoch < ECIP1099_ACTIVATION_EPOCH) {
return static_cast<uint64_t>(epoch) * EPOCH_LENGTH_OLD;
}
// After ECIP-1099
return ECIP1099_ACTIVATION_BLOCK +
static_cast<uint64_t>(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<uint32_t>(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<uint32_t>(q);
lightCache.increment = 1;
} else {
lightCache.reciprocal = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(q);
lightCache.increment = 1;
} else {
lightCache.reciprocal = static_cast<uint32_t>(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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef XMRIG_ETCHASH_H
#define XMRIG_ETCHASH_H
#include <cstdint>
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<uint32_t>(blockNumber / EPOCH_LENGTH); }
static uint64_t epochStartBlock(uint32_t epoch) { return static_cast<uint64_t>(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