diff --git a/src/stratum.cpp b/src/stratum.cpp index 7877dd171..d9cfffb44 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -1035,6 +1035,22 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork } blkhdr.nNonce = (uint256) nonce; + // Cheap SHA256d filter first. This test used to sit *below* the RandomX verify, so 32 + // arbitrary bytes from any peer bought a full ~65ms randomx_calculate_hash before anything + // rejected them -- on the shared HTTP/RPC libevent thread, and holding the global + // cs_randomx_validator that block validation also takes. GetHash() is SerializeHash over the + // header including nSolution, so passing this costs real grinding. Semantics are unchanged: + // an empty local_diff still parses to zero and still rejects, exactly as before. + if (!instance_of_cstratumparams.fAllowLowDiffShares && + UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff)) { + CBlockIndex diff_index; + diff_index.nBits = UintToArith256(blkhdr.GetHash()).GetCompact(); + const double share_diff = GetDifficulty(&diff_index); + diff_index.nBits = arith_uint256(current_work.local_diff).GetCompact(); + const double target_diff = GetDifficulty(&diff_index); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", share_diff, target_diff)); + } + // block is constructed, now it's time to VerifyEH if (StratumIsRandomX()) { @@ -1092,10 +1108,8 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork std::chrono::duration elapsed; uint64_t shares_accepted_since_last; - // TODO: we need to check hash > local port diff, and if it's true -> throw an exception -> diff too low (!) - if (!instance_of_cstratumparams.fAllowLowDiffShares) - if (UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff)) - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", hush_real_diff, hush_local_diff)); + // (the low-diff share check moved above the RandomX verify -- see SubmitBlock's cheap + // SHA256d filter -- so that attacker-controlled bytes cannot buy a RandomX hash) if (finish > start) { @@ -1343,6 +1357,18 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) const std::string method("mining.submit"); BoundParams(method, params, 5,5); + + // Parity with every other handler (GetWorkUnit, mining.aux.*, mining.extranonce.*), which all + // refuse an unauthorized client. NOTE this is not authentication: mining.authorize validates no + // credential, so it only costs an attacker one extra line. It is here so the submit path cannot + // be reached without at least completing the handshake; the cheap-target check below is what + // actually bounds the work an unknown peer can force. + if (!client.m_authorized && client.m_aux_addr.empty()) { + const std::string msg = strprintf("%s: share submitted by an unauthorized client", __func__); + LogPrint("stratum", "%s\n", msg); + throw JSONRPCError(RPC_INVALID_REQUEST, "Stratum client not authorized. Use mining.authorize first, with a DragonX R.. address as the username or 'x' to mine to the default address."); + } + // First parameter is the client username, which is ignored. /* EWBF 31 bytes job_id fix */