From 0addccebe82917d5f4de76e6d57bb34ec111d3c1 Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:37:12 -0500 Subject: [PATCH] =?UTF-8?q?feat(crypto):=20comb-accelerated=20onchip=20pro?= =?UTF-8?q?file=20=E2=80=94=2031.0=20s=20handshake=20@64=20MHz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USE_NISTCURVES_ONCHIP_COMB=1 (implies USE_NISTCURVES_ONCHIP): consume the sibling's comb ecdsa256 + points256_comb + Lim-Lee data (composed from the full onchip archive, trimmed to the P-256 set), add the ec_precompute_256 boot pass (REU bank 2 $0000-$3FFF anchors, SPEC §8.5), and gate out the in-tree no-comb ec_scalar_mul shim under the flag. CRYPTO_HOT can't absorb the +1.1 KB comb code (29 B slack), so the profile uses cfg/c64-https-uci-onchip.cfg — a documented variant that moves RODATA/CRYPTO_RODATA/P256_RODATA + the new LIMLEE segment into the free CRYPTO_OVERLAY (COLD_SHADOW is flush-full). Boot cost: ec_precompute_256 is ~50 s at 64 MHz / ~3.5 min at 16 / ~40 min at stock — bench + e2e scripts grew C64_INIT_WAIT and the bench now boots at the first sweep speed (it used to inherit leftover device config). KAT script grew C64_INIT_TIMEOUT for the same reason. Measured (C64U): verify 49.4/24.9/16.5/12.4 s at 16/32/48/64 MHz (D~0.2 s, C=787 MHz*s); REU-profile crossover drops to ~7 MHz; comb dominates no-comb onchip at every clock. HTTPS e2e: 38.4 s @48, **31.0 s @64** — at the top edge of the 10-30 s real-server window. VICE KAT oracle 3/3 (precompute + comb u1*G path); all hardware runs oracle-gated. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW --- CLAUDE.md | 60 ++-- Makefile | 27 +- cfg/c64-https-uci-onchip.cfg | 332 +++++++++++++++++++++ src/boot.s | 13 + src/crypto/ecdsa_verify.s | 7 + tools/integration/build_nistcurves_p256.sh | 27 +- tools/test_ecdsa_kat_oracle.py | 5 +- tools/uci/bench_ecdsa_u64e.py | 13 +- tools/uci/test_https_local.py | 4 +- 9 files changed, 454 insertions(+), 34 deletions(-) create mode 100644 cfg/c64-https-uci-onchip.cfg diff --git a/CLAUDE.md b/CLAUDE.md index 37ab2df..6b98c74 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,14 +38,21 @@ Variables: CRYPTO_OVERLAY slot at PRG-load (UCI; mutually exclusive with USE_X25519_SIBLING / USE_OVERLAY_P384_EMBED) - - `USE_NISTCURVES_ONCHIP=1` — link the libs/nistcurves v0.5.0 + - `USE_NISTCURVES_ONCHIP=1` — link the libs/nistcurves FP_ONCHIP_MUL turbo-profile P-256 verify - archive (no REU row-fetch DMA; ~34 MHz - crossover vs the default REU profile — see - the ECDSA wall-clock section). Mutually - exclusive with USE_X25519_SIBLING and both - overlay-embed flags (MUL_CODE occupies - CRYPTO_OVERLAY). + archive (no REU row-fetch DMA; ~22 MHz + crossover vs the default REU profile at + v0.6.0 — see the ECDSA wall-clock section). + Mutually exclusive with USE_X25519_SIBLING + and both overlay-embed flags (MUL_CODE + occupies CRYPTO_OVERLAY). + - `USE_NISTCURVES_ONCHIP_COMB=1` — comb-accelerated onchip profile + (implies USE_NISTCURVES_ONCHIP): Lim-Lee + fixed-base u1*G + ec_precompute_256 boot + pass into REU bank 2. Fastest verify above + ~7 MHz; boot costs ~50 s at 64 MHz (test + scripts: set C64_INIT_WAIT). Uses + cfg/c64-https-$(BACKEND)-onchip.cfg. - `CA65`, `LD65` — toolchain overrides - `VICE` — override the `make run` emulator @@ -580,6 +587,7 @@ C64U, fits T(f)=D+C/f, residuals <=4.1%): v0.5.0 REU 72.2 57.7 53.7 49.3 42.9 s 471 MHz*s v0.5.0 onchip 117.5 59.6 41.2 31.0 2.5 s 1839 MHz*s v0.6.0 onchip 88.3 43.3 30.9 22.9 1.1 s 1396 MHz*s + v0.6.0 onchip+comb 49.4 24.9 16.5 12.4 ~0.2 s 787 MHz*s - The REU-profile floor is ~42 s (an earlier 2-point fit said 28.4 s — that number was ill-conditioned and is superseded; at @@ -587,26 +595,30 @@ C64U, fits T(f)=D+C/f, residuals <=4.1%): - v0.5.0's REU path is performance-identical to v0.3.0. - The onchip profile ELIMINATES the floor (D = 2.5 s) at the cost of ~3.9x the CPU work; it scales 3.79x for a 4x clock. - - **Measured crossover: ~34 MHz for v0.5.0 shape-1; ~22 MHz for - v0.6.0 shape-2** (inline quarter-square row gen, issue #71 — - onchip now wins at 32 MHz too: 43.3 vs 57.7 s). At stock 1 MHz - REU remains the right default (~2.5x faster). Ship both - profiles; note these numbers are for the no-comb verify archive - (the library's comb-PRG numbers are ~2x faster in absolute - terms). + - **Measured crossovers vs the REU profile: ~34 MHz (v0.5.0 + shape-1), ~22 MHz (v0.6.0 shape-2), ~7 MHz (shape-2 + comb)**. + The comb build (USE_NISTCURVES_ONCHIP_COMB=1) dominates the + no-comb onchip build at every clock; its costs are the + ec_precompute_256 boot pass (~50 s at 64 MHz, ~3.5 min at + 16 MHz, ~40 min at stock — scripts need C64_INIT_WAIT) and REU + bank 2 $0000-$3FFF residency. At stock 1 MHz the REU profile + remains the right default. HTTPS e2e handshake wall-clock (C64U, local listener): - profile 48 MHz 64 MHz - v0.3.0 REU 73.0 s 64.7-65.9 s - v0.5.0 onchip 59.9 s 47.5 s (n=3: 47.0/47.6/47.8) - v0.6.0 onchip 51.0 s **39.7 s** - - 39.7 s @ 64 MHz brushes the top of a typical 10-30 s - internet-server handshake window. The remaining big lever is the - comb-accelerated full archive (`nistcurves-onchip.a` + precompute - boot + REU bank-2 residency): upstream's comb verify measures - 11.8 s @ 64 MHz, projecting a ~28-29 s handshake here. + profile 48 MHz 64 MHz + v0.3.0 REU 73.0 s 64.7-65.9 s + v0.5.0 onchip 59.9 s 47.5 s (n=3: 47.0/47.6/47.8) + v0.6.0 onchip 51.0 s 39.7 s + v0.6.0 onchip+comb 38.4 s **31.0 s** + + 31.0 s @ 64 MHz sits at the top edge of a typical 10-30 s + internet-server handshake window — the first configuration where + a real-server TLS connection is plausible. Remaining spend: + ~12.4 s verify + ~18.6 s of everything else (X25519, SHA-256 + transcript+HMACs, record I/O, UCI firmware/network latency) — + the non-verify side is now the bigger half and the next + profiling target. v0.3.0's hot-path code is essentially unchanged from v0.2.0; the small wall-clock improvement is within measurement noise across diff --git a/Makefile b/Makefile index f044dc6..5969a3e 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,9 @@ IP65_BUILD := ip65-build IP65_BIN := $(IP65_BUILD)/ip65-c64.bin CA65FLAGS := -I src -I src/inc -I src/crypto/shared -I src/net/$(BACKEND) -I build --debug-info -LD65FLAGS := -C $(CFG) -Ln build/labels.txt -m build/c64-https.map --dbgfile build/c64-https.dbg +# Lazy (=) so the USE_NISTCURVES_ONCHIP_COMB block below can retarget +# CFG to the cfg variant after this line. +LD65FLAGS = -C $(CFG) -Ln build/labels.txt -m build/c64-https.map --dbgfile build/c64-https.dbg # Source inventory. TOP_SRCS := $(wildcard src/*.s) @@ -69,6 +71,15 @@ UCI_SRCS := src/net/uci/net.s src/net/uci/uci_cmd.s # stops paying"). Gates: data.s yields sqtab to the lib's $BC00 equates, # poly1305.s provides the §8.3 canonical ct_mul_8x8 + SMC bake sites, # boot.s skips reu_mul_init + yields the reu_fetch_mul_row export. +# USE_NISTCURVES_ONCHIP_COMB=1: comb-accelerated onchip profile — the +# comb ecdsa256 + points256_comb + Lim-Lee data replace the no-comb +# verifier (u1*G via 8-way fixed-base comb instead of a second +# variable-base ladder). Implies USE_NISTCURVES_ONCHIP; adds the +# ec_precompute_256 boot pass (REU bank 2 $0000-$3FFF anchors) and +# switches to the CRYPTO_HOT-relieving cfg variant. +ifeq ($(USE_NISTCURVES_ONCHIP_COMB),1) +USE_NISTCURVES_ONCHIP := 1 +endif ifeq ($(USE_NISTCURVES_ONCHIP),1) ifeq ($(USE_X25519_SIBLING),1) $(error USE_NISTCURVES_ONCHIP and USE_X25519_SIBLING are mutually exclusive for now: both archives export reu_fetch_mul_row) @@ -79,8 +90,14 @@ endif ifeq ($(EMBED_P256_OVERLAY),1) $(error USE_NISTCURVES_ONCHIP places LIB_NISTCURVES_MUL_CODE in CRYPTO_OVERLAY - mutually exclusive with EMBED_P256_OVERLAY) endif -SIBLING_LIB_ARCHIVES := build/lib/nistcurves-p256-onchip.a CA65FLAGS += -D USE_NISTCURVES_ONCHIP=1 +ifeq ($(USE_NISTCURVES_ONCHIP_COMB),1) +SIBLING_LIB_ARCHIVES := build/lib/nistcurves-p256-onchip-comb.a +CA65FLAGS += -D USE_NISTCURVES_COMB=1 +CFG := cfg/c64-https-$(BACKEND)-onchip.cfg +else +SIBLING_LIB_ARCHIVES := build/lib/nistcurves-p256-onchip.a +endif else SIBLING_LIB_ARCHIVES := build/lib/nistcurves-p256.a endif @@ -306,6 +323,12 @@ build/lib/nistcurves-p256-onchip.a: @mkdir -p build/lib bash tools/integration/build_nistcurves_p256.sh onchip +# Comb-accelerated onchip variant: full onchip archive trimmed to the +# P-256 comb verify set (comb ecdsa256 + points256_comb + Lim-Lee data). +build/lib/nistcurves-p256-onchip-comb.a: + @mkdir -p build/lib + bash tools/integration/build_nistcurves_p256.sh onchip-comb + # Phase C.5: c64-x25519 v0.4.0 X25519 archive — replaces the in-tree # fe25519.s + x25519.s + X25519 buffer declarations in src/data.s when # USE_X25519_SIBLING=1. Linked into the PRG under BOTH backends. The diff --git a/cfg/c64-https-uci-onchip.cfg b/cfg/c64-https-uci-onchip.cfg new file mode 100644 index 0000000..4eb06f7 --- /dev/null +++ b/cfg/c64-https-uci-onchip.cfg @@ -0,0 +1,332 @@ +# ============================================================================= +# cfg/c64-https-uci-onchip.cfg — UCI memory map, USE_NISTCURVES_ONCHIP_COMB=1 +# +# GENERATED VARIANT of cfg/c64-https-uci.cfg (keep in sync — the only +# intended deltas are listed here). The comb-accelerated onchip profile +# adds ~1.1 KB of LIB_NISTCURVES_P256_CODE (points256_comb + comb +# ecdsa256) to a CRYPTO_HOT that has only ~29 B of slack, so this +# variant relieves CRYPTO_HOT by moving the read-only segments into the +# otherwise-free CRYPTO_OVERLAY (file-backed; mutually exclusive with +# the overlay-embed flags, enforced by the Makefile): +# RODATA / CRYPTO_RODATA / LIB_NISTCURVES_P256_RODATA -> CRYPTO_OVERLAY +# and places the comb's new segment: +# LIB_NISTCURVES_P256_LIMLEE_BSS -> CRYPTO_OVERLAY (type rw; COLD is flush-full) +# ============================================================================= +# c64-https ld65 config — UCI (Ultimate Command Interface) backend +# +# Target: Commodore Ultimate 64 / U64E using the host-visible UCI +# ($DF1B-$DF1F) in place of ip65 + RR-Net. +# +# Memory map (post-W1 partial — see comment block at the MEMORY block +# below for the fit constraints that forced this layout): +# $0801-$1FFF : LOADER (BASIC stub + boot + http + net wrapper) +# $2000-$3B25 : NET_CODE (UCI adapter + LOADER_OVERFLOW tail + +# TLS_CODE + CRYPTO_AUX_CODE) +# $3B26-$3FFF : NET_BSS_TAIL (BSS spill-over reclaimed from the +# NET_CODE tail — UCI_BSS + +# LIB_NISTCURVES_P256_BSS land here) +# $4000-$41FF : UCI_BSS_REGION (zero-size alias post-W1; UCI_BSS +# moved into NET_BSS_TAIL above) +# $4200-$5FFF : CRYPTO_OVERLAY (7.5 KB swappable overlay slot — used +# by the X25519 sibling / P-384 overlay +# / W3 P-256 overlay embed) +# $6000-$BFFF : CRYPTO_HOT (24 KB file-backed; always-resident +# hot + warm + much of the cold path: +# ChaCha20-Poly1305, SHA-256, HKDF/ +# HMAC-DRBG, TLS, HTTP, mul tables, +# AEAD, transcript, libs/nistcurves +# P-256 verify + most BSS. The W1 plan +# wanted a 16 KB hot / 8 KB cold split +# here but the bumped library does not +# fit that partition — see comment +# block at the MEMORY entry.) +# $C000-$CFFF : TCP_BUF (tcp_recv_buf, 4 KB ring) +# +# --- W1 hot/cold partition (post-bump library-ingestion architecture) --- +# Before: CRYPTO_RESIDENT was a single 24 KB file-backed region $6000-$BFFF +# carrying every byte of crypto code + rodata + BSS. CLAUDE.md recorded it +# as "100% full" after Phase 6; the libs/nistcurves cfa9085 bump (which +# adds ~768 B of new code via PR #34 + PR #26) would have overflowed it. +# +# The W1 split lets the file-backed region carry only what needs PRG-load +# byte initialization (code + rodata, $6000-$9FFF = 16 KB), and reclaims +# $A000-$BFFF as plain RAM under BASIC ROM banking for BSS that is +# zero-initialized at runtime anyway. This is the same "$01 = $36" RAM +# slice that pre-Phase-6 hosted the original SHADOW_BSS region; bringing +# it back as a dedicated BSS slot leaves room in CRYPTO_HOT for the +# nistcurves bump's additional code bytes. +# +# Library segments (under c64-lib-contract / libs/nistcurves cfa9085+): +# +# LIB_NISTCURVES_P256_CODE - fp256, mod256, points256_core, ecdsa256. +# Default: CRYPTO_HOT (always-resident). +# Under EMBED_P256_OVERLAY=1: routed via +# OVERLAY_BLOB_P256 to the live overlay +# slot at boot. +# LIB_NISTCURVES_P256_RODATA - curve256, mod256 constants. CRYPTO_HOT. +# LIB_NISTCURVES_P256_BSS - data_p256 working buffers. CRYPTO_HOT BSS. +# LIB_NISTCURVES_P384_* - routed via the OVERLAY_P384_CURVE slot +# (paged from REU bank 7 at handshake time). +# LIB_NISTCURVES_SHA384_* - routed via the OVERLAY_P384_SHA384 slot +# (paged from REU bank 6 at handshake time). +# +# (For the W2 ip65-side restructure, see cfg/c64-https-ip65.cfg. ip65 +# stays on the pre-W1 layout for now — out of scope here.) + +FEATURES { + STARTADDRESS: default = $0801; +} + +MEMORY { + ZP_CRYPTO: start = $0022, size = $001E, type = rw, define = yes; + ZP_WIDE: start = $0040, size = $0040, type = rw, define = yes; + + LOADADDR: start = $07FF, size = $0002, file = %O; + LOADER: start = $0801, size = $17FF, file = %O, define = yes, fill = yes, fillval = $00; + # W1 partial: NET_CODE shrunk to fit current content (~$1B26 = 6950 B + # on branch tip), tail reclaimed as NET_BSS_TAIL to spill BSS that + # does not fit in CRYPTO_HOT under the bumped libs/nistcurves. UCI_BSS + # now shares NET_BSS_TAIL too (the dedicated UCI_BSS_REGION was 512 B + # but only used ~$125 — folding it into NET_BSS_TAIL frees ~370 B + # for the libs/nistcurves spill-over). ip65 has had an equivalent + # NET_BSS_TAIL since Phase C.4 — UCI now adopts the same pattern. + # Grown $40 (iface-fallback loop in net_dhcp_acquire, C64U WiFi + # support) — donated from NET_BSS_TAIL, whose occupancy tops out at + # $625 (LIB_NISTCURVES_P256_BSS) leaving $B5 slack pre-donation. + NET_CODE: start = $2000, size = $1B66, file = %O, define = yes, fill = yes, fillval = $00; + # NET_BSS_TAIL spans NET_CODE end through the start of CRYPTO_OVERLAY, + # subsuming the historical UCI_BSS_REGION at $4000-$41FF (UCI_BSS now + # rides on the head of this combined region). Holds 1.7 KB total — + # ld65 distributes UCI_BSS (~293 B) + LIB_NISTCURVES_P256_BSS (~1.5 + # KB) + a stub UCI_BSS_REGION definition. + NET_BSS_TAIL: start = $3B66, size = $069A, file = %O, define = yes, fill = yes, fillval = $00; + # UCI_BSS_REGION kept as a zero-size alias so any external tooling + # that resolves the symbol via labels.txt still finds it (the cfg's + # `define = yes` emits __UCI_BSS_REGION_START__ etc.). + UCI_BSS_REGION: start = $4000, size = $0000, type = rw, define = yes; + CRYPTO_OVERLAY: start = $4200, size = $1E00, file = %O, define = yes, fill = yes, fillval = $00; + + # W1 HOT/COLD SPLIT (Worker I — root-cause fix for the + # boot zero-fill regression): + # + # The W1 partial layout had CRYPTO_HOT spanning the full 24 KB + # $6000-$BFFF as a single file-backed region. Under the bumped + # libs/nistcurves, ld65 placed `LIB_NISTCURVES_P256_CODE` at + # $868F-$A631 — *straddling $A000*. Boot (`src/boot.s` zbss loop) + # zeros $A000-$BFFF as "SHADOW_BSS" zero-init, which wiped the + # upper 1.5 KB of `ecdsa_verify_256`; the first call to verify + # landed the CPU on `00 00 …` ⇒ BRK ⇒ KERNAL warm restart ⇒ + # BASIC READY ⇒ eternal hang during the TLS CertVerify step. + # + # The fix carves the region as originally intended: + # + # CRYPTO_HOT $6000-$9FFF 16 KB file-backed. + # Code + rodata + small BSS that + # do not need page alignment. + # No segment crosses $A000. + # CRYPTO_COLD_SHADOW $A000-$BFFF 8 KB file-backed (zero-filled). + # Catch-all for the large BSS + # segments — wiped at boot by + # the zbss loop, which is benign + # because they are BSS anyway. + # File-backed (`fill = yes`) so + # the PRG stays contiguous on + # disk; the zero bytes are + # overwritten at runtime. + # + # See SEGMENTS{} below for the new routings (BSS / CRYPTO_BSS / + # TABLES_BSS / BSS_TAIL → CRYPTO_COLD_SHADOW; small UCI_BSS stays + # in CRYPTO_HOT). The 16 KB hot half fits the bumped library + # comfortably; the 8 KB cold half has enough room for the four + # largest BSS chunks routed there. + CRYPTO_HOT: start = $6000, size = $4000, file = %O, define = yes, fill = yes, fillval = $00; + CRYPTO_COLD_SHADOW: start = $A000, size = $2000, file = %O, define = yes, fill = yes, fillval = $00; + + # Phase 3: file-backed pad region from $C000-$DFFF. ld65 emits + # contiguous file output; the under-KERNAL OVERLAY_BLOB_CURVE_RAM + # region at $E000-$FDFF requires the gap between CRYPTO_HOT/SHADOW + # and $E000 to land in the file as zeros so KERNAL LOAD writes the + # curve blob bytes to $E000 (not $C801). $C000-$CFFF is TCP_BUF + # at runtime (RAM, populated by ip65/UCI rx callback after net + # init); the zero-fill PRG-load write is harmless because TCP_BUF + # is zero-initialised at first use anyway. $D000-$DFFF is I/O on + # a real C64 + 1541 the PRG load WOULD momentarily corrupt VIC / + # SID / CIA registers; production targets are VICE warp + U64 + # fastload, both of which inject bytes directly to RAM and bypass + # CPU I/O writes during PRG load. + OVERLAY_FILE_PAD: start = $C000, size = $2000, file = %O, define = yes, fill = yes, fillval = $00; + + # Phase 3: under-KERNAL ROM RAM at $E000-$FDFF holds the P-384 + # CURVE overlay blob (7,680 B) at PRG load time. Boot DMAs it to + # REU bank 7 then this region is reusable. KERNAL LOAD writes + # pass through to the underlying RAM regardless of $01 banking. + OVERLAY_BLOB_CURVE_RAM: start = $E000, size = $1E00, file = %O, define = yes, fill = yes, fillval = $00; + + # NOTE: the historical TCP_BUF MEMORY region at $C000-$CFFF was + # removed in Phase 3 -- the actual TCP rx ring is just the + # `tcp_recv_buf = $c000` equate in src/constants.inc and the bytes + # at runtime live inside OVERLAY_FILE_PAD's address range. PRG + # load zeros the ring; the rx callback overwrites it after net + # init. The optional TCP_RECV_BUF segment was also dropped from + # SEGMENTS below (no .s file references the segment name; the + # buffer is addressed via the equate, not via a segment label). +} + +SEGMENTS { + ZEROPAGE: load = ZP_CRYPTO, type = zp, optional = yes; + ZP_WIDE: load = ZP_WIDE, type = zp, optional = yes; + + LOADADDR: load = LOADADDR, type = ro; + EXEHDR: load = LOADER, type = ro; + STARTUP: load = LOADER, type = ro, optional = yes; + CODE: load = LOADER, type = ro; + RODATA: load = CRYPTO_OVERLAY, type = ro; # onchip-comb variant: relieve CRYPTO_HOT + INIT: load = LOADER, type = ro, optional = yes; + + # NET_CODE hosts the UCI adapter + the LOADER_OVERFLOW tail. + # Phase C.2 (UCI only): TLS_CODE and CRYPTO_AUX_CODE (SHA-256, + # HMAC-DRBG, ecdsa_verify dispatcher) are relocated here as well + # to open headroom in CRYPTO_HOT. The UCI adapter is ~1.7 KB + # so NET_CODE has ~6.3 KB free after UCI_CODE + LOADER_OVERFLOW; + # TLS_CODE ~1.8 KB + CRYPTO_AUX_CODE ~3 KB fits with room to spare. + NET_CODE: load = NET_CODE, type = ro, optional = yes; + UCI_CODE: load = NET_CODE, type = ro, optional = yes; + LOADER_OVERFLOW: load = NET_CODE, type = ro, optional = yes; + TLS_CODE: load = NET_CODE, type = ro, optional = yes; + CRYPTO_AUX_CODE: load = NET_CODE, type = ro, optional = yes; + # Phase C.4: hmac_drbg.s moved to CRYPTO_AUX_CODE2 so the ip65 cfg + # can split it off into NET_CODE while sha256 stays in NET_BSS_TAIL. + # Under UCI both segments flow into NET_CODE identically. + CRYPTO_AUX_CODE2: load = NET_CODE, type = ro, optional = yes; + # UCI_BSS (~293 B) is small and stays in CRYPTO_HOT (16 KB slot + # has room for it alongside code + rodata; keeping it here saves + # space in the 8 KB CRYPTO_COLD_SHADOW for the larger BSS chunks). + UCI_BSS: load = CRYPTO_HOT, type = bss, optional = yes; + # NET_BSS_TAIL is the spill-over BSS region carved from the + # NET_CODE tail. Under the W1 hot/cold split (Worker I) it is + # mostly freed: LIB_NISTCURVES_P256_BSS no longer rides here + # (it moved to CRYPTO_COLD_SHADOW), so this region is effectively + # available for harness scratch. The segment declaration stays + # for any future BSS routing or harness use. + NET_BSS_TAIL: load = NET_BSS_TAIL, type = bss, optional = yes; + # BSS_TAIL hosts the largest c64-https in-tree BSS buffers + # (tls_rec_buf 548 B + cert_buf 1.5 KB). Worker I routed it to + # CRYPTO_COLD_SHADOW ($A000-$BFFF) under the hot/cold split. + BSS_TAIL: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; + + # --- Overlay slot. --- + # The P-256 / P-384 OVERLAY segments are declared so ld65 has a + # valid load address even when nothing is currently linked. The + # production PRG does not always embed an overlay image; under + # EMBED_P256_OVERLAY=1 the P-256 .bin is .incbin'd via + # OVERLAY_BLOB_P256; under USE_OVERLAY_P384_EMBED=1 the P-384 split + # blobs ride OVERLAY_BLOB_SHA384. + OVERLAY_P256: load = CRYPTO_OVERLAY, type = ro, optional = yes; + OVERLAY_P384: load = CRYPTO_OVERLAY, type = ro, optional = yes; + + # Phase C.5: sibling c64-x25519 rodata tables (mul38, sqr_lo/hi, + # a24_b0..b3 — ~2 KB) AND the sibling's page-aligned BSS buffers + # (fe25519_tmp1..4, x25_*, mul_dma_lo/hi/carry — ~1.5 KB) ride + # CRYPTO_OVERLAY under UCI to keep CRYPTO_HOT inside its budget. + # CRYPTO_OVERLAY is otherwise unused in the production UCI build + # unless an overlay-embed flag is set (mutually exclusive across + # USE_X25519_SIBLING / EMBED_P256_OVERLAY / USE_OVERLAY_P384_EMBED). + # align = $100 so the .align 256 directives in + # data_x25519_{rodata,bss}_raw.s land on real page boundaries. + X25519_RODATA: load = CRYPTO_OVERLAY, type = ro, optional = yes, align = $100; + X25519_BSS: load = CRYPTO_OVERLAY, type = bss, optional = yes, align = $100; + + # --- Resident crypto + TLS code / rodata. --- + CRYPTO_CODE: load = CRYPTO_HOT, type = ro; + CRYPTO_RODATA: load = CRYPTO_OVERLAY, type = ro; # onchip-comb variant: relieve CRYPTO_HOT + RESIDENT_RODATA: load = CRYPTO_HOT, type = ro, optional = yes; + CRYPTO_INIT_CODE: load = CRYPTO_HOT, type = ro, optional = yes; + + # --- W5: libs/nistcurves segments (c64-lib-contract SPEC §4). --- + # Under the default flag set (EMBED_P256_OVERLAY=0), the P-256 + # verify primitives are always-resident in CRYPTO_HOT — mirrors + # the pre-restructure behavior. Under EMBED_P256_OVERLAY=1 they + # ride the overlay slot; the segment definitions below stay + # routed to CRYPTO_HOT because the embed path operates on a .bin + # image (built by tools/integration/build_nistcurves_p256_bin.sh + # from this same archive), not by re-routing the segments at + # link time. + LIB_NISTCURVES_P256_CODE: load = CRYPTO_HOT, type = ro, optional = yes; + LIB_NISTCURVES_P256_RODATA: load = CRYPTO_OVERLAY, type = ro, optional = yes; # onchip-comb variant + # P-256 BSS is ~1.5 KB (fp256/mod256/points256 scratch + ecdsa256 + # input/output staging + data_p256 working buffers). Stays in + # NET_BSS_TAIL under the W1 hot/cold split — CRYPTO_COLD_SHADOW + # is full carrying the in-tree BSS chunks (BSS / CRYPTO_BSS / + # TABLES_BSS / BSS_TAIL = ~8.1 KB out of the 8 KB budget once + # TABLES_BSS page-alignment is accounted for). NET_BSS_TAIL has + # ~185 B slack after the P-256 claim — adequate for harness use + # via the candidate-fallback path in tools/uci/_memory_policy.py. + LIB_NISTCURVES_P256_BSS: load = NET_BSS_TAIL, type = bss, optional = yes; + # Comb profile: Lim-Lee anchors + working k (data_p256_limlee.o). + # CRYPTO_COLD_SHADOW is flush-full (sqtab ends at $BFFF), so this + # rides in the file-backed CRYPTO_OVERLAY with the other variant + # segments. + LIB_NISTCURVES_P256_LIMLEE_BSS: load = CRYPTO_OVERLAY, type = rw, optional = yes; + # The P-256 archive does not include data_shared.o or mul_8x8.o + # (c64-https provides those — see tools/integration/build_nistcurves_p256.sh), + # so the LIB_NISTCURVES_BSS / _TABLES / _MUL_CODE segments are + # declared `optional = yes` for forward compatibility but receive + # zero bytes today. + LIB_NISTCURVES_BSS: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; + LIB_NISTCURVES_TABLES: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes, align = $100; + # Onchip profile (issue #69): og_common + sqtab equates. Placed in + # CRYPTO_OVERLAY (free under USE_NISTCURVES_ONCHIP — the Makefile + # forbids combining it with the P-384/P-256 overlay flags, whose + # runtime swap-ins would clobber resident code here) because the + # onchip fp256 growth overflows CRYPTO_HOT by ~59 B otherwise. + LIB_NISTCURVES_MUL_CODE: load = CRYPTO_OVERLAY, type = ro, optional = yes; + # libs/nistcurves P-384 segments — routed via the OVERLAY_P384_CURVE + # bin staging (DMA'd to REU bank 7 at boot, paged into the live + # overlay slot on a P-384 handshake). Mirrors the pre-contract + # OVERLAY_P384_CURVE segment name in the old build_nistcurves_p384.sh + # output. The library now emits the new names natively. + LIB_NISTCURVES_P384_CODE: load = CRYPTO_OVERLAY, type = ro, optional = yes; + LIB_NISTCURVES_P384_RODATA: load = CRYPTO_OVERLAY, type = ro, optional = yes; + LIB_NISTCURVES_P384_BSS: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; + LIB_NISTCURVES_P384_DATA_BSS: load = CRYPTO_COLD_SHADOW, type = rw, optional = yes; + # libs/nistcurves SHA-384 segments — routed via the OVERLAY_P384_SHA384 + # bin staging (DMA'd to REU bank 6 at boot). + LIB_NISTCURVES_SHA384_CODE: load = CRYPTO_OVERLAY, type = ro, optional = yes; + LIB_NISTCURVES_SHA384_RODATA: load = CRYPTO_OVERLAY, type = ro, optional = yes; + LIB_NISTCURVES_SHA384_TABLES: load = CRYPTO_OVERLAY, type = ro, optional = yes, align = $100; + LIB_NISTCURVES_SHA384_BSS: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; + + # --- Resident BSS — routed to CRYPTO_COLD_SHADOW under the W1 + # hot/cold split (Worker I). The BSS / CRYPTO_BSS segments are + # the catch-all for in-tree c64-https state declarations + # (src/data.s); landing them in the $A000-$BFFF banked-on RAM + # slice frees the 16 KB CRYPTO_HOT region for code + rodata + # (which MUST stay below $A000 because boot zeros that span as + # zero-init BSS). + BSS: load = CRYPTO_COLD_SHADOW, type = bss; + CRYPTO_BSS: load = CRYPTO_COLD_SHADOW, type = bss; + # TABLES_BSS pins the 1.5 KB sqtab/mul_dma tables on a page + # boundary. Page alignment is load-bearing for the no-page-penalty + # `lda abs,Y` addressing in the hot-path multiply routines. + TABLES_BSS: load = CRYPTO_COLD_SHADOW, type = bss, align = $100; + + # Phase 3: P-384 split overlay blobs embedded in the PRG. Boot + # DMAs them out to REU banks 6/7 then the staging RAM is free. + # Both segments are `optional = yes` so that builds without the + # .bin files (e.g. before make p384-overlay has run) still link; + # in that case the segments are empty and boot's reu_p384_overlay_init + # DMAs zero bytes. The .ifndef USE_X25519_SIBLING guard inside + # src/crypto/shared/p384_overlay_blobs.s keeps the segments empty + # under the sibling flag (CRYPTO_OVERLAY is taken by X25519_RODATA + # in that build). + OVERLAY_BLOB_SHA384: load = CRYPTO_OVERLAY, type = ro, optional = yes; + OVERLAY_BLOB_CURVE: load = OVERLAY_BLOB_CURVE_RAM, type = ro, optional = yes; + + # W3: P-256 verify overlay blob. Optional / off by default; embedded + # when the Makefile flag EMBED_P256_OVERLAY=1 is set (which causes + # src/crypto/shared/p256_overlay_blobs.s to .incbin the .bin file + # under USE_OVERLAY_P256_EMBED). Shares the CRYPTO_OVERLAY slot + # with OVERLAY_BLOB_SHA384 at PRG-load time -- mutually exclusive: + # the Makefile disables P-384 SHA embedding when EMBED_P256_OVERLAY=1. + OVERLAY_BLOB_P256: load = CRYPTO_OVERLAY, type = ro, optional = yes; +} diff --git a/src/boot.s b/src/boot.s index 421b3c4..6a5f7ca 100644 --- a/src/boot.s +++ b/src/boot.s @@ -132,6 +132,11 @@ .import tls_hostname .import tls_hostname_len + ; ---- imports: comb precompute (sibling nistcurves, comb profile) ---- + .ifdef USE_NISTCURVES_COMB + .import ec_precompute_256 + .endif + ; ---- imports: multiply / REU staging (data.asm) ---- .import mul_8x8 .import mul_dma_lo @@ -285,6 +290,14 @@ start: ; c64-test-harness#137 experiment log. jsr reu_mul_init + ; Comb profile (SPEC §8.5): build the P-256 Lim-Lee anchor table + ; into REU bank 2 $0000-$3FFF. Needs sqtab (built above) + the + ; onchip row generator; runs once per boot. ~seconds at turbo, + ; ~25 s at stock 1 MHz. + .ifdef USE_NISTCURVES_COMB + jsr ec_precompute_256 + .endif + ; Phase 3: stash both P-384 split overlay images in REU banks 6 ; and 7 from the .incbin'd staging blocks at $4200 and $E000. ; Inert under USE_X25519_SIBLING=1 / BACKEND=ip65 (see diff --git a/src/crypto/ecdsa_verify.s b/src/crypto/ecdsa_verify.s index f27a579..641558b 100644 --- a/src/crypto/ecdsa_verify.s +++ b/src/crypto/ecdsa_verify.s @@ -47,7 +47,12 @@ ; --- Exports --- .export ecdsa_verify .export ecdsa_parse_der_sig +; Under the comb profile the sibling's points256_comb.o provides the +; real Lim-Lee ec_scalar_mul — the shim below (seed G, run the +; variable-base ladder) is only for no-comb archives. +.ifndef USE_NISTCURVES_COMB .export ec_scalar_mul ; shim for sibling's Lim-Lee slot +.endif .segment "CRYPTO_CODE" @@ -90,6 +95,7 @@ ecdsa_verify: ; contiguous 32-byte slots (X then Y) in the sibling's data segments, so ; a single 64-byte copy loop suffices for both coordinates. ; ============================================================================= +.ifndef USE_NISTCURVES_COMB ec_scalar_mul: ldy #63 @cp_g: lda ec_gx256,y ; also covers ec_gy256 at +32 @@ -97,6 +103,7 @@ ec_scalar_mul: dey bpl @cp_g jmp ec_scalar_mul_var +.endif ; ============================================================================= diff --git a/tools/integration/build_nistcurves_p256.sh b/tools/integration/build_nistcurves_p256.sh index af1a9ff..0b5b9b3 100755 --- a/tools/integration/build_nistcurves_p256.sh +++ b/tools/integration/build_nistcurves_p256.sh @@ -48,7 +48,7 @@ set -eo pipefail PROFILE="${1:-reu}" -case "$PROFILE" in reu|onchip) ;; *) echo "ERROR: profile must be reu|onchip, got '$PROFILE'" >&2; exit 2;; esac +case "$PROFILE" in reu|onchip|onchip-comb) ;; *) echo "ERROR: profile must be reu|onchip|onchip-comb, got '$PROFILE'" >&2; exit 2;; esac # --- Paths --- PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" @@ -56,7 +56,18 @@ LIB_DIR="$PROJECT_ROOT/libs/nistcurves" LIB_SRC="$LIB_DIR/src" LIB_BUILD="$LIB_DIR/build" OUT_DIR="$PROJECT_ROOT/build/lib" -if [ "$PROFILE" = "onchip" ]; then +if [ "$PROFILE" = "onchip-comb" ]; then + # Comb-accelerated turbo profile: stage from the FULL onchip archive + # (the only shipped archive whose ecdsa256.o is the comb variant and + # which carries points256_comb.o + data_p256_limlee.o), then drop + # everything non-P-256 in step 4b. Consumer boot obligation grows by + # ec_precompute_256 (REU bank 2 $0000-$3FFF anchors, SPEC §8.3/§8.5). + UPSTREAM_TARGET="lib-onchip" + UPSTREAM_ARCHIVE="$LIB_BUILD/lib/nistcurves-onchip.a" + STAGING="$PROJECT_ROOT/build/lib/nistcurves_p256_onchip_comb_staging" + ARCHIVE="$OUT_DIR/nistcurves-p256-onchip-comb.a" + SIZES="$OUT_DIR/nistcurves-p256-onchip-comb.sizes.txt" +elif [ "$PROFILE" = "onchip" ]; then UPSTREAM_TARGET="lib-p256-verify-onchip" UPSTREAM_ARCHIVE="$LIB_BUILD/lib/nistcurves-p256-verify-onchip.a" STAGING="$PROJECT_ROOT/build/lib/nistcurves_p256_onchip_staging" @@ -116,7 +127,17 @@ cp "$UPSTREAM_ARCHIVE" "$STAGING/upstream.a" # --- 4. Drop conflicting members / rebuild the onchip mul object --- rm -f "$STAGING/mul_8x8.o" "$STAGING/data_shared.o" -if [ "$PROFILE" = "onchip" ]; then +# 4b. onchip-comb: the full onchip archive carries both curves + SHA-384 + +# reference-inverse extras; keep only the P-256 comb verify set. +if [ "$PROFILE" = "onchip-comb" ]; then + rm -f "$STAGING"/fp384_onchip.o "$STAGING"/mod384.o "$STAGING"/curve384.o \ + "$STAGING"/points384_core.o "$STAGING"/points384_comb.o \ + "$STAGING"/data_p384.o "$STAGING"/data_p384_limlee.o \ + "$STAGING"/ecdsa384.o "$STAGING"/ecdsa384_msg.o \ + "$STAGING"/sha384*.o "$STAGING"/data_sha.o \ + "$STAGING"/inv256.o "$STAGING"/data_p256_invref.o +fi +if [ "$PROFILE" = "onchip" ] || [ "$PROFILE" = "onchip-comb" ]; then # Rebuild (not drop): fp256_onchip.o imports og_common/og_src_ld which # only this TU provides. The SHARED_* defines strip everything that # would collide with the in-tree providers (see header comment #3). diff --git a/tools/test_ecdsa_kat_oracle.py b/tools/test_ecdsa_kat_oracle.py index 6ef0128..1ac6334 100644 --- a/tools/test_ecdsa_kat_oracle.py +++ b/tools/test_ecdsa_kat_oracle.py @@ -301,7 +301,10 @@ def main(): print(f" VICE PID={inst.pid}, port={inst.port}") print(" Waiting for main menu...") - grid = wait_for_text(transport, "Q=QUIT", timeout=60.0, verbose=False) + # Comb-profile boots run ec_precompute_256 (256 point mults) before + # the menu — minutes of VICE time even under warp. Overridable. + _menu_to = float(os.environ.get("C64_INIT_TIMEOUT", "60")) + grid = wait_for_text(transport, "Q=QUIT", timeout=_menu_to, verbose=False) if grid is None: print("FATAL: Main menu did not appear") sys.exit(1) diff --git a/tools/uci/bench_ecdsa_u64e.py b/tools/uci/bench_ecdsa_u64e.py index 358a184..dee0e3d 100644 --- a/tools/uci/bench_ecdsa_u64e.py +++ b/tools/uci/bench_ecdsa_u64e.py @@ -439,15 +439,22 @@ def main() -> int: enable_uci(client) uci_enabled = True + # Boot at the first sweep speed: comb-profile boots run + # ec_precompute_256 (~30-50 s even at 64 MHz, ~40 min at stock), + # so booting at leftover config speed is not survivable. Also + # matches the C64U boot-at-speed rule (runtime-switch quirk). + set_turbo_mhz(client, MHZ_LIST[0]) + time.sleep(0.5) + print("Resetting machine...") client.reset() time.sleep(2.5) print("run_prg(PRG)...") client.run_prg(prg) - # Wait for auto-init (entropy + sqtab + reu_mul + DHCP). - # reu_mul_init is the slow step; 22s is what test_https_local uses. - time.sleep(22.0) + # Wait for auto-init (entropy + sqtab + reu_mul + DHCP; comb + # profile adds ec_precompute_256). Override for slow profiles. + time.sleep(float(os.environ.get("C64_INIT_WAIT", "22"))) init_flag = transport.read_memory(labels["net_initialized"], 1)[0] print(f"net_initialized = ${init_flag:02X}") diff --git a/tools/uci/test_https_local.py b/tools/uci/test_https_local.py index 2e54ce5..56ce49d 100644 --- a/tools/uci/test_https_local.py +++ b/tools/uci/test_https_local.py @@ -1206,7 +1206,9 @@ def main() -> int: client.run_prg(prg) # Wait for auto-init (entropy, REU stash, DHCP). Scales with TURBO_MHZ # so stock 1 MHz runs allow enough time for entropy + REU sqtab init. - time.sleep(22.0 * _TIMEOUT_SCALE) + # C64_INIT_WAIT overrides the base (comb-profile boots run + # ec_precompute_256: ~30-50 s at 64 MHz — use 90+). + time.sleep(float(os.environ.get("C64_INIT_WAIT", "22")) * _TIMEOUT_SCALE) init_flag = transport.read_memory(labels["net_initialized"], 1)[0] print(f"net_initialized = ${init_flag:02X}")