Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
51787d9
Add Stratum V2 (SV2) protocol support with Noise encryption and fallback
warioishere Feb 16, 2026
fcf6397
Move internal enums from protocol_coordinator.h to .c
warioishere Feb 16, 2026
5edb1c9
Add SV2 extended channel support, UI improvements, and testnet addres…
warioishere Feb 20, 2026
d3cd797
Restore comments stripped from stratum_v1_task.c
warioishere Feb 25, 2026
c063442
Remove testnet address detection (moved to separate PR #1578)
warioishere Feb 25, 2026
fc25dfc
Clarify SV2 authority pubkey tooltip and log message
warioishere Feb 25, 2026
77a8dc2
Group SV2/SV1 options under container divs instead of repeating condi…
warioishere Feb 25, 2026
391dcdc
Reuse truediffone and le256todouble from utils instead of duplicating
warioishere Feb 25, 2026
098b220
Combine 3 SHA256 update calls into single call with length 10
warioishere Feb 25, 2026
ca43286
Add missing fallback SV2 authority pubkey and protocol to /info endpoint
warioishere Feb 25, 2026
3114955
Add missing ?? 0 for coinbaseOutputs length check
warioishere Feb 25, 2026
bca624e
Use consistent payload + pos buffer writing style in sv2_protocol.c
warioishere Feb 25, 2026
61f0064
Move getProtocolLabel() logic from frontend to backend
warioishere Feb 25, 2026
be5401f
Simplify hasCoinbaseVisibility() to derive from API data
warioishere Feb 25, 2026
66c511d
Rename SV2 API fields to consistent stratumV2 naming
warioishere Feb 25, 2026
c21f890
Fix duplicate shares on SV2 standard channel
warioishere Feb 28, 2026
554981c
Address code review: poolConnectionInfo + packed struct
warioishere Mar 4, 2026
f01ccf5
Fix compiler warnings for unused return values in sv2_noise.c
warioishere Mar 4, 2026
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "components/libsecp256k1/libsecp256k1"]
path = components/libsecp256k1/libsecp256k1
url = https://github.com/bitcoin-core/secp256k1.git
15 changes: 15 additions & 0 deletions components/libsecp256k1/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
idf_component_register(
SRCS "libsecp256k1/src/secp256k1.c"
"libsecp256k1/src/precomputed_ecmult.c"
"libsecp256k1/src/precomputed_ecmult_gen.c"
INCLUDE_DIRS "libsecp256k1/include"
PRIV_INCLUDE_DIRS "libsecp256k1" "libsecp256k1/src"
)
target_compile_definitions(${COMPONENT_LIB} PRIVATE
ECMULT_WINDOW_SIZE=4
ECMULT_GEN_PREC_BITS=2
ENABLE_MODULE_ELLSWIFT=1
ENABLE_MODULE_SCHNORRSIG=1
ENABLE_MODULE_EXTRAKEYS=1
)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-parameter)
1 change: 1 addition & 0 deletions components/libsecp256k1/libsecp256k1
Submodule libsecp256k1 added at 0cdc75
6 changes: 6 additions & 0 deletions components/stratum/include/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ void free_bm_job(bm_job *job);
void calculate_coinbase_tx_hash(const char *coinbase_1, const char *coinbase_2,
const char *extranonce, const char *extranonce_2, uint8_t dest[32]);

void calculate_coinbase_tx_hash_bin(const uint8_t *prefix, size_t prefix_len,
const uint8_t *extranonce_prefix, size_t ep_len,
const uint8_t *extranonce_2, size_t e2_len,
const uint8_t *suffix, size_t suffix_len,
uint8_t dest[32]);

void calculate_merkle_root_hash(const uint8_t coinbase_tx_hash[32], const uint8_t merkle_branches[][32], const int num_merkle_branches, uint8_t dest[32]);

void construct_bm_job(mining_notify *params, const uint8_t merkle_root[32], const uint32_t version_mask, const uint32_t difficulty, bm_job* new_job);
Expand Down
2 changes: 2 additions & 0 deletions components/stratum/include/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ void reverse_32bit_words(const uint8_t src[32], uint8_t dest[32]);

void reverse_endianness_per_word(uint8_t data[32]);

extern const double truediffone;

double le256todouble(const void *target);

