Repository navigation
Description
Captured during the attempt to measure ECDSA-P384 handshake wall-clock per the
"ECDSA P-384 verify wall-clock" subsection of CLAUDE.md. The dispatcher and
overlay swap (Phase 5) work — the regression is upstream of where the
P-384 verify dispatcher fires.
Symptom
After a freshly-bootstrapped UCI build (make BACKEND=uci USE_OVERLAY_P384_EMBED=0
then plain make BACKEND=uci), running tools/uci/test_https_local_p384.py
against a U64E at 48 MHz turbo:
- C64 sends ClientHello, receives + parses ServerHello (key share),
derives handshake keys (HK1 + KEYS screen markers print) - Enters
tls_recv_encryptedfor EncryptedExtensions (ENC1 + RX markers print) - Never prints
GOT2— meaningtls_record_recv_and_decryptnever
returns C=0 once HS keys are derived - After
enc_timeoutexhausts (16-bit retry counter, ≈565 s wall-clock at
48 MHz given the TOD-bounded 5 s waits),tls_recv_encryptedreturns C=1 tls_connectgoes to@error, setstls_state=0xFF,tls_last_state=0x03
(ENCRYPTED_EXT),net_last_error=0x86(UCI_ERR_READ_FAIL)
Reproduces deterministically across 2 retries with DEBUG_CAPTURE=0 (which
rules out the orthogonal c64-test-harness FPGA-wedge issue #129 in
that repo).
P-256 baseline (tools/uci/test_https_local.py) passes cleanly in 82.7 s
on the same hardware ~5 min apart from the P-384 failures, so this is not a
device or environment issue.
Concrete evidence
Reference passing run (P-256, artifact /tmp/uci_https_debug/20260519_210656/):
- Screen:
... HK1 KEYS ENC1 RX GOT2 GOT DEC PROC CERT ENC1 RX GOT2 GOT DEC PROC CV ... FIN CFIN http_status=200,http_resp_buf="HELLO FROM TLS SERVER"tls_recv_sub_progress=0x0A(success path intls_record_recv_and_decrypt)- Wall-clock 82.7 s
Failing run (P-384, /tmp/p384_v4.log):
- Screen:
... HK1 KEYS ENC1 RX— stops at RX, NO GOT2 net_last_error=0x86,tls_state=0xFF,tls_last_state=0x03,
tls_recv_progress=0x05,tls_recv_sub_progress=0x02tcp_recv_head = tcp_recv_tail = $032F(815 bytes consumed and drained from ring)- The ring's stored bytes show ServerHello (95 B) + ChangeCipherSpec (6 B) +
first encrypted record (5 + 50 B body) + second encrypted record
(5 + 463 B body, plausible Certificate size for P-384 cert) + ~191 B of next.
The records were physically pulled from the ring, but decrypt never
succeeded.
Where the regression lives
CLAUDE.md says P-384 reached read_seq=4 on 2026-05-16 (artifact
/tmp/uci_https_debug/20260516_152824/). Five commits since:
4c6d4a3test(uci): harness lock/health pattern — TEST onlye52216erefactor(tools/uci): P-384 arbiter helper — TEST only3c60550fix(uci): bounded uci_wait_not_busy — code (memory says cleared
by bisect, but the bisect criterion was likely "dispatcher reaches
ecdsa_verify_384" which is downstream of THIS failure)8f28941fix(uci): bound uci_drain_resp + uci_drain_status — code (same
bisect caveat as above)28e042ebuild(make): cc65 -g/--dbgfile — build only0ae3ee5test(uci): bump SENTINEL_POLL_TIMEOUT — TEST onlyf9f746ddocs(CLAUDE.md) — docs only
Worth re-bisecting with the new criterion: "does GOT2 appear during EE
recv?"
ClientHello sig_algs verified
src/tls_handshake.s:599-604 correctly advertises both:
sig_algs_ext_data:
.byte $00, $0d
.byte $00, $06
.byte $00, $04
.byte $04, $03 ; ecdsa_secp256r1_sha256
.byte $05, $03 ; ecdsa_secp384r1_sha384
So the server's choice of 0x0503 is the C64-side intent. ServerHello on
the failing run is 86 B body — same shape as the P-256 case. The failure is
clearly downstream of negotiation.
What an agent researching this could look at
- HKDF / key-schedule output divergence. Capture
tls_hs_read_key,
tls_hs_read_iv, and the 50 B ciphertext + 16 B AEAD tag of the first
encrypted record. Run an off-C64 reference AEAD decrypt against them with
the same nonce-construction (IV XOR seq=0). If the reference also fails,
the C64's keys are wrong. If reference succeeds, the C64'saead_decrypt
path is the bug. - Re-bisect against the new pass criterion. The 5-commit window above
has two code commits (3c60550, 8f28941) whosebcs-on-bounded-drain
callers innet.smight have introduced a subtle SOCKET_READ post-error
path that drops or corrupts buffered bytes. Specifically: does the new
bcs @pe_drain_toskip-the-ack-on-timeout shape leave the firmware in a
state where the NEXT SOCKET_READ returns READ_FAIL spuriously? - Inspect the ring-consumption order vs the @Retry CCS skip.
tls_record_recv_and_decryptconsumes a CCS record (6 B) via @Retry,
then reads the next record's header. With the new TOD-bounded drain
semantics, is there a scenario where uci_drain_status sets C=1 spuriously
and the caller forces tcp_state=ERROR mid-flight, even though there was
no actual firmware error? - Compare AEAD nonce construction across P-256 PASS vs P-384 FAIL —
should beIV XOR (read_seq=0)in both cases on the first record.
Repro
After git checkout master (HEAD f9f746d), bootstrap build:
cd /Users/someone/Documents/c64-https
make clean
make BACKEND=uci USE_OVERLAY_P384_EMBED=0 # produce labels.txt
make BACKEND=uci # embed overlays
Then on a host with U64E LAN access:
U64_HOST=10.43.23.81 C64_SKIP_BUILD=1 TURBO_MHZ=48 \
DEBUG_CAPTURE=0 \
/Users/someone/Documents/c64-ChaCha20-Poly1305/.venv/bin/python3 \
tools/uci/test_https_local_p384.py
DEBUG_CAPTURE=0 is intentional — with capture on, the U64E FPGA hits a
sustained-workload wedge that produces a noisy second failure mode
(Ultimate64TimeoutError on REST read_memory) that hides the underlying
TLS issue. That harness-side wedge is filed separately as
c64-test-harness#129.
Cross-refs
- Memory
p384-session-handoff-2026-05-20(this session's full notes) - Memory
p384-session-handoff-2026-05-17(prior session, dispatcher OK) - CLAUDE.md "ECDSA P-384 verify wall-clock" — still says "Not yet measured
end-to-end"; this issue blocks closing that out
Activity
JC-000 commentedon Jul 26, 2026
Closing as obsolete — the configuration this regression was captured on can no longer be built or bisected at master @ 6373036 (2026-07-26):
make BACKEND=uci USE_OVERLAY_P384_EMBED=1dies in the sibling wrapper at thear65staging step under libs/nistcurves v0.6.0 (verified today). Earlier post-v0.3.0 state had already broken it differently (SHA-384 LUTs overflowing the overlay slot by 1536 B), superseding the "build current" premise of this repro.The underlying goal (ECDSA-P384 handshake wall-clock) remains open work, but it now starts with fixing the P-384 build against v0.6.0 (see the closing comment on #32), then a fresh e2e repro. Whatever failure surfaces at that point deserves a fresh issue with fresh evidence; this one's diagnosis chain is no longer actionable.
docs: correct P-384 build-failure state — v0.6.0 breaks the wrapper s…
docs: correct P-384 build-failure state — v0.6.0 breaks the wrapper s…