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 {