void prettyHex(unsigned char *buf, int len);
Expand Down
24 changes: 20 additions & 4 deletions components/stratum/mining.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ void calculate_coinbase_tx_hash(const char *coinbase_1, const char *coinbase_2,
double_sha256_bin(coinbase_tx_bin, coinbase_tx_bin_len, dest);
}

void calculate_coinbase_tx_hash_bin(const uint8_t *prefix, size_t prefix_len,
const uint8_t *extranonce_prefix, size_t ep_len,
const uint8_t *extranonce_2, size_t e2_len,
const uint8_t *suffix, size_t suffix_len,
uint8_t dest[32])
{
size_t total_len = prefix_len + ep_len + e2_len + suffix_len;
uint8_t *buf = malloc(total_len);
if (!buf) return;

size_t offset = 0;
memcpy(buf + offset, prefix, prefix_len); offset += prefix_len;
memcpy(buf + offset, extranonce_prefix, ep_len); offset += ep_len;
memcpy(buf + offset, extranonce_2, e2_len); offset += e2_len;
memcpy(buf + offset, suffix, suffix_len);

double_sha256_bin(buf, total_len, dest);
free(buf);
}

void calculate_merkle_root_hash(const uint8_t coinbase_tx_hash[32], const uint8_t merkle_branches[][32], const int num_merkle_branches, uint8_t dest[32])
{
uint8_t both_merkles[64];
Expand Down Expand Up @@ -112,10 +132,6 @@ void extranonce_2_generate(uint64_t extranonce_2, uint32_t length, char dest[sta
}

///////cgminer nonce testing
/* truediffone == 0x00000000FFFF0000000000000000000000000000000000000000000000000000
*/
static const double truediffone = 26959535291011309493156476344723991336010898738574164086137773096960.0;

/* testing a nonce and return the diff - 0 means invalid */
double test_nonce_value(const bm_job *job, const uint32_t nonce, const uint32_t rolled_version)
{
Expand Down
3 changes: 3 additions & 0 deletions components/stratum/stratum_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ esp_transport_handle_t STRATUM_V1_transport_init(tls_mode tls, char * cert)

void STRATUM_V1_initialize_buffer()
{
// Free any existing buffer (may be non-NULL if a previous V1 task was running)
free(json_rpc_buffer);

json_rpc_buffer = malloc(BUFFER_SIZE);
json_rpc_buffer_size = BUFFER_SIZE;
if (json_rpc_buffer == NULL) {
Expand Down
2 changes: 1 addition & 1 deletion components/stratum/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ void reverse_endianness_per_word(uint8_t data[32])
d[7] = __builtin_bswap32(d[7]);
}

// static const double truediffone = 26959535291011309493156476344723991336010898738574164086137773096960.0;
const double truediffone = 26959535291011309493156476344723991336010898738574164086137773096960.0;
static const double bits192 = 6277101735386680763835789423207666416102355444464034512896.0;
static const double bits128 = 340282366920938463463374607431768211456.0;
static const double bits64 = 18446744073709551616.0;
Expand Down
5 changes: 5 additions & 0 deletions components/stratum_v2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
idf_component_register(
SRCS "sv2_protocol.c" "sv2_noise.c"
INCLUDE_DIRS "include"
REQUIRES "mbedtls" "libsecp256k1" "tcp_transport" "stratum"
)
38 changes: 38 additions & 0 deletions components/stratum_v2/include/sv2_noise.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef SV2_NOISE_H
#define SV2_NOISE_H

#include <stdint.h>
#include <stdbool.h>
#include "esp_transport.h"

typedef struct sv2_noise_ctx sv2_noise_ctx_t;

// Create a new Noise context (allocates secp256k1 context internally).
sv2_noise_ctx_t *sv2_noise_create(void);

// Destroy a Noise context and free all resources.
void sv2_noise_destroy(sv2_noise_ctx_t *ctx);

// Perform Noise_NX handshake as initiator over the given transport.
// authority_pubkey is an optional 32-byte x-only public key for Schnorr
// signature verification of the responder's certificate. Pass NULL to skip.
// Returns 0 on success, -1 on error.
int sv2_noise_handshake(sv2_noise_ctx_t *ctx, esp_transport_handle_t transport,
const uint8_t *authority_pubkey);

// Send an SV2 frame (header + payload) encrypted via Noise.
// frame points to the complete plaintext frame (header + payload).
// Returns 0 on success, -1 on error.
int sv2_noise_send(sv2_noise_ctx_t *ctx, esp_transport_handle_t transport,
const uint8_t *frame, int frame_len);

// Receive and decrypt an SV2 frame via Noise.
// hdr_out receives the 6-byte decrypted frame header.
// payload_out receives the decrypted payload (up to max_payload_len bytes).
// payload_len_out receives the actual payload length.
// Returns 0 on success, -1 on error.
int sv2_noise_recv(sv2_noise_ctx_t *ctx, esp_transport_handle_t transport,
uint8_t hdr_out[6], uint8_t *payload_out,
int max_payload_len, int *payload_len_out);

#endif /* SV2_NOISE_H */
198 changes: 198 additions & 0 deletions components/stratum_v2/include/sv2_protocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#ifndef SV2_PROTOCOL_H
#define SV2_PROTOCOL_H

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

// Frame header size (extension_type[2] + msg_type[1] + msg_length[3])
#define SV2_FRAME_HEADER_SIZE 6

// Common message types
#define SV2_MSG_SETUP_CONNECTION 0x00
#define SV2_MSG_SETUP_CONNECTION_SUCCESS 0x01
#define SV2_MSG_SETUP_CONNECTION_ERROR 0x02

// Mining protocol message types (values from SV2 spec / SRI reference)
#define SV2_MSG_OPEN_STANDARD_MINING_CHANNEL 0x10
#define SV2_MSG_OPEN_STANDARD_MINING_CHANNEL_SUCCESS 0x11
#define SV2_MSG_OPEN_MINING_CHANNEL_ERROR 0x12
#define SV2_MSG_OPEN_EXTENDED_MINING_CHANNEL 0x13
#define SV2_MSG_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS 0x14
#define SV2_MSG_NEW_MINING_JOB 0x15
#define SV2_MSG_NEW_EXTENDED_MINING_JOB 0x1f
#define SV2_MSG_SUBMIT_SHARES_STANDARD 0x1a
#define SV2_MSG_SUBMIT_SHARES_EXTENDED 0x1b
#define SV2_MSG_SUBMIT_SHARES_SUCCESS 0x1c
#define SV2_MSG_SUBMIT_SHARES_ERROR 0x1d
#define SV2_MSG_SET_NEW_PREV_HASH 0x20
#define SV2_MSG_SET_TARGET 0x21

#define SV2_MAX_MERKLE_BRANCHES 20

// Extension type flag for channel messages
#define SV2_CHANNEL_MSG_FLAG 0x8000

// Channel type selection
typedef enum {
SV2_CHANNEL_EXTENDED = 0,
SV2_CHANNEL_STANDARD = 1
} sv2_channel_type_t;

// Frame header (parsed)
typedef struct {
uint16_t extension_type;
uint8_t msg_type;
uint32_t msg_length; // 24-bit value stored in 32-bit
} sv2_frame_header_t;

// Complete SV2 job (NewMiningJob + SetNewPrevHash combined)
typedef struct {
uint32_t job_id;
uint32_t version;
uint8_t merkle_root[32]; // Internal byte order (as received from SV2)
uint8_t prev_hash[32]; // Internal byte order (as received from SV2)
uint32_t ntime;
uint32_t nbits;
bool clean_jobs;
} sv2_job_t;

// Pending future job (waiting for SetNewPrevHash)
typedef struct {
uint32_t job_id;
uint32_t version;
uint8_t merkle_root[32];
bool valid;
} sv2_pending_job_t;

// Extended mining job (heap-allocated, owns coinbase pointers)
typedef struct {
uint32_t job_id;
uint32_t version;
bool version_rolling_allowed;
uint8_t prev_hash[32];
uint32_t ntime;
uint32_t nbits;
bool clean_jobs;
uint8_t merkle_path[SV2_MAX_MERKLE_BRANCHES][32];
uint8_t merkle_path_count;
uint8_t *coinbase_prefix; // heap
uint16_t coinbase_prefix_len;
uint8_t *coinbase_suffix; // heap
uint16_t coinbase_suffix_len;
} sv2_ext_job_t;

#define SV2_PENDING_JOBS_SIZE 8

// SV2 connection state
typedef struct sv2_conn {
uint32_t channel_id;
uint32_t sequence_number;
uint8_t target[32]; // U256 LE target
bool channel_opened;

// Pending future jobs ring buffer (standard channels)
sv2_pending_job_t pending_jobs[SV2_PENDING_JOBS_SIZE];

// Latest prev_hash state
uint8_t prev_hash[32];
uint32_t prev_hash_ntime;
uint32_t prev_hash_nbits;
bool has_prev_hash;

// Extended channel state (zero for standard channels)
sv2_channel_type_t channel_type;
uint8_t extranonce_prefix[32];
uint8_t extranonce_prefix_len;
uint8_t extranonce_size; // total extranonce bytes assigned by pool
sv2_ext_job_t *ext_pending_jobs[SV2_PENDING_JOBS_SIZE];
} sv2_conn_t;

// --- Frame encode/decode ---

// Parse 6-byte frame header. Returns 0 on success.
int sv2_parse_frame_header(const uint8_t *data, sv2_frame_header_t *header);

// Encode 6-byte frame header into dest. Returns 6.
int sv2_encode_frame_header(uint8_t *dest, uint16_t extension_type, uint8_t msg_type, uint32_t msg_length);

// --- Message builders (return total frame size, or -1 on error) ---

int sv2_build_setup_connection(uint8_t *buf, size_t buf_len,
const char *host, uint16_t port,
const char *vendor, const char *hw_version,
const char *firmware, const char *device_id,
uint32_t flags);

int sv2_build_open_standard_mining_channel(uint8_t *buf, size_t buf_len,
uint32_t request_id,
const char *user_identity,
float nominal_hash_rate);

int sv2_build_submit_shares_standard(uint8_t *buf, size_t buf_len,
uint32_t channel_id, uint32_t sequence_number,
uint32_t job_id, uint32_t nonce,
uint32_t ntime, uint32_t version);

// --- Message parsers (return 0 on success, -1 on error) ---

int sv2_parse_setup_connection_success(const uint8_t *payload, uint32_t len,
uint16_t *used_version, uint32_t *flags);

int sv2_parse_open_channel_success(const uint8_t *payload, uint32_t len,
uint32_t *request_id, uint32_t *channel_id,
uint8_t target[32],
uint8_t *extranonce_prefix, uint8_t *extranonce_prefix_len,
uint32_t *group_channel_id);

int sv2_parse_new_mining_job(const uint8_t *payload, uint32_t len,
uint32_t *channel_id, uint32_t *job_id,
bool *has_min_ntime, uint32_t *min_ntime,
uint32_t *version,
uint8_t merkle_root[32]);

int sv2_parse_set_new_prev_hash(const uint8_t *payload, uint32_t len,
uint32_t *channel_id, uint32_t *job_id,
uint8_t prev_hash[32],
uint32_t *min_ntime, uint32_t *nbits);

int sv2_parse_set_target(const uint8_t *payload, uint32_t len,
uint32_t *channel_id, uint8_t max_target[32]);

int sv2_parse_submit_shares_success(const uint8_t *payload, uint32_t len,
uint32_t *channel_id);

int sv2_parse_submit_shares_error(const uint8_t *payload, uint32_t len,
uint32_t *channel_id, uint32_t *seq_num,
char *error_code, size_t error_code_size);

// --- Extended channel message builders/parsers ---

int sv2_build_open_extended_mining_channel(uint8_t *buf, size_t buf_len,
uint32_t request_id, const char *user_identity,
float nominal_hash_rate, uint16_t min_extranonce_size);

int sv2_build_submit_shares_extended(uint8_t *buf, size_t buf_len,
uint32_t channel_id, uint32_t sequence_number,
uint32_t job_id, uint32_t nonce, uint32_t ntime,
uint32_t version, const uint8_t *extranonce,
uint8_t extranonce_len);

int sv2_parse_open_extended_channel_success(const uint8_t *payload, uint32_t len,
uint32_t *request_id, uint32_t *channel_id,
uint8_t target[32], uint16_t *extranonce_size,
uint8_t *extranonce_prefix,
uint8_t *extranonce_prefix_len,
uint32_t *group_channel_id);

sv2_ext_job_t *sv2_parse_new_extended_mining_job(const uint8_t *payload, uint32_t len,
uint32_t *channel_id_out);

void sv2_ext_job_free(sv2_ext_job_t *job);

// --- Helpers ---

// Convert U256 LE target to pool difficulty (pdiff)
uint32_t sv2_target_to_pdiff(const uint8_t target[32]);

#endif /* SV2_PROTOCOL_H */
Loading