Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 41 additions & 23 deletions src/datum_protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,6 @@ unsigned char datum_protocol_setup_new_job_idx(void *sx) {
return a;
}

static inline void datum_xor_header_key(void *h, uint32_t key) {
*((uint32_t *)h) ^= key;
}

uint32_t datum_header_xor_feedback(const uint32_t i) {
uint32_t s = 0xb10cfeed;
uint32_t h = s;
Expand All @@ -172,6 +168,32 @@ uint32_t datum_header_xor_feedback(const uint32_t i) {
return h;
}

static
void datum_header_pk(uint8_t * const dst, const size_t offset, const T_DATUM_PROTOCOL_HEADER * const h, uint32_t * const xor_key) {
uint32_t raw = (h->cmd_len & 0x3fffffUL) |
((uint32_t)h->is_signed << 24) |
((uint32_t)h->is_encrypted_pubkey << 25) |
((uint32_t)h->is_encrypted_channel << 26) |
((uint32_t)(h->proto_cmd & 0x1f) << 27);
raw ^= *xor_key;
*xor_key = datum_header_xor_feedback(*xor_key);

pk_u32le(dst, offset, raw);
}

static
void datum_header_upk(T_DATUM_PROTOCOL_HEADER * const h, const uint8_t * const src, const size_t offset, uint32_t * const xor_key) {
uint32_t raw = upk_u32le(src, offset);
raw ^= *xor_key;
*xor_key = datum_header_xor_feedback(*xor_key);

h->cmd_len = raw & 0x003fffffUL;
h->is_signed = raw & 0x01000000UL;
h->is_encrypted_pubkey = raw & 0x02000000UL;
h->is_encrypted_channel = raw & 0x04000000UL;
h->proto_cmd = (raw >> 27) & 0x1f;
}

// Take the hexidecimal public key string and store it in a DATUM_ENC_KEYS
int datum_pubkey_to_struct(const char *input, DATUM_ENC_KEYS *key) {
int i;
Expand Down Expand Up @@ -229,6 +251,7 @@ int datum_protocol_mining_cmd(void *data, int len) {
// encypt and send a standard mining sub-command
// this can be called from other threads so must be thread safe!
T_DATUM_PROTOCOL_HEADER h;
unsigned char wire_h[T_DATUM_PROTOCOL_HEADER_WIRE_BYTES];
int i;

memset(&h, 0, sizeof(T_DATUM_PROTOCOL_HEADER));
Expand All @@ -244,11 +267,10 @@ int datum_protocol_mining_cmd(void *data, int len) {

crypto_box_easy_afternm(data, data, len, session_nonce_sender, session_precomp.precomp_remote);
//DLOG_DEBUG("mining cmd 5--- len %d, send header key %8.8x, raw %8.8lx", h.cmd_len, sending_header_key, (unsigned long)upk_u32le(h, 0));
datum_xor_header_key(&h, sending_header_key);
sending_header_key = datum_header_xor_feedback(sending_header_key);
datum_header_pk(wire_h, 0, &h, &sending_header_key);
datum_increment_session_nonce(session_nonce_sender);

i = datum_protocol_chars_to_server((unsigned char *)&h, sizeof(T_DATUM_PROTOCOL_HEADER));
i = datum_protocol_chars_to_server(wire_h, sizeof(wire_h));
if (i < 1) {
pthread_mutex_unlock(&datum_protocol_sender_stage1_lock);
return -1;
Expand Down Expand Up @@ -1038,16 +1060,12 @@ int datum_protocol_send_hello(int sockfd) {
i+=crypto_sign_BYTES;

// seal it up
crypto_box_seal(&enc_hello_msg[sizeof(T_DATUM_PROTOCOL_HEADER)], hello_msg, i, pool_keys.pk_x25519);
crypto_box_seal(&enc_hello_msg[T_DATUM_PROTOCOL_HEADER_WIRE_BYTES], hello_msg, i, pool_keys.pk_x25519);
i+=crypto_box_SEALBYTES;

h.cmd_len = i;

memcpy(enc_hello_msg, &h, sizeof(T_DATUM_PROTOCOL_HEADER));

// apply our initial xor key to the header, just to obfuscate it a tiny bit
// kinda pointless, but ok
datum_xor_header_key(&enc_hello_msg[0], sending_header_key);
datum_header_pk(enc_hello_msg, 0, &h, &sending_header_key);

DLOG_DEBUG("Sending handshake init (%d bytes)", h.cmd_len);

Expand All @@ -1070,7 +1088,7 @@ int datum_protocol_send_hello(int sockfd) {
// FIXME: why is this mixed-endian?
//DLOG_DEBUG("Session Nonce: %8.8X%8.8X%8.8X%8.8X%8.8X%8.8X", upk_u32le(session_nonce_receiver, 0), upk_u32le(session_nonce_receiver, 4), upk_u32le(session_nonce_receiver, 8), upk_u32le(session_nonce_receiver, 12), upk_u32le(session_nonce_receiver, 16), upk_u32le(session_nonce_receiver, 20));

return datum_protocol_chars_to_server(enc_hello_msg, i+sizeof(T_DATUM_PROTOCOL_HEADER));
return datum_protocol_chars_to_server(enc_hello_msg, T_DATUM_PROTOCOL_HEADER_WIRE_BYTES + i);
}

int datum_protocol_decrypt_sealed(T_DATUM_PROTOCOL_HEADER *h, unsigned char *data) {
Expand Down Expand Up @@ -1464,6 +1482,7 @@ void *datum_protocol_client(void *args) {
bool break_again = false;
int sent = 0;
T_DATUM_PROTOCOL_HEADER s_header;
unsigned char wire_h[T_DATUM_PROTOCOL_HEADER_WIRE_BYTES];

pthread_rwlock_wrlock(&datum_jobs_rwlock);
for(i=0;i<MAX_DATUM_PROTOCOL_JOBS;i++) {
Expand Down Expand Up @@ -1709,7 +1728,7 @@ void *datum_protocol_client(void *args) {
case 1:
case 2:
case 3: {
n = recv(sockfd, ((unsigned char *)&s_header) + (sizeof(T_DATUM_PROTOCOL_HEADER) - protocol_state), protocol_state, MSG_DONTWAIT);
n = recv(sockfd, &wire_h[sizeof(wire_h) - protocol_state], protocol_state, MSG_DONTWAIT);
if (n <= 0) {
if ((n < 0) && ((errno == EAGAIN || errno == EWOULDBLOCK))) {
continue;
Expand All @@ -1718,34 +1737,34 @@ void *datum_protocol_client(void *args) {
break_again = true; break;
}

if ((n+(sizeof(T_DATUM_PROTOCOL_HEADER) - protocol_state)) != sizeof(T_DATUM_PROTOCOL_HEADER)) {
if ((n + (sizeof(wire_h) - protocol_state)) != sizeof(wire_h)) {
if ((n+protocol_state) > 4) {
DLOG_DEBUG("recv() issue. too many header bytes. protocol_state=%d, n=%d, errno=%d (%s)", protocol_state, n, errno, strerror(errno));
break_again = true; break;
Comment on lines +1740 to 1743
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check uses a hard-coded header size (4). Since the header size is now abstracted via wire_h/T_DATUM_PROTOCOL_HEADER_WIRE_BYTES, use sizeof(wire_h) (or the macro) here too to avoid future inconsistencies if the wire header size changes.

Copilot uses AI. Check for mistakes.
}

protocol_state = sizeof(T_DATUM_PROTOCOL_HEADER) - n - (sizeof(T_DATUM_PROTOCOL_HEADER) - protocol_state); // should give us a state equal to the number of. consoluted to show the process. (compiler optimizes)
protocol_state = sizeof(wire_h) - n - (sizeof(wire_h) - protocol_state); // should give us a state equal to the number of. consoluted to show the process. (compiler optimizes)
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: “consoluted” should be “convoluted”.

Suggested change
protocol_state = sizeof(wire_h) - n - (sizeof(wire_h) - protocol_state); // should give us a state equal to the number of. consoluted to show the process. (compiler optimizes)
protocol_state = sizeof(wire_h) - n - (sizeof(wire_h) - protocol_state); // should give us a state equal to the number of. convoluted to show the process. (compiler optimizes)

Copilot uses AI. Check for mistakes.
continue;
}

protocol_state = 4;
continue; // cant fall through to 0, so loop around back to this to jump to 4
}
case 0: {
n = recv(sockfd, &s_header, sizeof(T_DATUM_PROTOCOL_HEADER), MSG_DONTWAIT);
n = recv(sockfd, &wire_h[0], sizeof(wire_h), MSG_DONTWAIT);
if (n <= 0) {
if ((n < 0) && ((errno == EAGAIN || errno == EWOULDBLOCK))) {
continue;
}
DLOG_DEBUG("recv() issue. protocol_state=%d, n=%d, errno=%d (%s)", protocol_state, n, errno, strerror(errno));
break_again = true; break;
}
if (n != sizeof(T_DATUM_PROTOCOL_HEADER)) {
if (n != sizeof(wire_h)) {
if (n > 4) {
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch still hard-codes 4 as the maximum header byte count. For consistency with the rest of this state machine, replace it with sizeof(wire_h) (or T_DATUM_PROTOCOL_HEADER_WIRE_BYTES).

Suggested change
if (n > 4) {
if (n > sizeof(wire_h)) {

Copilot uses AI. Check for mistakes.
DLOG_DEBUG("recv() issue. too many header bytes (B). protocol_state=%d, n=%d, errno=%d (%s)", protocol_state, n, errno, strerror(errno));
break_again = true; break;
}
protocol_state = sizeof(T_DATUM_PROTOCOL_HEADER)-n;
protocol_state = sizeof(wire_h) - n;
continue;
}

Expand All @@ -1755,9 +1774,8 @@ void *datum_protocol_client(void *args) {
}

case 4: {
datum_xor_header_key(&s_header, receiving_header_key);
//DLOG_DEBUG("Server CMD: cmd=%u, len=%u, raw = %8.8x ... rkey = %8.8x", s_header.proto_cmd, s_header.cmd_len, upk_u32le(s_header, 0), receiving_header_key);
receiving_header_key = datum_header_xor_feedback(receiving_header_key);
datum_header_upk(&s_header, wire_h, 0, &receiving_header_key);
//DLOG_DEBUG("Server CMD: cmd=%u, len=%u, raw = %8.8x ... rkey(after) = %8.8x", s_header.proto_cmd, s_header.cmd_len, upk_u32le(s_header, 0), receiving_header_key);
protocol_state = 5;
server_in_buf = 0;
if (!s_header.cmd_len) {
Expand Down
10 changes: 5 additions & 5 deletions src/datum_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@
// It's likely possible to brute force the XOR key to break packets down into individual commands, but the contents and nature of the
// cmd is still obfuscated and unrecoverable without the session keys.

typedef struct __attribute__((packed)) T_DATUM_PROTOCOL_HEADER {
typedef struct T_DATUM_PROTOCOL_HEADER {
uint32_t cmd_len:22; // max cmd size is 2^22 (~4MB), which is roughly the max block size for a raw submission or a raw template validation
Comment thread
luke-jr marked this conversation as resolved.
uint8_t reserved:2; // save for later use
bool is_signed:1;
bool is_encrypted_pubkey:1;
bool is_encrypted_channel:1;
bool is_signed;
bool is_encrypted_pubkey;
bool is_encrypted_channel;
uint8_t proto_cmd:5; // 32 protocol level commands
} T_DATUM_PROTOCOL_HEADER;
#define T_DATUM_PROTOCOL_HEADER_WIRE_BYTES 4

typedef struct {
bool is_remote;
Expand Down
Loading