// Copyright (c) 2026 The Limenka developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include namespace { /** Cache of computed delay remainders keyed by (prev_hash, steps). * ComputeDelay is a pure function, so caching is consensus-safe. It * stops DoS amplification: an attacker cannot re-stall validation with * repeated invalid blocks on the same parent - each new parent costs * them a valid fork block (~617s of hashing). */ Mutex g_delay_cache_mutex; std::map, uint64_t> g_delay_cache GUARDED_BY(g_delay_cache_mutex); constexpr size_t DELAY_CACHE_MAX{4096}; } // namespace uint64_t DelayDivisor(const uint256& prev_hash) { uint64_t d; memcpy(&d, prev_hash.begin(), sizeof(d)); return d | 1; // odd } // high 128 bits of (u1:u0) * (v1:v0) - the 128x128 product high half, // computed explicitly because the compiler truncates u128*u128. static inline uint64_t MulHi128(uint64_t u1, uint64_t u0, uint64_t v1, uint64_t v0) { uint64_t hi0 = (uint64_t)((unsigned __int128)u0 * v0 >> 64); unsigned __int128 t1 = (unsigned __int128)u0 * v1 + hi0; uint64_t lo1 = (uint64_t)t1, hi1 = (uint64_t)(t1 >> 64); unsigned __int128 t2 = (unsigned __int128)u1 * v0 + lo1; uint64_t lo2 = (uint64_t)t2, hi2 = (uint64_t)(t2 >> 64); (void)lo2; return (uint64_t)((unsigned __int128)u1 * v1 + hi1 + hi2); } uint64_t DelayStep(uint64_t r, uint64_t w, uint64_t d, unsigned __int128 v) { // qhat = ((r<<64 | w) * v) >> 128 with v = floor((2^128-1)/d): // exact or 1 too small; one conditional correction. uint64_t q = MulHi128(r, w, (uint64_t)(v >> 64), (uint64_t)v); unsigned __int128 u = ((unsigned __int128)r << 64) | w; unsigned __int128 rem = u - (unsigned __int128)q * d; if (rem >= d) { rem -= d; q++; } (void)q; return (uint64_t)rem; } uint64_t ComputeDelay(const uint256& prev_hash, uint64_t steps) { const auto cache_key = std::make_pair(prev_hash, steps); { LOCK(g_delay_cache_mutex); const auto it = g_delay_cache.find(cache_key); if (it != g_delay_cache.end()) return it->second; } const uint64_t d = DelayDivisor(prev_hash); // Barrett reciprocal, precomputed once per block const unsigned __int128 v = (((unsigned __int128)0 - 1) << 64 | (unsigned __int128)0xffffffffffffffffULL) / d; // xorshift64* stream seeded from the previous block hash uint64_t state; memcpy(&state, prev_hash.begin() + 8, sizeof(state)); state = (state == 0) ? 0x9e3779b97f4a7c15ULL : state; uint64_t r = 0; for (uint64_t i = 0; i < steps; i++) { // xorshift64* uint64_t x = state; x ^= x >> 12; x ^= x << 25; x ^= x >> 27; state = x; const uint64_t w = x * 0x2545F4914F6CDD1DULL; r = DelayStep(r, w, d, v); } LOCK(g_delay_cache_mutex); if (g_delay_cache.size() >= DELAY_CACHE_MAX) g_delay_cache.clear(); g_delay_cache.emplace(cache_key, r); return r; } int GetDelayOutputIndex(const CTransaction& coinbase) { int pos = NO_DELAY_OUTPUT; for (size_t o = 0; o < coinbase.vout.size(); ++o) { const CScript& spk = coinbase.vout[o].scriptPubKey; CScript::const_iterator pc = spk.begin(); opcodetype opcode; std::vector data; if (!spk.GetOp(pc, opcode, data) || opcode != OP_RETURN) continue; if (!spk.GetOp(pc, opcode, data)) continue; if (data.size() == 2 && data[0] == DELAY_MAGIC_BYTE0 && data[1] == DELAY_MAGIC_BYTE1) { pos = static_cast(o); } } return pos; } std::optional GetDelayOutputValue(const CTxOut& out) { const CScript& spk = out.scriptPubKey; CScript::const_iterator pc = spk.begin(); opcodetype opcode; std::vector data; if (!spk.GetOp(pc, opcode, data) || opcode != OP_RETURN) return std::nullopt; if (!spk.GetOp(pc, opcode, data)) return std::nullopt; if (data.size() != 2 || data[0] != DELAY_MAGIC_BYTE0 || data[1] != DELAY_MAGIC_BYTE1) return std::nullopt; if (!spk.GetOp(pc, opcode, data)) return std::nullopt; if (data.size() != 8) return std::nullopt; uint64_t v = 0; for (int i = 0; i < 8; i++) v |= (uint64_t)data[i] << (8 * i); return v; }