Fix nBits validation bypass and restore CheckProofOfWork rejection for HACs
Two critical vulnerabilities allowed an attacker to flood the DragonX chain
with minimum-difficulty blocks starting at height 2879907:
1. ContextualCheckBlockHeader only validated nBits for HUSH3 mainnet
(gated behind `if (ishush3)`), never for HAC/smart chains. An attacker
could submit blocks claiming any difficulty and the node accepted them.
Add nBits validation for all non-HUSH3 smart chains, gated above
daaForkHeight (default 450000) to maintain consensus with early chain
history that was mined by a different binary.
2. The rebrand commit (85c8d7f7d) commented out the `return false` block
in CheckProofOfWork that rejects blocks whose hash does not meet the
claimed target. This made PoW validation a no-op — any hash passed.
Restore the rejection block and add RANDOMX_VALIDATION height-gated
logic so blocks after the activation height are always validated even
during initial block loading.
Vulnerability #1 was inherited from the upstream hush3 codebase.
Vulnerability #2 was introduced by the DragonX rebrand.
This commit is contained in:
20
src/main.cpp
20
src/main.cpp
@@ -5140,6 +5140,26 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta
|
||||
}
|
||||
}
|
||||
|
||||
// Check Proof-of-Work difficulty for smart chains (HACs)
|
||||
// Without this check, an attacker can submit blocks with arbitrary nBits
|
||||
// (e.g., powLimit / diff=1) and they will be accepted, allowing the chain
|
||||
// to be flooded with minimum-difficulty blocks.
|
||||
// Only enforce above daaForkHeight to avoid consensus mismatch with early
|
||||
// chain blocks that were mined by a different binary version.
|
||||
if (!ishush3 && SMART_CHAIN_SYMBOL[0] != 0 && nHeight > daaForkHeight) {
|
||||
unsigned int nNextWork = GetNextWorkRequired(pindexPrev, &block, consensusParams);
|
||||
if (fDebug) {
|
||||
LogPrintf("%s: HAC nbits height=%d expected=%lu actual=%lu\n",
|
||||
__func__, nHeight, (unsigned long)nNextWork, (unsigned long)block.nBits);
|
||||
}
|
||||
if (block.nBits != nNextWork) {
|
||||
return state.DoS(100,
|
||||
error("%s: Incorrect diffbits for %s at height %d: expected %lu got %lu",
|
||||
__func__, SMART_CHAIN_SYMBOL, nHeight, (unsigned long)nNextWork, (unsigned long)block.nBits),
|
||||
REJECT_INVALID, "bad-diffbits");
|
||||
}
|
||||
}
|
||||
|
||||
// Check timestamp against prev
|
||||
if (ASSETCHAINS_ADAPTIVEPOW <= 0 || nHeight < 30) {
|
||||
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast() )
|
||||
|
||||
14
src/pow.cpp
14
src/pow.cpp
@@ -863,10 +863,17 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t
|
||||
// Check proof of work matches claimed amount
|
||||
if ( UintToArith256(hash = blkHeader.GetHash()) > bnTarget )
|
||||
{
|
||||
if ( HUSH_LOADINGBLOCKS != 0 )
|
||||
return true;
|
||||
// During initial block loading/sync, skip PoW validation for blocks
|
||||
// before RandomX validation height. After activation, always validate
|
||||
// to prevent injection of blocks with fake PoW.
|
||||
if ( HUSH_LOADINGBLOCKS != 0 ) {
|
||||
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX && ASSETCHAINS_RANDOMX_VALIDATION > 0 && height >= ASSETCHAINS_RANDOMX_VALIDATION) {
|
||||
// Fall through to reject the block — do NOT skip validation after activation
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if ( SMART_CHAIN_SYMBOL[0] != 0 || height > 792000 )
|
||||
{
|
||||
if ( Params().NetworkIDString() != "regtest" )
|
||||
@@ -886,7 +893,6 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
/*for (i=31; i>=0; i--)
|
||||
fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);
|
||||
|
||||
Reference in New Issue
Block a user