diff --git a/main/global_state.h b/main/global_state.h index 63646c5ad..cacf85de0 100644 --- a/main/global_state.h +++ b/main/global_state.h @@ -38,6 +38,7 @@ typedef struct PoolConfig bool decode_coinbase_tx; uint16_t sv2_channel_type; char * sv2_authority_pubkey; + bool sv2_require_auth; } PoolConfig; #define HISTORY_LENGTH 100 diff --git a/main/http_server/axe-os/src/app/components/pool/pool.component.html b/main/http_server/axe-os/src/app/components/pool/pool.component.html index b556157a2..3f15bcdf1 100644 --- a/main/http_server/axe-os/src/app/components/pool/pool.component.html +++ b/main/http_server/axe-os/src/app/components/pool/pool.component.html @@ -192,6 +192,20 @@

Pool Configurations + +
+ +
+ diff --git a/main/http_server/axe-os/src/app/components/pool/pool.component.ts b/main/http_server/axe-os/src/app/components/pool/pool.component.ts index ceee5da4a..6b7994ac6 100644 --- a/main/http_server/axe-os/src/app/components/pool/pool.component.ts +++ b/main/http_server/axe-os/src/app/components/pool/pool.component.ts @@ -98,7 +98,8 @@ export class PoolComponent implements OnInit { stratumCert: '', stratumDecodeCoinbase: true, stratumV2ChannelType: 'extended', - stratumV2AuthorityPubkey: '' + stratumV2AuthorityPubkey: '', + stratumV2RequireAuth: false }); } @@ -117,7 +118,8 @@ export class PoolComponent implements OnInit { stratumCert: '', stratumDecodeCoinbase: true, stratumV2ChannelType: 'extended', - stratumV2AuthorityPubkey: '' + stratumV2AuthorityPubkey: '', + stratumV2RequireAuth: false }); } @@ -149,7 +151,8 @@ export class PoolComponent implements OnInit { stratumCert: [pool.stratumCert || ''], stratumDecodeCoinbase: [pool.stratumDecodeCoinbase == true, [Validators.required]], stratumV2ChannelType: [pool.stratumV2ChannelType || 'extended'], - stratumV2AuthorityPubkey: [pool.stratumV2AuthorityPubkey || '', [this.base58Validator()]] + stratumV2AuthorityPubkey: [pool.stratumV2AuthorityPubkey || '', [this.base58Validator()]], + stratumV2RequireAuth: [pool.stratumV2RequireAuth == true] }); }); @@ -273,7 +276,8 @@ export class PoolComponent implements OnInit { stratumCert: [''], stratumDecodeCoinbase: [true, [Validators.required]], stratumV2ChannelType: ['extended'], - stratumV2AuthorityPubkey: ['', [this.base58Validator()]] + stratumV2AuthorityPubkey: ['', [this.base58Validator()]], + stratumV2RequireAuth: [false] }); this.poolsArray.push(poolGroup); diff --git a/main/http_server/axe-os/src/app/services/system.service.ts b/main/http_server/axe-os/src/app/services/system.service.ts index 8daf7963c..36d298c16 100644 --- a/main/http_server/axe-os/src/app/services/system.service.ts +++ b/main/http_server/axe-os/src/app/services/system.service.ts @@ -112,7 +112,8 @@ export class SystemApiService { stratumCert: "", stratumDecodeCoinbase: true, stratumV2ChannelType: "extended" as const, - stratumV2AuthorityPubkey: "" + stratumV2AuthorityPubkey: "", + stratumV2RequireAuth: false }, { id: 1, @@ -127,7 +128,8 @@ export class SystemApiService { stratumCert: "", stratumDecodeCoinbase: true, stratumV2ChannelType: "extended" as const, - stratumV2AuthorityPubkey: "" + stratumV2AuthorityPubkey: "", + stratumV2RequireAuth: false } ], stratumProtocol: "SV1" as const, diff --git a/main/http_server/http_server.c b/main/http_server/http_server.c index b9ddb933b..5207e38ba 100644 --- a/main/http_server/http_server.c +++ b/main/http_server/http_server.c @@ -770,6 +770,7 @@ static bool validate_pool_json(const cJSON *pool_item, int i) { } if (!validate_string_field(cJSON_GetObjectItem(pool_item, "stratumV2AuthorityPubkey"), "stratumV2AuthorityPubkey", 128, i)) return false; + if (!validate_bool_or_num(cJSON_GetObjectItem(pool_item, "stratumV2RequireAuth"), "stratumV2RequireAuth", i)) return false; return true; } @@ -813,6 +814,7 @@ static void update_pool_nvs(const cJSON *pool_item, int i) { add_bool_field_default(p_obj, pool_item, "stratumDecodeCoinbase", true); add_string_field_default(p_obj, pool_item, "stratumV2ChannelType", SV2_CHANNEL_TYPE_EXTENDED); add_string_field_default(p_obj, pool_item, "stratumV2AuthorityPubkey", ""); + add_bool_field_default(p_obj, pool_item, "stratumV2RequireAuth", false); char *json_str = cJSON_PrintUnformatted(p_obj); if (json_str) { diff --git a/main/http_server/openapi.yaml b/main/http_server/openapi.yaml index f3397b577..7f5791b69 100644 --- a/main/http_server/openapi.yaml +++ b/main/http_server/openapi.yaml @@ -90,6 +90,9 @@ components: type: string description: SV2 authority public key for certificate verification (base58-encoded) pattern: "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]*$" + stratumV2RequireAuth: + type: boolean + description: Refuse to connect unless the SV2 server certificate is verified against the authority pubkey id: type: integer description: Pool NVS slot index (0 to 7) diff --git a/main/http_server/system_api_json.c b/main/http_server/system_api_json.c index 2b834e57e..6680a8125 100644 --- a/main/http_server/system_api_json.c +++ b/main/http_server/system_api_json.c @@ -181,7 +181,8 @@ static void system_api_add_config(cJSON *root, GlobalState *g) { cJSON_AddBoolToObject(p_obj, "stratumDecodeCoinbase", p->decode_coinbase_tx); cJSON_AddStringToObject(p_obj, "stratumV2ChannelType", p->sv2_channel_type == SV2_CHANNEL_STANDARD ? SV2_CHANNEL_TYPE_STANDARD : SV2_CHANNEL_TYPE_EXTENDED); cJSON_AddStringToObject(p_obj, "stratumV2AuthorityPubkey", p->sv2_authority_pubkey ? p->sv2_authority_pubkey : ""); - + cJSON_AddBoolToObject(p_obj, "stratumV2RequireAuth", p->sv2_require_auth); + cJSON_AddItemToArray(pools_arr, p_obj); } } diff --git a/main/system.c b/main/system.c index d46390253..dab89f6b7 100644 --- a/main/system.c +++ b/main/system.c @@ -57,6 +57,7 @@ static void parse_pool_config_json(const char *json_str, PoolConfig *cfg, int in cfg->decode_coinbase_tx = true; cfg->sv2_channel_type = SV2_CHANNEL_EXTENDED; cfg->sv2_authority_pubkey = strdup(""); + cfg->sv2_require_auth = false; if (!json_str || strlen(json_str) == 0) { return; @@ -136,6 +137,11 @@ static void parse_pool_config_json(const char *json_str, PoolConfig *cfg, int in cfg->sv2_authority_pubkey = strdup(item->valuestring); } + item = cJSON_GetObjectItem(root, "stratumV2RequireAuth"); + if (item && (cJSON_IsBool(item) || cJSON_IsNumber(item))) { + cfg->sv2_require_auth = cJSON_IsTrue(item) || (cJSON_IsNumber(item) && item->valueint != 0); + } + cJSON_Delete(root); } diff --git a/main/tasks/stratum_v2_task.c b/main/tasks/stratum_v2_task.c index 95030a11e..d4db1056c 100644 --- a/main/tasks/stratum_v2_task.c +++ b/main/tasks/stratum_v2_task.c @@ -714,9 +714,24 @@ void stratum_v2_task(void *pvParameters) } GLOBAL_STATE->sv2_noise_ctx = noise_ctx; - // Load optional authority pubkey from NVS + // Load the optional authority pubkey and whether this pool requires it uint8_t auth_key[32]; bool has_auth = stratum_v2_load_authority_pubkey(GLOBAL_STATE, auth_key, use_fallback); + uint16_t auth_pool_idx = use_fallback ? GLOBAL_STATE->SYSTEM_MODULE.secondary_pool_index + : GLOBAL_STATE->SYSTEM_MODULE.primary_pool_index; + bool require_auth = GLOBAL_STATE->SYSTEM_MODULE.pools[auth_pool_idx].sv2_require_auth; + + // When auth is required but no usable authority key is configured, + // refuse to connect rather than mine against an unverifiable server + if (require_auth && !has_auth) { + ESP_LOGE(TAG, "SV2 authentication required but no authority pubkey configured, refusing to connect"); + snprintf(GLOBAL_STATE->SYSTEM_MODULE.pool_connection_info, + sizeof(GLOBAL_STATE->SYSTEM_MODULE.pool_connection_info), "SV2: Auth required - no key"); + stratum_v2_close_connection(GLOBAL_STATE); + retry_attempts++; + continue; + } + if (has_auth) { ESP_LOGI(TAG, "Authority pubkey configured, will verify server certificate"); } else {