Fixing miner issues

- Switch from full-block to header-only (140-byte) RandomX input
- Add 32-byte SoloNonce system for solo mining mode
- Compute proper difficulty target from compact bits field
- Add SHA256D dual-hash PoW check in CpuWorker for solo mining
- Raise RandomX dataset/scratchpad limits to 4GB/4MB
- Use standard RandomX share filtering in pool (stratum) mode
This commit is contained in:
2026-03-05 06:00:55 -06:00
parent a27a1e327b
commit 95d3ff2c4a
12 changed files with 529 additions and 185 deletions

View File

@@ -91,6 +91,7 @@ set(HEADERS_CRYPTO
src/crypto/common/HugePagesInfo.h
src/crypto/common/MemoryPool.h
src/crypto/common/Nonce.h
src/crypto/common/SoloNonce.h
src/crypto/common/portable/mm_malloc.h
src/crypto/common/VirtualMemory.h
)
@@ -128,6 +129,7 @@ set(SOURCES_CRYPTO
src/crypto/common/HugePagesInfo.cpp
src/crypto/common/MemoryPool.cpp
src/crypto/common/Nonce.cpp
src/crypto/common/SoloNonce.cpp
src/crypto/common/VirtualMemory.cpp
)

View File

@@ -32,6 +32,7 @@
#include "base/net/stratum/Job.h"
#include "base/tools/Alignment.h"
#include "crypto/common/Nonce.h"
#include "crypto/common/SoloNonce.h"
namespace xmrig {
@@ -89,6 +90,29 @@ public:
inline int32_t nonceOffset() const { return currentJob().nonceOffset(); }
inline size_t nonceSize() const { return currentJob().nonceSize(); }
// Solo mining nonce management
inline bool isSoloMining() const { return m_isSoloMining; }
inline void setSoloMining(bool solo) { m_isSoloMining = solo; }
inline uint8_t* soloNonce(size_t i = 0) { return m_soloNonces[index()] + i * 32; }
inline const uint8_t* soloNonce(size_t i = 0) const { return m_soloNonces[index()] + i * 32; }
inline void initSoloNonces() {
for (size_t i = 0; i < N; ++i) {
SoloNonce::initialize(soloNonce(i));
// Copy to blob at nonce offset
SoloNonce::copyToBlob(blob() + (i * currentJob().size()) + nonceOffset(), 0, soloNonce(i));
}
}
inline bool nextRoundSolo() {
for (size_t i = 0; i < N; ++i) {
SoloNonce::increment(soloNonce(i));
// Copy to blob at nonce offset
SoloNonce::copyToBlob(blob() + (i * currentJob().size()) + nonceOffset(), 0, soloNonce(i));
}
return true; // Never exhausted with 256-bit nonces
}
private:
inline uint64_t nonceMask() const { return m_nonce_mask[index()]; }
@@ -115,6 +139,10 @@ private:
uint64_t m_nonce_mask[2] = { 0, 0 };
uint64_t m_sequence = 0;
uint8_t m_index = 0;
// Solo mining nonces (32 bytes per job slot per N)
alignas(8) uint8_t m_soloNonces[2][32 * N]{};
bool m_isSoloMining = false;
};

View File

