Skip to content

Commit 3ecc694

Browse files
SneezeGUIclaude
andcommitted
fix(c3/s2): use correct HW SHA full double-hash instead of software (#34)
ESP32-C3/S2 builds were mining in pure software (~1 KH/s) because setupTasks() spawned the software miner_task_core0 on single-core SoCs; the hardware HAL mining task (miner_task_core1) was never instantiated and was effectively dead code. The reporter's "Software (Optimized)" banner and low hashrate were the symptom. Simply routing C3 to the old HAL task would NOT have worked: its sha256_ll_double_hash() restores a midstate into SHA_H and issues SHA_CONTINUE, which the S2/S3/C3 SHA engine ignores (the same root cause as the S3 zero-shares bug, #28). So this: - Implements sha256_ll_double_hash_full() for S2/S3/C3 (previously a stub returning false). It hashes block 1 + block 2 with the legitimate START -> CONTINUE flow and the second SHA with a fresh START -- no external midstate injection -- which is correct on every variant. It re-hashes block 1 each nonce (no midstate cache), trading a little speed for correctness; still far faster than software. - Rewrites the C3/S2 miner_task_core1 to mine with that HW path and re-verify every candidate in software (miner_sha256_header) before submitting, so a wrong HW hash can never become a bad share. Enable -D DEBUG_SHARE_VALIDATION to log PASS/FAIL per candidate. - Routes single-core builds to miner_task_core1 at low priority with frequent yields, so WiFi/Stratum/Monitor stay responsive on the one core. - Fixes the boot banner: C3 envs now define USE_HARDWARE_SHA (was the dead USE_SOFTWARE_SHA), so the reported SHA implementation is honest. Needs on-device confirmation on an ESP32-C3 (watch for [HW-DBG] SW verify=PASS lines and shares landing at the pool). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8036985 commit 3ecc694

4 files changed

Lines changed: 76 additions & 37 deletions

File tree

platformio.ini

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,9 @@ build_unflags = -Os
577577
build_flags =
578578
-D AUTO_VERSION=\"v2.9.5\"
579579
-D ESP32_C3_SUPERMINI=1
580-
; Software SHA (RISC-V has no Xtensa assembly)
581-
-D USE_SOFTWARE_SHA=1
580+
; Hardware SHA-256 via direct register access (full double-hash, no midstate
581+
; restore -- midstate restore is unsupported on ESP32-C3). See issue #34.
582+
-D USE_HARDWARE_SHA=1
582583
-D USE_DISPLAY=0
583584
-D BUTTON_PIN=9
584585
; Optimization for single-core
@@ -611,8 +612,9 @@ build_unflags = -Os
611612
build_flags =
612613
-D AUTO_VERSION=\"v2.9.5\"
613614
-D ESP32_C3_OLED=1
614-
; Software SHA (RISC-V has no Xtensa assembly)
615-
-D USE_SOFTWARE_SHA=1
615+
; Hardware SHA-256 via direct register access (full double-hash, no midstate
616+
; restore -- midstate restore is unsupported on ESP32-C3). See issue #34.
617+
-D USE_HARDWARE_SHA=1
616618
-D USE_DISPLAY=0
617619
-D USE_OLED_DISPLAY=1
618620
; OLED configuration (128x64 SSD1306 I2C)

src/main.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -562,18 +562,21 @@ void setupTasks() {
562562

563563
Serial.println("[INIT] All tasks created (dual-core mining)");
564564
#else
565-
// Single-core (C3, S2): Run only one miner task, not pinned
566-
// Must yield frequently to let WiFi/Stratum work
565+
// Single-core (C3, S2): one HARDWARE-SHA miner task, not pinned.
566+
// Uses the full double-hash HW path (sha256_ll_double_hash_full); the
567+
// midstate-restore path is unsupported on these chips (issue #34). Runs at
568+
// low priority and yields frequently so WiFi/Stratum/Monitor stay alive on
569+
// the single shared core.
567570
xTaskCreate(
568-
miner_task_core0,
571+
miner_task_core1,
569572
"Miner",
570-
MINER_0_STACK,
573+
MINER_1_STACK,
571574
NULL,
572575
MINER_0_PRIORITY,
573-
&miner0Task
576+
&miner1Task
574577
);
575578

576-
Serial.println("[INIT] All tasks created (single-core mining)");
579+
Serial.println("[INIT] All tasks created (single-core HW-SHA mining)");
577580
#endif
578581
} else {
579582
Serial.println("[INIT] Monitor task created (mining disabled - no wallet)");

src/mining/miner.cpp

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -894,22 +894,25 @@ void miner_task_core1(void *param) {
894894
// Fallback for ESP32-C3/S2: Use sequential HAL-based mining with Midstate Optimization
895895

896896
void miner_task_core1(void *param) {
897-
block_header_t hb;
898-
sha256_hash_t ctx;
897+
block_header_t hb; // unswapped header (nonce source + software verify)
898+
sha256_hash_t hwHash; // hardware double-SHA result
899+
sha256_hash_t swHash; // software re-hash for verification
900+
sha256_hash_t sw_midstate; // software midstate for verification
899901
char jobId[MAX_JOB_ID_LEN];
900902
uint32_t minerId = 1;
901903

902-
Serial.printf("[MINER1] Started on core %d (Hardware SHA Midstate, priority %d)\n",
904+
Serial.printf("[MINER1] Started on core %d (HW SHA full double-hash, priority %d)\n",
903905
xPortGetCoreID(), uxTaskPriorityGet(NULL));
904906

905907
// Wait for first job
906908
while (!s_miningActive) {
907909
vTaskDelay(100 / portTICK_PERIOD_MS);
908910
}
909-
Serial.println("[MINER1] Got first job, starting mining loop");
911+
Serial.println("[MINER1] Got first job, starting HW SHA mining loop");
910912

911913
while (true) {
912914
if (!s_miningActive) {
915+
s_core1Mining = false;
913916
vTaskDelay(100 / portTICK_PERIOD_MS);
914917
continue;
915918
}
@@ -922,50 +925,53 @@ void miner_task_core1(void *param) {
922925
strncpy(jobId, s_currentJobId, MAX_JOB_ID_LEN);
923926
xSemaphoreGive(s_jobMutex);
924927

925-
// Create swapped header for hardware SHA
928+
// Byte-swapped header (big-endian words) for the hardware SHA engine
926929
uint32_t header_swapped[20];
927930
uint32_t *header_words = (uint32_t *)&hb;
928931
for (int i = 0; i < 20; i++) {
929932
header_swapped[i] = __builtin_bswap32(header_words[i]);
930933
}
934+
const uint8_t *header_bytes = (const uint8_t *)header_swapped;
931935

932-
// Set starting nonce for this core
933-
hb.nonce = s_startNonce[minerId];
936+
// Software midstate on the UNSWAPPED header, used to re-verify candidates
937+
miner_sha256_midstate(&sw_midstate, &hb);
934938

935-
// Prepare midstate variables
936-
uint32_t midstate[8];
937-
uint8_t *header_bytes = (uint8_t *)header_swapped;
939+
hb.nonce = s_startNonce[minerId];
938940

939-
// Acquire hardware SHA lock for this mining burst
940941
sha256_ll_acquire();
941942

942-
// Compute midstate once for the block
943-
sha256_ll_midstate(midstate, header_bytes);
944-
943+
uint32_t yieldCounter = 0;
945944
while (s_miningActive) {
946-
// Optimized midstate mining
947-
// Uses pre-computed midstate and only hashes the tail (last 16 bytes + padding)
948-
// header_bytes[64] is the start of the 2nd chunk (tail)
949-
if (sha256_ll_double_hash(midstate, &header_bytes[64], hb.nonce, ctx.bytes)) {
950-
hashCheck(jobId, &ctx, hb.timestamp, hb.nonce);
945+
// Full hardware double-SHA256. Re-hashes block 1 every nonce (no midstate
946+
// restore -- that is unsupported on S2/S3/C3, issue #34) but is correct.
947+
if (sha256_ll_double_hash_full(header_bytes, hb.nonce, hwHash.bytes)) {
948+
// The raw-register HW path is not yet hardware-verified on these chips,
949+
// so re-hash in software (the proven BitsyMiner path) before submitting.
950+
// This gate guarantees a wrong HW hash can never become a bad share.
951+
bool swVerified = miner_sha256_header(&sw_midstate, &swHash, &hb);
952+
#if defined(DEBUG_SHARE_VALIDATION)
953+
Serial.printf("[HW-DBG] candidate nonce=%08lx SW verify=%s hash[28-31]=%02x%02x%02x%02x\n",
954+
(unsigned long)hb.nonce, swVerified ? "PASS" : "FAIL",
955+
swHash.bytes[28], swHash.bytes[29], swHash.bytes[30], swHash.bytes[31]);
956+
#endif
957+
if (swVerified) {
958+
hashCheck(jobId, &swHash, hb.timestamp, hb.nonce);
959+
}
951960
}
952961

953962
hb.nonce++;
954963
s_stats.hashes++;
964+
s_core1Hashes++;
955965

956-
// Yield periodically to prevent WDT (every ~1M nonces)
957-
if ((hb.nonce & 0xFFFFF) == 0) {
966+
// Single shared core: yield often so WiFi/Stratum/Monitor stay responsive.
967+
if ((++yieldCounter & 0x7FF) == 0) {
958968
sha256_ll_release();
959969
vTaskDelay(1);
960970
sha256_ll_acquire();
961-
// Recompute midstate after yield just in case hardware state was lost (unlikely but safe)
962-
sha256_ll_midstate(midstate, header_bytes);
963971
}
964972
}
965973

966-
// Release hardware SHA lock
967974
sha256_ll_release();
968-
969975
s_core1Mining = false;
970976
vTaskDelay(20 / portTICK_PERIOD_MS);
971977
}

src/mining/sha256_ll.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,36 @@ bool IRAM_ATTR sha256_ll_double_hash_full(const uint8_t *header, uint32_t nonce,
370370

371371
return ll_read_digest_if(hash_out);
372372
#else
373-
// For other ESP32 variants, use the existing implementation
374-
return false;
373+
// ESP32-S2/S3/C3: full double SHA-256 with NO external midstate injection.
374+
// These chips' SHA engine ignores SHA_H writes on CONTINUE, so the cached-
375+
// midstate path in sha256_ll_double_hash() silently computes the wrong hash
376+
// (the root cause of the zero-shares bug, issues #34/#28/#10/#5). This path
377+
// re-hashes block 1 on every call (no midstate caching) but uses only the
378+
// legitimate START -> CONTINUE flow, which is correct on every variant.
379+
uint32_t *reg = (uint32_t *)(SHA_TEXT_BASE);
380+
uint32_t *hdr_words = (uint32_t *)header;
381+
382+
// First SHA, block 1: first 64 header bytes (fresh START)
383+
for (int i = 0; i < 16; i++) {
384+
REG_WRITE(&reg[i], hdr_words[i]);
385+
}
386+
REG_WRITE(SHA_MODE_REG, SHA2_256);
387+
REG_WRITE(SHA_START_REG, 1);
388+
sha256_ll_wait_idle();
389+
390+
// First SHA, block 2: last 16 header bytes + nonce + padding (CONTINUE)
391+
ll_fill_second_block(header + 64, nonce);
392+
REG_WRITE(SHA_MODE_REG, SHA2_256);
393+
REG_WRITE(SHA_CONTINUE_REG, 1);
394+
sha256_ll_wait_idle();
395+
396+
// Second SHA: hash of the 32-byte first digest (fresh START)
397+
ll_fill_inter_block(); // copy SHA_H -> SHA_TEXT[0..7] and append padding
398+
REG_WRITE(SHA_MODE_REG, SHA2_256);
399+
REG_WRITE(SHA_START_REG, 1);
400+
sha256_ll_wait_idle();
401+
402+
return ll_read_digest_if(hash_out);
375403
#endif
376404
}
377405

0 commit comments

Comments
 (0)