Skip to content
Draft
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
10 changes: 6 additions & 4 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,9 @@ build_unflags = -Os
build_flags =
-D AUTO_VERSION=\"v2.9.5\"
-D ESP32_C3_SUPERMINI=1
; Software SHA (RISC-V has no Xtensa assembly)
-D USE_SOFTWARE_SHA=1
; Hardware SHA-256 via direct register access (full double-hash, no midstate
; restore -- midstate restore is unsupported on ESP32-C3). See issue #34.
-D USE_HARDWARE_SHA=1
-D USE_DISPLAY=0
-D BUTTON_PIN=9
; Optimization for single-core
Expand Down Expand Up @@ -611,8 +612,9 @@ build_unflags = -Os
build_flags =
-D AUTO_VERSION=\"v2.9.5\"
-D ESP32_C3_OLED=1
; Software SHA (RISC-V has no Xtensa assembly)
-D USE_SOFTWARE_SHA=1
; Hardware SHA-256 via direct register access (full double-hash, no midstate
; restore -- midstate restore is unsupported on ESP32-C3). See issue #34.
-D USE_HARDWARE_SHA=1
-D USE_DISPLAY=0
-D USE_OLED_DISPLAY=1
; OLED configuration (128x64 SSD1306 I2C)
Expand Down
15 changes: 9 additions & 6 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,18 +562,21 @@ void setupTasks() {

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

Serial.println("[INIT] All tasks created (single-core mining)");
Serial.println("[INIT] All tasks created (single-core HW-SHA mining)");
#endif
} else {
Serial.println("[INIT] Monitor task created (mining disabled - no wallet)");
Expand Down
56 changes: 31 additions & 25 deletions src/mining/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -894,22 +894,25 @@ void miner_task_core1(void *param) {
// Fallback for ESP32-C3/S2: Use sequential HAL-based mining with Midstate Optimization

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

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

// Wait for first job
while (!s_miningActive) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
Serial.println("[MINER1] Got first job, starting mining loop");
Serial.println("[MINER1] Got first job, starting HW SHA mining loop");

while (true) {
if (!s_miningActive) {
s_core1Mining = false;
vTaskDelay(100 / portTICK_PERIOD_MS);
continue;
}
Expand All @@ -922,50 +925,53 @@ void miner_task_core1(void *param) {
strncpy(jobId, s_currentJobId, MAX_JOB_ID_LEN);
xSemaphoreGive(s_jobMutex);

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

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

// Prepare midstate variables
uint32_t midstate[8];
uint8_t *header_bytes = (uint8_t *)header_swapped;
hb.nonce = s_startNonce[minerId];

// Acquire hardware SHA lock for this mining burst
sha256_ll_acquire();

// Compute midstate once for the block
sha256_ll_midstate(midstate, header_bytes);

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

hb.nonce++;
s_stats.hashes++;
s_core1Hashes++;

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

// Release hardware SHA lock
sha256_ll_release();

s_core1Mining = false;
vTaskDelay(20 / portTICK_PERIOD_MS);
}
Expand Down
32 changes: 30 additions & 2 deletions src/mining/sha256_ll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,36 @@ bool IRAM_ATTR sha256_ll_double_hash_full(const uint8_t *header, uint32_t nonce,

return ll_read_digest_if(hash_out);
#else
// For other ESP32 variants, use the existing implementation
return false;
// ESP32-S2/S3/C3: full double SHA-256 with NO external midstate injection.
// These chips' SHA engine ignores SHA_H writes on CONTINUE, so the cached-
// midstate path in sha256_ll_double_hash() silently computes the wrong hash
// (the root cause of the zero-shares bug, issues #34/#28/#10/#5). This path
// re-hashes block 1 on every call (no midstate caching) but uses only the
// legitimate START -> CONTINUE flow, which is correct on every variant.
uint32_t *reg = (uint32_t *)(SHA_TEXT_BASE);
uint32_t *hdr_words = (uint32_t *)header;

// First SHA, block 1: first 64 header bytes (fresh START)
for (int i = 0; i < 16; i++) {
REG_WRITE(&reg[i], hdr_words[i]);
}
REG_WRITE(SHA_MODE_REG, SHA2_256);
REG_WRITE(SHA_START_REG, 1);
sha256_ll_wait_idle();

// First SHA, block 2: last 16 header bytes + nonce + padding (CONTINUE)
ll_fill_second_block(header + 64, nonce);
REG_WRITE(SHA_MODE_REG, SHA2_256);
REG_WRITE(SHA_CONTINUE_REG, 1);
sha256_ll_wait_idle();

// Second SHA: hash of the 32-byte first digest (fresh START)
ll_fill_inter_block(); // copy SHA_H -> SHA_TEXT[0..7] and append padding
REG_WRITE(SHA_MODE_REG, SHA2_256);
REG_WRITE(SHA_START_REG, 1);
sha256_ll_wait_idle();

return ll_read_digest_if(hash_out);
#endif
}

Expand Down
Loading