From 0da68ebbc76ecbff61f0801b35d091b0aceecad4 Mon Sep 17 00:00:00 2001 From: dan_s Date: Tue, 17 Feb 2026 16:43:41 -0600 Subject: [PATCH] randomx validation enforcement with activation height --- src/hush_bitcoind.h | 6 +++ src/hush_defs.h | 1 + src/hush_globals.h | 1 + src/hush_utils.h | 12 +++++ src/main.cpp | 2 + src/miner.cpp | 11 +++-- src/pow.cpp | 102 +++++++++++++++++++++++++++++++++++++++++ src/pow.h | 9 ++++ src/primitives/block.h | 27 +++++++++++ 9 files changed, 166 insertions(+), 5 deletions(-) diff --git a/src/hush_bitcoind.h b/src/hush_bitcoind.h index 218f09db7..20806ffe7 100644 --- a/src/hush_bitcoind.h +++ b/src/hush_bitcoind.h @@ -23,6 +23,7 @@ #include "script/standard.h" #include "cc/CCinclude.h" #include "sietch.h" +#include "pow.h" // This is the address for pubkey = 0x000000000000000000000000000000000 (33 bytes) // Funds sent to a burn address can never be spent because the pubkey is invalid @@ -1693,6 +1694,11 @@ int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height) fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckEquihashSolution failed\n",slowflag,height); return(-1); } + if ( !CheckRandomXSolution(pblock, height) ) + { + fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckRandomXSolution failed\n",slowflag,height); + return(-1); + } hash = pblock->GetHash(); bnTarget.SetCompact(pblock->nBits,&fNegative,&fOverflow); bhash = UintToArith256(hash); diff --git a/src/hush_defs.h b/src/hush_defs.h index 720fa70b5..7438031e5 100644 --- a/src/hush_defs.h +++ b/src/hush_defs.h @@ -573,6 +573,7 @@ extern uint64_t ASSETCHAINS_SUPPLY, ASSETCHAINS_FOUNDERS_REWARD; extern int32_t ASSETCHAINS_LWMAPOS, ASSETCHAINS_SAPLING, ASSETCHAINS_OVERWINTER,ASSETCHAINS_BLOCKTIME; extern uint64_t ASSETCHAINS_TIMELOCKGTE; extern uint32_t ASSETCHAINS_ALGO,ASSETCHAINS_EQUIHASH,ASSETCHAINS_RANDOMX, HUSH_INITDONE; +extern int32_t ASSETCHAINS_RANDOMX_VALIDATION; extern int32_t HUSH_MININGTHREADS,HUSH_LONGESTCHAIN,ASSETCHAINS_SEED,IS_HUSH_NOTARY,USE_EXTERNAL_PUBKEY,HUSH_CHOSEN_ONE,HUSH_ON_DEMAND,HUSH_PASSPORT_INITDONE,ASSETCHAINS_STAKED,HUSH_NSPV; extern uint64_t ASSETCHAINS_COMMISSION, ASSETCHAINS_LASTERA,ASSETCHAINS_CBOPRET; extern uint64_t ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_NOTARY_PAY[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_TIMELOCKGTE, ASSETCHAINS_NONCEMASK[],ASSETCHAINS_NK[2]; diff --git a/src/hush_globals.h b/src/hush_globals.h index 128d719da..8f3482089 100644 --- a/src/hush_globals.h +++ b/src/hush_globals.h @@ -92,6 +92,7 @@ uint64_t ASSETCHAINS_NONCEMASK[] = {0xffff}; uint32_t ASSETCHAINS_NONCESHIFT[] = {32}; uint32_t ASSETCHAINS_HASHESPERROUND[] = {1}; uint32_t ASSETCHAINS_ALGO = _ASSETCHAINS_EQUIHASH; +int32_t ASSETCHAINS_RANDOMX_VALIDATION = -1; // activation height for RandomX validation (-1 = disabled) // min diff returned from GetNextWorkRequired needs to be added here for each algo, so they can work with ac_staked. uint32_t ASSETCHAINS_MINDIFF[] = {537857807}; int32_t ASSETCHAINS_LWMAPOS = 0; // percentage of blocks should be PoS diff --git a/src/hush_utils.h b/src/hush_utils.h index f1bb80697..250972e1a 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1916,6 +1916,18 @@ void hush_args(char *argv0) strncpy(SMART_CHAIN_SYMBOL,name.c_str(),sizeof(SMART_CHAIN_SYMBOL)-1); const bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false; + // Set RandomX validation activation height per chain + if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) { + if (strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) == 0) { + ASSETCHAINS_RANDOMX_VALIDATION = 2838976; // TBD: set to coordinated upgrade height + } else if (strncmp(SMART_CHAIN_SYMBOL, "TUMIN", 5) == 0) { + ASSETCHAINS_RANDOMX_VALIDATION = 100; // TBD: set to coordinated upgrade height + } else { + ASSETCHAINS_RANDOMX_VALIDATION = 1; // all other RandomX HACs: enforce from height 1 + } + printf("ASSETCHAINS_RANDOMX_VALIDATION set to %d for %s\n", ASSETCHAINS_RANDOMX_VALIDATION, SMART_CHAIN_SYMBOL); + } + ASSETCHAINS_LASTERA = GetArg("-ac_eras", 1); if(ishush3) { ASSETCHAINS_LASTERA = 3; diff --git a/src/main.cpp b/src/main.cpp index ff3c3ea64..1447b7d4a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4993,6 +4993,8 @@ bool CheckBlockHeader(int32_t *futureblockp,int32_t height,CBlockIndex *pindex, { if ( !CheckEquihashSolution(&blockhdr, Params()) ) return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),REJECT_INVALID, "invalid-solution"); + if ( !CheckRandomXSolution(&blockhdr, height) ) + return state.DoS(100, error("CheckBlockHeader(): RandomX solution invalid"),REJECT_INVALID, "invalid-randomx-solution"); } // Check proof of work matches claimed amount /*hush_index2pubkey33(pubkey33,pindex,height); diff --git a/src/miner.cpp b/src/miner.cpp index fce0179bb..bc1a216c6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -1011,8 +1011,8 @@ enum RandomXSolverCancelCheck Reason2 }; -int GetRandomXInterval() { return GetArg("-ac_randomx_interval",1024); } -int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); } +int GetRandomXInterval(); +int GetRandomXBlockLag(); #ifdef ENABLE_WALLET void static RandomXMiner(CWallet *pwallet) @@ -1268,16 +1268,17 @@ void static RandomXMiner() arith_uint256 hashTarget; hashTarget = HASHTarget; + CRandomXInput rxInput(pblocktemplate->block); CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION); - // Use the current block as randomx input - randomxInput << pblocktemplate->block; + // Serialize block header without nSolution but with nNonce for deterministic RandomX input + randomxInput << rxInput; // std::cerr << "RandomXMiner: randomxInput=" << HexStr(randomxInput) << "\n"; // fprintf(stderr,"RandomXMiner: created randomxKey=%s , randomxInput.size=%lu\n", randomxKey, randomxInput.size() ); //randomxInput); rxdebug("%s: randomxKey=%s randomxInput=%s\n", randomxKey, HexStr(randomxInput).c_str()); rxdebug("%s: calculating randomx hash\n"); - randomx_calculate_hash(myVM, &randomxInput, sizeof randomxInput, randomxHash); + randomx_calculate_hash(myVM, &randomxInput[0], randomxInput.size(), randomxHash); rxdebug("%s: calculated randomx hash\n"); rxdebug("%s: randomxHash="); diff --git a/src/pow.cpp b/src/pow.cpp index 4aa00b094..ac7205795 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -28,6 +28,8 @@ #include "uint256.h" #include "util.h" #include "sodium.h" +#include "RandomX/src/randomx.h" +#include #ifdef ENABLE_RUST #include "librustzcash.h" @@ -683,6 +685,106 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& param return true; } +int GetRandomXInterval() { return GetArg("-ac_randomx_interval", 1024); } +int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); } + +// Cached RandomX validation state — reused across calls, protected by mutex +static std::mutex cs_randomx_validator; +static randomx_cache *s_rxCache = nullptr; +static randomx_vm *s_rxVM = nullptr; +static std::string s_rxCurrentKey; // tracks current key to avoid re-init + +CBlockIndex *hush_chainactive(int32_t height); + +bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height) +{ + // Only applies to RandomX chains + if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX) + return true; + + // Disabled if activation height is negative + if (ASSETCHAINS_RANDOMX_VALIDATION < 0) + return true; + + // Not yet at activation height + if (height < ASSETCHAINS_RANDOMX_VALIDATION) + return true; + + // Do not affect initial block loading + extern int32_t HUSH_LOADINGBLOCKS; + if (HUSH_LOADINGBLOCKS != 0) + return true; + + // nSolution must be exactly RANDOMX_HASH_SIZE (32) bytes + if (pblock->nSolution.size() != RANDOMX_HASH_SIZE) { + return error("CheckRandomXSolution(): nSolution size %u != expected %d at height %d", + pblock->nSolution.size(), RANDOMX_HASH_SIZE, height); + } + + static int randomxInterval = GetRandomXInterval(); + static int randomxBlockLag = GetRandomXBlockLag(); + + // Determine the correct RandomX key for this height + char initialKey[82]; + snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT); + + std::string rxKey; + if (height < randomxInterval + randomxBlockLag) { + // Use initial key derived from chain params + rxKey = std::string(initialKey, strlen(initialKey)); + } else { + // Use block hash at the key height + int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval; + CBlockIndex *pKeyIndex = hush_chainactive(keyHeight); + if (pKeyIndex == nullptr) { + return error("CheckRandomXSolution(): cannot get block index at key height %d for block %d", keyHeight, height); + } + uint256 blockKey = pKeyIndex->GetBlockHash(); + rxKey = std::string((const char*)&blockKey, sizeof(blockKey)); + } + + // Serialize the block header without nSolution (but with nNonce) as RandomX input + CRandomXInput rxInput(*pblock); + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << rxInput; + + char computedHash[RANDOMX_HASH_SIZE]; + + { + std::lock_guard lock(cs_randomx_validator); + + // Initialize cache + VM if needed, or re-init if key changed + if (s_rxCache == nullptr) { + randomx_flags flags = randomx_get_flags(); + s_rxCache = randomx_alloc_cache(flags); + if (s_rxCache == nullptr) { + return error("CheckRandomXSolution(): failed to allocate RandomX cache"); + } + randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size()); + s_rxCurrentKey = rxKey; + s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr); + if (s_rxVM == nullptr) { + randomx_release_cache(s_rxCache); + s_rxCache = nullptr; + return error("CheckRandomXSolution(): failed to create RandomX VM"); + } + } else if (s_rxCurrentKey != rxKey) { + randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size()); + s_rxCurrentKey = rxKey; + randomx_vm_set_cache(s_rxVM, s_rxCache); + } + + randomx_calculate_hash(s_rxVM, &ss[0], ss.size(), computedHash); + } + + // Compare computed hash against nSolution + if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) { + return error("CheckRandomXSolution(): RandomX hash mismatch at height %d", height); + } + + return true; +} + int32_t hush_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp); int32_t hush_currentheight(); void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height); diff --git a/src/pow.h b/src/pow.h index 9a86bee64..208ce5d06 100644 --- a/src/pow.h +++ b/src/pow.h @@ -38,6 +38,15 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg, /** Check whether the Equihash solution in a block header is valid */ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&); +/** Check whether a block header contains a valid RandomX solution */ +bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height); + +/** Return the RandomX key rotation interval in blocks */ +int GetRandomXInterval(); + +/** Return the RandomX key change lag in blocks */ +int GetRandomXBlockLag(); + /** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t height, const Consensus::Params& params); CChainPower GetBlockProof(const CBlockIndex& block); diff --git a/src/primitives/block.h b/src/primitives/block.h index 245be24ed..bd0d429e8 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -237,6 +237,33 @@ public: } }; +/** + * Custom serializer for CBlockHeader that includes nNonce but omits nSolution, + * for use as deterministic input to RandomX hashing. + */ +class CRandomXInput : private CBlockHeader +{ +public: + CRandomXInput(const CBlockHeader &header) + { + CBlockHeader::SetNull(); + *((CBlockHeader*)this) = header; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(this->nVersion); + READWRITE(hashPrevBlock); + READWRITE(hashMerkleRoot); + READWRITE(hashFinalSaplingRoot); + READWRITE(nTime); + READWRITE(nBits); + READWRITE(nNonce); + } +}; + /** Describes a place in the block chain to another node such that if the * other node doesn't have the same branch, it can find a recent common trunk.