deployd/server/pow.c
Arija A. 3e7e9c6711
Improve dproto spec
Signed-off-by: Arija A. <ari@ari.lt>
2025-07-27 02:00:02 +03:00

177 lines
4.9 KiB
C

#include "include/conf.h"
#include <unistd.h>
#include <sys/random.h>
#include "include/def.h"
#include "include/pow.h"
#include "include/proto.h"
#include "include/blake2s.h"
#define DP_POW_XOR_ONES_RANGE 32
static const uint8_t dp_pow_difficulty = 20; /* Primary difficulty */
static const uint8_t dp_pow_ones = 8; /* Secondary difficulty */
bool dp_pow_has_leading_zero_bits(
const uint8_t digest[DP_BLAKE2S_OUTPUT_SIZE_MAX], uint8_t difficulty) {
if (!digest || difficulty == 0) {
return false;
}
const uint8_t full_bytes = difficulty / 8;
const uint8_t remainder_bytes = difficulty % 8;
for (uint8_t idx = 0; idx < full_bytes; ++idx) {
if (digest[idx] != '\x00') {
return false;
}
}
if (remainder_bytes > 0) {
const uint8_t next_byte = digest[full_bytes];
const uint8_t mask =
((uint8_t)((uint8_t)0xff << (uint8_t)(8 - remainder_bytes)) &
(uint8_t)0xff);
if ((next_byte & mask) != 0x00) {
return false;
}
}
return true;
}
static inline uint8_t dp_count_ones(uint8_t num) {
/* Fast bit-counting using Hacker's Delight method */
num = (uint8_t)(num - ((uint8_t)(num >> (uint8_t)1) & (uint8_t)0x55));
num = (uint8_t)((num & (uint8_t)0x33) +
((uint8_t)(num >> (uint8_t)2) & (uint8_t)0x33));
return (uint8_t)(((uint8_t)(num + (num >> (uint8_t)4)) & (uint8_t)0x0F));
}
bool dp_pow_has_xor_ones(const uint8_t digest[DP_BLAKE2S_OUTPUT_SIZE_MAX],
const uint8_t challenge[DP_POW_CHALLENGE_SIZE],
uint8_t ones) {
if (!digest || !challenge || ones == 0) {
return false;
}
uint8_t ok_ones = 0;
for (uint8_t idx = 0; idx < DP_POW_XOR_ONES_RANGE; ++idx) {
const uint8_t xored = (digest[idx % DP_BLAKE2S_OUTPUT_SIZE_MAX] ^
challenge[idx % DP_POW_CHALLENGE_SIZE]);
if (dp_count_ones(xored) >= 6) {
++ok_ones;
}
if (ok_ones == ones) {
return true;
}
}
return false;
}
bool dp_pow_protect(SSL *ssl, dp_Logger *logger) {
if (!ssl) {
return false;
}
uint8_t buf[DP_POW_CHALLENGE_SIZE + 1 + 1] = {0};
if (getrandom(buf, DP_POW_CHALLENGE_SIZE, 0) != DP_POW_CHALLENGE_SIZE) {
return false;
}
buf[DP_POW_CHALLENGE_SIZE] = dp_pow_difficulty;
buf[DP_POW_CHALLENGE_SIZE + 1] = dp_pow_ones;
if (SSL_write(ssl, buf, sizeof(buf)) != sizeof(buf)) {
return false;
}
uint8_t sol_buf[8] = {0};
uint8_t pings = 0;
dp_log(logger, DP_LOG_INFO, DP_POW_LOG "/protect",
"Protecting route using PoW");
while (pings < 64) {
if (SSL_read(ssl, sol_buf, 1) != 1) {
return false;
}
if (sol_buf[0] == dp_PacketType_ping) {
if (pings == 64) {
dp_proto_error(ssl, dp_PacketError_pow_too_many_pings,
"Too many PoW pings", logger);
return false;
}
if (!dp_proto_ping_reply(ssl)) {
return false;
}
++pings;
sleep(1);
continue;
}
if (sol_buf[0] != dp_PacketType_ready) {
dp_proto_skip(ssl, sol_buf[0]);
dp_proto_error(ssl, dp_PacketError_status,
"Invalid status packet", logger);
return false;
}
if (SSL_read(ssl, sol_buf, 8) != 8) {
dp_proto_error(ssl, dp_PacketError_proto_packet_too_short,
"Packet too short for PoW nonce", logger);
return false;
}
/* rest - nonce bytes */
char str[21] = {0};
const int len = dp_u642str(str, dp_buf2u64_le(sol_buf));
if (len < 1) {
return false;
}
dp_Blake2sCtx ctx = {0};
if (!dp_Blake2sCtx_init(&ctx, DP_BLAKE2S_OUTPUT_SIZE_MAX, buf,
DP_POW_CHALLENGE_SIZE)) {
return false;
}
if (!dp_Blake2sCtx_update(&ctx, str, (size_t)len)) {
return false;
}
if (!dp_Blake2sCtx_final(&ctx)) {
return false;
}
uint8_t digest[DP_BLAKE2S_OUTPUT_SIZE_MAX] = {0};
if (!dp_Blake2sCtx_to_digest(&ctx, digest)) {
return false;
}
if (dp_pow_has_leading_zero_bits(digest, dp_pow_difficulty) &&
dp_pow_has_xor_ones(digest, buf, dp_pow_ones)) {
static const uint8_t allowed = dp_PacketType_allowed;
SSL_write(ssl, &allowed, 1);
return true;
}
dp_proto_error(ssl, dp_PacketError_pow_bad_solution,
"Bad solution provided", logger);
}
dp_proto_error(ssl, dp_PacketError_pow_bad_solution, "No solution provided",
logger);
return false;
}