@@ -40,6 +40,35 @@
#include "net/JobResults.h"
#ifdef XMRIG_FEATURE_TLS
# include <openssl/sha.h>
#else
// Standalone SHA-256 for non-TLS builds
# ifdef _WIN32
# include <windows.h>
# include <bcrypt.h>
static void SHA256(const uint8_t* data, size_t len, uint8_t* out) {
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_HASH_HANDLE hHash = nullptr;
BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0);
BCryptHashData(hHash, const_cast<PUCHAR>(data), static_cast<ULONG>(len), 0);
BCryptFinishHash(hHash, out, 32, 0);
BCryptDestroyHash(hHash);
BCryptCloseAlgorithmProvider(hAlg, 0);
}
# else
# include "crypto/ghostrider/sph_sha2.h"
static void SHA256(const uint8_t* data, size_t len, uint8_t* out) {
sph_sha256_context ctx;
sph_sha256_init(&ctx);
sph_sha256(&ctx, data, len);
sph_sha256_close(&ctx, out);
}
# endif
#endif
#ifdef XMRIG_ALGO_RANDOMX
# include "crypto/randomx/randomx.h"
#endif
@@ -55,6 +84,37 @@ namespace xmrig {
static constexpr uint32_t kReserveCount = 32768;
// ── DRAGONX/HUSH PoW hash ────────────────────────────────────────────────────
//
// DragonX block difficulty is checked against double_sha256(173-byte header),
// NOT the RandomX hash directly (unlike Monero).
//
// Full 173-byte header structure:
// [0:108] header_base = version(4) + prevhash(32) + merkle(32)
// + commitments(32) + time(4) + bits(4)
// [108:140] nonce = 32-byte miner nonce
// [140] 0x20 = compact_size for 32-byte solution
// [141:173] rx_solution = RandomX hash result (32 bytes)
//
// @param blob 140-byte header WITH nonce already at bytes [108:140]
// @param rx_hash 32-byte RandomX hash result
// @param out 32-byte output buffer for double_sha256
static inline void dragonx_pow_hash(const uint8_t* blob, const uint8_t* rx_hash, uint8_t* out)
{
uint8_t full_header[173];
memcpy(full_header, blob, 140); // header (108 bytes) + nonce (32 bytes)
full_header[140] = 0x20; // compact_size = 32 (solution length)
memcpy(full_header + 141, rx_hash, 32); // RandomX hash = the PoW solution
// double SHA256 = SHA256(SHA256(full_header))
uint8_t tmp[32];
SHA256(full_header, 173, tmp);
SHA256(tmp, 32, out);
}
// ─────────────────────────────────────────────────────────────────────────────
#ifdef XMRIG_ALGO_CN_HEAVY
static std::mutex cn_heavyZen3MemoryMutex;
VirtualMemory* cn_heavyZen3Memory = nullptr;
@@ -267,8 +327,13 @@ void xmrig::CpuWorker<N>::start()
}
uint32_t current_job_nonces[N];
alignas(8) uint8_t current_solo_nonces[N * 32];
for (size_t i = 0; i < N; ++i) {
current_job_nonces[i] = readUnaligned(m_job.nonce(i));
// Save solo nonces BEFORE they get incremented by nextRound()
if (m_job.isSoloMining()) {
memcpy(current_solo_nonces + i * 32, m_job.soloNonce(i), 32);
}
}
# ifdef XMRIG_FEATURE_BENCHMARK
@@ -337,18 +402,50 @@ void xmrig::CpuWorker<N>::start()
if (valid) {
for (size_t i = 0; i < N; ++i) {
const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
# ifdef XMRIG_FEATURE_BENCHMARK
if (m_benchSize) {
const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
if (current_job_nonces[i] < m_benchSize) {
BenchState::add(value);
}
continue;
}
else
# endif
if (value < job.target()) {
JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), job.hasMinerSignature() ? miner_signature_saved : nullptr);
if (job.algorithm() == Algorithm::RX_HUSH && m_job.isSoloMining()) {
// ── DRAGONX/HUSH solo mining: use double_sha256(173-byte header) for difficulty ──
//
// The daemon checks GetHash() = double_sha256(173 bytes) < target,
// NOT the RandomX hash directly. We must filter shares the same way
// so that every submitted share is a genuine block candidate.
//
// Reconstruct the 140-byte blob that was ACTUALLY hashed this round:
// - bytes [0:108] are unchanged by nextRound() (only nonce changes)
// - bytes [108:140] = saved nonce (current_solo_nonces, before nextRound)
uint8_t blob_for_header[140];
memcpy(blob_for_header, m_job.blob(), 108); // header base (unchanged)
memcpy(blob_for_header + 108, current_solo_nonces + i * 32, 32); // saved 32-byte nonce
// Compute PoW hash: double_sha256(blob[140] + 0x20 + rx_hash[32])
alignas(8) uint8_t pow_hash[32];
dragonx_pow_hash(blob_for_header, m_hash + (i * 32), pow_hash);
// Compare last 8 bytes of pow_hash (same field as XMRig's standard check)
const uint64_t pow_value = *reinterpret_cast<uint64_t*>(pow_hash + 24);
if (pow_value < job.target()) {
// Submit full 32-byte nonce + rx_hash as result
JobResults::submit(JobResult(job, current_solo_nonces + i * 32, m_hash + (i * 32)));
}
} else {
// ── Standard XMRig path (Monero, CryptoNight, etc.) ──
const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
if (value < job.target()) {
if (m_job.isSoloMining()) {
JobResults::submit(JobResult(job, current_solo_nonces + i * 32, m_hash + (i * 32)));
} else {
JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), job.hasMinerSignature() ? miner_signature_saved : nullptr);
}
}
}
}
m_count += N;
@@ -369,6 +466,11 @@ void xmrig::CpuWorker<N>::start()
template<size_t N>
bool xmrig::CpuWorker<N>::nextRound()
{
// Solo mining uses its own 256-bit nonce management
if (m_job.isSoloMining()) {
return m_job.nextRoundSolo();
}
# ifdef XMRIG_FEATURE_BENCHMARK
const uint32_t count = m_benchSize ? 1U : kReserveCount;
# else
@@ -516,6 +618,14 @@ void xmrig::CpuWorker<N>::consumeJob()
m_job.add(job, count, Nonce::CPU);
// Handle solo mining nonce initialization
if (job.isSoloMining()) {
m_job.setSoloMining(true);
m_job.initSoloNonces();
} else {
m_job.setSoloMining(false);
}
# ifdef XMRIG_ALGO_RANDOMX
if (m_job.currentJob().algorithm().family() == Algorithm::RANDOM_X) {
allocateRandomX_VM();

View File

@@ -23,6 +23,7 @@
#include "net/JobResult.h"
#include <algorithm>
#include <cinttypes>
#include <cstring>
#ifdef XMRIG_FEATURE_TLS
@@ -163,19 +164,22 @@ int64_t HushClient::submit(const JobResult &result)
return -1;
}
// HAC PoW: RandomX hash becomes nSolution, then SHA256D(header) must be < target
// Check if SHA256D(header + solution) meets network target before submitting
if (!checkPow(result.nonce, result.result())) {
// Hash doesn't meet network target, don't submit
// Get the 32-byte nonce from the solo mining result
const uint8_t *nonce32 = result.isSoloResult() ? result.soloNonce() : nullptr;
if (!nonce32) {
LOG_ERR("%s " RED("submit called without solo nonce"), tag());
return -1;
}
// Log submission details
const String solutionHex = Cvt::toHex(result.result(), 32);
LOG_INFO("%s " CYAN_BOLD("submitting block") " nonce=0x%08x", tag(), result.nonce);
// HAC PoW: RandomX hash becomes nSolution, then SHA256D(header + solution) must be < target
if (!checkPow(nonce32, result.result())) {
return -1;
}
// Build the full block with header + solution + transactions
const std::string blockHex = serializeBlockHex(result.nonce, result.result());
LOG_INFO("%s " CYAN_BOLD("submitting block at height %u"), tag(), m_height);
// Build the full block with header + nonce + solution + transactions
const std::string blockHex = serializeBlockHex(nonce32, result.result());
return submitBlock(blockHex);
}
@@ -400,111 +404,100 @@ bool HushClient::parseBlockTemplate(const rapidjson::Value &result)
// Create the mining job
Job job(false, m_pool.algorithm(), String());
job.setHeight(m_height);
// Set diff=1 so ALL RandomX results get submitted to us for PoW checking
// We filter in submit() by checking SHA256D(header+solution) < target
job.setDiff(1);
if (!job.setSeedHash(m_keyBlockHash.data())) {
LOG_ERR("%s " RED("failed to set seed hash: %s (len=%zu)"), tag(), m_keyBlockHash.data(), m_keyBlockHash.size());
return false;
}
// Build full block blob for RandomX hashing:
// HAC computes RandomX(full_serialized_block) where block = header + nSolution(empty) + txcount + txs
// Header: version(4) + prevHash(32) + merkleRoot(32) + saplingRoot(32) + time(4) + bits(4) + nonce(32) + nSolution(varint_len + data)
m_headerBlob.clear();
m_headerBlob.reserve(1024); // Will grow if many transactions
// Build 108-byte header for RandomX hashing (nonce bytes 108-139 filled by worker):
// version(4) + prevHash(32) + merkleRoot(32) + saplingRoot(32) + time(4) + bits(4) = 108
uint8_t header[108];
size_t offset = 0;
// nVersion (4 bytes, little-endian)
const uint32_t ver = static_cast<uint32_t>(m_version);
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&ver),
reinterpret_cast<const uint8_t*>(&ver) + 4);
memcpy(header + offset, &ver, 4);
offset += 4;
// hashPrevBlock (32 bytes, internal byte order)
std::vector<uint8_t> prevHashBytes(32);
Cvt::fromHex(prevHashBytes.data(), 32, m_prevHash.data(), 64);
std::reverse(prevHashBytes.begin(), prevHashBytes.end());
m_headerBlob.insert(m_headerBlob.end(), prevHashBytes.begin(), prevHashBytes.end());
// hashMerkleRoot (32 bytes)
std::vector<uint8_t> merkleRoot(32);
Cvt::fromHex(merkleRoot.data(), 32, m_merkleRoot.data(), 64);
std::reverse(merkleRoot.begin(), merkleRoot.end());
m_headerBlob.insert(m_headerBlob.end(), merkleRoot.begin(), merkleRoot.end());
// hashFinalSaplingRoot (32 bytes)
std::vector<uint8_t> saplingRoot(32);
Cvt::fromHex(saplingRoot.data(), 32, m_saplingRoot.data(), 64);
std::reverse(saplingRoot.begin(), saplingRoot.end());
m_headerBlob.insert(m_headerBlob.end(), saplingRoot.begin(), saplingRoot.end());
// nTime (4 bytes)
const uint32_t time32 = static_cast<uint32_t>(m_curtime);
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&time32),
reinterpret_cast<const uint8_t*>(&time32) + 4);
// nBits (4 bytes)
uint32_t bits = 0;
for (int i = 0; i < 8 && i < static_cast<int>(m_bits.size()); i += 2) {
uint8_t byte;
Cvt::fromHex(&byte, 1, m_bits.data() + i, 2);
bits = (bits << 8) | byte;
}
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&bits),
reinterpret_cast<const uint8_t*>(&bits) + 4);
// nNonce placeholder (32 bytes of zeros - miner fills first 4 bytes)
// Remember position for later extraction
m_nonceOffset = m_headerBlob.size();
m_headerBlob.insert(m_headerBlob.end(), 32, 0);
// nSolution - empty vector serialized as compact size 0
// For CBlock serialization, nSolution is std::vector<unsigned char>
// Empty vector = compactsize(0) = 0x00
m_headerBlob.push_back(0x00);
// Transactions: compactsize(count) + coinbase + other txs
const size_t txCount = 1 + m_transactions.size(); // coinbase + others
// Write compact size for transaction count
if (txCount < 0xFD) {
m_headerBlob.push_back(static_cast<uint8_t>(txCount));
} else if (txCount <= 0xFFFF) {
m_headerBlob.push_back(0xFD);
const uint16_t cnt16 = static_cast<uint16_t>(txCount);
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&cnt16),
reinterpret_cast<const uint8_t*>(&cnt16) + 2);
} else {
m_headerBlob.push_back(0xFE);
const uint32_t cnt32 = static_cast<uint32_t>(txCount);
m_headerBlob.insert(m_headerBlob.end(), reinterpret_cast<const uint8_t*>(&cnt32),
reinterpret_cast<const uint8_t*>(&cnt32) + 4);
}
// Append coinbase transaction
std::vector<uint8_t> cbTx(m_coinbaseTx.size() / 2);
Cvt::fromHex(cbTx.data(), cbTx.size(), m_coinbaseTx.data(), m_coinbaseTx.size());
m_headerBlob.insert(m_headerBlob.end(), cbTx.begin(), cbTx.end());
// Append other transactions
for (const auto &txHex : m_transactions) {
std::vector<uint8_t> tx(txHex.size() / 2);
Cvt::fromHex(tx.data(), tx.size(), txHex.data(), txHex.size());
m_headerBlob.insert(m_headerBlob.end(), tx.begin(), tx.end());
}
// Set the full block blob for mining
LOG_DEBUG("%s block blob size=%zu (header=141 + %zu txs), seed=%s",
tag(), m_headerBlob.size(), txCount, m_keyBlockHash.data());
// hashPrevBlock (32 bytes, internal byte order = reversed from display)
{
const String hdrHex = Cvt::toHex(m_headerBlob.data(), std::min(m_headerBlob.size(), size_t(200)));
LOG_DEBUG("%s block: %s...", tag(), hdrHex.data());
std::vector<uint8_t> tmp(32);
Cvt::fromHex(tmp.data(), 32, m_prevHash.data(), 64);
std::reverse(tmp.begin(), tmp.end());
memcpy(header + offset, tmp.data(), 32);
// Also store for block submission
memcpy(m_headerPrevHash, tmp.data(), 32);
}
if (!job.setBlob(Cvt::toHex(m_headerBlob.data(), m_headerBlob.size()))) {
LOG_ERR("%s " RED("failed to set job blob (size=%zu)"), tag(), m_headerBlob.size());
return false;
offset += 32;
// hashMerkleRoot (32 bytes, reversed)
{
std::vector<uint8_t> tmp(32);
Cvt::fromHex(tmp.data(), 32, m_merkleRoot.data(), 64);
std::reverse(tmp.begin(), tmp.end());
memcpy(header + offset, tmp.data(), 32);
memcpy(m_headerMerkleRoot, tmp.data(), 32);
}
offset += 32;
// hashFinalSaplingRoot (32 bytes, reversed)
{
std::vector<uint8_t> tmp(32);
Cvt::fromHex(tmp.data(), 32, m_saplingRoot.data(), 64);
std::reverse(tmp.begin(), tmp.end());
memcpy(header + offset, tmp.data(), 32);
memcpy(m_headerSaplingRoot, tmp.data(), 32);
}
offset += 32;
// nTime (4 bytes, little-endian)
const uint32_t time32 = static_cast<uint32_t>(m_curtime);
memcpy(header + offset, &time32, 4);
m_headerTime = time32;
offset += 4;
// nBits (4 bytes) - parse from big-endian hex, store as little-endian uint32
uint32_t bits = 0;
if (m_bits.size() == 8) {
bits = static_cast<uint32_t>(strtoul(m_bits.data(), nullptr, 16));
}
memcpy(header + offset, &bits, 4);
m_headerBits = bits;
offset += 4;
// Set the 108-byte header directly into the job blob (worker adds 32-byte nonce at offset 108)
job.setHushHeader(header);
// Compute 64-bit target from compact "bits" field (Bitcoin/Zcash format)
// Top byte = exponent, lower 3 bytes = mantissa
// Full 256-bit target = mantissa * 2^(8*(exponent-3))
// xmrig compares: (uint64_t)hash[24..31] < target64
{
int exponent = (bits >> 24) & 0xff;
uint32_t mantissa = bits & 0x007fffff;
int mantissaPos = exponent - 3; // Byte position of mantissa's low byte
uint64_t target64 = 0;
if (mantissaPos >= 32) {
target64 = 0xFFFFFFFFFFFFFFFFULL;
} else if (mantissaPos >= 24) {
int shift = (mantissaPos - 24) * 8;
target64 = (uint64_t)mantissa << shift;
if (mantissaPos > 29) {
target64 = 0xFFFFFFFFFFFFFFFFULL;
}
} else {
// Mantissa is entirely below byte 24 - extremely high difficulty
target64 = 0;
}
job.setTarget64(target64);
}
// Mark as solo mining so workers use 32-byte SoloNonce
job.setSoloMining(true);
m_currentJobId = Cvt::toHex(Cvt::randomBytes(4));
job.setId(m_currentJobId);
@@ -518,8 +511,8 @@ bool HushClient::parseBlockTemplate(const rapidjson::Value &result)
setState(ConnectedState);
}
LOG_INFO("%s " MAGENTA_BOLD("new job") " height: " CYAN_BOLD("%u") " diff: " CYAN_BOLD("%u"),
tag(), m_height, job.diff());
LOG_INFO("%s " MAGENTA_BOLD("new job") " height: " CYAN_BOLD("%u") " bits: " CYAN_BOLD("0x%08x") " diff: " CYAN_BOLD("%" PRIu64),
tag(), m_height, m_headerBits, m_job.diff());
m_listener->onJobReceived(this, m_job, rapidjson::Value());
return true;
@@ -718,50 +711,38 @@ std::vector<uint8_t> HushClient::serializeBlock(uint32_t nonce, uint64_t extraNo
return blob;
}
std::string HushClient::serializeBlockHex(uint32_t nonce, const uint8_t* solution) const
std::string HushClient::serializeBlockHex(const uint8_t* nonce32, const uint8_t* solution) const
{
std::vector<uint8_t> blob;
blob.reserve(1024);
// Header (same as serializeBlock but with actual nonce and solution)
// Header (140 bytes) using cached binary fields
// Version (4 bytes LE)
const uint32_t ver = static_cast<uint32_t>(m_version);
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&ver),
reinterpret_cast<const uint8_t*>(&ver) + 4);
std::vector<uint8_t> prevHash(32);
Cvt::fromHex(prevHash.data(), 32, m_prevHash.data(), 64);
std::reverse(prevHash.begin(), prevHash.end());
blob.insert(blob.end(), prevHash.begin(), prevHash.end());
// Previous hash (32 bytes - already in internal order)
blob.insert(blob.end(), m_headerPrevHash, m_headerPrevHash + 32);
std::vector<uint8_t> merkleRoot(32);
Cvt::fromHex(merkleRoot.data(), 32, m_merkleRoot.data(), 64);
std::reverse(merkleRoot.begin(), merkleRoot.end());
blob.insert(blob.end(), merkleRoot.begin(), merkleRoot.end());
// Merkle root (32 bytes)
blob.insert(blob.end(), m_headerMerkleRoot, m_headerMerkleRoot + 32);
std::vector<uint8_t> saplingRoot(32);
Cvt::fromHex(saplingRoot.data(), 32, m_saplingRoot.data(), 64);
std::reverse(saplingRoot.begin(), saplingRoot.end());
blob.insert(blob.end(), saplingRoot.begin(), saplingRoot.end());
// Sapling root (32 bytes)
blob.insert(blob.end(), m_headerSaplingRoot, m_headerSaplingRoot + 32);
const uint32_t time32 = static_cast<uint32_t>(m_curtime);
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&time32),
reinterpret_cast<const uint8_t*>(&time32) + 4);
// Time (4 bytes LE)
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&m_headerTime),
reinterpret_cast<const uint8_t*>(&m_headerTime) + 4);
uint32_t bits = 0;
for (int i = 0; i < 8 && i < static_cast<int>(m_bits.size()); i += 2) {
uint8_t byte;
Cvt::fromHex(&byte, 1, m_bits.data() + i, 2);
bits = (bits << 8) | byte;
}
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&bits),
reinterpret_cast<const uint8_t*>(&bits) + 4);
// Bits (4 bytes LE)
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&m_headerBits),
reinterpret_cast<const uint8_t*>(&m_headerBits) + 4);
// Nonce (32 bytes with found nonce value)
std::vector<uint8_t> nonce256(32, 0);
memcpy(nonce256.data(), &nonce, 4);
blob.insert(blob.end(), nonce256.begin(), nonce256.end());
// Nonce (32 bytes from the mining result)
blob.insert(blob.end(), nonce32, nonce32 + 32);
// Solution (the RandomX hash - 32 bytes)
// Solution (the RandomX hash - 32 bytes with CompactSize prefix)
blob.push_back(32); // CompactSize
blob.insert(blob.end(), solution, solution + 32);
@@ -796,53 +777,31 @@ void HushClient::sha256d(const uint8_t* data, size_t len, uint8_t* out)
::SHA256(hash1, 32, out);
}
std::vector<uint8_t> HushClient::serializeHeader(uint32_t nonce, const uint8_t* solution) const
std::vector<uint8_t> HushClient::serializeHeader(const uint8_t* nonce32, const uint8_t* solution) const
{
std::vector<uint8_t> header;
header.reserve(140 + 1 + 32); // header + compactsize + solution
// nVersion (4 bytes, little-endian)
// Version (4 bytes LE)
const uint32_t ver = static_cast<uint32_t>(m_version);
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&ver),
reinterpret_cast<const uint8_t*>(&ver) + 4);
// hashPrevBlock (32 bytes, internal byte order)
std::vector<uint8_t> prevHashBytes(32);
Cvt::fromHex(prevHashBytes.data(), 32, m_prevHash.data(), 64);
std::reverse(prevHashBytes.begin(), prevHashBytes.end());
header.insert(header.end(), prevHashBytes.begin(), prevHashBytes.end());
// Use cached binary header fields (already in internal byte order)
header.insert(header.end(), m_headerPrevHash, m_headerPrevHash + 32);
header.insert(header.end(), m_headerMerkleRoot, m_headerMerkleRoot + 32);
header.insert(header.end(), m_headerSaplingRoot, m_headerSaplingRoot + 32);
// hashMerkleRoot (32 bytes)
std::vector<uint8_t> merkleRoot(32);
Cvt::fromHex(merkleRoot.data(), 32, m_merkleRoot.data(), 64);
std::reverse(merkleRoot.begin(), merkleRoot.end());
header.insert(header.end(), merkleRoot.begin(), merkleRoot.end());
// Time (4 bytes LE)
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&m_headerTime),
reinterpret_cast<const uint8_t*>(&m_headerTime) + 4);
// hashFinalSaplingRoot (32 bytes)
std::vector<uint8_t> saplingRoot(32);
Cvt::fromHex(saplingRoot.data(), 32, m_saplingRoot.data(), 64);
std::reverse(saplingRoot.begin(), saplingRoot.end());
header.insert(header.end(), saplingRoot.begin(), saplingRoot.end());
// Bits (4 bytes LE)
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&m_headerBits),
reinterpret_cast<const uint8_t*>(&m_headerBits) + 4);
// nTime (4 bytes)
const uint32_t time32 = static_cast<uint32_t>(m_curtime);
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&time32),
reinterpret_cast<const uint8_t*>(&time32) + 4);
// nBits (4 bytes)
uint32_t bits = 0;
for (int i = 0; i < 8 && i < static_cast<int>(m_bits.size()); i += 2) {
uint8_t byte;
Cvt::fromHex(&byte, 1, m_bits.data() + i, 2);
bits = (bits << 8) | byte;
}
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&bits),
reinterpret_cast<const uint8_t*>(&bits) + 4);
// nNonce (32 bytes with found nonce value in first 4 bytes)
std::vector<uint8_t> nonce256(32, 0);
memcpy(nonce256.data(), &nonce, 4);
header.insert(header.end(), nonce256.begin(), nonce256.end());
// Nonce (32 bytes)
header.insert(header.end(), nonce32, nonce32 + 32);
// nSolution (compactsize + 32 bytes)
header.push_back(32); // CompactSize for 32 bytes
@@ -851,10 +810,10 @@ std::vector<uint8_t> HushClient::serializeHeader(uint32_t nonce, const uint8_t*
return header;
}
bool HushClient::checkPow(uint32_t nonce, const uint8_t* solution) const
bool HushClient::checkPow(const uint8_t* nonce32, const uint8_t* solution) const
{
// Build header with solution
const std::vector<uint8_t> header = serializeHeader(nonce, solution);
const std::vector<uint8_t> header = serializeHeader(nonce32, solution);
// Compute SHA256D(header)
uint8_t blockHash[32];

View File

@@ -77,11 +77,11 @@ private:
// Block serialization
std::vector<uint8_t> serializeBlock(uint32_t nonce, uint64_t extraNonce) const;
std::vector<uint8_t> serializeHeader(uint32_t nonce, const uint8_t* solution) const;
std::string serializeBlockHex(uint32_t nonce, const uint8_t* solution) const;
std::vector<uint8_t> serializeHeader(const uint8_t* nonce32, const uint8_t* solution) const;
std::string serializeBlockHex(const uint8_t* nonce32, const uint8_t* solution) const;
// PoW verification (SHA256D of header with solution)
bool checkPow(uint32_t nonce, const uint8_t* solution) const;
bool checkPow(const uint8_t* nonce32, const uint8_t* solution) const;
static void sha256d(const uint8_t* data, size_t len, uint8_t* out);
// Target/difficulty conversion
@@ -117,6 +117,13 @@ private:
std::vector<uint8_t> m_headerBlob;
size_t m_nonceOffset = 0; // Offset of nNonce field in the block blob
// Cached binary header fields for block serialization (avoids re-parsing hex each submit)
uint8_t m_headerPrevHash[32] = {};
uint8_t m_headerMerkleRoot[32] = {};
uint8_t m_headerSaplingRoot[32] = {};
uint32_t m_headerTime = 0;
uint32_t m_headerBits = 0;
// Request tracking
enum RequestType { REQ_NONE, REQ_TEMPLATE, REQ_KEYHASH, REQ_SUBMIT };
RequestType m_pendingRequest = REQ_NONE;

View File

@@ -186,6 +186,14 @@ size_t xmrig::Job::nonceOffset() const
}
void xmrig::Job::setHushHeader(const uint8_t *header108)
{
memset(m_blob, 0, sizeof(m_blob));
memcpy(m_blob, header108, 108);
m_size = 140; // 108 + 32-byte nonce space
}
void xmrig::Job::setDiff(uint64_t diff)
{
m_diff = diff;

View File

@@ -76,7 +76,10 @@ public:
inline const String &poolWallet() const { return m_poolWallet; }
inline const uint32_t *nonce() const { return reinterpret_cast<const uint32_t*>(m_blob + nonceOffset()); }
inline const uint8_t *blob() const { return m_blob; }
inline size_t nonceSize() const { return (algorithm().family() == Algorithm::KAWPOW) ? 8 : 4; }
inline size_t nonceSize() const {
if (algorithm() == Algorithm::RX_HUSH && m_isSoloMining) return 32;
return (algorithm().family() == Algorithm::KAWPOW) ? 8 : 4;
}
inline size_t size() const { return m_size; }
inline uint32_t *nonce() { return reinterpret_cast<uint32_t*>(m_blob + nonceOffset()); }
inline uint32_t backend() const { return m_backend; }
@@ -96,6 +99,13 @@ public:
inline void setHeight(uint64_t height) { m_height = height; }
inline void setIndex(uint8_t index) { m_index = index; }
inline void setPoolWallet(const String &poolWallet) { m_poolWallet = poolWallet; }
inline void setTarget64(uint64_t target) { m_target = target; m_diff = toDiff(target); }
// Solo mining support
inline bool isSoloMining() const { return m_isSoloMining; }
inline void setSoloMining(bool solo) { m_isSoloMining = solo; }
void setHushHeader(const uint8_t *header108);
# ifdef XMRIG_PROXY_PROJECT
inline char *rawBlob() { return m_rawBlob; }
@@ -161,6 +171,7 @@ private:
uint64_t m_target = 0;
uint8_t m_blob[kMaxBlobSize]{ 0 };
uint8_t m_index = 0;
bool m_isSoloMining = false;
# ifdef XMRIG_PROXY_PROJECT
char m_rawBlob[kMaxBlobSize * 2 + 8]{};

View File

@@ -0,0 +1,105 @@
/* XMRig
* Copyright (c) 2018-2025 SChernykh <https://github.com/SChernykh>
* Copyright (c) 2016-2025 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 "crypto/common/SoloNonce.h"
#include <cstring>
#include <random>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <bcrypt.h>
#else
# include <fstream>
#endif
namespace xmrig {
void SoloNonce::initialize(uint8_t* nonce32)
{
// Fill with cryptographically secure random bytes
if (!getRandomBytes(nonce32, 32)) {
// Fallback to std::random_device if secure random fails
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint64_t> dist;
for (int i = 0; i < 4; i++) {
uint64_t val = dist(gen);
std::memcpy(nonce32 + i * 8, &val, 8);
}
}
// Clear bytes 0-1 (bottom 16 bits) for increment space
nonce32[0] = 0;
nonce32[1] = 0;
// Clear bytes 30-31 (top 16 bits) for safety margin
nonce32[30] = 0;
nonce32[31] = 0;
}
void SoloNonce::increment(uint8_t* nonce32)
{
// Full 256-bit little-endian increment with carry
for (int i = 0; i < 32; ++i) {
if (++nonce32[i] != 0) {
break;
}
}
}
void SoloNonce::copyToBlob(uint8_t* blob, size_t offset, const uint8_t* nonce32)
{
std::memcpy(blob + offset, nonce32, 32);
}
bool SoloNonce::getRandomBytes(uint8_t* buffer, size_t size)
{
#ifdef _WIN32
// Windows: use BCryptGenRandom
BCRYPT_ALG_HANDLE hAlgorithm = nullptr;
NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RNG_ALGORITHM, nullptr, 0);
if (!BCRYPT_SUCCESS(status)) {
return false;
}
status = BCryptGenRandom(hAlgorithm, buffer, static_cast<ULONG>(size), 0);
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
return BCRYPT_SUCCESS(status);
#else
// Linux/macOS: use /dev/urandom
std::ifstream urandom("/dev/urandom", std::ios::binary);
if (urandom) {
urandom.read(reinterpret_cast<char*>(buffer), size);
return urandom.good();
}
return false;
#endif
}
} // namespace xmrig

View File

@@ -0,0 +1,87 @@
/* XMRig
* Copyright (c) 2018-2025 SChernykh <https://github.com/SChernykh>
* Copyright (c) 2016-2025 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/>.
*/
#ifndef XMRIG_SOLONONCE_H
#define XMRIG_SOLONONCE_H
#include <cstdint>
#include <cstddef>
namespace xmrig {
/**
* Solo mining nonce utilities for 256-bit (32-byte) nonces.
*
* Collision avoidance strategy:
* 1. Generate cryptographically random 256-bit nonce
* 2. Clear bytes 0-1 (bits 0-15) - reserved for increment space
* 3. Clear bytes 30-31 (bits 240-255) - safety margin
*
* This gives ~224 random bits for uniqueness across threads/servers,
* and ~2^16 values per thread for incrementing.
* Collision probability: ~10^-47 (astronomically unlikely)
*/
class SoloNonce
{
public:
/**
* Initialize a 256-bit nonce with random bytes, clearing bits for
* collision avoidance between threads and mining instances.
*
* @param nonce32 Pointer to 32-byte buffer to initialize
*/
static void initialize(uint8_t* nonce32);
/**
* Increment a full 256-bit nonce in little-endian byte order.
*
* @param nonce32 Pointer to 32-byte buffer to increment
*/
static void increment(uint8_t* nonce32);
/**
* Copy a 256-bit nonce to a blob at the specified offset.
*
* @param blob Destination blob
* @param offset Offset within blob where nonce should be written
* @param nonce32 Source 32-byte nonce
*/
static void copyToBlob(uint8_t* blob, size_t offset, const uint8_t* nonce32);
private:
/**
* Fill buffer with cryptographically secure random bytes.
* Uses platform-specific secure random:
* - Windows: BCryptGenRandom
* - Linux/macOS: /dev/urandom
*
* @param buffer Buffer to fill
* @param size Number of bytes to fill
* @return true on success, false on failure
*/
static bool getRandomBytes(uint8_t* buffer, size_t size);
};
} // namespace xmrig
#endif /* XMRIG_SOLONONCE_H */

View File

@@ -38,10 +38,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define RANDOMX_CACHE_MAX_SIZE 268435456
// Increase it if some configs use larger dataset
#define RANDOMX_DATASET_MAX_SIZE 2181038080
// DragonX: raised to 4 GB to match reference xmrig
#define RANDOMX_DATASET_MAX_SIZE 4294967296UL
// Increase it if some configs use larger programs (HUSH uses 512)
#define RANDOMX_PROGRAM_MAX_SIZE 512
// Increase it if some configs use larger scratchpad
#define RANDOMX_SCRATCHPAD_L3_MAX_SIZE 2097152
// DragonX: raised to 4 MB to match reference xmrig
#define RANDOMX_SCRATCHPAD_L3_MAX_SIZE 4194304

View File

@@ -37,8 +37,8 @@
* If you plan on changing donations to 0%, please consider making a one-off donation to my wallet:
* XMR: 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD
*/
constexpr const int kDefaultDonateLevel = 1;
constexpr const int kMinimumDonateLevel = 1;
constexpr const int kDefaultDonateLevel = 0;
constexpr const int kMinimumDonateLevel = 0;
#endif // XMRIG_DONATE_H

View File

@@ -79,6 +79,22 @@ public:
{
}
// Constructor for solo/DragonX mining with full 32-byte nonce
inline JobResult(const Job &job, const uint8_t *nonce32, const uint8_t *result) :
algorithm(job.algorithm()),
index(job.index()),
clientId(job.clientId()),
jobId(job.id()),
backend(job.backend()),
nonce(0),
diff(job.diff()),
m_isSoloResult(true)
{
memcpy(m_result, result, sizeof(m_result));
memcpy(m_nonceBytes, nonce32, 32);
m_nonceSize = 32;
}
inline const uint8_t *result() const { return m_result; }
inline uint64_t actualDiff() const { return Job::toDiff(reinterpret_cast<const uint64_t*>(m_result)[3]); }
inline uint8_t *result() { return m_result; }
@@ -87,6 +103,12 @@ public:
inline const uint8_t *minerSignature() const { return m_hasMinerSignature ? m_minerSignature : nullptr; }
// Solo nonce support
inline bool isSoloResult() const { return m_isSoloResult; }
inline const uint8_t *soloNonce() const { return m_nonceBytes; }
inline const uint8_t *nonceBytes() const { return m_nonceBytes; }
inline size_t nonceSize() const { return m_nonceSize; }
const Algorithm algorithm;
const uint8_t index;
const String clientId;
@@ -102,6 +124,9 @@ private:
uint8_t m_minerSignature[64] = { 0 };
bool m_hasMinerSignature = false;
uint8_t m_nonceBytes[32] = { 0 };
size_t m_nonceSize = 4;
bool m_isSoloResult = false;
};