/* keccak256_miner.c — honest HASHBATTLE PoW solver (community edition).
 *
 * Equation (same as the on-chain workHash view):
 *   work = uint256(keccak256(miner || nonce || prevWork || anchor))
 *   accept iff work < target   (big-endian uint256 comparison)
 *
 * Layout of the 116-byte preimage:
 *   [0:20]   miner address
 *   [20:52]  nonce (uint256 big-endian; we sweep a uint64 window)
 *   [52:84]  prevWork (uint256 big-endian)
 *   [84:116] anchor block hash
 *
 * Modes:
 *   keccak256_miner --test                read hex lines on stdin, print keccak hex
 *   keccak256_miner MINE PW ANCHOR TARGET NONCE0 OUTFILE [MAX_HASHES]
 *                                         mine and write JSON result to OUTFILE
 *   exit 0: hit; 1: exhausted window (JSON nonce/hash null); 2: bad input.
 *
 * Keccak-f[1600] + rate-136 sponge with raw keccak padding (0x01 .. 0x80),
 * i.e. keccak-256 as used by Ethereum. Verified against pycryptodome.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <inttypes.h>

#define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y))))

static const uint64_t rndc[24] = {
    0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL,
    0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL,
    0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL,
    0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL,
    0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL,
    0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL,
    0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL,
    0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL
};
static const int rotc[24] = {
    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
    27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44
};
static const int piln[24] = {
    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
    15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1
};

static void keccakf(uint64_t st[25]) {
    int i, j, r;
    uint64_t t, bc[5];
    for (r = 0; r < 24; r++) {
        for (i = 0; i < 5; i++)
            bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20];
        for (i = 0; i < 5; i++) {
            t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1);
            for (j = 0; j < 25; j += 5) st[j + i] ^= t;
        }
        t = st[1];
        for (i = 0; i < 24; i++) {
            j = piln[i];
            bc[0] = st[j];
            st[j] = ROTL64(t, rotc[i]);
            t = bc[0];
        }
        for (j = 0; j < 25; j += 5) {
            for (i = 0; i < 5; i++) bc[i] = st[j + i];
            for (i = 0; i < 5; i++)
                st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5];
        }
        st[0] ^= rndc[r];
    }
}

/* keccak-256 of buf[0..len-1] into out[0..31]. len must be < 136 here
 * (our preimage is fixed at 116 bytes; test mode also enforces this). */
static void keccak256_block(const uint8_t *buf, size_t len, uint8_t out[32]) {
    uint64_t st[25];
    uint8_t *sb = (uint8_t *)st;
    memset(st, 0, sizeof(st));
    memcpy(sb, buf, len);
    sb[len] ^= 0x01;
    sb[135] ^= 0x80;
    keccakf(st);
    memcpy(out, sb, 32);
}

static int hexval(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    return -1;
}
static int hex2bin(const char *h, uint8_t *out, size_t outlen) {
    if (h[0] == '0' && (h[1] == 'x' || h[1] == 'X')) h += 2;
    size_t n = strlen(h);
    if (n != outlen * 2) return -1;
    for (size_t i = 0; i < outlen; i++) {
        int hi = hexval(h[2 * i]), lo = hexval(h[2 * i + 1]);
        if (hi < 0 || lo < 0) return -1;
        out[i] = (uint8_t)((hi << 4) | lo);
    }
    return 0;
}

static double now_s(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec * 1e-9;
}

/* strtoull alone accepts negatives, suffixes and silently overflowing input. */
static int parse_u64(const char *text, uint64_t *out) {
    if (!text[0]) return -1;
    for (const char *p = text; *p; p++) {
        if (*p < '0' || *p > '9') return -1;
    }
    char *end;
    errno = 0;
    uintmax_t value = strtoumax(text, &end, 10);
    if (errno == ERANGE || *end || value > UINT64_MAX) return -1;
    *out = (uint64_t)value;
    return 0;
}

