Skip to content

performance: bounded replay store, synchronous JS SHA-256 miner, accurate cost docs - #4

Merged
bornmw merged 2 commits into
refactor/architecturefrom
perf/mining-and-replay-store
Sep 3, 2026
Merged

bornmw merged 2 commits into
refactor/architecturefrom
perf/mining-and-replay-store

Conversation

@bornmw

@bornmw bornmw commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Stacked on #3 (architecture), which is on top of #2 (security). Fixes the performance findings from the review in #1 (P1, P2, P3) and closes S5/S7, which live in the same worker file.

Changes

P1 — bounded replay store (includes/class-cardea-core.php, uninstall.php): per-comment replay state was one pair of wp_options rows per comment (2 rows each: cardea_used_<sig> + its timeout). Now a single cardea_used option holding entries {signature, time, window}, capped at 1024 with oldest-first eviction and prune-on-write (each stored entry keeps its own window, so window changes are honored per entry). Loaded autoload=no; delete_option('cardea_used') added to uninstall.php alongside the legacy transient sweep for upgrades.

P2 + S5 + S7 — synchronous JS SHA-256 miner (assets/js/pow-worker.js): replaced the crypto.subtle batched loop (1000 digests per Promise.all round, crypto.getRandomValues-free but TLS/secure-context-bound, 100M counter cap that could silently give up) with a compact FIPS 180-4 SHA-256 implemented in JS (32-bit ops, incremental, ~250 lines).

  • No crypto.subtle dependency → mining works on non-secure (plain HTTP) contexts too (fixes S5), with zero per-digest Promise overhead.
  • Unbounded counter (S7): the loop runs until a solution is found; no more silent give-up at 1e8.
  • Wire format unchanged: the server still verifies hash('sha256', $challengeString . $solution) — the client mines exactly that (tested against PHP-side verification semantics).
  • The SHA-256 constants in the file are derived from their FIPS definitions (fractional parts of sqrt/cbrt of primes); the implementation is validated by the new jest suite below (KATs + cross-checks against node:crypto across block boundaries).
  • The server remains the sole verifier — client-side implementation can never forge a valid solution.

P3 — difficulty guidance (includes/class-cardea-admin.php): the difficulty field description now carries expected solve times (≈0.1s @3, 1-2s @4, 10-30s @5 on a typical laptop CPU).

Tests: tests/js/pow-worker.test.js now imports the real worker (module export under jsdom) instead of mirroring the logic: FIPS KATs, node:crypto cross-checks (lengths 0-1024 incl. 55-char production-shaped challenges, block boundaries 63/64/65/119/120), difficulty check, mining at d=1..4 with server-side verification semantics, determinism, and the worker postMessage interface.

WP 7.1 compatibility found in-flight: WordPress 7.1 has removed set_option() (verified via function_exists inside a live WP 7.1 Playground: get_option/update_option/add_option/delete_option exist, set_option does not). The store therefore writes via update_option() (the current API; same 3-arg signature, present in all supported WP versions).

Pushback (per #1)

  • P2 ("prefix precompute" / padded counter) — not applicable. The challenge string (~55 chars) straddles a SHA-256 64-byte block boundary, so there is no reusable compression state between counters; a padded/extended-counter scheme would change the wire format (server hashes challenge . counter as sent) — no benefit, regression risk. The real win is removing per-digest Promise/batch overhead with a synchronous miner, which is what this does.
  • P1 (cron sweep / per-token rows) — single capped option instead. No wp-cron dependency, no per-token rows, bounded memory, per-entry window semantics preserved.
  • P3 (hard attempt cap) — none. A legitimate difficulty-8 solve may need up to ~4.3×10⁹ attempts; a hard cap would make high difficulties non-functional for real users while leaving bots (with precomputed pools) unaffected. Documented solve times give the operator the decision instead.

Benchmark (Node/V8, 55-char challenge)

Miner Throughput d=4 solve
Old crypto.subtle batched path (as-is) ≈169k hashes/s (incl. Promise/batch overhead) ≈690ms est.
New sync JS miner ≈353k hashes/s ≈350ms (measured, d=4)

≈2.1× faster end-to-end in Node; browser numbers expected to trend the same direction (and the new miner is the only option on plain-HTTP sites). Browser measurements will be added in follow-up if useful.

Test evidence

Full suite green (Docker, make test + make lint):

  • Jest 16/16 (real-worker suite, incl. FIPS KATs and node:crypto cross-checks)
  • PHPUnit OK (33 tests, 59 assertions)
  • Playwright 20/20
  • PHPCS clean

…tible), synchronous JS SHA-256 miner, accurate docs
@bornmw
bornmw merged commit cf6e37b into refactor/architecture Sep 3, 2026
2 checks passed
@bornmw bornmw mentioned this pull request Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant