audit F4/F8/F9: boot_check asserts a real boot; cipher-suite doc fix; runnable no-REU KAT - #80
Conversation
…ul boot The pass criterion was "screen RAM contains any printable text and >= 3 distinct byte values". That distinguishes a booted machine from a blank screen and nothing more: an ip65/RR-Net PRG booted on a U64E draws its banner, prints NETWORK INIT FAILED, and still returned PASS (audit F4, reproduced on hardware at 10.43.23.81). boot_check now asserts what the boot actually has to produce, and knows which build it is checking, because the banner is backend-aware (`net_banner_str`: "UCI NETWORKING" under uci, "RR-NET (CS8900A) ETHERNET" under ip65): - `BACKEND` env (default `uci`) selects the expected banner. - The PRG image on disk is checked for that banner before the device is touched, so a stale artifact from a different `BACKEND=` is caught without burning a hardware slot. - Screen checks: common banner present, expected backend banner present, the other backend's banner absent, no `FAILED` anywhere, and the main menu (`Q=QUIT`) reached. - Boot wait polls for the menu (default 60 s budget, `BOOT_TIMEOUT`) instead of a fixed 3 s sleep, which was shorter than boot's own reu_mul_init pass (~15-18 s on the U64E) — the old script routinely read a mid-boot screen. - `enable_uci` before reset when the expected backend is uci, matching phase2_check.py; without it $DF1D never answers $C9 and net_init reports NOT_PRESENT. - `C64_PRG` overrides the PRG path so a deliberate wrong-backend run can be staged. `evaluate_screen` / `check_prg_image` are pure functions so the criterion can be replayed against a captured screen dump. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CLAUDE.md:429 said the client "negotiates only TLS_AES_128_GCM_SHA256".
It offers exactly one cipher suite, 0x1303 TLS_CHACHA20_POLY1305_SHA256
(src/tls_handshake.s:85, echo-verified against ServerHello at :380), and
there is no AES implementation anywhere in src/crypto/. CLAUDE.md's
packaging validation record already stated it correctly, so the document
contradicted itself.
The claim is load-bearing — it is the stated reason the P-384 path keeps
a SHA-256 transcript hash. That conclusion is unchanged and still
correct: RFC 8446 §4.4.1 ties the transcript hash to the negotiated
suite's hash function, and 0x1303's hash is also SHA-256. Only the
reason is corrected.
Repo-wide sweep; same error fixed in:
- src/crypto/ecdsa_verify_384.s (3 comment sites)
- tools/package/listener/{listener.py,README.md}
tools/test_tls_handshake.py:674 keeps its mention: 0x1301 genuinely is
TLS_AES_128_GCM_SHA256 there, used as a suite the client must reject.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>CLAUDE.md's packaging validation record says the shipped onchip PRG "passes the 3-vector ECDSA KAT in VICE without REU", but every VICE test routes through default_vice_config(), which hard-codes `-reu -reusize 512`. The claim was true and unreproducible from the repo: checking it required monkeypatching the helper. default_vice_config() now honours C64_VICE_NO_REU=1 by dropping the REU flags and saying so on stderr. `-reu` stays the default on purpose — a no-REU run of a REU-profile build does not error, it silently returns wrong answers, because fp_mul's REU row fetch no-ops and mul_dma_lo/hi keeps reu_mul_init's a=255 residue. Implemented in the shared helper rather than in the KAT script so the opt-out is available to every VICE test, and documented where the claim is made (packaging validation record) plus a cross-reference from the "VICE harness gotcha" note. Evidence, onchip build (make BACKEND=uci USE_NISTCURVES_ONCHIP=1), no patching: C64_SKIP_BUILD=1 C64_VICE_NO_REU=1 tools/test_ecdsa_kat_oracle.py 3/3 pass, rc=0 (x64sc launched with no -reu) C64_SKIP_BUILD=1 tools/test_ecdsa_kat_oracle.py 3/3 pass, rc=0 (control, x64sc has -reu -reusize 512) and, on a REU-profile build, the same no-REU invocation rejects all three valid vectors with C=1 — the silent-garbage mode that keeps REU the default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CLAUDE.md's Known-issues entry claimed the UCI build's banner line says "ULTIMATE 64 ELITE (UCI)". The string in src/net/uci/net.s is "UCI NETWORKING", and has been throughout — verified against a real U64E boot screen while validating boot_check.py. A maintainer following that line would have written the banner assertion against a string that never appears. Also updates the boot_check.py entry in the UCI test-script list to describe what it now asserts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JC-000
commented
Aug 13, 2026
Spot-checked by the supervising session. Two claims verified directly against the repo:
Two judgement calls here that I think are right: Putting The two structural fixes to Confirms audit findings F4, F8, F9. |
Audit remediation, lane E: three findings — one harness oracle (F4) and two
claims that could not be checked from the repo (F8 doc error, F9 untestable
guarantee). Based on
docs/benchmark-refresh, one commit per finding.F4 —
tools/uci/boot_check.pypassed a failed bootDefect. The pass criterion was "screen RAM contains printable text and at
least 3 distinct byte values", which distinguishes a booted machine from a
blank screen and nothing else.
Fix. The criterion is now backend-aware — the banner differs per backend
(
net_banner_str:UCI NETWORKINGunder uci,RR-NET (CS8900A) ETHERNETunder ip65) — and asserts what a successful boot must produce: the PRG image on
disk carries the expected backend's banner (checked before the device is
touched), the screen shows the common banner and the expected backend line, the
other backend's line is absent, no
FAILEDappears anywhere, and the main menuwas reached. Boot now polls for the menu (default 60 s,
BOOT_TIMEOUT) insteadof
sleep(3)— shorter than boot's ownreu_mul_initpass (15-18 s on theU64E), so the old script routinely graded a mid-boot screen.
enable_uciisissued before reset when the expected backend is
uci(asphase2_check.pydoes);
BACKENDselects the expectation andC64_PRGoverrides the image.Acceptance A/B — on hardware (U64E 10.43.23.81, DeviceLock + enable_uci):
Correct UCI build (
make BACKEND=uci USE_NISTCURVES_ONCHIP=1, 62,977 B) ⇒ PASS:ip65/RR-Net PRG on the same U64E (
C64_PRG=…/ip65.prg BACKEND=uci) ⇒ FAIL:The old criterion replayed against that exact ip65 screen still returns
PASS (
unique=35 any_text=True), which is the finding.Blast radius. Note the ip65 image does reach the menu —
do_net_init'sfailure is non-fatal in
boot.s— so "menu reached" alone would not havecaught it; the backend-banner identity and the
FAILEDscan are what do thework.
evaluate_screen/check_prg_imageare pure functions so the criterioncan be replayed against a captured dump without hardware.
F8 — CLAUDE.md named the wrong cipher suite
Defect. CLAUDE.md:429 said the client "negotiates only
TLS_AES_128_GCM_SHA256". It offers exactly one suite,
0x1303TLS_CHACHA20_POLY1305_SHA256 (
src/tls_handshake.s:85, echo-verified againstServerHello at :380), and there is no AES anywhere in
src/crypto/. Thepackaging validation record in the same file already said ChaCha20-Poly1305, so
the document contradicted itself.
Fix. The claim is load-bearing — it is the stated reason the P-384 path
keeps a SHA-256 transcript hash. That conclusion is unchanged and still correct
(RFC 8446 §4.4.1 binds the transcript hash to the negotiated suite's hash, and
0x1303's hash is also SHA-256); only the reason is corrected, with the sourcereference attached. Repo-wide sweep fixed the same error in
src/crypto/ecdsa_verify_384.s(3 comment sites) andtools/package/listener/{listener.py,README.md}.tools/test_tls_handshake.py:674keeps its mention and is correct there:0x1301genuinely is TLS_AES_128_GCM_SHA256, used as a suite the client mustreject — which is itself corroboration that the client does not speak it.
Acceptance.
grep -rni "aes_128_gcm|aes-128|aes128"over the repo (minuslibs/,ip65/,build/) now returns exactly that one correct line. Buildre-run after the comment edits:
make BACKEND=ucirc=0,ecdsa_verify_384.ostill assembles.
F9 — the "no REU required" shipped-artifact claim had no runnable test
Defect. CLAUDE.md's packaging record states the onchip PRG "passes the
3-vector ECDSA KAT in VICE without REU", but every VICE test routes through
tools/_vice_helpers.py::default_vice_config(), which hard-codes-reu -reusize 512. The claim was true but only reproducible bymonkeypatching the helper.
Fix.
C64_VICE_NO_REU=1makesdefault_vice_config()drop the REU flagsand announce it on stderr.
-reustays the default, deliberately: a no-REU runof a REU-profile build does not error, it silently returns wrong answers. The
invocation is documented where the claim is made (packaging validation record)
and cross-referenced from the "VICE harness gotcha" note.
tools/test_ecdsa_kat_oracle.pyis untouched (lane C owns it for F7), so theopt-out is available to every VICE test rather than one script.
Acceptance (onchip build
make BACKEND=uci USE_NISTCURVES_ONCHIP=1,sha256
c9e1740e…, no patching):Process-level proof the flag reaches VICE (same PRG, concurrent runs):
And the reason
-reustays the default — the same invocation against aREU-profile build (
make BACKEND=uci, sha2562f43b6f3…):Three valid CAVP vectors rejected, no error, no diagnostic. Loud on stderr
and opt-in only, for that reason.
Blast radius. The env var is global to
default_vice_config(), so any VICEtest honours it. That is intended (
test_x509.pygroup 3 would be the nextconsumer) and safe in the direction that matters: a wrongly-enabled no-REU run
makes valid signatures reject, i.e. tests fail loudly rather than pass
falsely.
Neither
tools/uci/boot_check.pynortools/_vice_helpers.pyreads anyprocessor register by name, so the dead-
"P"-key trap another lane found intools/test_tls_handshake.pydoes not apply here;test_ecdsa_kat_oracle.pyalready latches carry through a 6502 trampoline (
LDA #0 / ROL A / STA $0352)and reads it back over DMA.
PRG-neutral (verified, clean builds)
This branch changes documentation, one comment block in
src/crypto/ecdsa_verify_384.s, and two test-side files. It moves no byte ofthe shipped binary:
make cleanis load-bearing in that check, not ceremony. An incremental buildacross a
BACKEND=switch produces a same-size, different-content PRG with nowarning — I hit it while running exactly this verification, and it is written
up with its mechanism in #86.