int main(int argc, char **argv) {
    if (argc == 2 && strcmp(argv[1], "--test") == 0) {
        char line[512];
        while (fgets(line, sizeof(line), stdin)) {
            line[strcspn(line, "\r\n")] = 0;
            size_t n = strlen(line);
            if (line[0] == '0' && (line[1] == 'x' || line[1] == 'X')) {
                memmove(line, line + 2, n - 1);
                n -= 2;
            }
            if (n == 0 || (n & 1) || n > 268) continue; /* cap < 136 bytes */
            uint8_t buf[136], out[32];
            if (hex2bin(line, buf, n / 2) != 0) continue;
            keccak256_block(buf, n / 2, out);
            for (int i = 0; i < 32; i++) printf("%02x", out[i]);
            printf("\n");
        }
        return 0;
    }
    if (argc != 7 && argc != 8) {
        fprintf(stderr,
            "usage: %s --test\n"
            "       %s MINER20 PREVWORK32 ANCHOR32 TARGET32 NONCE0 OUTFILE [MAX_HASHES]\n",
            argv[0], argv[0]);
        return 2;
    }
    uint8_t pre[136];
    memset(pre, 0, sizeof(pre));
    if (hex2bin(argv[1], pre + 0, 20) != 0) { fprintf(stderr, "bad miner\n"); return 2; }
    if (hex2bin(argv[2], pre + 52, 32) != 0) { fprintf(stderr, "bad prevWork\n"); return 2; }
    if (hex2bin(argv[3], pre + 84, 32) != 0) { fprintf(stderr, "bad anchor\n"); return 2; }
    uint8_t target[32];
    if (hex2bin(argv[4], target, 32) != 0) { fprintf(stderr, "bad target\n"); return 2; }
    uint64_t nonce, max_hashes = 1ULL << 34;
    if (parse_u64(argv[5], &nonce) != 0) {
        fprintf(stderr, "bad nonce: expected uint64 decimal\n"); return 2;
    }
    if (argc == 8 && (parse_u64(argv[7], &max_hashes) != 0 || max_hashes == 0)) {
        fprintf(stderr, "bad max_hashes: expected positive uint64 decimal\n"); return 2;
    }

    /* padding for the single 136-byte block; capacity bytes 136..199 of the
     * sponge state MUST be zero at every permutation — the loop rebuilds the
     * full 200-byte state from this template each iteration. */
    pre[116] = 0x01;
    pre[135] = 0x80;
    uint8_t base[200];
    memcpy(base, pre, 136);
    memset(base + 136, 0, 64);

    uint64_t st[25];
    uint8_t *sb = (uint8_t *)st;
    uint8_t out[32];
    double t0 = now_s(), last_report = t0;
    uint64_t hashes = 0;
    for (; hashes < max_hashes;) {
        /* nonce as uint256 big-endian at bytes [20:52]; a uint64 nonce lives
         * in bytes 44..51 (bytes 20..43 stay zero). */
        memcpy(sb, base, 200);
        sb[44] = (uint8_t)(nonce >> 56);
        sb[45] = (uint8_t)(nonce >> 48);
        sb[46] = (uint8_t)(nonce >> 40);
        sb[47] = (uint8_t)(nonce >> 32);
        sb[48] = (uint8_t)(nonce >> 24);
        sb[49] = (uint8_t)(nonce >> 16);
        sb[50] = (uint8_t)(nonce >> 8);
        sb[51] = (uint8_t)nonce;
        keccakf(st);
        hashes++;
        memcpy(out, sb, 32);
        /* big-endian uint256 compare: hash < target */
        if (memcmp(out, target, 32) < 0) {
            double el = now_s() - t0;
            FILE *f = fopen(argv[6], "w");
            if (!f) { perror("outfile"); return 3; }
            fprintf(f, "{\"nonce\": %llu, \"hash\": \"", (unsigned long long)nonce);
            for (int i = 0; i < 32; i++) fprintf(f, "%02x", out[i]);
            fprintf(f, "\", \"hashes\": %llu, \"seconds\": %.3f, \"mhs\": %.3f}\n",
                    (unsigned long long)hashes, el,
                    el > 0 ? hashes / el / 1e6 : 0.0);
            fclose(f);
            fprintf(stderr, "SOLVED nonce=%llu hashes=%llu %.2fs (%.1f MH/s)\n",
                    (unsigned long long)nonce, (unsigned long long)hashes, el,
                    el > 0 ? hashes / el / 1e6 : 0.0);
            return 0;
        }
        if (now_s() - last_report > 5.0) {
            last_report = now_s();
            fprintf(stderr, "progress: %llu hashes (%.1f MH/s)\n",
                    (unsigned long long)hashes, hashes / (last_report - t0) / 1e6);
        }
        if (nonce == UINT64_MAX) break; /* never wrap to an already scanned nonce */
        nonce++;
    }
    double el = now_s() - t0;
    FILE *f = fopen(argv[6], "w");
    if (!f) { perror("outfile"); return 3; }
    fprintf(f, "{\"nonce\": null, \"hash\": null, \"hashes\": %llu, "
            "\"seconds\": %.3f, \"mhs\": %.3f}\n",
            (unsigned long long)hashes, el, el > 0 ? hashes / el / 1e6 : 0.0);
    fclose(f);
    fprintf(stderr, "no solution in window\n");
    return 1;
}
