// Originally by: https://github.com/boykopovar/aiodeepseek #include "pow.h" #include #include #include #include #include #include #include #include #include static constexpr size_t RATE = 136; static constexpr size_t RATE_W = RATE / 8; static constexpr size_t MAX_NONCE_DEC = 20; /* * Динамическая область должна учитывать: * * base_len % 8 * + nonce до 20 символов * + байт padding 0x06 * * 24 байт в исходном варианте может оказаться недостаточно. */ static constexpr size_t DYN_SIZE = 32; static const uint64_t RC[24] = { 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808AULL, 0x8000000080008000ULL, 0x000000000000808BULL, 0x0000000080000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008AULL, 0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000AULL, 0x000000008000808BULL, 0x800000000000008BULL, 0x8000000000008089ULL, 0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL, 0x000000000000800AULL, 0x800000008000000AULL, 0x8000000080008081ULL, 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL, }; static constexpr char DIGITS_00_99[] = "00010203040506070809" "10111213141516171819" "20212223242526272829" "30313233343536373839" "40414243444546474849" "50515253545556575859" "60616263646566676869" "70717273747576777879" "80818283848586878889" "90919293949596979899"; static inline int hex_nibble(char c) { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } return -1; } static bool valid_hex_digest(const std::string& value) { if (value.size() != 64) { return false; } for (char c : value) { if (hex_nibble(c) < 0) { return false; } } return true; } static inline int fast_u64_to_dec(uint64_t value, char* out) { char tmp[20]; char* p = tmp + sizeof(tmp); while (value >= 100) { const uint64_t quotient = value / 100; const uint64_t remainder = value - quotient * 100; p -= 2; p[0] = DIGITS_00_99[remainder * 2]; p[1] = DIGITS_00_99[remainder * 2 + 1]; value = quotient; } if (value < 10) { *--p = static_cast('0' + value); } else { p -= 2; p[0] = DIGITS_00_99[value * 2]; p[1] = DIGITS_00_99[value * 2 + 1]; } const int len = static_cast( tmp + sizeof(tmp) - p ); std::memcpy( out, p, static_cast(len) ); return len; } struct PowCtx { uint64_t static_state[25]; uint64_t target4[4]; uint8_t dyn_tpl[DYN_SIZE]; size_t base_len; size_t dyn_word_start; size_t dyn_word_count; size_t dyn_offset; }; static PowCtx build_ctx( const std::string& base, const std::string& challenge_hex ) { PowCtx ctx = {}; ctx.base_len = base.size(); for (int word = 0; word < 4; ++word) { uint64_t value = 0; for (int byte = 0; byte < 8; ++byte) { const int position = (word * 8 + byte) * 2; const auto high = static_cast( hex_nibble(challenge_hex[position]) ); const auto low = static_cast( hex_nibble(challenge_hex[position + 1]) ); const uint8_t decoded = static_cast( (high << 4) | low ); value |= static_cast(decoded) << (byte * 8); } ctx.target4[word] = value; } ctx.dyn_word_start = ctx.base_len / 8; ctx.dyn_offset = ctx.base_len % 8; const size_t dyn_last = ctx.base_len + MAX_NONCE_DEC + 1; const size_t dyn_word_end = dyn_last / 8 + 1; ctx.dyn_word_count = dyn_word_end - ctx.dyn_word_start; if ( ctx.dyn_word_start + ctx.dyn_word_count > RATE_W ) { ctx.dyn_word_count = RATE_W - ctx.dyn_word_start; } std::memset( ctx.dyn_tpl, 0, sizeof(ctx.dyn_tpl) ); const size_t dyn_byte_start = ctx.dyn_word_start * 8; if (ctx.base_len > dyn_byte_start) { const size_t prefix_len = ctx.base_len - dyn_byte_start; std::memcpy( ctx.dyn_tpl, base.data() + dyn_byte_start, prefix_len ); } alignas(8) uint8_t block[RATE] = {}; std::memcpy( block, base.data(), ctx.base_len ); /* * Последний SHA3 padding-бит. * Первый байт padding 0x06 добавляется после nonce. */ block[RATE - 1] = 0x80; std::memset( ctx.static_state, 0, sizeof(ctx.static_state) ); for (size_t word = 0; word < RATE_W; ++word) { const bool dynamic = word >= ctx.dyn_word_start && word < ctx.dyn_word_start + ctx.dyn_word_count; if (dynamic) { continue; } uint64_t value; std::memcpy( &value, &block[word * 8], sizeof(value) ); ctx.static_state[word] ^= value; } return ctx; } #define R4(x, n) \ _mm256_or_si256( \ _mm256_slli_epi64((x), (n)), \ _mm256_srli_epi64((x), 64 - (n)) \ ) #define KF_ROUND4(i) \ do { \ C[0] = _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256(A[0], A[5]), \ A[10] \ ), \ A[15] \ ), \ A[20] \ ); \ C[1] = _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256(A[1], A[6]), \ A[11] \ ), \ A[16] \ ), \ A[21] \ ); \ C[2] = _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256(A[2], A[7]), \ A[12] \ ), \ A[17] \ ), \ A[22] \ ); \ C[3] = _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256(A[3], A[8]), \ A[13] \ ), \ A[18] \ ), \ A[23] \ ); \ C[4] = _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256( \ _mm256_xor_si256(A[4], A[9]), \ A[14] \ ), \ A[19] \ ), \ A[24] \ ); \ \ D[0] = _mm256_xor_si256(C[4], R4(C[1], 1)); \ D[1] = _mm256_xor_si256(C[0], R4(C[2], 1)); \ D[2] = _mm256_xor_si256(C[1], R4(C[3], 1)); \ D[3] = _mm256_xor_si256(C[2], R4(C[4], 1)); \ D[4] = _mm256_xor_si256(C[3], R4(C[0], 1)); \ \ for (int j = 0; j < 25; ++j) { \ A[j] = _mm256_xor_si256(A[j], D[j % 5]); \ } \ \ B[0] = A[0]; \ B[1] = R4(A[6], 44); \ B[2] = R4(A[12], 43); \ B[3] = R4(A[18], 21); \ B[4] = R4(A[24], 14); \ B[5] = R4(A[3], 28); \ B[6] = R4(A[9], 20); \ B[7] = R4(A[10], 3); \ B[8] = R4(A[16], 45); \ B[9] = R4(A[22], 61); \ B[10] = R4(A[1], 1); \ B[11] = R4(A[7], 6); \ B[12] = R4(A[13], 25); \ B[13] = R4(A[19], 8); \ B[14] = R4(A[20], 18); \ B[15] = R4(A[4], 27); \ B[16] = R4(A[5], 36); \ B[17] = R4(A[11], 10); \ B[18] = R4(A[17], 15); \ B[19] = R4(A[23], 56); \ B[20] = R4(A[2], 62); \ B[21] = R4(A[8], 55); \ B[22] = R4(A[14], 39); \ B[23] = R4(A[15], 41); \ B[24] = R4(A[21], 2); \ \ A[0] = _mm256_xor_si256(B[0], _mm256_andnot_si256(B[1], B[2])); \ A[1] = _mm256_xor_si256(B[1], _mm256_andnot_si256(B[2], B[3])); \ A[2] = _mm256_xor_si256(B[2], _mm256_andnot_si256(B[3], B[4])); \ A[3] = _mm256_xor_si256(B[3], _mm256_andnot_si256(B[4], B[0])); \ A[4] = _mm256_xor_si256(B[4], _mm256_andnot_si256(B[0], B[1])); \ \ A[5] = _mm256_xor_si256(B[5], _mm256_andnot_si256(B[6], B[7])); \ A[6] = _mm256_xor_si256(B[6], _mm256_andnot_si256(B[7], B[8])); \ A[7] = _mm256_xor_si256(B[7], _mm256_andnot_si256(B[8], B[9])); \ A[8] = _mm256_xor_si256(B[8], _mm256_andnot_si256(B[9], B[5])); \ A[9] = _mm256_xor_si256(B[9], _mm256_andnot_si256(B[5], B[6])); \ \ A[10] = _mm256_xor_si256(B[10], _mm256_andnot_si256(B[11], B[12])); \ A[11] = _mm256_xor_si256(B[11], _mm256_andnot_si256(B[12], B[13])); \ A[12] = _mm256_xor_si256(B[12], _mm256_andnot_si256(B[13], B[14])); \ A[13] = _mm256_xor_si256(B[13], _mm256_andnot_si256(B[14], B[10])); \ A[14] = _mm256_xor_si256(B[14], _mm256_andnot_si256(B[10], B[11])); \ \ A[15] = _mm256_xor_si256(B[15], _mm256_andnot_si256(B[16], B[17])); \ A[16] = _mm256_xor_si256(B[16], _mm256_andnot_si256(B[17], B[18])); \ A[17] = _mm256_xor_si256(B[17], _mm256_andnot_si256(B[18], B[19])); \ A[18] = _mm256_xor_si256(B[18], _mm256_andnot_si256(B[19], B[15])); \ A[19] = _mm256_xor_si256(B[19], _mm256_andnot_si256(B[15], B[16])); \ \ A[20] = _mm256_xor_si256(B[20], _mm256_andnot_si256(B[21], B[22])); \ A[21] = _mm256_xor_si256(B[21], _mm256_andnot_si256(B[22], B[23])); \ A[22] = _mm256_xor_si256(B[22], _mm256_andnot_si256(B[23], B[24])); \ A[23] = _mm256_xor_si256(B[23], _mm256_andnot_si256(B[24], B[20])); \ A[24] = _mm256_xor_si256(B[24], _mm256_andnot_si256(B[20], B[21])); \ \ A[0] = _mm256_xor_si256( \ A[0], \ _mm256_set1_epi64x(static_cast(RC[i])) \ ); \ } while (0) static void keccak_f_4way(__m256i A[25]) { __m256i C[5]; __m256i D[5]; __m256i B[25]; /* * DeepSeekHashV1 использует раунды 1..23. * Стандартный Keccak использовал бы также RC[0]. */ KF_ROUND4(1); KF_ROUND4(2); KF_ROUND4(3); KF_ROUND4(4); KF_ROUND4(5); KF_ROUND4(6); KF_ROUND4(7); KF_ROUND4(8); KF_ROUND4(9); KF_ROUND4(10); KF_ROUND4(11); KF_ROUND4(12); KF_ROUND4(13); KF_ROUND4(14); KF_ROUND4(15); KF_ROUND4(16); KF_ROUND4(17); KF_ROUND4(18); KF_ROUND4(19); KF_ROUND4(20); KF_ROUND4(21); KF_ROUND4(22); KF_ROUND4(23); } #undef KF_ROUND4 #undef R4 static void worker_avx2( const PowCtx& ctx, uint64_t from, uint64_t to, std::atomic& result ) { const __m256i target0 = _mm256_set1_epi64x( static_cast(ctx.target4[0]) ); const __m256i target1 = _mm256_set1_epi64x( static_cast(ctx.target4[1]) ); const __m256i target2 = _mm256_set1_epi64x( static_cast(ctx.target4[2]) ); const __m256i target3 = _mm256_set1_epi64x( static_cast(ctx.target4[3]) ); alignas(32) uint8_t dynamic[4][DYN_SIZE]; char nonce_buffer[4][21]; for (uint64_t nonce = from; nonce < to; nonce += 4) { if ( (nonce & 0xFFFu) == 0 && result.load(std::memory_order_relaxed) >= 0 ) { return; } uint64_t lanes = to - nonce; if (lanes > 4) { lanes = 4; } for (uint64_t lane = 0; lane < lanes; ++lane) { std::memcpy( dynamic[lane], ctx.dyn_tpl, sizeof(ctx.dyn_tpl) ); const int nonce_len = fast_u64_to_dec( nonce + lane, nonce_buffer[lane] ); const size_t padding_position = ctx.dyn_offset + static_cast(nonce_len); if (padding_position >= DYN_SIZE) { return; } std::memcpy( dynamic[lane] + ctx.dyn_offset, nonce_buffer[lane], static_cast(nonce_len) ); dynamic[lane][padding_position] = 0x06; } for (uint64_t lane = lanes; lane < 4; ++lane) { std::memcpy( dynamic[lane], dynamic[0], sizeof(dynamic[0]) ); } alignas(32) __m256i state[25]; for (int word = 0; word < 25; ++word) { state[word] = _mm256_set1_epi64x( static_cast( ctx.static_state[word] ) ); } for ( size_t word = 0; word < ctx.dyn_word_count; ++word ) { uint64_t value0; uint64_t value1; uint64_t value2; uint64_t value3; std::memcpy( &value0, &dynamic[0][word * 8], sizeof(value0) ); std::memcpy( &value1, &dynamic[1][word * 8], sizeof(value1) ); std::memcpy( &value2, &dynamic[2][word * 8], sizeof(value2) ); std::memcpy( &value3, &dynamic[3][word * 8], sizeof(value3) ); const __m256i values = _mm256_set_epi64x( static_cast(value3), static_cast(value2), static_cast(value1), static_cast(value0) ); state[ ctx.dyn_word_start + word ] = _mm256_xor_si256( state[ctx.dyn_word_start + word], values ); } keccak_f_4way(state); int mask = _mm256_movemask_pd( _mm256_castsi256_pd( _mm256_cmpeq_epi64( state[0], target0 ) ) ); if (!mask) { continue; } mask &= _mm256_movemask_pd( _mm256_castsi256_pd( _mm256_cmpeq_epi64( state[1], target1 ) ) ); if (!mask) { continue; } mask &= _mm256_movemask_pd( _mm256_castsi256_pd( _mm256_cmpeq_epi64( state[2], target2 ) ) ); if (!mask) { continue; } mask &= _mm256_movemask_pd( _mm256_castsi256_pd( _mm256_cmpeq_epi64( state[3], target3 ) ) ); if (!mask) { continue; } mask &= static_cast( (1u << lanes) - 1u ); for ( int lane = 0; lane < static_cast(lanes); ++lane ) { if (!(mask & (1 << lane))) { continue; } int64_t expected = -1; result.compare_exchange_strong( expected, static_cast(nonce) + lane, std::memory_order_relaxed ); return; } } } static int64_t solve_internal( const std::string& base, const std::string& challenge_hex, int64_t difficulty ) { if (!valid_hex_digest(challenge_hex)) { return -3; } if (difficulty <= 0) { return -3; } /* * Solver рассчитан на один SHA3-256 rate block. */ if (base.size() > RATE - MAX_NONCE_DEC - 1) { return -3; } PowCtx ctx = build_ctx( base, challenge_hex ); std::atomic result{-1}; unsigned thread_count = std::thread::hardware_concurrency(); if (thread_count == 0) { thread_count = 1; } if ( static_cast(thread_count) > difficulty ) { thread_count = static_cast(difficulty); } const uint64_t unsigned_difficulty = static_cast(difficulty); const uint64_t chunk = ( unsigned_difficulty + thread_count - 1 ) / thread_count; std::vector threads; threads.reserve(thread_count); for ( unsigned thread = 0; thread < thread_count; ++thread ) { const uint64_t from = static_cast(thread) * chunk; const uint64_t to = std::min( from + chunk, unsigned_difficulty ); if (from >= to) { break; } threads.emplace_back( worker_avx2, std::cref(ctx), from, to, std::ref(result) ); } for (auto& thread : threads) { thread.join(); } return result.load( std::memory_order_relaxed ); } extern "C" int64_t deepseek_pow_solve( const char* base, size_t base_len, const char* challenge_hex, size_t challenge_hex_len, int64_t difficulty ) { if ( base == nullptr || challenge_hex == nullptr ) { return -2; } try { const std::string base_string( base, base_len ); const std::string challenge_string( challenge_hex, challenge_hex_len ); return solve_internal( base_string, challenge_string, difficulty ); } catch (const std::exception&) { return -4; } catch (...) { return -4; } }