Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a4de513
fix(tls): per-backend post-ServerHello drain budget — UCI 125.4 s -> …
JC-000 Jul 29, 2026
e044f51
tools/uci: skip redundant turbo write — unblocks C64U 64 MHz measurement
JC-000 Jul 29, 2026
f9d0573
fix(tools/uci): turbo probe read the wrong JSON shape — skip was unre…
JC-000 Jul 29, 2026
1ddc830
docs: refresh benchmarks + ip65 status for #68/#69/#71/#74
JC-000 Jul 31, 2026
17c6e1b
chore: untrack LSP tooling artifacts (.ca65-ls cache, .mcp.json)
JC-000 Aug 13, 2026
f0127a0
docs: align UCI net_poll cost + x25519 ip65 overflow classification
JC-000 Aug 13, 2026
bfffe69
fix(test/uci): drop the screen-RAM HELLO fallback pass criterion (aud…
JC-000 Aug 13, 2026
f0bcf8a
fix(test): AEAD tamper oracle in test 4b must fail closed (audit F1)
JC-000 Aug 13, 2026
e44ffa2
fix(tools/uci): boot_check asserted "screen has text", not a successf…
JC-000 Aug 13, 2026
4fb8972
docs: c64-https offers ChaCha20-Poly1305, not AES-128-GCM
JC-000 Aug 13, 2026
4d6907c
fix(test/uci): assert the listener's server_result in the verdict (au…
JC-000 Aug 13, 2026
21ac8f0
test(vice): C64_VICE_NO_REU — make the "no REU required" claim runnable
JC-000 Aug 13, 2026
aa76533
docs: the UCI boot banner reads "UCI NETWORKING" (F4 follow-on)
JC-000 Aug 13, 2026
79f0a61
fix(test): handshake ServerHello oracle — dead carry gate + stub skip…
JC-000 Aug 13, 2026
fb8ba56
docs: correct the x25519 sibling record + add tools/check_upstream_pi…
JC-000 Aug 13, 2026
c952558
fix(test): a missing label is a failed group, not a silent skip (F3)
JC-000 Aug 13, 2026
0eb9a6d
test(ecdsa): give the KAT oracle negative vectors (F7)
JC-000 Aug 13, 2026
a0f6a58
fix(p384): stage ecdsa384_nocomb.o — un-break the P-384 archive build
JC-000 Aug 13, 2026
8c8bacc
test(tls): cover the server-Finished rejection path (audit F2)
JC-000 Aug 13, 2026
0080274
test(x25519): run the RFC 7748 vectors by default; name any skipped g…
JC-000 Aug 13, 2026
7497e48
fix(test): --skip-slow silently dropped the entire x509 suite (F3, se…
JC-000 Aug 13, 2026
54498cc
docs(tools): make check_upstream_pins' own cost claim executable
JC-000 Aug 13, 2026
4e74302
docs: the ip65 blob is not committed — document the fresh-clone build
JC-000 Aug 13, 2026
c9e2b95
docs: the blob reproduction is three worktrees over three months, not…
JC-000 Aug 13, 2026
07192aa
docs: switching BACKEND without `make clean` silently links a mixed PRG
JC-000 Aug 13, 2026
a0039b1
docs: the stale-object trap has a second mode — make skips the link e…
JC-000 Aug 13, 2026
dc016a7
docs: why PRG hashes are checkable and object hashes are not
JC-000 Aug 13, 2026
64c0e78
docs: drop the byte offset from the object-timestamp note
JC-000 Aug 13, 2026
244d6a9
Merge pull request #78 from JC-000/fix/audit-f1-aead-oracle
JC-000 Aug 13, 2026
29dcbcc
Merge pull request #79 from JC-000/fix/audit-f10-handshake-oracle
JC-000 Aug 13, 2026
b67c321
Merge pull request #80 from JC-000/fix/audit-f4-f8-f9-harness-docs
JC-000 Aug 13, 2026
c0bbf84
Merge pull request #86 from JC-000/docs/build-section-fresh-clone
JC-000 Aug 13, 2026
1371282
Merge pull request #81 from JC-000/chore/x25519-alignment
JC-000 Aug 13, 2026
b52afdd
Merge pull request #82 from JC-000/fix/audit-f5-f6-e2e-oracle
JC-000 Aug 13, 2026
a0e46c4
Merge pull request #83 from JC-000/fix/audit-f3-f7-ecdsa-coverage
JC-000 Aug 13, 2026
dc392b3
Merge pull request #85 from JC-000/fix/audit-f2-bad-finished
JC-000 Aug 13, 2026
6feb89f
Merge docs/benchmark-refresh into upstream-contract-alignment
JC-000 Aug 13, 2026
e597b70
Merge pull request #84 from JC-000/chore/upstream-contract-alignment
JC-000 Aug 13, 2026
562e9a8
Merge origin/master into docs/benchmark-refresh
JC-000 Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,3 +12,5 @@ tools/https_e2e/certs/*
tools/diag_4de0_*.py
tools/diag_read_live.py
.serena/
.ca65-ls/
.mcp.json
592 changes: 530 additions & 62 deletions CLAUDE.md

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,8 @@
# make — default, produces build/c64-https.prg + build/labels.txt
# make clean — remove build artifacts
# make run — launch the PRG in VICE x64sc
# make ip65-libs — rebuild ip65 object libraries from the submodule
# make ip65-libs — build ip65 object libraries from the submodule
# (required once per fresh clone, BACKEND=ip65 only)
# make ip65-blob — rebuild ip65-build/ip65-c64.bin (requires ip65-libs first)
#
# Variables:
Expand DownExpand Up@@ -418,13 +419,16 @@ ifeq ($(USE_OVERLAY_P384_EMBED),1)
build/crypto/ecdsa_verify_384.o: build/p384_overlay_equates.inc
endif

# Build ip65 object libraries from the submodule. Only needed if the ip65
# submodule changes; the prebuilt blob is committed to ip65-build/.
# Build ip65 object libraries from the submodule. Required once per fresh
# clone — the submodule ships sources, and the ip65-blob link below needs
# the .lib archives this target produces. Re-run when the submodule moves.
ip65-libs:
cd $(IP65_DIR) && $(MAKE) -C ip65 && $(MAKE) -C drivers

# Build the ip65 binary blob (ip65-build/ip65-c64.bin). The resulting file is
# committed to the repo so a normal `make` does not need to rebuild it.
# Build the ip65 binary blob (ip65-build/ip65-c64.bin). The file is a
# gitignored build artifact (.gitignore: ip65-build/*.bin), NOT committed.
# `all` depends on it under BACKEND=ip65, so a normal `make` builds it once
# and then reuses it — `clean` only removes build/, so it survives.
ip65-blob: $(IP65_BIN)

$(IP65_BIN): $(IP65_BUILD)/ip65_stub.s $(IP65_BUILD)/ip65.cfg
Expand Down
12 changes: 7 additions & 5 deletions src/crypto/ecdsa_verify_384.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,9 +33,9 @@
;
; Phase 5 Fix A: blob length is 130 bytes, not 146. RFC 8446
; §4.4.1 specifies the transcript-hash uses the negotiated cipher
; suite's hash function — c64-https only negotiates
; TLS_AES_128_GCM_SHA256, so the transcript is always 32 B SHA-256
; regardless of the signature scheme. The 46+33+1+32 = 130 layout
; suite's hash function — c64-https offers exactly one suite,
; TLS_CHACHA20_POLY1305_SHA256 (0x1303), so the transcript is
; always 32 B SHA-256 regardless of the signature scheme. The 46+33+1+32 = 130 layout
; is what the server signed; padding to 48 B for SHA-384's digest
; width would feed the verifier a different message than the one
; the server hashed. SHA-384(blob) still produces a 48 B digest
Expand All@@ -49,7 +49,9 @@
; 6. crypto_swap_to_p384_curve -> ecdsa_verify_384.
; C=0 valid / C=1 invalid -- propagated to caller.
;
; Phase 5 note: c64-https only negotiates TLS_AES_128_GCM_SHA256, so
; Phase 5 note: c64-https offers exactly one cipher suite,
; TLS_CHACHA20_POLY1305_SHA256 (0x1303) — see src/tls_handshake.s:85
; and the ServerHello echo check at :380 — and its hash is SHA-256, so
; the TLS 1.3 transcript-hash function is always SHA-256 (RFC 8446
; §4.4.1 ties transcript-hash to the cipher suite's hash, not to the
; signature_algorithm). The signed-content blob therefore embeds a
Expand DownExpand Up@@ -249,7 +251,7 @@ ecdsa_verify_384_tls:
; [98..129] transcript hash (32 B SHA-256). Phase 5 Fix A:
; copy the 32 B SHA-256 tls_transcript verbatim — no padding.
; The TLS 1.3 transcript-hash is bound to the cipher suite
; (SHA-256 via TLS_AES_128_GCM_SHA256), independent from the
; (SHA-256 via TLS_CHACHA20_POLY1305_SHA256), independent of the
; signature_algorithm's hash (SHA-384 here). Padding to 48 B
; would feed the verifier a different message than the server
; signed.
Expand Down
6 changes: 3 additions & 3 deletions src/net/uci/net_tuning.inc
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,10 +17,10 @@
; uci_wait_not_busy, uci_begin_cmd, 4x uci_put_byte, uci_push_wait,
; uci_check_err, header read, uci_drain_resp + uci_drain_status +
; uci_ack — ~25 fenced register accesses plus FPGA turnaround).
; Measured on a C64 Ultimate at 48 MHz: ~37 ms per poll, of which only
; ~2.8 ms is fence time — the remainder is clock-invariant firmware
; Measured on a C64 Ultimate at 48 MHz: ~40 ms per poll, of which only
; ~3 ms is fence time — the remainder is clock-invariant firmware
; turnaround, so turbo does not help. ip65's 2000-poll budget therefore
; cost ~70 s and regressed the shipped handshake from 51.0 s to 125.4 s
; cost ~80 s and regressed the shipped handshake from 51.0 s to 125.4 s
; (issue #73).
;
; 16 polls (~0.6 s at 48 MHz) is a deliberate small hedge rather than 0:
Expand Down
4 changes: 2 additions & 2 deletions src/tls13.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -471,9 +471,9 @@ tls_recv_server_hello:
; The budget is BACKEND-SENSITIVE and therefore lives in the
; per-backend net_tuning.inc (issue #73): an ip65 net_poll is a
; cheap NIC pump, but a UCI net_poll is a full firmware command
; round-trip (~37 ms measured at 48 MHz, mostly clock-invariant
; round-trip (~40 ms measured at 48 MHz, mostly clock-invariant
; FPGA turnaround). Sizing this loop on ip65's poll cost alone
; cost UCI ~70 s of pure wall-clock — and UCI firmware ACKs
; cost UCI ~80 s of pure wall-clock — and UCI firmware ACKs
; autonomously, so the drain has nothing to buy there anyway.
; See each backend's net_tuning.inc for the values + rationale.
ldy #NET_SH_DRAIN_OUTER
Expand Down
51 changes: 50 additions & 1 deletion tools/_vice_helpers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,12 +8,37 @@

See user memory ``vice_reu_required_for_p256`` and the project's
"VICE harness gotcha" note in ``CLAUDE.md`` for the canonical motivation.

Opt-in no-REU mode
------------------
Setting ``C64_VICE_NO_REU=1`` in the environment drops the REU flags, so
the packaging claim "the onchip PRG passes the ECDSA KAT without an REU"
has a runnable test instead of requiring a monkeypatched copy of the
script. It is deliberately opt-in and noisy: a no-REU run of a
*REU-profile* build does not error, it silently computes wrong answers
(a valid signature verifies as C=1). Only use it on
``USE_NISTCURVES_ONCHIP=1`` images.
"""

from __future__ import annotations

import os
import sys

from c64_test_harness import ViceConfig

#: Environment variable that opts a run out of the mandatory REU flags.
NO_REU_ENV = "C64_VICE_NO_REU"


def no_reu_requested(env: dict | None = None) -> bool:
"""Return True when the environment opts out of the REU flags.

:param env: mapping to inspect (defaults to ``os.environ``).
"""
src = os.environ if env is None else env
return str(src.get(NO_REU_ENV, "")).strip().lower() in ("1", "true", "yes", "on")


def default_vice_config(
*,
Expand All@@ -35,6 +60,20 @@ def default_vice_config(
options (e.g. ``-warp``, custom monitor flags) without losing the REU
enablement.

Setting ``C64_VICE_NO_REU=1`` omits the REU flags (and announces it on
stderr). That mode exists to test the REU-less onchip profile — the
shipped ``c64-https-uci-onchip.prg`` claims "no REU required", and this
is how that claim is reproduced:

.. code-block:: sh

make clean && make BACKEND=uci USE_NISTCURVES_ONCHIP=1
C64_SKIP_BUILD=1 C64_VICE_NO_REU=1 \\
python3 tools/test_ecdsa_kat_oracle.py

On any other build the same invocation returns wrong answers without
complaining, which is exactly why REU stays the default.

Remaining keyword arguments are forwarded verbatim to ``ViceConfig``;
typical callers pass ``prg_path``, ``warp``, ``ntsc``, ``sound`` etc.

Expand All@@ -44,7 +83,17 @@ def default_vice_config(
:param kwargs: forwarded to :class:`c64_test_harness.ViceConfig`.
:returns: a configured ``ViceConfig`` instance.
"""
base_args = ["-reu", "-reusize", "512"]
if no_reu_requested():
print(
f"[{NO_REU_ENV}] VICE launching WITHOUT -reu — valid only for "
"USE_NISTCURVES_ONCHIP builds; any REU-profile image will "
"silently compute wrong results.",
file=sys.stderr,
flush=True,
)
base_args: list[str] = []
else:
base_args = ["-reu", "-reusize", "512"]
if extra_args:
base_args = base_args + list(extra_args)
return ViceConfig(extra_args=base_args, **kwargs)
Loading