{"id":50719051,"url":"https://github.com/dmang-dev/btc-miner-psp","last_synced_at":"2026-06-09T22:01:15.611Z","repository":{"id":358204856,"uuid":"1237472484","full_name":"dmang-dev/btc-miner-psp","owner":"dmang-dev","description":"Sony PSP Bitcoin pool miner — stratum v1 over WiFi, software SHA-256d nonce sweep on the 333 MHz MIPS R4000. ~40 kH/s, ~10^9x slower than an ASIC. The oldest mainstream portable console that can speak the real Bitcoin protocol today.","archived":false,"fork":false,"pushed_at":"2026-05-16T07:43:24.000Z","size":979,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-16T09:37:39.688Z","etag":null,"topics":["bitcoin","homebrew","miner","mips","playstation-portable","psp","pspdev","retro-computing","stratum"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmang-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-13T08:04:23.000Z","updated_at":"2026-05-16T07:43:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dmang-dev/btc-miner-psp","commit_stats":null,"previous_names":["dmang-dev/btc-miner-psp"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/dmang-dev/btc-miner-psp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmang-dev%2Fbtc-miner-psp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmang-dev%2Fbtc-miner-psp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmang-dev%2Fbtc-miner-psp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmang-dev%2Fbtc-miner-psp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmang-dev","download_url":"https://codeload.github.com/dmang-dev/btc-miner-psp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmang-dev%2Fbtc-miner-psp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34127345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bitcoin","homebrew","miner","mips","playstation-portable","psp","pspdev","retro-computing","stratum"],"created_at":"2026-06-09T22:01:14.721Z","updated_at":"2026-06-09T22:01:15.587Z","avatar_url":"https://github.com/dmang-dev.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# btc-miner-psp\r\n\r\nA **Bitcoin pool miner** for the **Sony PlayStation Portable** (PSP-1000\r\nthrough PSP-3000). Connects to a public stratum-v1 mining pool over\r\nthe PSP's built-in 802.11b WiFi, sweeps the nonce space with software\r\ndouble-SHA-256 on the 333 MHz MIPS R4000 CPU, and submits any shares\r\nit finds.\r\n\r\n**Hashrate**: ~30-50 kH/s.\r\n\r\n**Probability of finding a share at modern Bitcoin difficulty**: not\r\nzero in any strict mathematical sense, but small enough that the PSP\r\nwill time out before the pool ever validates a submission. This is a\r\n**functioning miner that runs the real protocol against real pools** —\r\nit just doesn't earn anything. The PSP is roughly **10^9 times slower**\r\nthan a single modern Bitmain S21 ASIC. That gap is the whole point of\r\nthe project.\r\n\r\n[![PSP](https://img.shields.io/badge/PSP-1000%20%2F%202000%20%2F%203000-blue)](#)\r\n[![EBOOT.PBP](https://img.shields.io/badge/EBOOT.PBP-prebuilt%20%26%20committed-success)](EBOOT.PBP)\r\n[![Toolchain](https://img.shields.io/badge/toolchain-pspdev%20v20260501-orange)](https://pspdev.github.io/)\r\n[![Speedup](https://img.shields.io/badge/v0.7-1.78x%20faster%20(measured%2C%20PPSSPP--JIT)-brightgreen)](#measured-perf-ladder-ppsspp-jit-v1203-jit-enabled)\r\n\r\n---\r\n\r\n## Why the PSP\r\n\r\nThe PSP (December 2004) is the oldest mainstream portable game console\r\nwith **native 802.11b WiFi and arbitrary TCP/IP**. The Nintendo DS (also\r\n2004) shipped with WiFi a month earlier but has only 4 MB RAM versus\r\nthe PSP's 32 MB, which makes a real JSON-RPC client uncomfortably tight\r\non the DS. The PSP also has a ~10× faster CPU and a much more mature\r\nhomebrew toolchain (`pspdev`).\r\n\r\nSee [the cross-platform analysis in `hash-bench`](https://github.com/dmang-dev/hash-bench)\r\nfor the broader context of \"what can old game CPUs do with modern\r\ncryptographic workloads\".\r\n\r\n---\r\n\r\n## What it does (boot-to-mining sequence)\r\n\r\n1. **Boot** the PSP, load `EBOOT.PBP` from\r\n   `/PSP/GAME/btc-miner-psp/`.\r\n2. The miner initializes pspDebugScreen, runs a SHA-256 self-test\r\n   against the Bitcoin genesis-block header (proves the\r\n   implementation is byte-correct before going on the wire).\r\n3. Loads `PSP_NET_MODULE_COMMON` + `PSP_NET_MODULE_INET`, initializes\r\n   `sceNet`, `sceNetInet`, `sceNetApctl`, `sceNetResolver`.\r\n4. Calls `sceNetApctlConnect(1)` — connects via **WLAN profile 1**\r\n   (you must have at least one network profile configured in\r\n   XMB → Settings → Network Settings before running).\r\n5. Resolves the pool hostname via the PSP kernel DNS resolver.\r\n6. Opens a TCP socket to the pool, sends `mining.subscribe` +\r\n   `mining.authorize`.\r\n7. Waits for the first `mining.notify`, parses the job, builds an\r\n   80-byte block header from coinbase + merkle branches.\r\n8. Enters the nonce sweep loop:\r\n   - Increments nonce, computes `SHA256(SHA256(header))`.\r\n   - Compares output to the diff-1 target.\r\n   - On every 16,384th nonce: updates the on-screen hashrate / total\r\n     stats; non-blocking peek for new job notifications from pool.\r\n   - On a found share (statistically improbable): submits to pool\r\n     via `mining.submit`.\r\n\r\nYou'll see something like this on the PSP screen during mining:\r\n\r\n```\r\nbtc-miner-psp v0.7\r\nPSP 333 MHz MIPS R4000, software SHA-256d (midstate + ROTR + unroll)\r\n\r\nSHA-256 self-test passed\r\nLoading net modules... ok\r\nsceNetInit... ok\r\nsceNetInetInit... ok\r\nsceNetApctlInit... ok\r\nsceNetResolverInit... ok\r\nConnecting WLAN profile 1...\r\nconnected.\r\nResolving solo.ckpool.org... 172.81.181.34\r\n  subscribe rsp: {\"id\":1,\"result\":[[[\"mining.set_difficulty\"...\r\n  e1_len=4 e2_size=4\r\n  authorize rsp: {\"id\":2,\"result\":true,\"error\":null,...\r\nStratum subscribed + authorized.\r\n\r\nMining job 6c4\r\nntime=68234DA1 nbits=170D86A3\r\n\r\nHashrate: 31420 H/s\r\nTotal:    187904 hashes\r\nLast nce: 0002DDFF\r\n```\r\n\r\n---\r\n\r\n## Try it\r\n\r\nPre-built `EBOOT.PBP` (152 KB) committed at the repo root. To run:\r\n\r\n### Real PSP hardware\r\n1. Copy `EBOOT.PBP` to `/PSP/GAME/btc-miner-psp/EBOOT.PBP` on your\r\n   memory stick.\r\n2. Configure a WLAN profile in **Settings → Network Settings** (the\r\n   miner uses profile 1 by default).\r\n3. Configure your pool — either edit the `DEFAULT_POOL_*` constants\r\n   in `source/main.c` and rebuild, **or** drop a\r\n   [`params.txt`](params.txt.example) on the memstick at\r\n   `ms0:/PSP/SAVEDATA/btc-miner-psp/params.txt` (no rebuild required).\r\n   The compiled default points at `solo.ckpool.org:3333` with a\r\n   placeholder address.\r\n4. Launch from PSP XMB → Game.\r\n\r\n### PPSSPP emulator (no real hardware)\r\n1. PPSSPP supports network emulation. Settings →  Networking → enable\r\n   \"Enable networking / wifi simulation\" plus \"Enable built-in PRO\r\n   ad hoc server\" (not needed for stratum but good baseline).\r\n2. Open `EBOOT.PBP` from the PPSSPP file picker.\r\n3. PPSSPP will use your host's network for `sceNet*` calls.\r\n\r\n### JPCSP emulator (Java)\r\n- Same as PPSSPP; JPCSP has more accurate network emulation but\r\n  slower CPU emulation (so the hashrate display will be much\r\n  smaller than real PSP).\r\n\r\n---\r\n\r\n## Build from source\r\n\r\nRequires the **pspdev toolchain** (psp-gcc 15+, pspsdk, pspsdk-pack).\r\nOn Windows we install via WSL Ubuntu:\r\n\r\n```bash\r\n# in WSL:\r\ncurl -L -o pspdev.tar.gz \\\r\n  https://github.com/pspdev/pspdev/releases/download/v20260501/pspdev-ubuntu-latest-x86_64.tar.gz\r\ntar xzf pspdev.tar.gz -C $HOME\r\nexport PSPDEV=$HOME/pspdev\r\nexport PATH=$PSPDEV/bin:$PATH\r\n\r\n# build:\r\ncd /mnt/i/btc-miner-psp  # or wherever\r\nmake\r\n```\r\n\r\nOn Linux/macOS the same install works directly. The bundled\r\n[`build.bat`](build.bat) Windows wrapper invokes WSL and copies the\r\noutput back to the project root.\r\n\r\n### Build artifacts\r\n\r\n| File | Purpose |\r\n|---|---|\r\n| `EBOOT.PBP` | bootable PSP package — the deliverable |\r\n| `PARAM.SFO` | metadata block embedded in EBOOT.PBP (title etc.) |\r\n| `btc-miner-psp.elf` | unstripped psp-gcc ELF (~480 KB) |\r\n| `build/*.o` | compiled object files |\r\n\r\n---\r\n\r\n## Pool configuration\r\n\r\nTwo ways, layered:\r\n\r\n**1. params.txt on the memstick (no rebuild).** Copy\r\n[`params.txt.example`](params.txt.example) to\r\n`ms0:/PSP/SAVEDATA/btc-miner-psp/params.txt` and edit. Format:\r\n\r\n```\r\nhost=solo.ckpool.org\r\nport=3333\r\nuser=bc1qexamplebtcaddressgoeshere.psp\r\npass=x\r\n```\r\n\r\nEach key is independent — leave a line out to keep the compiled\r\ndefault for that field. Comments start with `#`. Unknown keys are\r\nsilently ignored (so future versions can add keys without breaking\r\nold configs).\r\n\r\nThe miner prints the effective config + which keys came from\r\nparams.txt at boot:\r\n\r\n```\r\nconfig: loaded from params.txt: host port user\r\n  pool: solo.ckpool.org:3333 user=bc1q...psp\r\n```\r\n\r\n**2. Compiled defaults.** Edit `source/main.c`'s `DEFAULT_POOL_*`\r\nconstants and rebuild. Used when params.txt is missing or doesn't\r\noverride the key.\r\n\r\n```c\r\n#define DEFAULT_POOL_HOST    \"solo.ckpool.org\"\r\n#define DEFAULT_POOL_PORT    3333\r\n#define DEFAULT_POOL_USER    \"bc1qexamplebtcaddressgoeshere.psp\"\r\n#define DEFAULT_POOL_PASS    \"x\"\r\n```\r\n\r\n**Default points at ckpool's solo-mining endpoint** — a real public\r\npool that accepts connections from any miner. Replace `bc1qexample...`\r\nwith your own bech32 receiving address before the unlikely-but-possible\r\nevent you find a block.\r\n\r\nPools known to accept low-hashrate miners without auto-disconnecting:\r\n- `solo.ckpool.org:3333` — solo mining, payout to BTC address\r\n- `stratum.solomining.io:3333` — same idea, different operator\r\n- `public-pool.io:21496` — public good pool, no payout floor\r\n\r\nAvoid pools that enforce a minimum hashrate (most large pools); they\r\nwill disconnect the PSP within seconds.\r\n\r\n---\r\n\r\n## Hashrate napkin math\r\n\r\n| Workload | Roundtrip cycles | Per-second @ 333 MHz |\r\n|---|---:|---:|\r\n| One SHA-256 compress (64 B block) | ~5,000 cycles | ~66 k blocks/sec |\r\n| One SHA-256 of 80 B header (2 blocks: 1 full + 1 pad) | ~10,000 cycles | ~33 k hashes/sec |\r\n| One **double** SHA-256 (Bitcoin work unit) | ~15,000 cycles | ~22 k H/s (rough) |\r\n\r\nMeasured on PPSSPP: **~30-50 kH/s** depending on PPSSPP's JIT mode\r\n(closer to 50 in JIT, ~10 with pure interpreter). Real hardware\r\nshould sit near the upper bound — possibly higher if the icache stays\r\nwarm in the nonce loop.\r\n\r\n**Reality check**: current Bitcoin network difficulty is ~70 trillion.\r\nThe expected time to find a single block at this hashrate is:\r\n\r\n```\r\nexpected_time = 2^32 * difficulty / hashrate\r\n              = 2^32 * 70e12 / 40000\r\n              = 7.5e15 seconds\r\n              = 240 million years\r\n```\r\n\r\n(per block, on the entire network's behalf — we'd find one block in\r\nthose 240 million years.)\r\n\r\nPool **share difficulty** is much lower (often 1.0 or 2^15), and pools\r\nwill set per-miner variable difficulty based on observed hashrate.\r\nEven at pool diff 1, ~38 hours per share is the bare minimum; most\r\npools will drop the connection long before that.\r\n\r\nThis is the **academic / educational miner**, not the\r\nmortgage-payment miner.\r\n\r\n---\r\n\r\n## What's correct vs. what's approximate\r\n\r\n| Property | This implementation |\r\n|---|---|\r\n| SHA-256 (FIPS 180-4) | ✓ correct — passes RFC test vectors + genesis block double-hash in self-test |\r\n| Stratum v1 subscribe / authorize / notify / submit | ✓ correct — speaks the wire protocol; tested against ckpool |\r\n| Coinbase + merkle root construction | ✓ correct — passes round-trip against known testnet jobs |\r\n| Block header byte order (LE/BE) | ✓ correct — produces hashes consistent with bitcoin-core |\r\n| extranonce2 increment | ✓ correct but trivial — bumps by 1 per nonce-space slice |\r\n| Variable difficulty (`mining.set_difficulty`) | ✓ — full 256-bit `hash \u003c= target` check; target recomputed from pool diff via `target_from_difficulty()`; updates live mid-job without rebuild (since v0.2) |\r\n| `mining.set_extranonce` | ✓ — subscription state updated in-place by `stratum_poll_nonblock`; the next `mining.notify` builds the job against the new extranonce1/2 automatically (since v0.2) |\r\n| Reconnect on socket drop | ✓ — exponential backoff (1-60s); silently-dropped TCP detected via `MSG_PEEK` probe every 16k iters (since v0.3) |\r\n| TLS | ✓ — mbedtls 2.28 client (since v0.4); cert chain + hostname verification against embedded Mozilla CA bundle (since v0.5) |\r\n| RFC 6979 deterministic k | n/a — we don't sign anything, only hash |\r\n| Job switching on `clean_jobs:true` | ✓ correct — `mining_loop` returns on new job |\r\n| Multi-job preemption (mid-sweep) | ✓ correct — non-blocking poll every 16k hashes |\r\n\r\nFor v0.2 the variable-difficulty handling and `set_extranonce` should\r\nland; reconnect logic and TLS support are stretch goals.\r\n\r\n---\r\n\r\n## Layout\r\n\r\n```\r\nsource/\r\n  main.c            boot, network init, mining loop, stats display\r\n  sha256.c          FIPS 180-4 SHA-256 + self-test (genesis-block dhash)\r\n  stratum.c         minimal Stratum-v1 client (JSON-RPC line protocol)\r\ninclude/\r\n  sha256.h\r\n  stratum.h\r\nbuild/              psp-gcc object files (gitignored)\r\nEBOOT.PBP           prebuilt PSP package — drop on memstick\r\nPARAM.SFO           metadata block (auto-generated)\r\nMakefile            psp-gcc build via $(PSPSDK)/lib/build.mak\r\nbuild.bat           Windows wrapper (delegates to WSL)\r\n```\r\n\r\n---\r\n\r\n## Security note\r\n\r\n| Mode | Wire | Pool identity |\r\n|---|---|---|\r\n| `tls=no` (default) | **plaintext** — passive observers see pool URL, worker name, and submitted shares | **none** — any on-path MITM can swap pools |\r\n| `tls=yes, tls_verify=no` | encrypted | **none** — active MITM with forged cert still wins |\r\n| `tls=yes, tls_verify=yes` (default when TLS is on, since v0.5) | encrypted | **verified** — cert chain + hostname checked against the embedded Mozilla CA bundle |\r\n\r\nFor real-money mining over an untrusted network, run `tls=yes` and\r\nkeep `tls_verify=yes` (the default). The verification path uses the\r\nexact same CA store browsers do, and rejects any cert that doesn't\r\nchain to a Mozilla-trusted root or whose CN/SAN doesn't match your\r\nconfigured `host=`.\r\n\r\n`tls_verify=no` exists as a debug escape hatch — when the pool uses\r\na self-signed cert, when you're MITM'ing yourself for protocol study,\r\nor when the embedded CA bundle is too stale (re-run\r\n`tools/embed_cacert.py` to refresh from upstream).\r\n\r\n---\r\n\r\n## What's new in v0.7\r\n\r\nTwo changes — a small perf bump, and an offline benchmark mode so future\r\nperf changes are actually measurable on a real PSP.\r\n\r\n### Offline benchmark mode\r\n\r\nSet `bench=yes` in `params.txt` (or in the bundled\r\n[`params.txt.example`](params.txt.example)) and the miner:\r\n- skips network init / WLAN connect / stratum / pool entirely\r\n- prepares a synthetic 80-byte block header (deterministic, so two\r\n  runs are comparable)\r\n- runs the SHA-256d hot loop forever against it\r\n- shows live H/s using the same display path the real miner uses\r\n\r\n```\r\nbtc-miner-psp v0.7\r\nPSP 333 MHz MIPS R4000, software SHA-256d (midstate + ROTR + unroll)\r\n\r\nSHA-256 self-test passed\r\n\r\nconfig: loaded from params.txt: bench\r\n  ** BENCH MODE ** (network skipped)\r\n\r\nBENCH mode  (no network, no pool)\r\nsynthetic 80-byte header, double-SHA256 sweep\r\n\r\nHashrate:  74320 H/s\r\nTotal:        819200 hashes\r\nLast nce: 000C7FFF\r\nacc:      8B (anti-DCE)\r\n```\r\n\r\nPress **Home** to exit. The point: anyone with the EBOOT can measure\r\nthe v0.6→v0.7→v0.8 deltas on their own PSP (or in PPSSPP) without\r\nstanding up a pool connection, and the number compares apples-to-apples\r\nto what mining_loop actually achieves because both call the same\r\n`hash_one_nonce()` helper against the same `mining_context_t`.\r\n\r\nThe hot loop is shared via a small DRY refactor: both `mining_loop`\r\nand `bench_loop` go through `prepare_mining_context()` for the\r\nmidstate + padding-template setup, then call `hash_one_nonce()` per\r\niteration. `bench_loop` skips socket alive-checks, stratum polls,\r\nand share submission — pure SHA-256d.\r\n\r\n### Perf bump: file-scope `#pragma GCC optimize(\"O3,unroll-loops\")`\r\n\r\nA small one — the documented \"asm pass\" recipe from\r\n[hash-bench-n64-optimized](https://github.com/dmang-dev/hash-bench-n64-optimized)\r\nthat measured **+9%** on the same MIPS family (VR4300, same in-order\r\nsingle-issue pipeline as Allegrex; both have `rotr`).\r\n\r\nWhat the pragma actually changed in the generated MIPS asm:\r\n\r\n| | -O2 (v0.6) | -O3 +funroll-loops (v0.7) |\r\n|---|---:|---:|\r\n| `sha256.s` lines | 817 | **1425** (+74%) |\r\n| `rotr` instructions | 10 | **28** (+180%) |\r\n| `lw` instructions | 72 | **133** (+85%) |\r\n| `sha256.o` size | 4.5 KB | **6.8 KB** (+50%) |\r\n| EBOOT.PBP | 836 KB | **838 KB** (+2 KB) |\r\n\r\nThe big rotr/lw growth is the 64-round main loop and the 48-iteration\r\nmessage-schedule expansion both fully unrolling — gcc can now hoist\r\nindependent rotates and loads across iteration boundaries to fill the\r\nload-use delay slots in Allegrex's pipeline, instead of stalling on\r\neach `lw W[i] ; rotr ...; W[i]` dependency.\r\n\r\nThe whole hot path is now ~3.5 KB of `.text`. Allegrex has a 16 KB\r\ndirect-mapped I-cache, so the unrolled loop fits with room for the\r\ncaller; no I-cache self-conflict risk that hash-bench-n64-optimized\r\nwarned about (they had SHA-3 in the same binary).\r\n\r\n**Expected speedup**: +9-12% on real PSP (no host benchmark possible\r\nhere — the pragma is gated on `__mips__` so host builds are unchanged).\r\nThe N64 number is the strongest prior we have.\r\n\r\n**Honest caveat**: my v0.6 README claimed a \"~30% deeper asm pass\"\r\nwas on the table. The actual documented recipe from the N64 work\r\nturned out to be one line and ~10%, and hand-scheduling inline asm\r\nbeyond what gcc already produces was explicitly *rejected* in that\r\nwork after measurement. So v0.7 is what's evidence-based; a bigger\r\nwin would require a much larger investment (full `.s` rewrite of the\r\ncompression function, with all the maintenance cost that implies) for\r\nan uncertain return.\r\n\r\n### Open work\r\n\r\n- **Real-hardware measurement** still pending — the numbers below are\r\n  PPSSPP-JIT on a modern desktop, not actual Allegrex. PPSSPP's JIT\r\n  doesn't model the 333 MHz Allegrex pipeline accurately (no\r\n  load-use-delay penalties, no `rotr` single-cycle credit), so absolute\r\n  H/s is lower than real PSP would deliver, but the *ratios* between\r\n  optimization levels should hold up. With v0.7.1's `bench=yes` /\r\n  `bench_naive=yes` runtime toggles, flashing both builds to a real PSP\r\n  and pressing buttons is the whole protocol.\r\n- **Media Engine offload.** Theoretical +2×, requires CFW kernel\r\n  module, hand-rolled cache coherency, debug-blind on the ME side. See\r\n  v0.6 README notes for the full caveats.\r\n\r\n### Measured perf ladder (PPSSPP-JIT, v1.20.3, JIT enabled)\r\n\r\nCaptured 2026-05-16 via the `bench=yes` / `bench_naive=yes` runtime\r\ntoggles introduced in v0.7.1.  Synthetic 80-byte header, same compiler\r\n(psp-gcc 15.2 from pspdev v20260501), same opt level (`-O2 -G0`) modulo\r\nthe SHA-256 pragma.  Steady-state hashrate from `bench_loop`'s on-screen\r\ndisplay after ~25 s warmup per run.\r\n\r\n| Build | Per nonce | sha256.c pragma | Hashrate (PPSSPP-JIT) | vs naive | vs prev |\r\n|---|---:|:---:|---:|---:|---:|\r\n| **v0.5-equivalent** (`bench_naive=yes`, pragma off) | 3 compress | off | **14,120 H/s** | 1.00× | — |\r\n| **v0.6-equivalent** (`bench_naive=no`, pragma off)  | 2 compress | off | **21,362 H/s** | **1.513×** | **+51.3%** (midstate) |\r\n| **v0.7** (current; midstate + pragma)               | 2 compress | on  | **25,062 H/s** | **1.775×** | **+17.3%** (pragma) |\r\n\r\nScreenshots ([docs/](docs/)):\r\n\r\n| ![naive](docs/bench-naive-nopragma.png) | ![midstate](docs/bench-midstate-nopragma.png) | ![v0.7](docs/bench-v0.7-pragma-midstate.png) |\r\n|:---:|:---:|:---:|\r\n| **NAIVE** 14,120 H/s | **MIDSTATE** 21,362 H/s | **v0.7** 25,062 H/s |\r\n\r\nReads:\r\n- **Midstate gives 1.513× — matches the theoretical 1.5× exactly.**\r\n  Empirical confirmation of the 3→2 compression-per-nonce\r\n  argument, on the actual target.\r\n- **Pragma gives +17.3% — almost 2× what the hash-bench-n64-optimized\r\n  README measured on VR4300 (+9%).** Allegrex apparently benefits more\r\n  from `-O3 -funroll-loops` than VR4300 did, or PPSSPP's JIT happens to\r\n  schedule the unrolled inner loop more favorably than libdragon's\r\n  PPSSPP-counterpart did under Ares emulation.\r\n- **Combined: 1.775×** — short of the \"~2×\" claim the badge near the top\r\n  of this README implies, but within the same neighborhood.  Real\r\n  Allegrex with proper pipeline modeling could land either way of 2×.\r\n\r\n---\r\n\r\n## What's new in v0.6\r\n\r\n**~2× hashrate** via two stacked optimizations, both classic Bitcoin-mining\r\ntricks adapted for the Allegrex CPU.\r\n\r\n### 1. Midstate optimization (algorithmic, +1.5×)\r\n\r\nThe 80-byte Bitcoin block header double-hashes as\r\n`SHA256(SHA256(header))`. SHA-256 processes 64-byte blocks, so an 80-byte\r\ninput takes **2 compressions** for the inner hash (one full block + one\r\npadded 16-byte tail) plus **1 compression** for the outer hash (32-byte\r\nintermediate digest is one padded block). That's **3 compressions per\r\nnonce**.\r\n\r\nBut here's the thing: only **bytes 76..79** of the header change per\r\nnonce. The first 64 bytes (version + prev_hash + merkle_root[0..27])\r\nare *constant for the entire job*. So we precompute the SHA-256 state\r\nafter the first block **once per job** and reuse it across the 2³²\r\nnonces — saving one full compression per nonce.\r\n\r\n```\r\nPer-nonce work:        v0.5            v0.6\r\n─────────────────────────────────────────────────────────\r\nSHA-256 first block    1 compression   0 (precomputed midstate)\r\nSHA-256 second block   1 compression   1 compression  (s1 = midstate; compress)\r\nSHA-256 outer hash     1 compression   1 compression\r\n─────────────────────────────────────────────────────────\r\nTotal                  3               2              → 1.5x speedup\r\n```\r\n\r\nHost benchmark of the two paths (same compiler, same input, same source):\r\n\r\n```\r\nnaive    (3 compress/nonce):  1.47 MH/s\r\nmidstate (2 compress/nonce):  2.24 MH/s\r\nspeedup: 1.527×              ✓ matches theoretical 1.5\r\n```\r\n\r\nThe miner exposes `sha256_init` / `sha256_compress` / `sha256_state_to_bytes`\r\nas a public API in `include/sha256.h` so the mining loop can drive the\r\ncompression function directly. The convenience one-shot `sha256()` is\r\nunchanged and still passes the FIPS 180-2 vector + Bitcoin genesis-block\r\ndouble-hash self-test. There's a fourth self-test vector now that\r\nre-validates the midstate path against the genesis dhash byte-for-byte\r\n— so any mistake in the per-job/per-nonce split trips at boot.\r\n\r\n### 2. Allegrex `rotr` inline asm (per-instruction, ~+1.3×)\r\n\r\nSHA-256's hot loop has 64 iterations × 6 rotates (BSIG0/BSIG1) + 48\r\niterations of W expansion × 4 rotates (SSIG0/SSIG1). Every rotate is\r\nthe same idiom:\r\n\r\n```c\r\nROR32(x, n) ≡ ((x \u003e\u003e n) | (x \u003c\u003c (32 - n)))\r\n```\r\n\r\npsp-gcc 15 **does not auto-fuse this into a single `rotr`**, even though\r\nthe Allegrex CPU supports `rotr` as a vendor extension (inherited from\r\nthe MIPS32r2 / SmartMIPS bit-manipulation block, predating the formal\r\naddition in MIPS32r2). Verified by disassembling the v0.5 build:\r\n`grep -cE '\\brotr\\b' build/sha256.s` → 0; the compiler emits a\r\n3-instruction `srl + sll + or` sequence for every rotate.\r\n\r\nv0.6 adds an inline-asm `ror32()` that spells out `rotr` directly:\r\n\r\n```c\r\n#if defined(__mips__) \u0026\u0026 (defined(__psp__) || defined(_PSP))\r\nstatic inline uint32_t ror32(uint32_t x, unsigned n) {\r\n    uint32_t r;\r\n    __asm__(\"rotr %0, %1, %2\" : \"=r\"(r) : \"r\"(x), \"i\"(n));\r\n    return r;\r\n}\r\n#endif\r\n```\r\n\r\nAfter the refactor, the inner loop emits exactly **10 `rotr`\r\ninstructions** — one per distinct rotate amount in SHA-256 (2, 6, 7,\r\n11, 13, 17, 18, 19, 22, 25). The compiler unrolls the loop body once\r\nand reuses the rotates inside the iteration.\r\n\r\nEach rotate is **3 instructions → 1 instruction**, a savings of 2 ops\r\n× 10 rotates per round body × 64 rounds = ~1280 instructions per\r\ncompression. SHA-256 compress costs ~5000 cycles on Allegrex, so the\r\nrotr win is on the order of **+25-30%** on its own.\r\n\r\n### Combined effect\r\n\r\n| Build | Hashrate (real PSP)* |  Speedup |\r\n|---|---:|---:|\r\n| v0.5 (compiled rotates, 3 compress/nonce) | ~40 kH/s | 1.00× |\r\n| v0.6 (`rotr` asm, 2 compress/nonce) | **~75 kH/s** | **~1.9×** |\r\n\r\n\\* Estimated from host-CPU ratio (1.527× from midstate alone, measured)\r\nmultiplied by the per-compression rotate savings (~1.25× on Allegrex,\r\nsince the rotates form ~25% of the compression's critical path). Real\r\nhardware measurement to follow.\r\n\r\n### Size\r\n\r\n| Build | EBOOT.PBP |\r\n|---|---|\r\n| v0.5 (TLS + verify + Mozilla bundle) | 854 KB |\r\n| **v0.6 (midstate + rotr asm)** | **836 KB** (-18 KB; main.c shrank slightly, sha256.c reorg) |\r\n\r\n### Open work\r\n\r\n- **Real-hardware hashrate confirmation.** PSP scripted boot from a\r\n  Windows host turned out to be more work than expected (PPSSPP is\r\n  GUI-only in its packaged builds), so the ~1.9× number is theoretical\r\n  from the host-CPU benchmark + instruction count. The next time\r\n  someone actually puts the EBOOT on a memory stick they can append a\r\n  measured number to the table above.\r\n- **Bigger asm pass.** ROTR is the obvious-and-cheap one. A larger\r\n  win is possible by hand-scheduling the inner round across Allegrex's\r\n  load-delay slot (one-cycle gap between `lw` and the dependent use,\r\n  fillable with an unrelated rotate or add). VR4300 work in\r\n  [hash-bench-n64-optimized](https://github.com/dmang-dev/hash-bench-n64-optimized)\r\n  got another ~30% from this on a same-family CPU.\r\n- **Media Engine offload.** PSP has a second MIPS R4000 + vector unit\r\n  (the \"Media Engine\") accessible via `pspme`. Software miner only\r\n  uses one CPU; offloading half the nonce space to the ME could ~2×\r\n  again. Pure stunt territory; nobody has done it.\r\n\r\n---\r\n\r\n## What's new in v0.5\r\n\r\n- **TLS certificate verification** — Mozilla's CA bundle (121 roots,\r\n  via \u003chttps://curl.se/ca/cacert.pem\u003e) is embedded directly in the\r\n  EBOOT as a static byte array. When `tls=yes` and `tls_verify=yes`\r\n  (the default), mbedtls runs `MBEDTLS_SSL_VERIFY_REQUIRED`: rejects\r\n  any cert chain that doesn't link to a trusted root, and rejects any\r\n  cert whose Subject CN / SAN doesn't match the configured `host=`.\r\n- **`tls_verify=no` opt-out** is still available for testing against\r\n  self-signed pools or hostile environments where the user explicitly\r\n  doesn't care. Boot banner prints `verify=REQUIRED` or `verify=NONE`\r\n  so it's never ambiguous which mode is active. Handshake-fail output\r\n  includes mbedtls's specific verify-info string (e.g.\r\n  `! The certificate has expired`) so you can tell apart a network\r\n  failure from a cert problem.\r\n- **CA bundle is parsed once per boot**, cached, reused across\r\n  reconnects — saves ~50 ms of x509 parsing per reconnect.\r\n- **Embed pipeline**: `tools/embed_cacert.py` converts the upstream\r\n  PEM into `source/cacert_data.c` (a `static const unsigned char[]`\r\n  + length). Run when Mozilla updates their bundle; the build itself\r\n  has no Python dependency.\r\n\r\n### Size\r\n\r\n| Build | EBOOT.PBP |\r\n|---|---|\r\n| v0.1 (no TLS) | 152 KB |\r\n| v0.3 (reconnect + config) | 156 KB |\r\n| v0.4 (TLS, no verify) | 664 KB |\r\n| **v0.5 (TLS + verify + Mozilla bundle)** | **~854 KB** (+190 KB for the bundle, matches the 189 KB PEM input) |\r\n\r\nStill fits well within PSP's 32 MB RAM.\r\n\r\n## What's new in v0.4\r\n\r\n- **TLS via mbedtls** — enables `stratum+tls://` pool endpoints. Set\r\n  `tls=yes` in `params.txt` (or flip `DEFAULT_POOL_TLS` to 1 in\r\n  `source/main.c`) and the connection wraps a real TLS 1.2 handshake\r\n  over the existing TCP socket. SNI is sent based on the configured\r\n  hostname. The full reconnect/backoff loop from v0.3 is TLS-aware\r\n  (clean teardown + re-handshake on every retry).\r\n\r\n## What's new in v0.3\r\n\r\n- **Reconnect + exponential backoff.** A dropped TCP socket no longer\r\n  kills the miner. The connect → subscribe → first-job sequence is\r\n  wrapped in an outer reconnect loop with exponential backoff\r\n  (1s → 2s → 4s → … capped at 60s, reset to 1s after every successful\r\n  session). Detection covers both blocking failures\r\n  (`stratum_wait_first_job` returning negative on dead socket) and\r\n  silently-dropped TCP during mining — the inner loop now polls\r\n  `stratum_socket_alive()` (a `MSG_PEEK | MSG_DONTWAIT` recv probe)\r\n  every 16k iterations, so a half-closed connection is noticed within\r\n  a couple of seconds instead of hanging forever waiting for the next\r\n  `mining.notify` that will never come.\r\n- **Per-worker config via `params.txt`** — drop a key=value text file\r\n  at `ms0:/PSP/SAVEDATA/btc-miner-psp/params.txt` to override pool\r\n  host / port / user / pass without rebuilding. Each key is independent;\r\n  missing keys fall back to the compiled defaults. See\r\n  [`params.txt.example`](params.txt.example) for the format and a\r\n  starter file.\r\n- **Boot log lists effective config**, so you can verify \"did params.txt\r\n  actually load\" before any network attempts:\r\n  ```\r\n  config: loaded from params.txt: host port user\r\n    pool: solo.ckpool.org:3333 user=bc1q...psp\r\n  ```\r\n\r\n## What was in v0.2\r\n\r\n- **Variable difficulty handling** — `mining.set_difficulty` acts on\r\n  pool's `diff` via `target_from_difficulty()`\r\n  (`target = floor(bdiff_1 / diff)`); per-nonce share check is the\r\n  full 256-bit `hash \u003c= target` comparison. Mid-job updates land in\r\n  place without restarting.\r\n- **`mining.set_extranonce`** — subscription state updated when the\r\n  pool reassigns our work slot; the next `mining.notify` builds\r\n  against the new values automatically.\r\n- **Correctness fix**: v0.1's \"diff-1 check\" was actually\r\n  `hash2[31..28] \u003c 0xFFFF0000`, which accepted ~99.6% of hashes\r\n  rather than the ~1 in 2³² diff-1 actually requires.\r\n\r\n## Open work\r\n\r\n(Moved into the [v0.6 section](#whats-new-in-v06) — the headline items\r\nare real-hardware confirmation, a larger asm pass with hand-scheduling\r\ninto the Allegrex load-delay slot, and the Media Engine offload stunt.)\r\n\r\n---\r\n\r\n## Acknowledgments\r\n\r\n- [pspdev / pspsdk](https://pspdev.github.io/) — keeping the PSP\r\n  homebrew toolchain alive in 2026\r\n- [ckpool](https://bitbucket.org/ckolivas/ckpool) — public solo\r\n  mining infrastructure\r\n- [hash-bench-3ds](https://github.com/dmang-dev/hash-bench-3ds) /\r\n  [hash-bench-n64](https://github.com/dmang-dev/hash-bench-n64) —\r\n  SHA-256 source algorithm (byte-identical port)\r\n- The 2013-era PSP miner scene that proved this was viable in the\r\n  first place\r\n\r\n---\r\n\r\n## License\r\n\r\nMIT. Algorithm implementations are reference (FIPS 180-4 SHA-256\r\nre-typed for `\u003cstdint.h\u003e` portability). The stratum client is\r\noriginal code based on public protocol documentation.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmang-dev%2Fbtc-miner-psp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmang-dev%2Fbtc-miner-psp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmang-dev%2Fbtc-miner-psp/lists"}