initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
// Package pow solves DeepSeekHashV1 proof-of-work challenges using a CGO-backed
|
||||
// AVX2 implementation.
|
||||
package pow
|
||||
+776
@@ -0,0 +1,776 @@
|
||||
// Originally by: https://github.com/boykopovar/aiodeepseek
|
||||
|
||||
#include "pow.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <immintrin.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
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<char>('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<int>(
|
||||
tmp + sizeof(tmp) - p
|
||||
);
|
||||
|
||||
std::memcpy(
|
||||
out,
|
||||
p,
|
||||
static_cast<size_t>(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<uint8_t>(
|
||||
hex_nibble(challenge_hex[position])
|
||||
);
|
||||
|
||||
const auto low = static_cast<uint8_t>(
|
||||
hex_nibble(challenge_hex[position + 1])
|
||||
);
|
||||
|
||||
const uint8_t decoded = static_cast<uint8_t>(
|
||||
(high << 4) | low
|
||||
);
|
||||
|
||||
value |= static_cast<uint64_t>(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<int64_t>(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<int64_t>& result
|
||||
) {
|
||||
const __m256i target0 = _mm256_set1_epi64x(
|
||||
static_cast<int64_t>(ctx.target4[0])
|
||||
);
|
||||
|
||||
const __m256i target1 = _mm256_set1_epi64x(
|
||||
static_cast<int64_t>(ctx.target4[1])
|
||||
);
|
||||
|
||||
const __m256i target2 = _mm256_set1_epi64x(
|
||||
static_cast<int64_t>(ctx.target4[2])
|
||||
);
|
||||
|
||||
const __m256i target3 = _mm256_set1_epi64x(
|
||||
static_cast<int64_t>(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<size_t>(nonce_len);
|
||||
|
||||
if (padding_position >= DYN_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(
|
||||
dynamic[lane] + ctx.dyn_offset,
|
||||
nonce_buffer[lane],
|
||||
static_cast<size_t>(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<int64_t>(
|
||||
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<int64_t>(value3),
|
||||
static_cast<int64_t>(value2),
|
||||
static_cast<int64_t>(value1),
|
||||
static_cast<int64_t>(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<int>(
|
||||
(1u << lanes) - 1u
|
||||
);
|
||||
|
||||
for (
|
||||
int lane = 0;
|
||||
lane < static_cast<int>(lanes);
|
||||
++lane
|
||||
) {
|
||||
if (!(mask & (1 << lane))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t expected = -1;
|
||||
|
||||
result.compare_exchange_strong(
|
||||
expected,
|
||||
static_cast<int64_t>(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<int64_t> result{-1};
|
||||
|
||||
unsigned thread_count =
|
||||
std::thread::hardware_concurrency();
|
||||
|
||||
if (thread_count == 0) {
|
||||
thread_count = 1;
|
||||
}
|
||||
|
||||
if (
|
||||
static_cast<int64_t>(thread_count) >
|
||||
difficulty
|
||||
) {
|
||||
thread_count =
|
||||
static_cast<unsigned>(difficulty);
|
||||
}
|
||||
|
||||
const uint64_t unsigned_difficulty =
|
||||
static_cast<uint64_t>(difficulty);
|
||||
|
||||
const uint64_t chunk =
|
||||
(
|
||||
unsigned_difficulty +
|
||||
thread_count -
|
||||
1
|
||||
) / thread_count;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(thread_count);
|
||||
|
||||
for (
|
||||
unsigned thread = 0;
|
||||
thread < thread_count;
|
||||
++thread
|
||||
) {
|
||||
const uint64_t from =
|
||||
static_cast<uint64_t>(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;
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package pow
|
||||
|
||||
/*
|
||||
#cgo CXXFLAGS: -std=c++17 -O3 -mavx2 -pthread
|
||||
#cgo LDFLAGS: -lstdc++ -pthread
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "pow.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound indicates that no nonce satisfies the challenge difficulty.
|
||||
ErrNotFound = errors.New("PoW solution not found")
|
||||
// ErrInvalidArgument indicates malformed or unsupported solver input.
|
||||
ErrInvalidArgument = errors.New("invalid PoW argument")
|
||||
// ErrInternalCppError indicates an exception in the native solver.
|
||||
ErrInternalCppError = errors.New("internal C++ solver error")
|
||||
)
|
||||
|
||||
// Challenge contains the inputs required to solve a DeepSeekHashV1 challenge.
|
||||
type Challenge struct {
|
||||
Algorithm string `json:"algorithm"`
|
||||
Challenge string `json:"challenge"`
|
||||
Salt string `json:"salt"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
ExpireAt uint64 `json:"expire_at"`
|
||||
Signature string `json:"signature"`
|
||||
TargetPath string `json:"target_path"`
|
||||
}
|
||||
|
||||
// Solve validates and solves c, returning a nonce smaller than Difficulty.
|
||||
func (c Challenge) Solve() (uint64, error) {
|
||||
if c.Algorithm != "" && c.Algorithm != "DeepSeekHashV1" {
|
||||
return 0, fmt.Errorf(
|
||||
"unsupported PoW algorithm %q",
|
||||
c.Algorithm,
|
||||
)
|
||||
}
|
||||
|
||||
if len(c.Challenge) != 64 {
|
||||
return 0, fmt.Errorf(
|
||||
"%w: challenge must contain 64 hexadecimal characters",
|
||||
ErrInvalidArgument,
|
||||
)
|
||||
}
|
||||
|
||||
if c.Salt == "" {
|
||||
return 0, fmt.Errorf(
|
||||
"%w: salt is empty",
|
||||
ErrInvalidArgument,
|
||||
)
|
||||
}
|
||||
|
||||
if c.Difficulty <= 0 {
|
||||
return 0, fmt.Errorf(
|
||||
"%w: difficulty must be positive",
|
||||
ErrInvalidArgument,
|
||||
)
|
||||
}
|
||||
|
||||
base := c.Salt +
|
||||
"_" +
|
||||
strconv.FormatUint(c.ExpireAt, 10) +
|
||||
"_"
|
||||
|
||||
return Solve(base, c.Challenge, c.Difficulty)
|
||||
}
|
||||
|
||||
// Solve searches the range [0, difficulty) for a nonce whose DeepSeekHashV1
|
||||
// digest equals challengeHex.
|
||||
func Solve(
|
||||
base string,
|
||||
challengeHex string,
|
||||
difficulty uint64,
|
||||
) (uint64, error) {
|
||||
if base == "" {
|
||||
return 0, fmt.Errorf(
|
||||
"%w: base is empty",
|
||||
ErrInvalidArgument,
|
||||
)
|
||||
}
|
||||
|
||||
if len(challengeHex) != 64 {
|
||||
return 0, fmt.Errorf(
|
||||
"%w: challenge must contain 64 hexadecimal characters",
|
||||
ErrInvalidArgument,
|
||||
)
|
||||
}
|
||||
|
||||
if difficulty <= 0 {
|
||||
return 0, fmt.Errorf(
|
||||
"%w: difficulty must be positive",
|
||||
ErrInvalidArgument,
|
||||
)
|
||||
}
|
||||
|
||||
baseBytes := []byte(base)
|
||||
challengeBytes := []byte(challengeHex)
|
||||
|
||||
result := C.deepseek_pow_solve(
|
||||
(*C.char)(unsafe.Pointer(&baseBytes[0])),
|
||||
C.size_t(len(baseBytes)),
|
||||
(*C.char)(unsafe.Pointer(&challengeBytes[0])),
|
||||
C.size_t(len(challengeBytes)),
|
||||
C.int64_t(difficulty),
|
||||
)
|
||||
|
||||
switch {
|
||||
case result >= 0:
|
||||
return uint64(result), nil
|
||||
|
||||
case result == -1:
|
||||
return 0, ErrNotFound
|
||||
|
||||
case result == -2:
|
||||
return 0, fmt.Errorf(
|
||||
"%w: null pointer passed to C++",
|
||||
ErrInvalidArgument,
|
||||
)
|
||||
|
||||
case result == -3:
|
||||
return 0, ErrInvalidArgument
|
||||
|
||||
case result == -4:
|
||||
return 0, ErrInternalCppError
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf(
|
||||
"unexpected C++ solver result: %d",
|
||||
int64(result),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef DEEPSEEK_POW_H
|
||||
#define DEEPSEEK_POW_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Возвращает:
|
||||
*
|
||||
* >= 0 — найденный nonce
|
||||
* -1 — решение не найдено
|
||||
* -2 — передан NULL
|
||||
* -3 — некорректные аргументы
|
||||
* -4 — внутреннее исключение 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
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKnownChallenge(t *testing.T) {
|
||||
challenge := Challenge{
|
||||
Algorithm: "DeepSeekHashV1",
|
||||
Challenge: "2f90572ad390d758b5e55b3bb74f14722166388023b3b28876d056a358591197",
|
||||
Salt: "2eeb8f3a703002bfca70",
|
||||
Difficulty: 144000,
|
||||
ExpireAt: 1785483643587,
|
||||
}
|
||||
|
||||
answer, err := challenge.Solve()
|
||||
if err != nil {
|
||||
t.Fatalf("Solve(): %v", err)
|
||||
}
|
||||
|
||||
t.Logf("answer=%d", answer)
|
||||
|
||||
/*
|
||||
* Для этого challenge ожидается 61830.
|
||||
*/
|
||||
const expected uint64 = 61830
|
||||
|
||||
if answer != expected {
|
||||
t.Fatalf(
|
||||
"unexpected answer: got %d, want %d",
|
||||
answer,
|
||||
expected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidChallenge(t *testing.T) {
|
||||
validDigest := "2f90572ad390d758b5e55b3bb74f14722166388023b3b28876d056a358591197"
|
||||
tests := []struct {
|
||||
name string
|
||||
base string
|
||||
challenge string
|
||||
difficulty uint64
|
||||
}{
|
||||
{name: "empty base", challenge: validDigest, difficulty: 1},
|
||||
{name: "short digest", base: "salt_123_", challenge: "not-hex", difficulty: 1},
|
||||
{name: "non-hex digest", base: "salt_123_", challenge: strings.Repeat("z", 64), difficulty: 1},
|
||||
{name: "zero difficulty", base: "salt_123_", challenge: validDigest},
|
||||
{name: "base exceeds one block", base: strings.Repeat("x", 116), challenge: validDigest, difficulty: 1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := Solve(tt.base, tt.challenge, tt.difficulty)
|
||||
if !errors.Is(err, ErrInvalidArgument) {
|
||||
t.Fatalf("Solve() error = %v, want ErrInvalidArgument", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChallengeValidation(t *testing.T) {
|
||||
validDigest := "2f90572ad390d758b5e55b3bb74f14722166388023b3b28876d056a358591197"
|
||||
tests := []struct {
|
||||
name string
|
||||
challenge Challenge
|
||||
wantMatch bool
|
||||
}{
|
||||
{name: "unsupported algorithm", challenge: Challenge{Algorithm: "other", Challenge: validDigest, Salt: "salt", Difficulty: 1}},
|
||||
{name: "invalid digest", challenge: Challenge{Challenge: "short", Salt: "salt", Difficulty: 1}, wantMatch: true},
|
||||
{name: "empty salt", challenge: Challenge{Challenge: validDigest, Difficulty: 1}, wantMatch: true},
|
||||
{name: "zero difficulty", challenge: Challenge{Challenge: validDigest, Salt: "salt"}, wantMatch: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := tt.challenge.Solve()
|
||||
if err == nil {
|
||||
t.Fatal("Challenge.Solve() error = nil")
|
||||
}
|
||||
if tt.wantMatch && !errors.Is(err, ErrInvalidArgument) {
|
||||
t.Fatalf("Challenge.Solve() error = %v, want ErrInvalidArgument", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user