diff --git a/Makefile b/Makefile index 6f0d607..52a2364 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,26 @@ CRYPTO_SRCS := $(CRYPTO_SRCS_EFFECTIVE) else ifeq ($(BACKEND),uci) NET_SRCS := $(UCI_SRCS) CRYPTO_SRCS := $(CRYPTO_SRCS_EFFECTIVE) + +# --- W3: EMBED_P256_OVERLAY=1 stages the P-256 verify .bin as a +# .incbin into CRYPTO_OVERLAY at PRG-load time. Mutually exclusive +# with USE_OVERLAY_P384_EMBED (same slot at $4200) — the rules below +# turn the P-384 SHA embed off when EMBED_P256_OVERLAY=1 to avoid +# overflowing the 7,680 B slot. Default 0 so the un-flagged UCI +# build is byte-identical to today. +# +# Mirrors the P-384 USE_OVERLAY_P384_EMBED bootstrap pattern: the user- +# visible Make flag is `EMBED_P256_OVERLAY=1`; the ca65-level symbol +# `USE_OVERLAY_P256_EMBED` (which gates the .incbin in +# src/crypto/shared/p256_overlay_blobs.s) defaults to the same value +# but can be explicitly overridden via the command line for the +# bootstrap prelim link (avoids the overlay-bin <-> labels.txt cycle on +# a clean tree). Bootstrap workflow: +# make BACKEND=uci EMBED_P256_OVERLAY=1 USE_OVERLAY_P256_EMBED=0 +# make BACKEND=uci EMBED_P256_OVERLAY=1 +EMBED_P256_OVERLAY ?= 0 +USE_OVERLAY_P256_EMBED ?= $(EMBED_P256_OVERLAY) + # Phase 3: embed the two P-384 split overlay blobs in the PRG so boot # can populate REU banks 6/7 at startup. Gated to UCI (ip65 has no # room for the SHA blob in main RAM) and to !USE_X25519_SIBLING (the @@ -100,12 +120,35 @@ ifneq ($(USE_X25519_SIBLING),1) # Phase 5 Fix D: respect a command-line USE_OVERLAY_P384_EMBED=0 so the # bootstrap rule below can do a no-overlay-embed prelim link to break # the overlay-bin <-> labels.txt cycle on a clean tree. Default is -# still 1 unless the operator explicitly disables it. -USE_OVERLAY_P384_EMBED ?= 1 +# still 1 unless the operator explicitly disables it. W3: also +# auto-turn-off when EMBED_P256_OVERLAY=1 (mutually-exclusive slot). +ifeq ($(EMBED_P256_OVERLAY),1) +USE_OVERLAY_P384_EMBED ?= 0 +else +# W5 / libs/nistcurves cfa9085+ bump: PR #25's SHA-384 rotr LUTs +# (LIB_NISTCURVES_SHA384_TABLES, 3 KB page-aligned) push the SHA-384 +# overlay-half archive above the 7.5 KB CRYPTO_OVERLAY slot limit by +# ~1.5 KB. Default flips to 0 until the SHA-384 LUTs are either: +# - relocated out of the overlay slot (consumer-side path: route +# LIB_NISTCURVES_SHA384_TABLES to a separate resident region and +# teach the standalone overlay cfg to omit them from the .bin), or +# - shrunk on the library side (eg by sharing rotr LUTs across the +# 8 shift amounts, or runtime-generating them at boot). +# Set USE_OVERLAY_P384_EMBED=1 explicitly to attempt the embed (will +# fail at link with an overflow until the SHA-384 LUTs are dealt with). +USE_OVERLAY_P384_EMBED ?= 0 +endif ifeq ($(USE_OVERLAY_P384_EMBED),1) CA65FLAGS += -D USE_OVERLAY_P384_EMBED=1 endif endif + +# W3: propagate USE_OVERLAY_P256_EMBED to ca65 (the .incbin in +# src/crypto/shared/p256_overlay_blobs.s is gated on this). The +# default-derivation from EMBED_P256_OVERLAY happens above. +ifeq ($(USE_OVERLAY_P256_EMBED),1) +CA65FLAGS += -D USE_OVERLAY_P256_EMBED=1 +endif # Phase C.3: add c64-nist-curves P-384 primitives as a REU overlay. # Variable-base P-384 point ops (double/add/jacobian-to-affine) only — # see tools/integration/build_nistcurves_p384.sh for the scope rationale. @@ -164,6 +207,19 @@ PRG_DEPS += build/p384_overlay_equates.inc build/crypto/shared/p384_overlay_blobs.o: build/lib/overlay-p384-sha384.bin build/lib/overlay-p384-curve.bin endif +# W3: when USE_OVERLAY_P256_EMBED is on, add the P-256 verify .bin to +# PRG_DEPS so make builds it before the .incbin in +# src/crypto/shared/p256_overlay_blobs.s tries to read it. The .bin +# rule below also has an order-only dep on build/labels.txt for the +# main-PRG label lookup (same bootstrap cycle as P-384). Gated on the +# ca65-level USE_OVERLAY_P256_EMBED rather than EMBED_P256_OVERLAY so +# the bootstrap prelim link (with USE_OVERLAY_P256_EMBED=0) skips the +# .bin dep cleanly. +ifeq ($(USE_OVERLAY_P256_EMBED),1) +PRG_DEPS += build/lib/nistcurves-p256-verify.bin +build/crypto/shared/p256_overlay_blobs.o: build/lib/nistcurves-p256-verify.bin +endif + $(PRG): $(PRG_DEPS) @mkdir -p build $(LD65) $(LD65FLAGS) -o $@ $(ALL_OBJS) $(SIBLING_LIB_ARCHIVES) @@ -258,6 +314,22 @@ build/lib/overlay-p384-sha384.bin build/lib/overlay-p384-curve.bin build/labels- p384-overlay: build/lib/overlay-p384-sha384.bin build/lib/overlay-p384-curve.bin \ build/labels-p384-sha384.txt build/labels-p384-curve.txt +# W3: P-256 verify overlay .bin (library-ingestion architecture). +# Mirrors the P-384 overlay .bin rule: depends on the P-256 sibling +# archive (build/lib/nistcurves-p256.a, already a default PRG dep) + +# the standalone overlay cfg. Order-only dep on build/labels.txt for +# the lookup_label() fallback (same pattern as P-384 — see Fix D +# comment block above). +build/lib/nistcurves-p256-verify.bin build/labels-p256-verify.txt: \ + build/lib/nistcurves-p256.a \ + cfg/p256-overlay-verify.cfg \ + tools/integration/build_nistcurves_p256_bin.sh \ + | build/labels.txt + bash tools/integration/build_nistcurves_p256_bin.sh + +.PHONY: p256-overlay +p256-overlay: build/lib/nistcurves-p256-verify.bin build/labels-p256-verify.txt + # Phase 5 Fix C: regenerate the P-384 overlay-resident symbol equates # (build/p384_overlay_equates.inc) from the overlay labels files so the # TLS-side dispatcher (src/crypto/ecdsa_verify_384.s) picks up address diff --git a/cfg/c64-https-ip65.cfg b/cfg/c64-https-ip65.cfg index 0af0345..194268a 100644 --- a/cfg/c64-https-ip65.cfg +++ b/cfg/c64-https-ip65.cfg @@ -1,22 +1,86 @@ # c64-https ld65 config — ip65/RR-Net backend # -# Memory map: -# $0801-$1FFF : LOADER (BASIC stub + boot + tls + http + net wrapper) -# $2000-$3FFF : NET_CODE (ip65 blob + LOADER_OVERFLOW tail) -# $4000-$5FFF : NET_BSS (ip65 BSS, fully used) -# $6000-$BFFF : CRYPTO_RESIDENT (24 KB — covers old CRYPTO + SHADOW_BSS span) -# $C000-$CFFF : TCP_BUF (tcp_recv_buf, 4 KB ring) -# -# The in-tree src/crypto/x25519.s + src/crypto/fe25519.s provide -# the x25519 primitives under ip65. Phase C.1 attempted a sibling-lib -# overlay integration under BACKEND=uci only; that integration was -# rolled back (see cfg/c64-https-uci.cfg for details). ip65 never -# adopted the overlay, so this cfg is unchanged from the Phase C.0 -# single contiguous CRYPTO_RESIDENT layout. +# Memory map (post-W1 hot/cold split — see comment block at the MEMORY +# entry for the fit constraints): +# $0801-$1FFF : LOADER (BASIC stub + boot + tls + http + net wrapper) +# $2000-$3FFF : NET_CODE (ip65 blob + LOADER_OVERFLOW tail + +# CRYPTO_AUX_CODE2) +# $4000-$4F8B : NET_BSS (ip65 blob's BSS — physical occupancy +# stops at $4F8B per ip65-build/ip65-c64.map) +# $4F8C-$5FFF : CRYPTO_OVERLAY (4,212 B reclaimed BSS-TAIL slot — used +# under W2 as overlay slot for ip65; same +# role as UCI's CRYPTO_OVERLAY at $4200. +# Now reserved for future P-384/SHA-384 +# overlay use; BSS no longer routed here.) +# $6000-$9FFF : CRYPTO_RESIDENT (16 KB file-backed — code + rodata. +# Stays below $A000 because boot zeros +# $A000-$BFFF as zero-init BSS.) +# $A000-$BFFF : CRYPTO_COLD_SHADOW (8 KB file-backed — large BSS chunks +# under banked-on RAM; zero-filled in +# PRG, overwritten at runtime.) +# $C000-$CFFF : TCP_BUF (tcp_recv_buf, 4 KB ring) # # CPU port $01 is set to $36 at boot (BASIC ROM off), so $8000-$BFFF is # plain RAM under what would otherwise be BASIC ROM shadow + the top of # the character ROM gap. +# +# --- W1 hot/cold split (Worker I — root-cause fix for the boot +# zero-fill regression on the UCI side; partial fit on ip65). --- +# +# Pre-W1 ip65 had `CRYPTO_RESIDENT` spanning the full 24 KB +# $6000-$BFFF as a single file-backed region. Worker F's W2 attempt +# carved $4F8C-$5FFF out of NET_BSS as `CRYPTO_OVERLAY` and re-routed +# TABLES_BSS / LIB_NISTCURVES_P256_BSS / BSS_TAIL there; the 4,212 B +# slot proved 1,024 B short of those three combined. +# +# The W1 split addresses the cross-cutting correctness issue +# symmetrically with the UCI cfg: +# +# CRYPTO_RESIDENT $6000-$9FFF 16 KB file-backed. Code + rodata +# plus TLS_CODE / CRYPTO_AUX_CODE in +# the small fraction that fits. +# Stays below $A000 so the boot +# zbss loop in src/boot.s (which +# wipes $A000-$BFFF as zero-init +# BSS) cannot wipe any executable +# bytes. Under the bumped library +# (cfa9085) LIB_NISTCURVES_P256_CODE +# would otherwise have straddled +# $A000 and been silently zeroed — +# the production regression that +# this split closes. +# CRYPTO_OVERLAY $4F8C-$5FFF 4,212 B. Holds TLS_CODE + +# CRYPTO_AUX_CODE (Phase C.4 +# placement) under the W1 split, +# freeing CRYPTO_RESIDENT for the +# resident P-256 verify path + +# in-tree TLS app-data primitives. +# Future P-384 / SHA-384 / X25519 +# sibling overlay segments are +# declared `optional = yes` here. +# CRYPTO_COLD_SHADOW $A000-$BFFF 8 KB file-backed (zero-filled). +# BSS chunks land here. +# +# RESIDUAL FIT ISSUE: the total c64-https + libs/nistcurves BSS claim +# (BSS 4,478 B + CRYPTO_BSS 21 B + TABLES_BSS 1,536 B page-aligned + +# BSS_TAIL 2,084 B + LIB_NISTCURVES_P256_BSS 1,573 B = ~9,692 B before +# alignment) exceeds the 8 KB CRYPTO_COLD_SHADOW budget by ~1.5 KB. +# ld65 surfaces a `BSS overflows CRYPTO_COLD_SHADOW by 1662 bytes` +# warning + a non-zero exit. Cfg-only relief is exhausted under the +# bumped library (see Worker I's trace at the W15 review). Resolution +# requires one of: +# (a) Library-side minimal-archive variant — drop data_p256 anchor +# RAM or audit data_p256.o for unused scratch (~1 KB+). +# (b) Source-level BSS split — partition src/data.s BSS into a +# hot/cold pair so cold-path BSS can be routed elsewhere +# (~1.5 KB+). +# (c) Defer-paging — SHA-384 / P-384 overlay-paging strategy +# extended to a c64-https in-tree BSS slice. +# +# UCI is unaffected (62977 B PRG builds clean under W1) — under UCI +# the NET_CODE region absorbs TLS_CODE+CRYPTO_AUX_CODE+CRYPTO_AUX_CODE2 +# (the UCI adapter is ~1.7 KB vs ip65's 6.95 KB blob), opening enough +# CRYPTO_RESIDENT slack to leave BSS budget intact. FEATURES { STARTADDRESS: default = $0801; @@ -30,23 +94,22 @@ MEMORY { LOADADDR: start = $07FF, size = $0002, file = %O; LOADER: start = $0801, size = $17FF, file = %O, define = yes, fill = yes, fillval = $00; NET_CODE: start = $2000, size = $2000, file = %O, define = yes, fill = yes, fillval = $00; - # Phase C.4: NET_BSS is split into the ip65-blob-owned portion - # ($4000-$4F8B per ip65-build/ip65-c64.map, rounded up to $4F8C = - # $F8C B) and a tail slack region reused for TLS_CODE + CRYPTO_AUX_CODE - # relocation. Sibling P-256 integration pushed CRYPTO_RESIDENT over - # its 24 KB budget under ip65; UCI had NET_BSS at only $4000-$41FF so - # it already had headroom. The blob's reserved BSS stops exactly at - # $4F8B; NET_BSS_TAIL starts at the next byte so no blob state is - # clobbered by the relocated code. - NET_BSS: start = $4000, size = $0F8C, file = %O, define = yes, fill = yes, fillval = $00; - NET_BSS_TAIL: start = $4F8C, size = $1074, file = %O, define = yes, fill = yes, fillval = $00; - CRYPTO_RESIDENT: start = $6000, size = $6000, file = %O, define = yes, fill = yes, fillval = $00; - - # CRYPTO_OVERLAY is not used under ip65 (no REU-overlay swapping). - # A zero-size rw alias is declared here only to satisfy - # `crypto_swap.s`'s `.import __CRYPTO_OVERLAY_START__` -- ip65 - # never actually issues the DMA, so the address value is unused. - CRYPTO_OVERLAY: start = $6000, size = $0000, type = rw, define = yes; + # NET_BSS holds the ip65 blob's BSS ($4000-$4F8B per ip65-build/ + # ip65-c64.map). The blob's reserved BSS stops exactly at $4F8B; + # the remainder of the original $4000-$5FFF span is reclaimed + # below as CRYPTO_OVERLAY. + NET_BSS: start = $4000, size = $0F8C, file = %O, define = yes, fill = yes, fillval = $00; + # CRYPTO_OVERLAY: the historical W2 reclaimed BSS-TAIL slot at + # $4F8C-$5FFF (4,212 B). Under the W1 hot/cold split this slot + # absorbs TLS_CODE + CRYPTO_AUX_CODE (Phase C.4 placement, 3,760 B, + # 452 B headroom). Future P-384 + SHA-384 archives are anchored + # here (`optional = yes`, zero bytes today). file-backed so any + # leftover content lands zero-filled in the PRG. + CRYPTO_OVERLAY: start = $4F8C, size = $1074, file = %O, define = yes, fill = yes, fillval = $00; + # 16 KB code+rodata stays below $A000 so boot's zbss zero-fill of + # $A000-$BFFF cannot wipe any executable bytes. + CRYPTO_RESIDENT: 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: ip65 backend does NOT embed the P-384 split overlay # blobs (no room in main RAM after the existing layout, and ip65 @@ -74,55 +137,75 @@ SEGMENTS { NET_CODE: load = NET_CODE, type = ro; # LOADER-overflow rides along with the ip65 blob in the NET_CODE tail. - # Phase C.4: CRYPTO_AUX_CODE2 (hmac_drbg alone) also rides the NET_CODE - # tail under ip65 because TLS_CODE + sha256's CRYPTO_AUX_CODE together - # would overflow NET_BSS_TAIL by 23 bytes if hmac_drbg were co-located - # there. NET_CODE has ~1 KB of tail slack after the ip65 blob + - # LOADER_OVERFLOW, which covers hmac_drbg's ~830 B. sha256 rides - # NET_BSS_TAIL alongside TLS_CODE. + # CRYPTO_AUX_CODE2 (hmac_drbg alone) also rides the NET_CODE tail to + # spread crypto code across NET_CODE / CRYPTO_RESIDENT / CRYPTO_OVERLAY. LOADER_OVERFLOW: load = NET_CODE, type = ro, optional = yes; CRYPTO_AUX_CODE2: load = NET_CODE, type = ro, optional = yes; NET_BSS: load = NET_BSS, type = bss, optional = yes; - # Phase C.4: TLS_CODE + CRYPTO_AUX_CODE (sha256) relocate out of - # CRYPTO_RESIDENT into NET_BSS_TAIL. hmac_drbg (CRYPTO_AUX_CODE2) - # goes to NET_CODE instead — see the NET_CODE SEGMENTS block. - TLS_CODE: load = NET_BSS_TAIL, type = ro, optional = yes; - CRYPTO_AUX_CODE: load = NET_BSS_TAIL, type = ro, optional = yes; - - # --- Overlay slot placeholders (unused under ip65). --- - # Declared `optional = yes` + `load = CRYPTO_RESIDENT` as harmless - # anchors so shared code that references the segment names still - # links; they receive no bytes under ip65 because no overlay - # archives are linked. - OVERLAY_P256: load = CRYPTO_RESIDENT, type = ro, optional = yes; - OVERLAY_P384: load = CRYPTO_RESIDENT, type = ro, optional = yes; - # Phase C.5: sibling c64-x25519 rodata + bss segments. Under ip65 - # there is no spare 4 KB region available — CRYPTO_OVERLAY is a - # zero-sized alias and NET_BSS_TAIL/NET_CODE both have <1 KB of - # slack. The segments are anchored at CRYPTO_RESIDENT and will - # overflow by ~3.3 KB under USE_X25519_SIBLING=1 until the cfg is - # restructured. Reported as a partial blocker for the integrator; - # USE_X25519_SIBLING=1 works under BACKEND=uci where CRYPTO_OVERLAY - # provides the headroom. - X25519_RODATA: load = CRYPTO_RESIDENT, type = ro, optional = yes, align = $100; - X25519_BSS: load = CRYPTO_RESIDENT, type = bss, optional = yes, align = $100; + # --- W1 hot/cold split: BSS catch-alls land in CRYPTO_COLD_SHADOW. --- + # The total in-tree + library BSS claim (~9.7 KB) exceeds the 8 KB + # CRYPTO_COLD_SHADOW budget. The residual overflow is surfaced at + # link time; the supervisor must decide between source-level BSS + # split, library-side minimal-archive variant, or another overlay + # paging strategy (see the MEMORY entry header). + LIB_NISTCURVES_P256_BSS: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; + # BSS_TAIL hosts the cert_buf (1.5 KB) + tls_rec_buf (548 B) per + # src/data.s and src/der_decode.s. + BSS_TAIL: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; + TABLES_BSS: load = CRYPTO_COLD_SHADOW, type = bss, align = $100; # --- Resident crypto + TLS code / rodata. --- - # Phase C.2 backend-divergence: under UCI, TLS_CODE and CRYPTO_AUX_CODE - # (SHA-256 + HMAC-DRBG + ecdsa_verify dispatcher) relocate to NET_CODE - # to free headroom for Phase C.3 overlays. Phase C.4 gives ip65 the - # same treatment but routes them into NET_BSS_TAIL instead (NET_CODE - # is ~88% full with the ip65 blob under ip65). + # W1 hot/cold split: TLS_CODE + CRYPTO_AUX_CODE ride the historical + # W2 CRYPTO_OVERLAY slot ($4F8C-$5FFF, 4,212 B), restoring their + # Phase C.4 placement. CRYPTO_RESIDENT keeps the resident P-256 + # verify path + chacha20/poly1305 + TLS app-data primitives + rodata + # below $A000. + TLS_CODE: load = CRYPTO_OVERLAY, type = ro, optional = yes; + CRYPTO_AUX_CODE: load = CRYPTO_OVERLAY, type = ro, optional = yes; CRYPTO_CODE: load = CRYPTO_RESIDENT, type = ro; CRYPTO_RODATA: load = CRYPTO_RESIDENT, type = ro; + # libs/nistcurves segment names (c64-lib-contract / cfa9085+). + # Default: P-256 always-resident in CRYPTO_RESIDENT (matches UCI's + # CRYPTO_HOT default). MUL / TABLES / shared-BSS segments are + # declared `optional = yes` for forward-compat (the minimal P-256 + # archive built by tools/integration/build_nistcurves_p256.sh + # excludes data_shared.o + mul_8x8.o, so these segments receive + # zero bytes today; they would land in CRYPTO_RESIDENT if a future + # archive includes them). + LIB_NISTCURVES_P256_CODE: load = CRYPTO_RESIDENT, type = ro, optional = yes; + LIB_NISTCURVES_P256_RODATA: load = CRYPTO_RESIDENT, type = ro, optional = yes; + LIB_NISTCURVES_BSS: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; + LIB_NISTCURVES_TABLES: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes, align = $100; + LIB_NISTCURVES_MUL_CODE: load = CRYPTO_RESIDENT, type = ro, optional = yes; + # P-384 + SHA-384 routed to CRYPTO_OVERLAY (the reclaimed + # BSS-TAIL slot). Mirrors UCI's overlay-resident routing for the + # same segments. Zero bytes today (no P-384 archive linked under + # ip65); future wiring picks them up here without further cfg + # change. P-384 / SHA-384 BSS lives in CRYPTO_COLD_SHADOW (read/write + # state, BSS-only, not file-backed code). + 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; + 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_RODATA: load = CRYPTO_RESIDENT, type = ro, optional = yes; - # --- Resident BSS. Everything that used to live in SHADOW_BSS now - # shares CRYPTO_RESIDENT; TABLES_BSS keeps page alignment. - BSS: load = CRYPTO_RESIDENT, type = bss; - CRYPTO_BSS: load = CRYPTO_RESIDENT, type = bss; - TABLES_BSS: load = CRYPTO_RESIDENT, type = bss, align = $100; + # Phase C.5: sibling c64-x25519 rodata + bss segments. Under ip65 + # CRYPTO_OVERLAY is now real ($4F8C-$5FFF, 4,212 B); the segments + # are anchored there for forward-compat under USE_X25519_SIBLING=1. + # Page alignment respected. + X25519_RODATA: load = CRYPTO_OVERLAY, type = ro, optional = yes, align = $100; + X25519_BSS: load = CRYPTO_OVERLAY, type = bss, optional = yes, align = $100; + + # --- Resident BSS. Catch-all c64-https in-tree BSS (src/data.s + # `.segment "BSS"`). Routed to CRYPTO_COLD_SHADOW under the W1 + # hot/cold split; CRYPTO_RESIDENT now carries code+rodata only. + BSS: load = CRYPTO_COLD_SHADOW, type = bss; + CRYPTO_BSS: load = CRYPTO_COLD_SHADOW, type = bss; # Phase 3: ip65 backend stays at the historical 47 KB PRG size -- # USE_OVERLAY_P384_EMBED is gated off in the Makefile under ip65, @@ -133,5 +216,11 @@ SEGMENTS { 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 (segment placeholder). Mirrors the + # UCI cfg's slot so src/crypto/shared/p256_overlay_blobs.s links + # cleanly even though ip65 has no live overlay slot and the .ifdef + # USE_OVERLAY_P256_EMBED guard keeps the segment empty regardless. + OVERLAY_BLOB_P256: load = CRYPTO_OVERLAY, type = ro, optional = yes; + TCP_RECV_BUF: load = TCP_BUF, type = bss, optional = yes; } diff --git a/cfg/c64-https-uci.cfg b/cfg/c64-https-uci.cfg index 9cedb90..ed4352b 100644 --- a/cfg/c64-https-uci.cfg +++ b/cfg/c64-https-uci.cfg @@ -3,34 +3,61 @@ # Target: Commodore Ultimate 64 / U64E using the host-visible UCI # ($DF1B-$DF1F) in place of ip65 + RR-Net. # -# Memory map: -# $0801-$1FFF : LOADER (BASIC stub + boot + http + net wrapper) -# $2000-$3FFF : NET_CODE (UCI adapter + LOADER_OVERFLOW tail + -# TLS_CODE + CRYPTO_AUX_CODE) -# $4000-$41FF : UCI_BSS (uci_host_buf + state, 512 B) -# $4200-$5FFF : CRYPTO_OVERLAY (7.5 KB swappable overlay slot — used -# by the external P-384 smoke test only) -# $6000-$BFFF : CRYPTO_RESIDENT (24 KB always-resident crypto + TLS + BSS) -# $C000-$CFFF : TCP_BUF (tcp_recv_buf, 4 KB ring) +# 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) # -# NOTE on UCI_BSS size: the plan's "256 B" target was optimistic — -# `src/net/uci/net.s` + `uci_cmd.s` allocate ~289 B (uci_host_buf 256 B + -# uci_ipaddr_resp 12 B + uci_socket_id/port/send/poll/... ~16 B + -# uci_resp control block 4 B). Rounded up to the next page (512 B) and -# the overlay slot trimmed accordingly (7.5 KB vs. 8 KB under ip65). +# --- 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. # -# CRYPTO_RESIDENT is 24 KB on UCI (vs 16 KB target on ip65) because UCI -# does not need $4000-$5FFF for backend BSS, letting CRYPTO_RESIDENT -# start at $6000 and claim the former NET_BSS space. +# 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. # -# --- x25519 overlay rollback note --- -# Phase C.1 (commit 6c9d2a3) integrated libs/x25519/ as a REU overlay -# and split CRYPTO_RESIDENT around a 1 KB sqtab hole. That integration -# deadlocked the TLS handshake at 48 MHz (x25519_scalarmult hung from -# TLS context) and was rolled back. The in-tree src/crypto/x25519.s + -# src/crypto/fe25519.s are now used under both backends. CRYPTO_OVERLAY -# still exists to serve the P-384 external smoke test (Phase C.3b, -# tools/test_p384_symbols.py). +# 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; @@ -42,14 +69,65 @@ MEMORY { LOADADDR: start = $07FF, size = $0002, file = %O; LOADER: start = $0801, size = $17FF, file = %O, define = yes, fill = yes, fillval = $00; - NET_CODE: start = $2000, size = $2000, file = %O, define = yes, fill = yes, fillval = $00; - UCI_BSS_REGION: start = $4000, size = $0200, 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. + NET_CODE: start = $2000, size = $1B26, 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 = $3B26, size = $06DA, 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; - CRYPTO_RESIDENT: start = $6000, size = $6000, 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_RESIDENT + # 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 @@ -85,13 +163,13 @@ SEGMENTS { EXEHDR: load = LOADER, type = ro; STARTUP: load = LOADER, type = ro, optional = yes; CODE: load = LOADER, type = ro; - RODATA: load = CRYPTO_RESIDENT, type = ro; + RODATA: load = CRYPTO_HOT, type = ro; 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_RESIDENT. The UCI adapter is ~1.7 KB + # 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; @@ -103,40 +181,107 @@ SEGMENTS { # 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: load = UCI_BSS_REGION, type = bss, 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: used only by the P-384 external smoke test. --- + # --- 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 embed any overlay image; the P-384 test - # harness DMAs a standalone overlay-p384.bin image into this slot - # at test time (see tools/test_p384_symbols.py). + # 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_RESIDENT inside its - # 24 KB budget. CRYPTO_OVERLAY is otherwise unused in the - # production UCI build (only the P-384 external smoke test DMAs - # into it at test time, and that's a harness operation rather - # than a production path). align = $100 so the .align 256 - # directives in data_x25519_{rodata,bss}_raw.s land on real - # page boundaries. + # 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_RESIDENT, type = ro; - CRYPTO_RODATA: load = CRYPTO_RESIDENT, type = ro; - RESIDENT_RODATA: load = CRYPTO_RESIDENT, type = ro, optional = yes; - CRYPTO_INIT_CODE: load = CRYPTO_RESIDENT, type = ro, optional = yes; + CRYPTO_CODE: load = CRYPTO_HOT, type = ro; + CRYPTO_RODATA: load = CRYPTO_HOT, type = ro; + RESIDENT_RODATA: load = CRYPTO_HOT, type = ro, optional = yes; + CRYPTO_INIT_CODE: load = CRYPTO_HOT, type = ro, optional = yes; - # --- Resident BSS. --- - BSS: load = CRYPTO_RESIDENT, type = bss; - CRYPTO_BSS: load = CRYPTO_RESIDENT, type = bss; - TABLES_BSS: load = CRYPTO_RESIDENT, type = bss, align = $100; + # --- 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_HOT, type = ro, optional = yes; + # 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; + # 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; + LIB_NISTCURVES_MUL_CODE: load = CRYPTO_HOT, 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. @@ -149,4 +294,12 @@ SEGMENTS { # 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/cfg/p256-overlay-verify.cfg b/cfg/p256-overlay-verify.cfg new file mode 100644 index 0000000..baaf571 --- /dev/null +++ b/cfg/p256-overlay-verify.cfg @@ -0,0 +1,54 @@ +# cfg/p256-overlay-verify.cfg -- ld65 config for the P-256 verify overlay +# image (W3 library-ingestion architecture). +# +# Mirrors cfg/p384-overlay-{sha384,curve}.cfg. Produces a padded .bin +# image that the boot stash (reu_p384_overlay_init in src/boot.s) loads +# into REU bank 2 slot $22100 (REU_OVERLAY_P256_VERIFY). Subsequent +# `crypto_swap_to_p256_verify` calls DMA the bytes back into the live +# CRYPTO_OVERLAY slot at $4200-$5FFF. +# +# The DATA / BSS exports stay at $C000 (matches the standalone P-384 +# overlay cfgs' RESIDENT layout). Under c64-https's main UCI cfg these +# RW buffers live in CRYPTO_BSS at $A000+; for the overlay-image link +# we just need stable addresses to satisfy linker references -- the +# .bin itself contains only the OVERLAY_P256_VERIFY segment bytes. +# +# Layout matches the live UCI CRYPTO_OVERLAY base ($4200) and size +# ($1E00 = 7,680 B / 7.5 KB) so the .bin DMAs into the live slot +# cleanly at harness time. + +FEATURES { + STARTADDRESS: default = $4200; +} + +MEMORY { + ZP: start = $0022, size = $001E, type = rw, define = yes; + OVERLAY_REGION: start = $4200, size = $1E00, file = %O, define = yes, + fill = yes, fillval = $00; + RESIDENT: start = $C000, size = $1000, type = rw, define = yes; +} + +SEGMENTS { + ZEROPAGE: load = ZP, type = zp, optional = yes; + + # W5: new library segment names (c64-lib-contract / libs/nistcurves + # cfa9085+) are the canonical set under the contract. The legacy + # OVERLAY_P256_VERIFY alias + CRYPTO_CODE/RODATA aliases stay for + # backward compat with any in-tree force-link path that hasn't + # migrated yet. + OVERLAY_P256_VERIFY: load = OVERLAY_REGION, type = ro, optional = yes; + LIB_NISTCURVES_P256_CODE: load = OVERLAY_REGION, type = ro, optional = yes; + LIB_NISTCURVES_P256_RODATA: load = OVERLAY_REGION, type = ro, optional = yes; + CRYPTO_CODE: load = OVERLAY_REGION, type = ro, optional = yes; + CRYPTO_RODATA: load = OVERLAY_REGION, type = ro, optional = yes; + RODATA: load = OVERLAY_REGION, type = ro, optional = yes; + CODE: load = OVERLAY_REGION, type = ro, optional = yes; + + # Resident RW buffers -- we don't write them to the .bin, but they + # need real addresses so labels are correct. + DATA: load = RESIDENT, type = rw, optional = yes; + BSS: load = RESIDENT, type = bss, optional = yes; + CRYPTO_BSS: load = RESIDENT, type = bss, optional = yes; + LIB_NISTCURVES_P256_BSS: load = RESIDENT, type = bss, optional = yes; + ZEROPAGE_BSS: load = ZP, type = bss, optional = yes; +} diff --git a/cfg/p384-overlay-curve.cfg b/cfg/p384-overlay-curve.cfg index 815ffe0..db98c9d 100644 --- a/cfg/p384-overlay-curve.cfg +++ b/cfg/p384-overlay-curve.cfg @@ -31,12 +31,21 @@ MEMORY { } SEGMENTS { - ZEROPAGE: load = ZP, type = zp, optional = yes; + ZEROPAGE: load = ZP, type = zp, optional = yes; - OVERLAY_P384_CURVE: load = OVERLAY_REGION, type = ro; + # W5: new library segment names (c64-lib-contract / libs/nistcurves + # cfa9085+). The legacy OVERLAY_P384_CURVE alias remains for the + # in-staging shim (build_nistcurves_p384.sh emits + # ec_scalar_mul_384_shim.s with this segment name). + # LIB_NISTCURVES_P384_* is the canonical set under the contract. + OVERLAY_P384_CURVE: load = OVERLAY_REGION, type = ro, optional = yes; + LIB_NISTCURVES_P384_CODE: load = OVERLAY_REGION, type = ro, optional = yes; + LIB_NISTCURVES_P384_RODATA: load = OVERLAY_REGION, type = ro, optional = yes; # Resident RW buffers — we don't write them to the .bin, but they # need real addresses so labels are correct. - DATA: load = RESIDENT, type = rw, optional = yes; - BSS: load = RESIDENT, type = bss, optional = yes; + DATA: load = RESIDENT, type = rw, optional = yes; + BSS: load = RESIDENT, type = bss, optional = yes; + LIB_NISTCURVES_P384_BSS: load = RESIDENT, type = bss, optional = yes; + LIB_NISTCURVES_P384_DATA_BSS: load = RESIDENT, type = rw, optional = yes; } diff --git a/cfg/p384-overlay-sha384.cfg b/cfg/p384-overlay-sha384.cfg index c020bbf..19b3d6c 100644 --- a/cfg/p384-overlay-sha384.cfg +++ b/cfg/p384-overlay-sha384.cfg @@ -34,10 +34,18 @@ MEMORY { SEGMENTS { ZEROPAGE: load = ZP, type = zp, optional = yes; - OVERLAY_P384_SHA384: load = OVERLAY_REGION, type = ro; + # W5: new library segment names (c64-lib-contract / libs/nistcurves + # cfa9085+). The legacy OVERLAY_P384_SHA384 alias remains for any + # in-tree force-link stub that still uses it; LIB_NISTCURVES_SHA384_* + # is the canonical set under the contract. + OVERLAY_P384_SHA384: load = OVERLAY_REGION, type = ro, optional = yes; + LIB_NISTCURVES_SHA384_CODE: load = OVERLAY_REGION, type = ro, optional = yes; + LIB_NISTCURVES_SHA384_RODATA: load = OVERLAY_REGION, type = ro, optional = yes; + LIB_NISTCURVES_SHA384_TABLES: load = OVERLAY_REGION, type = ro, optional = yes, align = $100; # Resident RW buffers — we don't write them to the .bin, but they # need real addresses so labels are correct. - DATA: load = RESIDENT, type = rw, optional = yes; - BSS: load = RESIDENT, type = bss, optional = yes; + DATA: load = RESIDENT, type = rw, optional = yes; + BSS: load = RESIDENT, type = bss, optional = yes; + LIB_NISTCURVES_SHA384_BSS: load = RESIDENT, type = bss, optional = yes; } diff --git a/cfg/x25519-overlay-scalarmult.cfg b/cfg/x25519-overlay-scalarmult.cfg new file mode 100644 index 0000000..d2a0110 --- /dev/null +++ b/cfg/x25519-overlay-scalarmult.cfg @@ -0,0 +1,56 @@ +# cfg/x25519-overlay-scalarmult.cfg -- ld65 config for the X25519 +# scalarmult overlay .bin image (W3 library-ingestion architecture). +# +# Mirrors cfg/p256-overlay-verify.cfg + cfg/p384-overlay-{sha384,curve}.cfg. +# Produces a 7,680 B padded .bin image of the c64-x25519 sibling's code +# + rodata + bss, suitable for embedding via .incbin into the PRG +# (planned W1 wiring) or for documentation / CI artefact use. +# +# Today the X25519 sibling rodata + bss is linker-placed into +# CRYPTO_OVERLAY at PRG-load time under USE_X25519_SIBLING=1 (see +# cfg/c64-https-uci.cfg's X25519_RODATA / X25519_BSS segments). This +# .bin is the same byte image in a standalone linker invocation -- a +# CI-friendly artefact that can be diffed against the in-PRG slot +# bytes for parity checking. +# +# Layout matches the live UCI CRYPTO_OVERLAY base ($4200) and size +# ($1E00 = 7,680 B / 7.5 KB) so the .bin DMAs into the live slot +# cleanly at harness time. + +FEATURES { + STARTADDRESS: default = $4200; +} + +MEMORY { + ZP: start = $0022, size = $001E, type = rw, define = yes; + OVERLAY_REGION: start = $4200, size = $1E00, file = %O, define = yes, + fill = yes, fillval = $00; + RESIDENT: start = $C000, size = $1000, type = rw, define = yes; +} + +SEGMENTS { + ZEROPAGE: load = ZP, type = zp, optional = yes; + + # The sibling archive uses CRYPTO_CODE for code and + # X25519_RODATA / X25519_BSS for its tables (set by + # tools/integration/build_x25519.sh). Route the code + rodata + # into the overlay region so the .bin is the byte image the main + # PRG's live slot will hold. X25519_BSS is routed to RESIDENT + # rather than OVERLAY_REGION: BSS bytes are zero-initialized at + # runtime by reu_clear_wide / x25519_init, so the .bin doesn't + # need them. Including BSS in the overlay region overflows by + # ~512 B (combined CRYPTO_CODE + X25519_RODATA + X25519_BSS just + # barely doesn't fit at $1E00). + CRYPTO_CODE: load = OVERLAY_REGION, type = ro, optional = yes; + X25519_RODATA: load = OVERLAY_REGION, type = ro, optional = yes, align = $100; + RODATA: load = OVERLAY_REGION, type = ro, optional = yes; + CODE: load = OVERLAY_REGION, type = ro, optional = yes; + + # Resident RW buffers (none used by x25519 archive today) -- pinned + # at $C000 just so any future symbol references resolve. + X25519_BSS: load = RESIDENT, type = bss, optional = yes, align = $100; + DATA: load = RESIDENT, type = rw, optional = yes; + BSS: load = RESIDENT, type = bss, optional = yes; + CRYPTO_BSS: load = RESIDENT, type = bss, optional = yes; + TABLES_BSS: load = RESIDENT, type = bss, optional = yes; +} diff --git a/libs/nistcurves b/libs/nistcurves index 90830c9..b67de54 160000 --- a/libs/nistcurves +++ b/libs/nistcurves @@ -1 +1 @@ -Subproject commit 90830c920af7fcc5ded7da6b4dd201ab535e57b4 +Subproject commit b67de54520bb4193b073d9703d5c85adcf29f505 diff --git a/libs/x25519 b/libs/x25519 index 47c0ad2..95fdd70 160000 --- a/libs/x25519 +++ b/libs/x25519 @@ -1 +1 @@ -Subproject commit 47c0ad21a57ae443632f5e7689cbe9f3de98460e +Subproject commit 95fdd705b0f7d780cada3dee08158084d327c3f9 diff --git a/src/boot.s b/src/boot.s index 9707bad..172ebb3 100644 --- a/src/boot.s +++ b/src/boot.s @@ -155,6 +155,35 @@ .include "reu_layout.inc" .endif + ; ---- imports: W3 embedded P-256 verify overlay blob anchor ---- + ; Mirror of the P-384 pattern above. Resolved by + ; src/crypto/shared/p256_overlay_blobs.s when + ; USE_OVERLAY_P256_EMBED is on (gated from the top-level Makefile + ; by EMBED_P256_OVERLAY=1). Mutually exclusive with + ; USE_OVERLAY_P384_EMBED at the cfg level -- both target the + ; CRYPTO_OVERLAY slot at PRG-load time, so the Makefile turns + ; P-384 embedding off when EMBED_P256_OVERLAY=1. + .ifdef USE_OVERLAY_P256_EMBED + .import p256_overlay_verify_blob + ; Same REU layout include rationale as the P-384 block above + ; (.ifndef-guarded; idempotent). + .include "reu_layout.inc" + .endif + + ; ---- imports: W3 X25519 sibling slot stash ---- + ; Under USE_X25519_SIBLING=1, the sibling's X25519_RODATA + + ; X25519_BSS segments load into CRYPTO_OVERLAY at PRG-load time. + ; Boot stashes those slot bytes (i.e. the sibling's running + ; code+rodata image) to REU bank 3 so a later + ; `crypto_swap_to_x25519` can refresh the slot from there after + ; a P-256 / P-384 swap has overwritten it. No new .incbin + ; needed -- the linker already pinned the bytes at $4200. + .ifdef USE_X25519_SIBLING + .import __CRYPTO_OVERLAY_START__ + ; Same REU layout include rationale as above. + .include "reu_layout.inc" + .endif + ; ============================================================================= ; BASIC stub: 10 SYS 2061 ; Loaded at $0801 via EXEHDR segment (first bytes of LOADER region). @@ -907,6 +936,83 @@ reu_p384_overlay_init: sta reu_command plp .endif ; .ifdef USE_OVERLAY_P384_EMBED + +; ----------------------------------------------------------------------------- +; W3: P-256 verify image stash (Makefile EMBED_P256_OVERLAY=1). +; +; When `USE_OVERLAY_P256_EMBED` is defined the cfg routes +; OVERLAY_BLOB_P256 into CRYPTO_OVERLAY at PRG-load time (mutually +; exclusive with OVERLAY_BLOB_SHA384 -- the Makefile turns +; USE_OVERLAY_P384_EMBED off when EMBED_P256_OVERLAY=1). Boot DMAs the +; slot bytes to REU_OVERLAY_P256_VERIFY (bank 2, $22100) so a later +; `crypto_swap_to_p256_verify` can refresh the slot. Same SEI window +; + ~8 ms cost as the P-384 stash above; STASH (C64->REU) command +; $90. +; ----------------------------------------------------------------------------- +.ifdef USE_OVERLAY_P256_EMBED + php + sei + lda #p256_overlay_verify_blob + sta reu_c64_hi + lda #REU_OVERLAY_P256_VERIFY + sta reu_reu_hi + lda #^REU_OVERLAY_P256_VERIFY + sta reu_reu_bank + lda #OVERLAY_SIZE + sta reu_len_hi + lda #0 + sta reu_addr_ctrl + lda #$90 ; execute + STASH (C64->REU) + sta reu_command + plp +.endif ; .ifdef USE_OVERLAY_P256_EMBED + +; ----------------------------------------------------------------------------- +; W3: X25519 sibling slot stash (USE_X25519_SIBLING=1). +; +; The sibling's X25519_RODATA + X25519_BSS segments load into +; CRYPTO_OVERLAY at PRG-load time (see cfg/c64-https-uci.cfg). Boot +; STASHes the slot bytes to REU_OVERLAY_X25519 (bank 3, $30000) so a +; later `crypto_swap_to_x25519` can refresh the slot after a P-256 / +; P-384 swap has overwritten it. Same SEI window + ~8 ms cost as the +; P-256 stash above. No .incbin -- the linker already pinned the +; sibling image into CRYPTO_OVERLAY. +; +; NB: this stashes the *initialized* portion of CRYPTO_OVERLAY (the +; sibling's rodata tables) plus any zero-init BSS bytes that fall in +; the same span. The BSS is fine to stash-and-restore because the +; sibling's `reu_mul_init` rebuilds the volatile mul tables anyway; +; the rodata round-trip is the load-bearing part. +; ----------------------------------------------------------------------------- +.ifdef USE_X25519_SIBLING + php + sei + lda #<__CRYPTO_OVERLAY_START__ + sta reu_c64_lo + lda #>__CRYPTO_OVERLAY_START__ + sta reu_c64_hi + lda #REU_OVERLAY_X25519 + sta reu_reu_hi + lda #^REU_OVERLAY_X25519 + sta reu_reu_bank + lda #OVERLAY_SIZE + sta reu_len_hi + lda #0 + sta reu_addr_ctrl + lda #$90 ; execute + STASH (C64->REU) + sta reu_command + plp +.endif ; .ifdef USE_X25519_SIBLING rts ; ============================================================================= diff --git a/src/crypto/shared/crypto_swap.s b/src/crypto/shared/crypto_swap.s index 6adb2a8..c938d0a 100644 --- a/src/crypto/shared/crypto_swap.s +++ b/src/crypto/shared/crypto_swap.s @@ -122,34 +122,47 @@ .include "constants.inc" ; reu_* register equates .include "reu_layout.inc" + .include "overlay_ids.inc" ; OV_* constants (W3) .export crypto_swap_to_x25519_sibling + .export crypto_swap_to_x25519 ; W3 new + .export crypto_swap_to_p256_verify ; W3 new .export crypto_swap_to_p384_sha384 .export crypto_swap_to_p384_curve .export crypto_swap_none + .export crypto_overlay_call ; W3 new .export current_overlay ; Export REU layout equates once (kept in sync with reu_layout.inc). .export REU_OVERLAY_P384_SHA384 .export REU_OVERLAY_P384_CURVE + .export REU_OVERLAY_P256_VERIFY ; W3 new + .export REU_OVERLAY_X25519 ; W3 new .export OVERLAY_SIZE ; Live overlay slot start address (from the cfg's MEMORY{} define). .import __CRYPTO_OVERLAY_START__ ; ----------------------------------------------------------------------------- -; Overlay IDs -- must stay in sync with `current_overlay` comments above. +; Overlay IDs -- canonical values live in `overlay_ids.inc` (W3). This +; file's local equates above (OV_NONE, OV_X25519_SIBLING, OV_P384_SHA384, +; OV_P384_CURVE) were folded into the include; the .ifndef-guarded +; definitions there are the single source of truth. `OV_P256_VERIFY` +; (id 2) and `OV_X25519` (id 3) are the new W3 additions. +; +; NB: the architecture plan's "OV_X25519=3, OV_P256_VERIFY=4" sketch +; assumed OV_P384_SHA384/CURVE were 1/2 — they are actually 4/5 (Phase 3 +; intentionally skipped 2/3 for headroom). The W3 IDs slot into the +; reserved gap so existing call sites that compare `current_overlay` +; against OV_P384_* see no renumber. ; ----------------------------------------------------------------------------- .export OV_NONE .export OV_X25519_SIBLING + .export OV_P256_VERIFY ; W3 new + .export OV_X25519 ; W3 new .export OV_P384_SHA384 .export OV_P384_CURVE -OV_NONE = 0 -OV_X25519_SIBLING = 1 -OV_P384_SHA384 = 4 -OV_P384_CURVE = 5 - ; REU command: execute REU->C64 stash (bit 7 = start, bits 1-0 = direction ; 01 = REU-to-C64). Matches the DMA issue used elsewhere in the codebase. REU_CMD_REU_TO_C64 = $91 @@ -175,6 +188,7 @@ REU_CMD_REU_TO_C64 = $91 crypto_swap_to_x25519_sibling: lda #OV_X25519_SIBLING sta current_overlay +swap_done_fast: rts ; ----------------------------------------------------------------------------- @@ -211,6 +225,60 @@ crypto_swap_to_p384_curve: sta current_overlay rts +; ----------------------------------------------------------------------------- +; crypto_swap_to_x25519 -- DMA X25519 sibling image from REU bank 3 +; (REU_OVERLAY_X25519) into the live CRYPTO_OVERLAY slot. Idempotent. +; +; Distinct from `crypto_swap_to_x25519_sibling` above: that entry point +; is the legacy state-only marker (used when the linker placed the +; X25519 sibling rodata into the slot at PRG load time). This new +; entry point DOES the DMA from REU, so it can be called after a +; P-384 or P-256 swap has overwritten the slot. Boot-time stash +; happens in `reu_p384_overlay_init` (boot.s). +; +; Idempotent: re-entering with OV_X25519 already current is a single +; byte compare + rts (no DMA). NB: arrival from the legacy +; OV_X25519_SIBLING state still triggers a DMA (the slot contents are +; assumed identical, but the marker IDs differ and the safe path is +; to refresh from REU rather than to assume the linker-placed bytes +; were not later overwritten). +; ----------------------------------------------------------------------------- +crypto_swap_to_x25519: + lda #OV_X25519 + cmp current_overlay + beq swap_done_fast + pha + lda #REU_OVERLAY_X25519 + ldy #^REU_OVERLAY_X25519 + jsr do_swap + pla + sta current_overlay + rts + +; ----------------------------------------------------------------------------- +; crypto_swap_to_p256_verify -- DMA P-256 verify image (sibling +; libs/nistcurves verify-only minimal subset) from REU_OVERLAY_P256_VERIFY +; (bank 2 slot $22100) into the live CRYPTO_OVERLAY slot. Idempotent. +; +; W3 new. Today the P-256 verify primitives are always-resident in +; CRYPTO_RESIDENT (Phase C.4 sibling integration); W1 will later move +; them into the cold-path overlay slot. Until that wiring, this +; entry point is callable but unused by TLS call sites. +; ----------------------------------------------------------------------------- +crypto_swap_to_p256_verify: + lda #OV_P256_VERIFY + cmp current_overlay + beq swap_done_fast + pha + lda #REU_OVERLAY_P256_VERIFY + ldy #^REU_OVERLAY_P256_VERIFY + jsr do_swap + pla + sta current_overlay + rts + ; ----------------------------------------------------------------------------- ; crypto_swap_none -- mark the slot as undefined. ; @@ -222,7 +290,87 @@ crypto_swap_none: sta current_overlay rts -swap_done_fast: +; ----------------------------------------------------------------------------- +; crypto_overlay_call -- swap-then-call convenience wrapper (W3 new). +; +; Performs an idempotent swap to the requested overlay (no-op if it is +; already current) and then JSRs to (slot_base + offset). Designed +; so a TLS call site can do: +; +; lda #OV_P256_VERIFY +; ldx #<(ecdsa_verify_256 - __CRYPTO_OVERLAY_START__) +; ldy #>(ecdsa_verify_256 - __CRYPTO_OVERLAY_START__) +; jsr crypto_overlay_call +; +; instead of two separate jsr's (swap, then jsr abs). The dispatcher +; is responsible for ensuring fn_offset+slot_base is a valid entry +; point — there is no symbol-table check here. +; +; Inputs: +; A = overlay id (OV_*) +; X = fn offset low byte (relative to __CRYPTO_OVERLAY_START__) +; Y = fn offset high byte +; +; Behaviour: +; * Stashes X / Y / A in self-modifying-code (SMC) slots before +; branching to the swap helper so the swap is free to clobber +; all three registers. +; * Dispatches on A to the matching crypto_swap_to_ entry point. +; Unknown IDs return immediately without swapping or JSRing +; (the call is a no-op; current_overlay is left untouched). +; * After the swap returns, indirect-jsrs through the SMC'd absolute +; address (slot_base + offset). +; * The callee's return value (C flag + A/X/Y) passes through +; unchanged to the caller. +; +; ABI mirror: documented identically in overlay_ids.inc usage notes. +; ----------------------------------------------------------------------------- +crypto_overlay_call: + ; Save the overlay id for the dispatch below. txa/tya in the + ; pointer math below clobbers A, so we have to stash it first. + pha + + ; Compute slot_base + (Y:X) and stash into the indirect JSR slot. + clc + txa + adc #<__CRYPTO_OVERLAY_START__ + sta @call_target+1 + tya + adc #>__CRYPTO_OVERLAY_START__ + sta @call_target+2 + + ; Recover the overlay id and dispatch. Order: most-frequent + ; first (X25519 + P-256 verify will be the W1 hot pair; the + ; P-384 pair is the legacy / 0x0503-only path). + pla + cmp #OV_X25519 + bne @not_x25519 + jsr crypto_swap_to_x25519 + jmp @call_target +@not_x25519: + cmp #OV_P256_VERIFY + bne @not_p256 + jsr crypto_swap_to_p256_verify + jmp @call_target +@not_p256: + cmp #OV_P384_SHA384 + bne @not_sha384 + jsr crypto_swap_to_p384_sha384 + jmp @call_target +@not_sha384: + cmp #OV_P384_CURVE + bne @not_curve + jsr crypto_swap_to_p384_curve + jmp @call_target +@not_curve: + ; Unknown overlay id -- no-op (caller error). Leaves + ; current_overlay untouched and returns with C=1 to surface + ; the misuse. + sec + rts + +@call_target: + jsr $0000 ; absolute address SMC'd above rts ; ----------------------------------------------------------------------------- diff --git a/src/crypto/shared/overlay_ids.inc b/src/crypto/shared/overlay_ids.inc new file mode 100644 index 0000000..8c57ff0 --- /dev/null +++ b/src/crypto/shared/overlay_ids.inc @@ -0,0 +1,57 @@ +; ============================================================================= +; overlay_ids.inc -- Canonical overlay-ID constants for the CRYPTO_OVERLAY +; paging slot. Single source of truth shared between the dispatcher +; (crypto_swap.s) and any caller that compares against `current_overlay` +; or passes an ID to `crypto_overlay_call`. +; +; The IDs are opaque to the swap engine itself -- they exist purely so +; idempotent callers can short-circuit a no-op swap by comparing against +; `current_overlay`. Numbering rules: +; * OV_NONE = 0 is the post-boot / undefined-slot sentinel. +; * Distinct non-zero values per overlay. Gaps are tolerated; new +; overlays should pick the lowest unused ID rather than renumber +; existing ones (forward compatibility across call-sites). +; * IDs are .ifndef-guarded so this file is safe to include twice in a +; single translation unit (e.g. boot.s + crypto_swap.s pulling in +; the same header). +; +; Phase reference: +; 1, 4, 5 -- pre-existing (Phase 3 dual-overlay edition). +; 2, 3 -- new entry points added by the library-ingestion +; architecture W3 work (X25519 + P-256 verify real DMA). +; ============================================================================= + +.ifndef OV_NONE +OV_NONE = 0 +.endif + +; OV_X25519_SIBLING (legacy marker) -- predates the W3 real DMA path. +; Retained because the existing `crypto_swap_to_x25519_sibling` entry +; point in crypto_swap.s still sets it (state-only, no DMA). New +; callers that DMA the sibling image from REU use OV_X25519 instead. +.ifndef OV_X25519_SIBLING +OV_X25519_SIBLING = 1 +.endif + +; OV_P256_VERIFY -- W3 new. Set by `crypto_swap_to_p256_verify` +; after DMA'ing the P-256 verify image (libs/nistcurves' P-256 verify +; minimal-subset) from REU_OVERLAY_P256_VERIFY into the live slot. +.ifndef OV_P256_VERIFY +OV_P256_VERIFY = 2 +.endif + +; OV_X25519 -- W3 new. Set by `crypto_swap_to_x25519` after DMA'ing +; the X25519 sibling image (code + rodata) from REU_OVERLAY_X25519 +; into the live slot. Distinct from OV_X25519_SIBLING (which is the +; state-only marker for the linker-placed PRG-load-time image). +.ifndef OV_X25519 +OV_X25519 = 3 +.endif + +; OV_P384_SHA384 / OV_P384_CURVE -- pre-existing (Phase 3). +.ifndef OV_P384_SHA384 +OV_P384_SHA384 = 4 +.endif +.ifndef OV_P384_CURVE +OV_P384_CURVE = 5 +.endif diff --git a/src/crypto/shared/p256_overlay_blobs.s b/src/crypto/shared/p256_overlay_blobs.s new file mode 100644 index 0000000..cc618a5 --- /dev/null +++ b/src/crypto/shared/p256_overlay_blobs.s @@ -0,0 +1,48 @@ +; ============================================================================= +; p256_overlay_blobs.s -- Embedded P-256 verify overlay image (W3). +; +; Mirror of p384_overlay_blobs.s for the P-256 verify minimal-subset +; overlay image. Embedded when the top-level Makefile flag +; EMBED_P256_OVERLAY=1 is set (which propagates to ca65 as +; -D USE_OVERLAY_P256_EMBED=1). +; +; Mutually exclusive with USE_OVERLAY_P384_EMBED at the cfg level: both +; target the CRYPTO_OVERLAY slot at $4200 at PRG-load time, so the +; Makefile turns USE_OVERLAY_P384_EMBED off when EMBED_P256_OVERLAY=1. +; If both were set the linker would overflow the 7,680 B slot. +; +; Output: +; build/lib/nistcurves-p256-verify.bin (7,680 B padded) -- staged into +; CRYPTO_OVERLAY at PRG load time, then DMA'd by +; reu_p384_overlay_init (boot.s) to REU bank 2 slot $22100. After +; the stash, future calls to `crypto_swap_to_p256_verify` DMA the +; image back into the live slot. +; +; Inert when USE_OVERLAY_P256_EMBED is undefined (default build): +; the segment is left empty (`optional = yes` in the cfg) and boot +; skips the DMA. This is the default state -- the P-256 verify +; primitives stay always-resident in CRYPTO_RESIDENT until W1 wires +; them into a cold-path overlay swap. +; ============================================================================= + + .setcpu "6502" + +.ifdef USE_OVERLAY_P256_EMBED + + .export p256_overlay_verify_blob + .export p256_overlay_verify_blob_end + +; ----------------------------------------------------------------------------- +; P-256 verify overlay image (REU_OVERLAY_P256_VERIFY source) +; +; Loads into the live CRYPTO_OVERLAY slot at $4200-$5FFF at PRG load +; time, then boot DMAs it to REU bank 2 slot $22100. The .incbin path +; is resolved by ca65 relative to this source file: from +; src/crypto/shared/ the build/ tree is two levels up. +; ----------------------------------------------------------------------------- + .segment "OVERLAY_BLOB_P256" +p256_overlay_verify_blob: + .incbin "../../../build/lib/nistcurves-p256-verify.bin" +p256_overlay_verify_blob_end: + +.endif ; .ifdef USE_OVERLAY_P256_EMBED diff --git a/src/crypto/shared/reu_layout.inc b/src/crypto/shared/reu_layout.inc index e954b6c..6a2d975 100644 --- a/src/crypto/shared/reu_layout.inc +++ b/src/crypto/shared/reu_layout.inc @@ -38,7 +38,50 @@ REU_OVERLAY_P256 = $22100 REU_OVERLAY_P384 = $24100 .endif +; --- W3: P-256 verify overlay image (library-ingestion architecture §2.5) --- +; The architecture plan §2.5 proposes "bank 2 at offset $1F00" for this +; image. Offset $1F00 falls inside the slot-1 span ($20100-$22100), so +; we reuse the existing REU_OVERLAY_P256 slot at $22100 instead: it is +; the second 8 KB-aligned slot in bank 2, it is already 8 KB-padded +; (matching OVERLAY_SIZE), and it has been declared since Phase 1.5 +; without a current production consumer (the P-256 sibling is always- +; resident, no live overlay swap). This avoids carving a second slot +; out of bank 2. The alias is `.ifndef`-guarded; downstream override +; via `--asm-define REU_OVERLAY_P256_VERIFY=$xxxxx` remains available. +.ifndef REU_OVERLAY_P256_VERIFY +REU_OVERLAY_P256_VERIFY = REU_OVERLAY_P256 +.endif + +; --- W3: X25519 sibling overlay image (library-ingestion architecture §2.5) --- +; Architecture plan §2.5: "x25519 sibling tables get bank 3 (resolves +; the c64-x25519 #43 collision concern)." Bank 3 ($30000) was nominally +; reserved for P-256 fixed-base precompute, but the TLS path uses +; ec_scalar_mul_var (variable-base) only — the reservation has no +; runtime consumer. Using bank 3 for the X25519 sibling code+rodata +; image keeps the production overlay store (bank 2) free for the +; existing P-384 SHA384/Curve halves at $22100/$24100 and the new +; P-256 verify slot above. +; +; This is the IMAGE address (DMA source for `crypto_swap_to_x25519`) +; — the sibling's running mul/doubled/17-bit-carry tables continue to +; live in their own REU homes (currently banks 0-2 under v0.4.0). +; When c64-x25519 #43 lands and the sibling's table bases become +; `--asm-define`-able, the consumer-side override will relocate the +; sibling tables (e.g. to bank 4+) so they don't collide with the +; bank-3 image staging. Until then, USE_X25519_SIBLING=1 + a real +; `crypto_swap_to_x25519` overwrite of CRYPTO_OVERLAY is gated behind +; W1's hot/cold partition (no current TLS caller). +.ifndef REU_OVERLAY_X25519 +REU_OVERLAY_X25519 = $30000 +.endif + ; --- P-256 precompute (1 bank) --- +; NB: REU_OVERLAY_X25519 above lives at the same bank ($30000). Both +; are reservations -- the P-256 precompute is unused at runtime (the +; TLS dispatcher uses variable-base scalar mul, no Lim-Lee table), +; and the X25519 sibling image is similarly cold until W1 wires a +; real swap into TLS. When either becomes hot, the cfg has to pick +; one and relocate the other. .ifndef REU_P256_PRECOMPUTE_BASE REU_P256_PRECOMPUTE_BASE = $30000 .endif diff --git a/src/data.s b/src/data.s index 541f6ad..e64c246 100644 --- a/src/data.s +++ b/src/data.s @@ -237,7 +237,15 @@ tls_read_seq: .res 8 tls_rec_header: .res 5 tls_rec_type: .res 1 tls_rec_len: .res 2 + +; W1 partial: tls_rec_buf (548 B) lives in BSS_TAIL — a separate BSS +; segment that the UCI cfg routes to the NET_BSS_TAIL region (the +; reclaimed tail of NET_CODE). Keeps the largest single c64-https BSS +; entry out of the CRYPTO_HOT overflow path. ip65 cfg aliases +; BSS_TAIL to BSS so the relocation is invisible there. +.segment "BSS_TAIL" tls_rec_buf: .res 548 +.segment "BSS" ; AEAD nonce construction .export tls_nonce diff --git a/src/der_decode.s b/src/der_decode.s index 00deda4..f7a4581 100644 --- a/src/der_decode.s +++ b/src/der_decode.s @@ -573,5 +573,12 @@ cert_sig_r: .res 48 ; signature r component (max 48 for P-38 cert_sig_s: .res 48 ; signature s component (max 48 for P-384) cert_sig_len: .res 1 ; 32 (P-256) or 48 (P-384) cert_curve_id: .res 1 ; 0=P-256, 1=P-384 +; W1 partial: cert_buf (1.5 KB) lives in BSS_TAIL — the same offload +; region as src/data.s::tls_rec_buf — routed to NET_BSS_TAIL under +; UCI. Keeps the cert parse staging out of the CRYPTO_HOT overflow +; path. ip65 cfg aliases BSS_TAIL to BSS so the relocation is +; invisible there. +.segment "BSS_TAIL" cert_buf: .res 1536 ; certificate DER buffer +.segment "BSS" cert_buf_len: .res 2 ; certificate length diff --git a/src/exports.s b/src/exports.s index 93dcc75..71e59a6 100644 --- a/src/exports.s +++ b/src/exports.s @@ -32,9 +32,15 @@ .export zp_ptr ; Phase C.4: c64-nist-curves fp256.s references a handful of REU DMA -; registers via `.import` (it was written to live in a linker-visible -; symbol world). Promote the numeric equates from constants.inc so ld65 -; can resolve the sibling's imports. -.export reu_reu_hi -.export reu_reu_bank -.export reu_command +; registers via `.import`. Pre-contract, the sibling shipped no +; constants.o, so c64-https had to promote its in-tree equates from +; constants.inc to satisfy the link. Under c64-lib-contract (libs/ +; nistcurves cfa9085+), the library publishes constants.o which +; `.export`s the same REU register equates. Those declarations now +; satisfy the .imports — remove the c64-https-side .export to avoid +; ld65 "Duplicate external identifier" errors. +; +; Code in c64-https that references these symbols through +; constants.inc continues to work because constants.inc still defines +; them as local equates; the library's .export only becomes +; load-bearing for sources that .import them. diff --git a/tools/integration/build_nistcurves_p256.sh b/tools/integration/build_nistcurves_p256.sh index d8f1cfc..558faa8 100755 --- a/tools/integration/build_nistcurves_p256.sh +++ b/tools/integration/build_nistcurves_p256.sh @@ -3,48 +3,45 @@ # tools/integration/build_nistcurves_p256.sh - Build c64-nist-curves P-256 # ECDSA verify primitives as a resident .a archive linked into the main PRG. # -# Phase C.4 of the sibling-lib integration. Produces build/lib/nistcurves-p256.a -# containing the P-256 field arithmetic, modular arithmetic, variable-base -# scalar multiply, Jacobian->affine conversion, and packaged ECDSA verify -# (ecdsa_verify_256). No overlay mechanism; all code always-resident. +# Phase C.4 + W5 (library-ingestion architecture). Under the c64-lib-contract +# (libs/nistcurves v0.3.0), the upstream library publishes a +# `make lib-p256-verify` build target that produces a minimal-subset archive +# carrying exactly the symbols needed for variable-base P-256 verify. This +# script delegates the heavy lifting to `make -C libs/nistcurves`, then +# performs two adjustments before placing the result at the location the +# top-level Makefile expects: # -# Excluded (to fit the budget + avoid REU precompute): -# - ec_scalar_mul - Lim-Lee fixed-base comb. Needs a 16 KB REU bank-2 -# precompute table built by ec_precompute_256 at boot. -# Replaced by a shim in src/crypto/ecdsa_verify.s that -# copies G into ec_base_x/y and tail-calls -# ec_scalar_mul_var. The dispatcher is the ONLY caller -# of ecdsa_verify_256, so the shim covers the sole -# in-PRG use of ec_scalar_mul. -# - ec_precompute_256 - builds the Lim-Lee anchor table into REU bank 2. -# Only useful with ec_scalar_mul. -# - Lim-Lee anchor tables (ec_anchor1_x..ec_anchor8_y, cm_k, ec_aff2g_256_*) -# and all sm256_reu_* REU DMA helpers that service them. -# - All P-384 data/arith (fp384_*, ec384_*, ecdsa384_*, cm_k_384, anchors). -# Lives in nistcurves-p384.a under the separate Phase C.3b smoke test. -# - Shared mul infrastructure (mul_cached_a, mul_src2_buf, mul_dma_lo/hi, -# mul_8x8, sqtab_init, sqtab_lo/hi, poly_prod_lo/hi, reu_fetch_mul_row) - -# the in-tree src/data.s + src/crypto/poly1305.s + src/boot.s already -# provide these and they are shared across fe25519 + P-256 via the REU -# DMA row-fetch pipeline. Adding the sibling's copies would collide. -# - ecdsa_inputs_256, ecdsa_result_256 test-driver scratch - only used by -# the nist-curves PRG's own test harness. +# 1. Rebuild `zp_config.o` with c64-https's ZP-slot overrides (the upstream +# defaults collide with c64-https's canonical map on three slots: +# zp_ptr2, fp_mul_i, fp_mul_j). The library's zp_config.s `.ifndef`- +# guards every slot, so an override-built version replaces the +# upstream default cleanly. # -# The script stages the sibling's .s files in build/lib/nistcurves_p256_staging/, -# applies sed patches to strip Lim-Lee bodies + provide a minimal P-256-only -# data.s, and assembles with canonical ZP equates passed via -D. +# 2. Drop `mul_8x8.o` and `data_shared.o` from the archive. c64-https's +# in-tree `src/crypto/poly1305.s` and `src/data.s` already export the +# same symbols (mul_8x8, sqtab_init, poly_prod_lo/hi, mul_cached_a, +# mul_src2_buf, mul_dma_lo/hi). Including the library's copies would +# cause ld65 duplicate-symbol errors. # -# Usage (from top-level Makefile): -# bash tools/integration/build_nistcurves_p256.sh -# Produces: -# build/lib/nistcurves-p256.a -# build/lib/nistcurves-p256.sizes.txt (per-source byte counts) +# Pre-contract this script was ~400 lines of `sed -i ''` strips and +# heredoc'd hand-extracted curve/data files. Post-contract the +# `make lib-p256-verify` target replaces all of that — no segment +# rewriting (upstream now emits `LIB_NISTCURVES_P256_*` segments by +# convention), no body strips (the lib-p256-verify variant excludes the +# Lim-Lee comb + precompute already), no hand-extracted data heredoc +# (upstream's `data_p256.s` is the canonical RW state list). +# +# Outputs: +# build/lib/nistcurves-p256.a - the consumer-side archive +# build/lib/nistcurves-p256.sizes.txt - per-source byte counts # ============================================================================= set -eo pipefail # --- Paths --- PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -LIB_SRC="$PROJECT_ROOT/libs/nistcurves/src" +LIB_DIR="$PROJECT_ROOT/libs/nistcurves" +LIB_SRC="$LIB_DIR/src" +LIB_BUILD="$LIB_DIR/build" STAGING="$PROJECT_ROOT/build/lib/nistcurves_p256_staging" OUT_DIR="$PROJECT_ROOT/build/lib" ARCHIVE="$OUT_DIR/nistcurves-p256.a" @@ -53,331 +50,97 @@ SIZES="$OUT_DIR/nistcurves-p256.sizes.txt" CA65="${CA65:-ca65}" AR65="${AR65:-ar65}" -# --- Canonical ZP defines --- -# Mirrors the P-384 build's -D flag set. zp_ptr2 is relocated into -# $3D-$3E (inside ZP_CRYPTO, otherwise unused) because the sibling's -# default ($fd-$fe) overlaps with c64-https's zp_temp/zp_count used -# by der_decode.s during cert parsing. ecdsa_verify_256 runs AFTER -# DER parsing completes, so the clobber would be fine in practice, -# but the relocation keeps the lifetime isolation explicit. -ZP_DEFINES=( - '-Dproc_port=$01' - '-Dzp_tmp1=$02' - '-Dzp_tmp2=$03' - '-Dzp_ptr1=$fb' - '-Dzp_ptr2=$3d' - '-Dfp_src1=$22' - '-Dfp_src2=$24' - '-Dfp_dst=$26' - '-Dfp_misc=$28' - '-Dfp_carry=$2a' - '-Dfp_loop=$2b' - '-Dfp_mul_i=$39' - '-Dfp_mul_j=$3a' - '-Dec_scalar_ptr=$3b' - '-Dpoly_i=$1a' - '-Dpoly_j=$1b' - '-Dpoly_carry=$1c' - '-Dpoly_tmp=$1d' +# --- ZP-slot overrides (c64-https canonical map) --- +# zp_ptr2 = $3D : library default $fd collides with c64-https zp_temp/zp_count +# used by der_decode.s during cert parsing. +# fp_mul_i = $39, fp_mul_j = $3A : library defaults $2c/$2d collide with +# c64-https fe25519 ZP claim ($2c-$37). $39-$3a is otherwise +# unused inside ZP_CRYPTO. +# Other slots match upstream defaults — see libs/nistcurves/src/zp_config.s. +ZP_OVERRIDES=( + '-D' 'zp_ptr2=$3d' + '-D' 'fp_mul_i=$39' + '-D' 'fp_mul_j=$3a' ) -# --- Stage sources --- -rm -rf "$STAGING" -mkdir -p "$STAGING" - -cp "$LIB_SRC"/constants.s "$STAGING/" -cp "$LIB_SRC"/zp_config.s "$STAGING/" -cp "$LIB_SRC"/fp256.s "$STAGING/fp256_raw.s" -cp "$LIB_SRC"/mod256.s "$STAGING/mod256_raw.s" -cp "$LIB_SRC"/points256.s "$STAGING/points256_raw.s" -cp "$LIB_SRC"/ecdsa256.s "$STAGING/ecdsa256_raw.s" - -# --- Strip points256.s of the Lim-Lee / REU precompute bodies --- -# Lines 762-1458 in the upstream file cover: -# - sm256_reu_stash_affine / sm256_reu_fetch_affine / sm256_calc_offset_64 -# / sm256_reu_restore (REU DMA helpers for bank-2 anchor table) -# - ec_precompute_256 and its internal helpers (load_G_jac, successive-double -# helpers, anchor accumulate) -# - ec_scalar_mul (Lim-Lee 8-way fixed-base comb) and its anchor-loader -# helpers + anchor base-address table + cm_* / sm256_* state vars -# Keeps ec_point_double (line 60-410), ec_point_add (411-761), -# ec_scalar_mul_var (1459-1609), ec_jacobian_to_affine (1610-end). -sed -i '' '762,1467d' "$STAGING/points256_raw.s" - -# Strip exports + imports that only the removed bodies used. -sed -i '' '/^\.export ec_precompute_256, ec_scalar_mul, ec_scalar_mul_var$/c\ -.export ec_scalar_mul_var' "$STAGING/points256_raw.s" -# Anchor + Lim-Lee state imports -sed -i '' '/^\.import ec_aff2g_256_x, ec_aff2g_256_y$/d' "$STAGING/points256_raw.s" -sed -i '' '/^\.import ec_anchor[1-8]_x, ec_anchor[1-8]_x, ec_anchor[1-8]_x, ec_anchor[1-8]_x$/d' "$STAGING/points256_raw.s" -sed -i '' '/^\.import ec_anchor[1-8]_y, ec_anchor[1-8]_y, ec_anchor[1-8]_y, ec_anchor[1-8]_y$/d' "$STAGING/points256_raw.s" -sed -i '' '/^\.import ec_anchor.*$/d' "$STAGING/points256_raw.s" -sed -i '' '/^\.import cm_k, mul_dma_lo$/d' "$STAGING/points256_raw.s" -sed -i '' '/^\.import ec_sc_byte, ec_sc_mask$/d' "$STAGING/points256_raw.s" -# REU DMA register imports. v0.2.0 added a "defensive REU register init" -# block at the top of ec_scalar_mul_var (lines 753-757) that touches -# reu_reu_lo + reu_addr_ctrl, so those two must stay imported even though -# ec_scalar_mul_var is the only retained body. The rest are only used by -# the stripped REU anchor helpers and Lim-Lee comb. -sed -i '' '/^\.import reu_c64_lo, reu_c64_hi, reu_reu_lo, reu_reu_hi$/c\ -.import reu_reu_lo' "$STAGING/points256_raw.s" -sed -i '' '/^\.import reu_reu_bank, reu_len_lo, reu_len_hi$/d' "$STAGING/points256_raw.s" -sed -i '' '/^\.import reu_addr_ctrl, reu_command$/c\ -.import reu_addr_ctrl' "$STAGING/points256_raw.s" -# ec_mulp / ec_sqrp are used by all three retained bodies - keep. -# fp_tmp1 is used by ec_scalar_mul_var - keep. - -# Sanity: no leftover non-comment references to stripped symbols. -# Filter out comment lines (first non-blank char is `;`) before checking. -if grep -v '^\s*;' "$STAGING/points256_raw.s" \ - | grep -qE '\bec_anchor[0-9]+_|\bcm_k\b|\bec_aff2g_256|\bec_sc_byte\b|\bec_sc_mask\b|\bsm256_reu|\bec_scalar_mul\b[^_]'; then - echo "ERROR: stripped points256 still references removed-body symbols" >&2 - grep -v '^\s*;' "$STAGING/points256_raw.s" \ - | grep -nE '\bec_anchor[0-9]+_|\bcm_k\b|\bec_aff2g_256|\bec_sc_byte\b|\bec_sc_mask\b|\bsm256_reu|\bec_scalar_mul\b[^_]' \ - | head -5 >&2 +# --- 1. Build upstream's lib-p256-verify archive --- +# Upstream's Makefile builds every module with the same recipe (no per-file +# CA65FLAGS hook), so we cannot pass -D overrides via `make CA65=...` here: +# the override would land on every .s, including fp256.s which only +# `.importzp`s the slots and would error on a redefinition. We therefore +# build upstream with its defaults, then rebuild zp_config.o ourselves with +# the overrides below. +# +# Note on c64-lib-contract SPEC §8.1: nistcurves v0.3.0's `mul_8x8.s` is +# the only TU that references sqtab_lo / sqtab_hi (via the local +# `.ifndef LIB_SHARED_SQTAB_BASE` equate in that file). Step 4 below +# drops `mul_8x8.o` from the archive entirely — c64-https provides the +# canonical `sqtab_lo` / `sqtab_hi` via src/data.s and the population +# init via src/crypto/poly1305.s::sqtab_init. So no LIB_SHARED_SQTAB_BASE +# / SHARED_SQTAB_INIT override is needed at the nistcurves Makefile +# invocation — the upstream default baked into mul_8x8.o is discarded +# before it reaches the link. +echo "[p256] building libs/nistcurves lib-p256-verify (upstream defaults)..." +make -s -C "$LIB_DIR" lib-p256-verify >/dev/null + +UPSTREAM_ARCHIVE="$LIB_BUILD/lib/nistcurves-p256-verify.a" +if [ ! -f "$UPSTREAM_ARCHIVE" ]; then + echo "ERROR: upstream archive missing: $UPSTREAM_ARCHIVE" >&2 exit 1 fi -# --- Strip curve256.s to ec_a256, ec_b256, ec_gx256, ec_gy256 only --- -# The test vector constants (ecdsa_test_*) are used only by the sibling's -# own test PRG and would add ~256 B of dead rodata here. -cat > "$STAGING/curve256_raw.s" <<'CURVE_EOF' -.setcpu "6502" - -; ============================================================================= -; curve256_raw.s - P-256 curve parameters for c64-https Phase C.4. -; Hand-trimmed from libs/nistcurves/src/curve256.s: test vectors dropped -; (only used by the sibling's standalone test harness). -; ============================================================================= - -.segment "RODATA" - -.export ec_a256, ec_b256, ec_gx256, ec_gy256 - -; Coefficient a = p - 3 -ec_a256: - .byte $FC, $FF, $FF, $FF, $FF, $FF, $FF, $FF - .byte $FF, $FF, $FF, $FF, $00, $00, $00, $00 - .byte $00, $00, $00, $00, $00, $00, $00, $00 - .byte $01, $00, $00, $00, $FF, $FF, $FF, $FF - -; Coefficient b -ec_b256: - .byte $4B, $60, $D2, $27, $3E, $3C, $CE, $3B - .byte $F6, $B0, $53, $CC, $B0, $06, $1D, $65 - .byte $BC, $86, $98, $76, $55, $BD, $EB, $B3 - .byte $E7, $93, $3A, $AA, $D8, $35, $C6, $5A - -; Generator x coordinate (LE) -ec_gx256: - .byte $96, $C2, $98, $D8, $45, $39, $A1, $F4 - .byte $A0, $33, $EB, $2D, $81, $7D, $03, $77 - .byte $F2, $40, $A4, $63, $E5, $E6, $BC, $F8 - .byte $47, $42, $2C, $E1, $F2, $D1, $17, $6B - -; Generator y coordinate (LE) -ec_gy256: - .byte $F5, $51, $BF, $37, $68, $40, $B6, $CB - .byte $CE, $5E, $31, $6B, $57, $33, $CE, $2B - .byte $16, $9E, $0F, $7C, $4A, $EB, $E7, $8E - .byte $9B, $7F, $1A, $FE, $E2, $42, $E3, $4F -CURVE_EOF - -# --- Emit minimal data_p256_raw.s --- -# Keeps only the RW buffers that fp256 / mod256 / points256 (post-strip) / -# ecdsa256 reference. Shared mul infrastructure (mul_cached_a, mul_src2_buf, -# mul_dma_lo, mul_dma_hi) is provided by in-tree src/data.s. P-384 data and -# Lim-Lee anchors are excluded. -cat > "$STAGING/data_p256_raw.s" <<'DATA_EOF' -.setcpu "6502" - -; ============================================================================= -; data_p256_raw.s - Minimal P-256 RW buffers for c64-https / c64-nist-curves -; integration (Phase C.4). Hand-extracted from the sibling's -; data.s so in-tree shared mul buffers remain unclobbered and -; P-384 / Lim-Lee state is omitted. -; -; All exports here are P-256-exclusive. -; ============================================================================= - -.segment "DATA" - -; --- P-256 field arithmetic working buffers (32 bytes each) --- -; fp_tmp2/3/4 and fp_r1/2/3 are declared by the sibling's full data.s -; but never .importe'd from the retained fp256/mod256/points256/ecdsa256 -; bodies; pruned here to save BSS (~192 B). -.export fp_wide -fp_wide: .res 64, 0 ; 512-bit product from multiply -.export fp_tmp1 -fp_tmp1: .res 32, 0 - -; --- P-256 result registers (only fp_r0 referenced) --- -.export fp_r0 -fp_r0: .res 32, 0 - -; --- P-256 modular inverse working space --- -.export fp_inv_u -fp_inv_u: .res 32, 0 -.export fp_inv_v -fp_inv_v: .res 32, 0 -.export fp_inv_x1 -fp_inv_x1: .res 32, 0 -.export fp_inv_x2 -fp_inv_x2: .res 32, 0 - -; --- P-256 point storage (Jacobian: X=32 + Y=32 + Z=32 = 96 bytes) --- -.export ec_p1 -ec_p1: .res 96, 0 -.export ec_p2 -ec_p2: .res 96, 0 -.export ec_p3 -ec_p3: .res 96, 0 - -; --- P-256 point math temporaries --- -.export ec_t1 -ec_t1: .res 32, 0 -.export ec_t2 -ec_t2: .res 32, 0 -.export ec_t3 -ec_t3: .res 32, 0 -.export ec_t4 -ec_t4: .res 32, 0 -.export ec_t5 -ec_t5: .res 32, 0 -.export ec_t6 -ec_t6: .res 32, 0 - -; --- P-256 affine output --- -.export ec_affine_x -ec_affine_x: .res 32, 0 -.export ec_affine_y -ec_affine_y: .res 32, 0 - -; --- Variable-base scalar-mul input (affine, 32 bytes each, LE). --- -.export ec_base_x -ec_base_x: .res 32, 0 -.export ec_base_y -ec_base_y: .res 32, 0 - -; --- Solinas reduction scratch (33 bytes: 32 + carry) --- -.export fp_red_tmp -fp_red_tmp: .res 33, 0 - -; --- ECDSA verify scratch (P-256). All 32-byte little-endian unless noted. --- -.export ecdsa_r -ecdsa_r: .res 32, 0 ; LE r (byte-reversed from BE input) -.export ecdsa_s -ecdsa_s: .res 32, 0 ; LE s -.export ecdsa_h -ecdsa_h: .res 32, 0 ; LE message hash -.export ecdsa_qx -ecdsa_qx: .res 32, 0 ; LE public-key affine X -.export ecdsa_qy -ecdsa_qy: .res 32, 0 ; LE public-key affine Y -.export ecdsa_w -ecdsa_w: .res 32, 0 ; LE w = s^-1 mod n -.export ecdsa_u1 -ecdsa_u1: .res 32, 0 ; LE u1 = h*w mod n -.export ecdsa_u2 -ecdsa_u2: .res 32, 0 ; LE u2 = r*w mod n -.export ecdsa_u1_be -ecdsa_u1_be: .res 32, 0 ; BE u1 (scalar_mul input) -.export ecdsa_u2_be -ecdsa_u2_be: .res 32, 0 ; BE u2 (scalar_mul_var input) -.export ecdsa_u1g_x -ecdsa_u1g_x: .res 32, 0 ; LE affine X of u1*G -.export ecdsa_u1g_y -ecdsa_u1g_y: .res 32, 0 ; LE affine Y of u1*G - -; --- fp_reverse32 staging buffer (one 32-byte scratch). --- -.export fp_rev_buf -fp_rev_buf: .res 32, 0 -DATA_EOF - -# --- Emit minimal REU register equates --- -# v0.2.0 added a "defensive REU register init" block at the top of -# ec_scalar_mul_var (and also in fp256/ecdsa256 modular-inverse paths) -# that touches reu_reu_lo + reu_addr_ctrl. The sibling's constants.s -# provides these but also exports VIC/CIA/KERNAL equates that would -# collide with c64-https's in-tree definitions, so we emit a minimal -# equate file with only what the retained bodies actually reference. -cat > "$STAGING/reu_equates_raw.s" <<'REU_EOF' -.setcpu "6502" - -; Minimal REU hardware register equates used by retained P-256 bodies -; in v0.2.0 (defensive REU register init in ec_scalar_mul_var, fp_inv, -; ecdsa inverse). Mirror of values in libs/nistcurves/src/constants.s. -.export reu_reu_lo, reu_addr_ctrl -reu_reu_lo = $df04 -reu_addr_ctrl = $df0a -REU_EOF - -# --- Route CODE segments in the raw .s files to CRYPTO_CODE. --- -# The sibling uses `.segment "CODE"`, which under c64-https's cfg is the -# LOADER region ($0801-$1FFF). We want this code in CRYPTO_RESIDENT. -for src in fp256_raw mod256_raw points256_raw ecdsa256_raw; do - sed -i '' 's/^\.segment "CODE"/.segment "CRYPTO_CODE"/' "$STAGING/$src.s" -done - -# --- Route DATA segment in data_p256_raw.s to CRYPTO_BSS. --- -# The c64-https cfg has no "DATA" segment slot; our minimal data file -# only contains `.res` (zero-init) declarations, so CRYPTO_BSS is the -# right home. Don't accidentally match anything inside a string or -# comment: the data_p256_raw.s we emit has exactly one such directive. -sed -i '' 's/^\.segment "DATA"$/.segment "CRYPTO_BSS"/' "$STAGING/data_p256_raw.s" - -# Sanity: no leftover `.segment "CODE"` hunks outside the expected -# pattern (the raw files should only have one CODE segment each). -for src in fp256_raw mod256_raw points256_raw ecdsa256_raw; do - if grep -qE '^\.segment "CODE"$' "$STAGING/$src.s"; then - echo "ERROR: leftover .segment \"CODE\" in $src.s" >&2 - exit 1 - fi -done - -# --- Assemble each staged .s file --- -OBJ_DIR="$STAGING/obj" -rm -rf "$OBJ_DIR" -mkdir -p "$OBJ_DIR" "$OUT_DIR" - -# zp_config.s is the single point of truth for ZP equates; we apply -D -# overrides so sibling defaults get replaced with c64-https's canonical map. -# `-g` embeds cc65 debug info into the .o files so the final ld65 --dbgfile -# (driven from the top-level Makefile) can merge per-source line/symbol -# records into build/c64-https.dbg. Does not change emitted code bytes. +# --- 2. Stage upstream object files --- +rm -rf "$STAGING" +mkdir -p "$STAGING" "$OUT_DIR" +cp "$UPSTREAM_ARCHIVE" "$STAGING/upstream.a" +(cd "$STAGING" && "$AR65" x upstream.a $( "$AR65" t upstream.a )) + +# --- 3. Rebuild zp_config.o with c64-https overrides --- +# `.ifndef`-guarded slots in src/zp_config.s let -D flags win cleanly. +# The .exportzp declarations propagate the override values to every +# `.importzp` site via the link. "$CA65" \ + --cpu 6502 \ -g \ - -I "$STAGING" \ - -I "$PROJECT_ROOT/src/crypto/shared" \ - "${ZP_DEFINES[@]}" \ - -o "$OBJ_DIR/zp_config.o" "$STAGING/zp_config.s" - -for src in fp256_raw mod256_raw points256_raw ecdsa256_raw curve256_raw data_p256_raw reu_equates_raw; do - "$CA65" \ - -g \ - -I "$STAGING" \ - -I "$PROJECT_ROOT/src/crypto/shared" \ - -o "$OBJ_DIR/$src.o" "$STAGING/$src.s" -done - -# --- Archive --- + -I "$LIB_SRC" \ + "${ZP_OVERRIDES[@]}" \ + -o "$STAGING/zp_config.o" \ + "$LIB_SRC/zp_config.s" + +# --- 4. Drop conflicting members --- +# mul_8x8.o: exports mul_8x8, sqtab_init, poly_prod_lo/hi, sqtab_lo/hi, +# reu_fetch_mul_row. c64-https's src/crypto/poly1305.s already +# exports these — including upstream's copy causes ld65 dup-sym. +# data_shared.o: exports mul_cached_a, mul_src2_buf, mul_dma_lo/hi. +# c64-https's src/data.s already exports these — same conflict. +rm -f "$STAGING/mul_8x8.o" "$STAGING/data_shared.o" + +# --- 5. Re-archive into c64-https's expected location --- +# Order matches upstream's lib-p256-verify recipe so labels.txt diffs +# stay readable across bumps. rm -f "$ARCHIVE" "$AR65" a "$ARCHIVE" \ - "$OBJ_DIR/zp_config.o" \ - "$OBJ_DIR/fp256_raw.o" \ - "$OBJ_DIR/mod256_raw.o" \ - "$OBJ_DIR/points256_raw.o" \ - "$OBJ_DIR/ecdsa256_raw.o" \ - "$OBJ_DIR/curve256_raw.o" \ - "$OBJ_DIR/data_p256_raw.o" \ - "$OBJ_DIR/reu_equates_raw.o" - -# --- Per-source byte counts --- + "$STAGING/lib_version.o" \ + "$STAGING/lib_manifest.o" \ + "$STAGING/zp_config.o" \ + "$STAGING/constants.o" \ + "$STAGING/reu_config.o" \ + "$STAGING/fp256.o" \ + "$STAGING/mod256.o" \ + "$STAGING/curve256.o" \ + "$STAGING/points256_core.o" \ + "$STAGING/ecdsa256.o" \ + "$STAGING/data_p256.o" + +# --- 6. Per-source byte counts (for the supervisor's PR description) --- { echo "# nistcurves-p256.a per-source byte counts (ca65 .o file sizes)" - for src in zp_config fp256_raw mod256_raw points256_raw ecdsa256_raw curve256_raw data_p256_raw reu_equates_raw; do - bytes=$(wc -c < "$OBJ_DIR/$src.o") - printf '%-24s %d bytes (.o)\n' "$src" "$bytes" + for src in lib_version lib_manifest zp_config constants reu_config \ + fp256 mod256 curve256 points256_core ecdsa256 data_p256; do + if [ -f "$STAGING/$src.o" ]; then + bytes=$(wc -c < "$STAGING/$src.o") + printf '%-24s %d bytes (.o)\n' "$src" "$bytes" + fi done } > "$SIZES" diff --git a/tools/integration/build_nistcurves_p256_bin.sh b/tools/integration/build_nistcurves_p256_bin.sh new file mode 100755 index 0000000..2151a78 --- /dev/null +++ b/tools/integration/build_nistcurves_p256_bin.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# ============================================================================= +# tools/integration/build_nistcurves_p256_bin.sh -- Link the always-resident +# P-256 sibling archive (`build/lib/nistcurves-p256.a`) as a padded +# overlay .bin image suitable for embedding into the PRG via +# .incbin (src/crypto/shared/p256_overlay_blobs.s). +# +# W3 (library-ingestion architecture) artefact. Today the P-256 verify +# primitives ship always-resident in CRYPTO_RESIDENT; the W1 follow-on +# will move them into the cold-path CRYPTO_OVERLAY slot. This .bin is +# the staging image for that move: boot will DMA the bytes from +# CRYPTO_OVERLAY (PRG-load placement) to REU bank 2 slot $22100, and +# `crypto_swap_to_p256_verify` will DMA them back on demand. +# +# Mirrors tools/integration/build_nistcurves_p384_bin.sh's contract: +# * Inputs: build/lib/nistcurves-p256.a (built by +# build_nistcurves_p256.sh). +# * Output: build/lib/nistcurves-p256-verify.bin (7,680 B padded). +# * Per-image size report: build/lib/nistcurves-p256-verify.sizes.txt. +# * Per-image label file: build/labels-p256-verify.txt. +# * Per-image cc65 .dbg sidecar: +# build/lib/nistcurves-p256-verify.dbg. +# +# The cfg `cfg/p256-overlay-verify.cfg` routes CRYPTO_CODE / +# CRYPTO_RODATA / RODATA into the OVERLAY_REGION ($4200, $1E00 B) and +# pins DATA / BSS at $C000 RESIDENT just so labels resolve. The .bin +# output is truncated to $1E00 so only the overlay portion lands in +# the file. +# +# Usage (from top-level Makefile): +# bash tools/integration/build_nistcurves_p256_bin.sh +# ============================================================================= +set -eo pipefail + +PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +ARCHIVE="$PROJECT_ROOT/build/lib/nistcurves-p256.a" +CFG="$PROJECT_ROOT/cfg/p256-overlay-verify.cfg" +OUT_DIR="$PROJECT_ROOT/build/lib" +BIN_OUT="$OUT_DIR/nistcurves-p256-verify.bin" +SIZES_OUT="$OUT_DIR/nistcurves-p256-verify.sizes.txt" +LABELS_OUT="$PROJECT_ROOT/build/labels-p256-verify.txt" +MAP_OUT="$OUT_DIR/nistcurves-p256-verify.map" +DBG_OUT="${BIN_OUT%.bin}.dbg" + +# Live UCI CRYPTO_OVERLAY slot size: $1E00 = 7,680 B. The .bin is +# truncated / padded to exactly this many bytes so it DMAs cleanly +# into the live slot. +SLOT_BYTES=7680 + +LD65="${LD65:-ld65}" +AR65="${AR65:-ar65}" + +if [ ! -f "$ARCHIVE" ]; then + echo "ERROR: archive missing -- run tools/integration/build_nistcurves_p256.sh first" >&2 + echo " missing: $ARCHIVE" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" + +# Extract archive members; ld65 wants plain .o files on the command line. +scratch="$OUT_DIR/p256_bin_scratch" +rm -rf "$scratch" +mkdir -p "$scratch" +cp "$ARCHIVE" "$scratch/" + +archive_basename=$(basename "$ARCHIVE") +members=$( (cd "$scratch" && "$AR65" t "$archive_basename") | tr -d '\r' ) +if [ -z "$members" ]; then + echo "ERROR: $archive_basename appears empty" >&2 + exit 1 +fi +(cd "$scratch" && "$AR65" x "$archive_basename" $members) + +obj_args=() +for m in $members; do + obj_args+=("$scratch/$m") +done + +# Symbol --defines. The P-256 minimal subset imports REU register +# equates (reu_reu_lo, reu_addr_ctrl from reu_equates_raw.s inside the +# archive) plus shared mul infrastructure (mul_cached_a, mul_dma_lo, +# mul_dma_hi, mul_8x8, poly_prod_lo/hi, reu_fetch_mul_row) provided by +# the main PRG's in-tree poly1305.s / data.s / boot.s. For the +# standalone overlay link we pin those imports at fixed addresses -- +# either resolved from build/labels.txt (the main PRG's runtime +# addresses, so the overlay's fp_mul ends up reading/writing the right +# cells when the .bin is DMA'd live) or stubbed. See the P-384 +# overlay script for the same pattern. +MAIN_LABELS="$PROJECT_ROOT/build/labels.txt" + +lookup_label () { + local name="$1" + local fallback="$2" + if [ ! -f "$MAIN_LABELS" ]; then + echo "$fallback" + return + fi + local hex + hex=$(grep -E " \.${name}\$" "$MAIN_LABELS" | head -n1 | awk '{print $2}' | sed 's|^C:||') + if [ -z "$hex" ]; then + echo "$fallback" + else + printf '$%s' "$hex" + fi +} + +# Stubs are $0000 (intentional -- the .bin will be regenerated on the +# second pass once build/labels.txt exists, mirroring the P-384 +# bootstrap workflow in the top-level Makefile). +DEF_MUL_CACHED_A=$(lookup_label mul_cached_a '$0000') +DEF_MUL_DMA_LO=$(lookup_label mul_dma_lo '$0000') +DEF_MUL_DMA_HI=$(lookup_label mul_dma_hi '$0000') +DEF_REU_FETCH_MUL_ROW=$(lookup_label reu_fetch_mul_row '$0000') +DEF_POLY_PROD_LO=$(lookup_label poly_prod_lo '$CFFE') +DEF_POLY_PROD_HI=$(lookup_label poly_prod_hi '$CFFF') +DEF_MUL_8X8=$(lookup_label mul_8x8 '$0000') +# ec_scalar_mul is provided by the shim in src/crypto/ecdsa_verify.s in +# the always-resident path -- for the standalone overlay link it is +# referenced from ecdsa256_raw.s but never called from any path we +# actually exercise (TLS uses ec_scalar_mul_var; ec_scalar_mul is the +# Lim-Lee fixed-base entry whose body was stripped). Pin to a stub +# address; the in-PRG link resolves it properly. Same for +# mul_src2_buf (lives in src/data.s under the main PRG). +DEF_EC_SCALAR_MUL=$(lookup_label ec_scalar_mul '$0000') +DEF_MUL_SRC2_BUF=$(lookup_label mul_src2_buf '$0000') + +# Link. Route the P-256 archive's segments into OVERLAY_REGION via +# the cfg (which lists CRYPTO_CODE / CRYPTO_RODATA / RODATA / CODE as +# overlay-bound and DATA / BSS / CRYPTO_BSS as RESIDENT-bound). The +# zp_config.o object emits ZP equates only -- nothing in the output +# image -- so no segment routing is needed for it. +# NB: the archive's reu_equates_raw.s already exports reu_reu_lo and +# reu_addr_ctrl (the two slots v0.2.0's defensive REU init touches in +# ec_scalar_mul_var). Defining them again here would conflict -- +# pass only the symbols the archive imports without providing. +"$LD65" \ + -C "$CFG" \ + -o "$BIN_OUT" \ + -Ln "$LABELS_OUT" \ + -m "$MAP_OUT" \ + --dbgfile "$DBG_OUT" \ + --define mul_cached_a="$DEF_MUL_CACHED_A" \ + --define mul_dma_lo="$DEF_MUL_DMA_LO" \ + --define mul_dma_hi="$DEF_MUL_DMA_HI" \ + --define poly_prod_lo="$DEF_POLY_PROD_LO" \ + --define poly_prod_hi="$DEF_POLY_PROD_HI" \ + --define reu_fetch_mul_row="$DEF_REU_FETCH_MUL_ROW" \ + --define mul_8x8="$DEF_MUL_8X8" \ + --define ec_scalar_mul="$DEF_EC_SCALAR_MUL" \ + --define mul_src2_buf="$DEF_MUL_SRC2_BUF" \ + "${obj_args[@]}" + +# Normalise labels to VICE format so c64-test-harness's +# Labels.from_file() reader accepts it identically to build/labels.txt. +sed -i '' 's/^al 00\([0-9a-fA-F]\{4\}\) /al C:\1 /' "$LABELS_OUT" + +# Compute the on-disk overlay segment size from the .map (this only +# captures the OVERLAY_P256_VERIFY segment by name; other segments +# routed into OVERLAY_REGION via the cfg add to the file size but +# don't show under the named segment). +seg_name="OVERLAY_P256_VERIFY" +overlay_hex=$(awk -v seg="$seg_name" ' + /^Segment list:/ { in_seg=1; next } + /^Exports list/ { in_seg=0 } + in_seg && $1 == seg { print $4; exit } +' "$MAP_OUT") +overlay_bytes="" +if [ -n "$overlay_hex" ]; then + overlay_bytes=$(printf '%d' "0x$overlay_hex") +fi + +# Truncate / pad to exactly $SLOT_BYTES so the .bin DMAs into the +# live UCI overlay slot ($1E00 = 7,680 B). +truncate -s "$SLOT_BYTES" "$BIN_OUT" + +size=$(wc -c < "$BIN_OUT") +if [ "$size" -ne "$SLOT_BYTES" ]; then + echo "ERROR: $BIN_OUT is $size bytes, expected $SLOT_BYTES" >&2 + exit 1 +fi + +{ + echo "# nistcurves-p256-verify overlay image (W3)" + echo "# slot size: $SLOT_BYTES B (\$1E00 -- UCI CRYPTO_OVERLAY)" + if [ -n "$overlay_bytes" ]; then + echo "# unpadded overlay: $overlay_bytes B" + echo "# padded .bin: $size B" + echo "# headroom: $((SLOT_BYTES - overlay_bytes)) B" + if [ "$overlay_bytes" -gt "$SLOT_BYTES" ]; then + echo "# *** OVERFLOW: overlay exceeds slot by $((overlay_bytes - SLOT_BYTES)) B ***" + fi + else + echo "# unpadded overlay: (unknown -- see $MAP_OUT)" + echo "# padded .bin: $size B" + fi +} > "$SIZES_OUT" + +if [ -n "$overlay_bytes" ] && [ "$overlay_bytes" -gt "$SLOT_BYTES" ]; then + echo "ERROR: $BIN_OUT overlay segment ($overlay_bytes B) exceeds 7,680 B slot by $((overlay_bytes - SLOT_BYTES)) B" >&2 + exit 1 +fi + +echo "built $BIN_OUT ($size B padded; overlay = ${overlay_bytes:-unknown} B)" +cat "$SIZES_OUT" diff --git a/tools/integration/build_nistcurves_p384.sh b/tools/integration/build_nistcurves_p384.sh index 4190cb3..e834bbc 100755 --- a/tools/integration/build_nistcurves_p384.sh +++ b/tools/integration/build_nistcurves_p384.sh @@ -1,85 +1,48 @@ #!/usr/bin/env bash # ============================================================================= # tools/integration/build_nistcurves_p384.sh - Build c64-nist-curves P-384 -# overlay archives for the UCI backend smoke test. +# overlay archives for the UCI backend smoke test + Phase 5 production use. # -# Phase 1.5 split. Phase 1b's monolithic OVERLAY_P384 segment was 12,836 B -# and overflowed the live UCI CRYPTO_OVERLAY slot (7,680 B at $4200-$5FFF). -# This script now produces TWO archives, each fitting the 7.5 KB slot: +# Phase 1.5 split + W5 (library-ingestion architecture). Under the +# c64-lib-contract (libs/nistcurves v0.3.0), upstream publishes +# `make lib-p384-sha384` and `make lib-p384-verify` build targets. This +# script delegates the heavy lifting to upstream `make`, then performs +# adjustments before placing the archives at the locations the top-level +# Makefile expects: # -# build/lib/nistcurves-p384-sha384.a - SHA-384 streaming hash (sha384.s -# + the SHA-384 portion of the -# minimal data heredoc). -# Segment: OVERLAY_P384_SHA384. -# build/lib/nistcurves-p384-curve.a - fp384 + mod384 + points384 -# (post-strip) + curve384 + -# ecdsa384 (verify_384 ONLY - -# the verify_with_message_384 -# wrapper that imports -# sha384_init/update/final is -# stripped here; TLS drives SHA -# via the sha384 overlay) + the -# ec_scalar_mul_384 shim. -# Segment: OVERLAY_P384_CURVE. +# 1. Rebuild zp_config.o with c64-https's ZP-slot overrides. The +# sibling defaults sha_src/sha_len/sha_w_ptr/sha_w_ptr2 ($04/$06/ +# $08/$0a) collide with c64-https's $04-$09 = w32_* (ChaCha20/ +# Poly1305) and $0A-$0D = sha_temp1 (SHA-256). $3D-$44 is the +# lowest 8-byte contiguous free block above the canonical crypto +# ZP map and is dedicated to the SHA-384 call window. fp_mul_i / +# fp_mul_j also relocated ($39/$3a vs upstream $2c/$2d) to dodge +# the fe25519 claim. Other slots inherit upstream defaults. # -# Both archives also contribute disjoint subsets of data_raw.s into the -# resident DATA segment (CRYPTO_RESIDENT under the live cfg, at $C000 in -# the standalone overlay cfgs). The split is byte-for-byte identical to -# Phase 1b's combined data_raw.s so resident DATA growth stays at the -# Phase 1b figure (3,541 B); see the per-half data heredocs below. +# 2. Drop `mul_8x8.o` + `data_shared.o` from the curve archive +# (same conflict reasoning as build_nistcurves_p256.sh — c64-https +# provides them via src/crypto/poly1305.s + src/data.s). # -# Wrapper strip: -# ecdsa_verify_with_message_384 + ecdsa_verify_with_msg_384_tramp are -# physically removed from the curve archive's ecdsa384_raw.s so the -# archive does not import sha384_init/update/final (those live only in -# the OTHER half). TLS will call sha384_init / update / final -# directly from the sha384 overlay, then swap in the curve overlay, -# then call ecdsa_verify_384 with the digest pre-spliced into -# ecdsa_inputs_384[96..143]. See Phase 4a's TLS dispatcher work for -# the call sequencing. +# 3. Emit an ec_scalar_mul_384 shim. The upstream lib-p384-verify +# archive excludes points384_comb.s (the Lim-Lee fixed-base +# comb), so `ec_scalar_mul_384` is unresolved. We provide the +# symbol via a one-page shim that copies G into +# ec_base384_x/y and tail-calls ec_scalar_mul_var_384. Pattern +# mirrors c64-https's existing src/crypto/ecdsa_verify.s::ec_scalar_mul +# (Phase C.4 P-256 dispatcher). # -# ZP allocation (Phase 1.5): -# sha_src = $3D, sha_len = $3F, -# sha_w_ptr = $41, sha_w_ptr2 = $43. -# These supersede the sibling defaults ($04/$06/$08/$0A) which collide -# with c64-https's canonical $04-$09 = w32_* (ChaCha20/Poly1305) and -# $0A-$0D = sha_temp1 (SHA-256). $3D-$44 is the lowest 8-byte -# contiguous free block above the canonical crypto ZP map (ec_scalar_ptr -# ends at $3C; nothing in src/* claims $3D-$FA except the universal -# $FB-$FF general pointers). Verified by grep against -# src/constants.inc, src/crypto/shared/zp_canon.inc, and all .s files -# under src/. Safe during the SHA-384 call window because no other -# crypto / TLS path uses these slots. -# -# Excluded (same as Phase 1b — see comments inline): -# - ec_precompute_384 / ec_scalar_mul_384 (Lim-Lee body) — replaced by -# the in-staging shim that copies G into ec_base384_x/y and -# tail-calls ec_scalar_mul_var_384. -# - Lim-Lee anchor tables and comb-scalar state. -# - sha384_msg_buf (1024 B test scratch). -# - mul_8x8 / sqtab_init / mul_dma_lo/hi / mul_cached_a / mul_src2_buf / -# reu_fetch_mul_row / poly_prod_lo/hi / sqtab_lo/hi - resolved at link -# time by build_nistcurves_p384_bin.sh's --define stubs. -# - ecdsa_verify_with_message_384 + ecdsa_verify_with_msg_384_tramp -# (Phase 1.5 NEW — see "Wrapper strip" above). -# -# The script stages the sibling's .s files in build/lib/nistcurves_p384_staging/, -# applies sed-patches to override their `.segment` directives and rewrite -# them into the new dual-segment scheme. -# -# Usage (from top-level Makefile): -# bash tools/integration/build_nistcurves_p384.sh -# Produces: -# build/lib/nistcurves-p384-sha384.a -# build/lib/nistcurves-p384-curve.a -# build/lib/nistcurves-p384-sha384.sizes.txt -# build/lib/nistcurves-p384-curve.sizes.txt +# Outputs: +# build/lib/nistcurves-p384-sha384.a - SHA-384 overlay archive +# build/lib/nistcurves-p384-curve.a - curve verify overlay archive +# build/lib/nistcurves-p384-{sha384,curve}.sizes.txt # ============================================================================= set -eo pipefail # --- Paths --- PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -LIB_SRC="$PROJECT_ROOT/libs/nistcurves/src" +LIB_DIR="$PROJECT_ROOT/libs/nistcurves" +LIB_SRC="$LIB_DIR/src" +LIB_BUILD="$LIB_DIR/build" STAGING="$PROJECT_ROOT/build/lib/nistcurves_p384_staging" OUT_DIR="$PROJECT_ROOT/build/lib" ARCHIVE_SHA="$OUT_DIR/nistcurves-p384-sha384.a" @@ -90,333 +53,101 @@ SIZES_CURVE="$OUT_DIR/nistcurves-p384-curve.sizes.txt" CA65="${CA65:-ca65}" AR65="${AR65:-ar65}" -# --- Canonical ZP defines --- -# The sibling's zp_config.s wraps every ZP equate in .ifndef, so command-line -# -D values win over the defaults. We pin the sibling to c64-https's -# canonical ZP map (src/crypto/shared/zp_canon.inc) AND override the SHA-384 -# pointer slots to $3D-$44 (Phase 1.5). -# -# Why $3D-$44? The sibling's defaults sha_src=$04, sha_len=$06, -# sha_w_ptr=$08, sha_w_ptr2=$0a collide with c64-https's canonical -# $04-$09 = w32_* (ChaCha20/Poly1305) and $0A-$0D = sha_temp1 (SHA-256). -# $3D-$44 is the lowest 8-byte contiguous free range above the canonical -# crypto ZP map (ec_scalar_ptr ends at $3C); see this file's header for -# the full audit. Demonstrated free during the SHA-384 call window: -# - Not used by ip65 ($02-$1B), ChaCha20/Poly1305 ($04-$1D), -# SHA-256 ($0A-$13), TLS record layer ($1E-$21), fp_* ECDSA bignum -# ($22-$2B + $39-$3C), fe25519 ($2C-$35), or x25519 ($38-$3A). -# - $36-$37 was reserved for fe25519 future expansion (only 2 bytes, -# insufficient for the 8 bytes SHA-384 needs). -# -# Note: fp_mul_i / fp_mul_j overlap with x25_byte_idx / x25_bit_mask at -# $39/$3a. This is fine because x25519 and P-384 run at different times -# (different overlays; only one resident at a time) and the canonical -# map documents the time-sharing. -ZP_DEFINES=( - '-Dproc_port=$01' - '-Dzp_tmp1=$02' - '-Dzp_tmp2=$03' - '-Dzp_ptr1=$fb' - '-Dzp_ptr2=$fd' - '-Dfp_src1=$22' - '-Dfp_src2=$24' - '-Dfp_dst=$26' - '-Dfp_misc=$28' - '-Dfp_carry=$2a' - '-Dfp_loop=$2b' - '-Dfp_mul_i=$39' - '-Dfp_mul_j=$3a' - '-Dec_scalar_ptr=$3b' - '-Dpoly_i=$1a' - '-Dpoly_j=$1b' - '-Dpoly_carry=$1c' - '-Dpoly_tmp=$1d' - # SHA-384 streaming pointer slots (Phase 1.5 — moved out of the - # sibling's $04-$0B defaults to avoid the canonical w32_* / sha_temp1 - # collision; see header). - '-Dsha_src=$3d' - '-Dsha_len=$3f' - '-Dsha_w_ptr=$41' - '-Dsha_w_ptr2=$43' +# --- ZP-slot overrides (c64-https canonical map + SHA-384 isolated window) --- +ZP_OVERRIDES=( + '-D' 'zp_ptr2=$3d' + '-D' 'fp_mul_i=$39' + '-D' 'fp_mul_j=$3a' + # SHA-384 streaming pointer slots (moved out of $04-$0b defaults + # to avoid w32_* / sha_temp1 collision; see header). + '-D' 'sha_src=$3d' + '-D' 'sha_len=$3f' + '-D' 'sha_w_ptr=$41' + '-D' 'sha_w_ptr2=$43' ) +# Note: zp_ptr2 and sha_src both pin $3d. zp_ptr2 is curve-archive-only +# (ecdsa384.s imports it); sha_src is SHA-archive-only. They never share +# a call window — curve overlay and sha overlay are mutually exclusive +# in the live CRYPTO_OVERLAY slot. The defaults below feed BOTH archives' +# zp_config.o builds, but each archive's call window only consumes the +# slot relevant to its own bodies. Safe. + +# --- 1. Build upstream's lib-p384-sha384 + lib-p384-verify archives --- +# Same caveat as P-256: upstream's Makefile builds every module with the +# same recipe, so we can't pass -D overrides via `make CA65=...` (would +# error on .importzp redefinition in non-zp_config files). Build upstream +# with defaults, then rebuild zp_config.o ourselves below. +# +# Note on c64-lib-contract SPEC §8.1: only nistcurves v0.3.0's +# `mul_8x8.s` references sqtab_lo / sqtab_hi (via the local +# `.ifndef LIB_SHARED_SQTAB_BASE` equate in that file). Step 4 drops +# `mul_8x8.o` and `data_shared.o` from the curve archive entirely; +# the SHA-384 archive (separate compile path) never linked mul_8x8 +# in the first place since SHA-384 is multiply-free. So the upstream +# default LIB_SHARED_SQTAB_BASE baked into mul_8x8.o is discarded +# before it reaches either overlay's link — no override required at +# the nistcurves Makefile invocation. +echo "[p384] building libs/nistcurves lib-p384-sha384 + lib-p384-verify (upstream defaults)..." +make -s -C "$LIB_DIR" lib-p384-sha384 lib-p384-verify >/dev/null + +UPSTREAM_SHA_ARCHIVE="$LIB_BUILD/lib/nistcurves-p384-sha384.a" +UPSTREAM_CURVE_ARCHIVE="$LIB_BUILD/lib/nistcurves-p384-verify.a" +for f in "$UPSTREAM_SHA_ARCHIVE" "$UPSTREAM_CURVE_ARCHIVE"; do + if [ ! -f "$f" ]; then + echo "ERROR: upstream archive missing: $f" >&2 + exit 1 + fi +done -# --- Stage sources --- +# --- 2. Stage upstream object files --- rm -rf "$STAGING" -mkdir -p "$STAGING" +mkdir -p "$STAGING/sha" "$STAGING/curve" "$OUT_DIR" +cp "$UPSTREAM_SHA_ARCHIVE" "$STAGING/sha/upstream.a" +cp "$UPSTREAM_CURVE_ARCHIVE" "$STAGING/curve/upstream.a" +(cd "$STAGING/sha" && "$AR65" x upstream.a $( "$AR65" t upstream.a )) +(cd "$STAGING/curve" && "$AR65" x upstream.a $( "$AR65" t upstream.a )) -# constants.s and zp_config.s are shared between both halves. zp_config.s -# is .include'd transitively; we assemble it once with -D overrides and -# add the resulting .o to BOTH archives. -cp "$LIB_SRC"/constants.s "$STAGING/" -cp "$LIB_SRC"/zp_config.s "$STAGING/" -cp "$LIB_SRC"/fp384.s "$STAGING/fp384_raw.s" -cp "$LIB_SRC"/mod384.s "$STAGING/mod384_raw.s" -cp "$LIB_SRC"/points384.s "$STAGING/points384_raw.s" -cp "$LIB_SRC"/curve384.s "$STAGING/curve384_raw.s" -cp "$LIB_SRC"/sha384.s "$STAGING/sha384_raw.s" -cp "$LIB_SRC"/ecdsa384.s "$STAGING/ecdsa384_raw.s" - -# --- Strip points384.s of ec_precompute_384 and ec_scalar_mul_384 --- -# Same surgery as Phase 1b. Bodies between lines 787 and 1488 inclusive -# are physically removed; the related `.export` and `.import` lines are -# scrubbed below. ec_gx384 / ec_gy384 imports are KEPT (used by the shim). -# -# OPTION A choice (Phase 1b): the Lim-Lee body for ec_scalar_mul_384 is -# stripped; an in-staging shim file (ec_scalar_mul_384_shim_raw.s, emitted -# below) provides the symbol by copying G into ec_base384_x/y and -# tail-calling ec_scalar_mul_var_384. This avoids the ~24 KB Lim-Lee -# anchor table + ~100 s ec_precompute_384 boot drag. Pattern mirrors -# src/crypto/ecdsa_verify.s::ec_scalar_mul (Phase C.4 for P-256). -# BSD-sed compat: macOS sed requires `-i ''` (empty extension). -sed -i '' '787,1488d' "$STAGING/points384_raw.s" -sed -i '' '/^\.export ec_precompute_384, ec_scalar_mul_384$/d' "$STAGING/points384_raw.s" -sed -i '' '/^\.import ec_anchor[1-8]_384_x/d' "$STAGING/points384_raw.s" -sed -i '' '/^\.import ec_anchor[1-8]_384_y/d' "$STAGING/points384_raw.s" -sed -i '' '/^\.import cm_k_384, mul_dma_lo$/d' "$STAGING/points384_raw.s" -sed -i '' '/^\.import ec384_sc_byte, ec384_sc_mask, ec384_precomp_i$/d' "$STAGING/points384_raw.s" - -# --- Strip ecdsa_verify_with_message_384 wrapper from the curve archive --- -# Phase 1.5 NEW. The wrapper imports sha384_init/update/final, which live -# in the OTHER overlay half (sha384 archive). TLS now drives the SHA -# overlay manually then swaps in the curve overlay and calls -# ecdsa_verify_384 directly with the digest pre-spliced into -# ecdsa_inputs_384[96..143]. +# --- 3. Rebuild zp_config.o with c64-https overrides (shared by both archives) --- +"$CA65" \ + --cpu 6502 \ + -g \ + -I "$LIB_SRC" \ + "${ZP_OVERRIDES[@]}" \ + -o "$STAGING/zp_config.o" \ + "$LIB_SRC/zp_config.s" + +# Distribute the overridden zp_config.o into both staging trees. +cp "$STAGING/zp_config.o" "$STAGING/sha/zp_config.o" +cp "$STAGING/zp_config.o" "$STAGING/curve/zp_config.o" + +# --- 4. Drop conflicting members from the curve archive --- +# Same reasoning as P-256: mul_8x8.o + data_shared.o collide with +# c64-https's in-tree src/crypto/poly1305.s + src/data.s exports. +rm -f "$STAGING/curve/mul_8x8.o" "$STAGING/curve/data_shared.o" + +# --- 5. Build ec_scalar_mul_384 shim (Option A: variable-base scalar-mul) --- +# The upstream lib-p384-verify archive excludes points384_comb.s (the +# Lim-Lee fixed-base comb), so `ec_scalar_mul_384` (the symbol ecdsa384.s +# imports at line 49) is unresolved. We provide it via this shim that +# copies G into ec_base384_x/y and tail-calls ec_scalar_mul_var_384. # -# In libs/nistcurves@90830c9 the wrapper + trampoline span lines 568-end -# of ecdsa384.s. We delete from line 568 to the end of file ("568,$d") -# and scrub: -# - the two wrapper .export lines (verify_with_message_384 + -# verify_with_msg_384_tramp) -# - the .import sha384_init/update/final line -# - the .import sha384_msg_buf reference (the test trampoline only) -# - the .import ecdsa384_msg_struct_ptr line (wrapper-only scratch) -# - the .import ecdsa_inputs_384, ecdsa_result_msg_384 line -# (test-trampoline only — the standalone curve archive doesn't need -# these symbols since the wrapper that consumed them is gone; ld65 -# would fail to resolve them if we left the .import in place since -# they live in the data heredoc as exports but nothing else references -# them after the wrapper is dropped — keep the .import to keep the -# symbol pulled in via .import-as-link-anchor; data_curve_raw.s still -# exports both for the harness driver path). -# We sed only on the curve copy AFTER making a separate sha-only copy is -# unnecessary because sha384_raw.s never sees ecdsa384_raw.s. -sed -i '' '568,$d' "$STAGING/ecdsa384_raw.s" -sed -i '' '/^\.export ecdsa_verify_with_message_384$/d' "$STAGING/ecdsa384_raw.s" -sed -i '' '/^\.export ecdsa_verify_with_msg_384_tramp$/d' "$STAGING/ecdsa384_raw.s" -sed -i '' '/^\.import sha384_init, sha384_update, sha384_final$/d' "$STAGING/ecdsa384_raw.s" -sed -i '' '/^\.import ecdsa384_msg_struct_ptr$/d' "$STAGING/ecdsa384_raw.s" - -# --- Drop test-only sha384_msg_buf import from sha384.s --- -# sha384.s `.import sha384_digest, sha384_msg_buf` at file scope but never -# references sha384_msg_buf in code. We drop the 1024-byte test scratch -# buffer from data_raw.s, so the import must go too. -sed -i '' 's/^\(\.import sha384_digest, sha384_msg_buf\)$/.import sha384_digest/' "$STAGING/sha384_raw.s" -# Same scrub on the curve-half ecdsa384_raw.s (the .import line is on a -# different line in ecdsa384.s; preserve only sha384_digest if the line is -# present after the wrapper-strip above — it should NOT be, since the -# import for sha384_init/update/final/digest/msg_buf is bundled together. -# Defensive: leave a no-op sed in case the upstream layout changes). -sed -i '' 's/^\(\.import sha384_digest, sha384_msg_buf\)$/.import sha384_digest/' "$STAGING/ecdsa384_raw.s" - -# --- Emit data_curve_raw.s (resident DATA exports for the curve archive) --- -# Hand-extracted from the sibling's data.s — non-SHA portion only. -# This is the SAME byte-for-byte content as Phase 1b's data_raw.s up to -# (but not including) the SHA-384 streaming state block. Land in DATA -# (= CRYPTO_RESIDENT in the live cfg, $C000 in the standalone cfgs). -cat > "$STAGING/data_curve_raw.s" <<'DATA_EOF' -; ============================================================================= -; data_curve_raw.s - Resident DATA exports for the curve / verify half of -; the split P-384 overlay (Phase 1.5). Non-SHA portion of Phase 1b's -; minimal data_raw.s. Lands in CRYPTO_RESIDENT under the live cfg. -; -; The 240 B BE input struct (ecdsa_inputs_384) is shared with the SHA -; archive's caller path -- TLS pre-stages r/s/Qx/Qy here, then drives -; sha384_init/update/final to populate the digest at struct[96..143], -; then swaps in this overlay and calls ecdsa_verify_384. -; ============================================================================= +# Pattern mirrors src/crypto/ecdsa_verify.s::ec_scalar_mul (Phase C.4 +# P-256 dispatcher). Slower per-call than the real Lim-Lee comb but +# avoids the ~24 KB REU bank-2 anchor table + ~100 s +# ec_precompute_384 boot drag. +cat > "$STAGING/curve/ec_scalar_mul_384_shim.s" <<'SHIM_EOF' .setcpu "6502" -.segment "DATA" - -; --- P-384 field arithmetic working buffers (48 bytes each) --- -.export fp384_wide -fp384_wide: .res 96, 0 ; 768-bit product from multiply -.export fp384_tmp1 -fp384_tmp1: .res 48, 0 -.export fp384_tmp2 -fp384_tmp2: .res 48, 0 -.export fp384_tmp3 -fp384_tmp3: .res 48, 0 -.export fp384_tmp4 -fp384_tmp4: .res 48, 0 - -; --- P-384 result registers --- -.export fp384_r0 -fp384_r0: .res 48, 0 -.export fp384_r1 -fp384_r1: .res 48, 0 -.export fp384_r2 -fp384_r2: .res 48, 0 -.export fp384_r3 -fp384_r3: .res 48, 0 - -; --- P-384 modular inverse working space --- -.export fp384_inv_u -fp384_inv_u: .res 48, 0 -.export fp384_inv_v -fp384_inv_v: .res 48, 0 -.export fp384_inv_x1 -fp384_inv_x1: .res 48, 0 -.export fp384_inv_x2 -fp384_inv_x2: .res 48, 0 - -; --- P-384 point storage (Jacobian: X=48 + Y=48 + Z=48 = 144 bytes) --- -.export ec384_p1 -ec384_p1: .res 144, 0 -.export ec384_p2 -ec384_p2: .res 144, 0 -.export ec384_p3 -ec384_p3: .res 144, 0 - -; --- P-384 point math temporaries --- -.export ec384_t1 -ec384_t1: .res 48, 0 -.export ec384_t2 -ec384_t2: .res 48, 0 -.export ec384_t3 -ec384_t3: .res 48, 0 -.export ec384_t4 -ec384_t4: .res 48, 0 -.export ec384_t5 -ec384_t5: .res 48, 0 -.export ec384_t6 -ec384_t6: .res 48, 0 - -; --- P-384 affine output --- -.export ec384_affine_x -ec384_affine_x: .res 48, 0 -.export ec384_affine_y -ec384_affine_y: .res 48, 0 - -; --- Variable-base scalar-mul input (affine, 48 bytes each, LE). -; Consumed by ec_scalar_mul_var_384 (ECDSA-verify building block) and -; populated by the ec_scalar_mul_384 shim (G -> ec_base384_x/y). -.export ec_base384_x -ec_base384_x: .res 48, 0 -.export ec_base384_y -ec_base384_y: .res 48, 0 - -; --- P-384 Solinas reduction scratch --- -.export fp384_red_tmp -fp384_red_tmp: .res 49, 0 - -; --- ECDSA verify scratch (P-384). All 48-byte little-endian unless noted. --- -.export ecdsa384_r -ecdsa384_r: .res 48, 0 ; LE r (byte-reversed from BE input) -.export ecdsa384_s -ecdsa384_s: .res 48, 0 ; LE s -.export ecdsa384_h -ecdsa384_h: .res 48, 0 ; LE message hash -.export ecdsa384_qx -ecdsa384_qx: .res 48, 0 ; LE public-key affine X -.export ecdsa384_qy -ecdsa384_qy: .res 48, 0 ; LE public-key affine Y -.export ecdsa384_w -ecdsa384_w: .res 48, 0 ; LE w = s^-1 mod n -.export ecdsa384_u1 -ecdsa384_u1: .res 48, 0 ; LE u1 = h*w mod n -.export ecdsa384_u2 -ecdsa384_u2: .res 48, 0 ; LE u2 = r*w mod n -.export ecdsa384_u1_be -ecdsa384_u1_be: .res 48, 0 ; BE u1 (scalar_mul input) -.export ecdsa384_u2_be -ecdsa384_u2_be: .res 48, 0 ; BE u2 (scalar_mul_var input) -.export ecdsa384_u1g_x -ecdsa384_u1g_x: .res 48, 0 ; LE affine X of u1*G -.export ecdsa384_u1g_y -ecdsa384_u1g_y: .res 48, 0 ; LE affine Y of u1*G - -; --- fp_reverse48 staging buffer (one 48-byte scratch). --- -.export fp_rev_buf_384 -fp_rev_buf_384: .res 48, 0 - -; --- ECDSA verify test-driver staging buffer (240 B BE struct). -; The c64-test-harness jsr() helper cannot pass register arguments, so -; the BE input struct is staged here and the test trampoline points -; A/X at it. TLS pre-fills r|s|Qx|Qy here, then runs SHA over the -; handshake transcript, then writes the digest into struct[96..143], -; then swaps in the curve overlay and calls ecdsa_verify_384. -.export ecdsa_inputs_384 -ecdsa_inputs_384: .res 240, 0 ; r|s|h|Qx|Qy each 48 B BE - -; --- ECDSA result byte (test driver / dispatcher result) --- -.export ecdsa_result_msg_384 -ecdsa_result_msg_384: .byte 0 -DATA_EOF - -# --- Emit data_sha_raw.s (resident DATA exports for the SHA archive) --- -# Hand-extracted from the sibling's data.s — SHA-384 portion only. -# Same byte-for-byte content as Phase 1b's data_raw.s SHA-384 block. -cat > "$STAGING/data_sha_raw.s" <<'DATA_EOF' -; ============================================================================= -; data_sha_raw.s - Resident DATA exports for the SHA-384 half of the split -; P-384 overlay (Phase 1.5). SHA-384 portion of Phase 1b's minimal -; data_raw.s. Lands in CRYPTO_RESIDENT under the live cfg. -; -; Storage convention: each 64-bit word is held LITTLE-ENDIAN-WITHIN-WORD, -; matching 6502 ADC carry propagation. All buffers are owned exclusively -; by sha384.s. sha384_msg_buf (1 KB test scratch) is intentionally OMITTED -; (would inflate resident DATA by ~25%; not used by sha384.s itself). -; ============================================================================= -.setcpu "6502" - -.segment "DATA" - -.export sha_state -sha_state: .res 64, 0 ; H[0..7], 8 bytes each LE-within-word -.export sha_w -sha_w: .res 640, 0 ; W[0..79] message schedule, 8 B each LE -.export sha_abcdefgh -sha_abcdefgh: .res 64, 0 ; working a..h, 8 B each LE -.export sha_t -sha_t: .res 16, 0 ; T1 (8 B) + T2 (8 B), LE -.export sha_scratch -sha_scratch: .res 64, 0 ; 8x 8-byte scratch slots for round helpers -.export sha_block_buf -sha_block_buf: .res 128, 0 ; current 1024-bit block (wire order) -.export sha_block_len -sha_block_len: .byte 0 ; bytes used in sha_block_buf, 0..127 -.export sha_total_len -sha_total_len: .res 16, 0 ; 128-bit total bit count, LE on-chip -.export sha384_digest -sha384_digest: .res 48, 0 ; final BE digest output (read by curve - ; overlay's ecdsa_verify_384 path after - ; TLS splices it into ecdsa_inputs_384[96..143]) -DATA_EOF - -# --- Emit ec_scalar_mul_384 shim (Option A) --- -# Pattern mirrors src/crypto/ecdsa_verify.s::ec_scalar_mul (Phase C.4 P-256 -# dispatcher). Lives in OVERLAY_P384_CURVE alongside the rest of the curve -# code. ec_gx384 and ec_gy384 are each contiguous 48-byte slots in -# curve384.s RODATA, so a simple ldy #47 / lda src,y / sta dst,y / dey / -# bpl loop works (47 = $2F has bit 7 clear; DEY updates N flag based on -# the decremented Y, not the LDA byte). -cat > "$STAGING/ec_scalar_mul_384_shim_raw.s" <<'SHIM_EOF' ; ============================================================================= -; ec_scalar_mul_384_shim_raw.s -- Phase 1b shim for the stripped Lim-Lee -; fixed-base scalar-mul (Option A). Provides ec_scalar_mul_384 by copying -; G into ec_base384_x/y and tail-calling ec_scalar_mul_var_384. +; ec_scalar_mul_384_shim.s -- Option A shim for the Lim-Lee fixed-base +; scalar-mul (excluded from lib-p384-verify). Provides ec_scalar_mul_384 +; by copying G into ec_base384_x/y and tail-calling ec_scalar_mul_var_384. ; -; Mirrors the Phase C.4 P-256 dispatcher pattern in -; src/crypto/ecdsa_verify.s::ec_scalar_mul. Slower per-call than the real -; Lim-Lee comb (double-and-add vs. windowed comb) but avoids the ~24 KB -; REU bank-2 anchor table + ~100 s ec_precompute_384 boot drag. -; -; Phase 1.5: lives in OVERLAY_P384_CURVE (was OVERLAY_P384 in Phase 1b). +; Lives in the P384 code segment (same overlay slot as the rest of the +; curve archive). ; ============================================================================= -.setcpu "6502" -.segment "OVERLAY_P384_CURVE" +.segment "LIB_NISTCURVES_P384_CODE" .export ec_scalar_mul_384 @@ -425,7 +156,7 @@ cat > "$STAGING/ec_scalar_mul_384_shim_raw.s" <<'SHIM_EOF' .import ec_scalar_mul_var_384 ec_scalar_mul_384: - ; Copy G.x -> ec_base384_x (48 bytes; ldy #47, dey/bpl safe) + ; Copy G.x -> ec_base384_x (48 bytes; ldy #47 / bpl safe since 47 < 128) ldy #47 @cp_x: lda ec_gx384,y sta ec_base384_x,y @@ -437,141 +168,65 @@ ec_scalar_mul_384: sta ec_base384_y,y dey bpl @cp_y - jmp ec_scalar_mul_var_384 ; tail-call: result and clobbers passthrough + jmp ec_scalar_mul_var_384 ; tail-call SHIM_EOF -# --- Route CODE / RODATA segments into per-half OVERLAY segments --- -# Phase 1.5 split: each source goes into either OVERLAY_P384_SHA384 (just -# sha384) or OVERLAY_P384_CURVE (everything else). -# -# fp384_raw.s also has a `.segment "BSS"` block at the tail (53 B) for -# fp384_sqr_extra / mul_src2_buf_384 / fp384_sqr_pairs. Those land in -# CRYPTO_RESIDENT BSS via the canonical BSS segment name (no rewrite -# needed) since the overlay gets swapped out between calls. -sed -i '' 's/^\.segment "CODE"/.segment "OVERLAY_P384_CURVE"/' "$STAGING/fp384_raw.s" -sed -i '' 's/^\.segment "CODE"/.segment "OVERLAY_P384_CURVE"/' "$STAGING/mod384_raw.s" -sed -i '' 's/^\.segment "CODE"/.segment "OVERLAY_P384_CURVE"/' "$STAGING/points384_raw.s" -sed -i '' 's/^\.segment "CODE"/.segment "OVERLAY_P384_CURVE"/' "$STAGING/ecdsa384_raw.s" -# curve384.s uses RODATA -- route into OVERLAY_P384_CURVE (read-only constants). -sed -i '' 's/^\.segment "RODATA"/.segment "OVERLAY_P384_CURVE"/' "$STAGING/curve384_raw.s" -# sha384.s: code (CODE) and IV/K[80] round constants (RODATA) both into -# the SHA-384 overlay. -sed -i '' 's/^\.segment "CODE"/.segment "OVERLAY_P384_SHA384"/' "$STAGING/sha384_raw.s" -sed -i '' 's/^\.segment "RODATA"/.segment "OVERLAY_P384_SHA384"/' "$STAGING/sha384_raw.s" - -# --- mod384.s curve constants (ec_p384, ec_n384) live in CODE segment in -# the sibling and are emitted inline with .byte directives. After the -# CODE->OVERLAY_P384_CURVE rewrite they flow into the curve overlay -# alongside the code that reads them; that is intentional -# (fp_mod_reduce384 reads ec_p384 and IS in the curve overlay). - -# --- Forbidden-symbol guard (curve archive only) --- -# After the strip, points384_raw.s must NOT reference any of the removed -# Lim-Lee comb / precompute symbols. ec_gx384 / ec_gy384 / cm_k_384 / -# ec_anchor*_384 patterns CAN appear as comments; we strip leading -# whitespace and a leading `;` before the grep so we only match active -# code. ec_gx384 / ec_gy384 are intentionally left LIVE in the staging -# tree (used by the shim). cm_k_384, ec_anchor[0-9]_384, ec384_sc_byte/ -# mask, ec384_precomp_i remain forbidden -- those bodies were physically -# removed. -if grep -v '^\s*;' "$STAGING/points384_raw.s" \ - | grep -qE '\bec384_sc_byte\b|\bec384_sc_mask\b|\bec384_precomp_i\b|\bcm_k_384\b|\bec_anchor[0-9]_384\b'; then - echo "ERROR: stripped points384 still references removed-body symbols" >&2 - grep -v '^\s*;' "$STAGING/points384_raw.s" \ - | grep -nE '\bec384_sc_byte\b|\bec384_sc_mask\b|\bec384_precomp_i\b|\bcm_k_384\b|\bec_anchor[0-9]_384\b' \ - | head -5 >&2 - exit 1 -fi - -# --- Forbidden-symbol guard (Phase 1.5 wrapper-strip) --- -# After the wrapper-strip, ecdsa384_raw.s must NOT reference any of the -# SHA-384 entry points (those live in the OTHER overlay half) or the -# wrapper-only labels. Active-code grep only. -if grep -v '^\s*;' "$STAGING/ecdsa384_raw.s" \ - | grep -qE '\bsha384_init\b|\bsha384_update\b|\bsha384_final\b|\becdsa_verify_with_message_384\b|\becdsa_verify_with_msg_384_tramp\b|\becdsa384_msg_struct_ptr\b'; then - echo "ERROR: stripped ecdsa384 still references wrapper / SHA symbols" >&2 - grep -v '^\s*;' "$STAGING/ecdsa384_raw.s" \ - | grep -nE '\bsha384_init\b|\bsha384_update\b|\bsha384_final\b|\becdsa_verify_with_message_384\b|\becdsa_verify_with_msg_384_tramp\b|\becdsa384_msg_struct_ptr\b' \ - | head -5 >&2 - exit 1 -fi - -# --- Assemble each staged .s file --- -OBJ_DIR="$STAGING/obj" -rm -rf "$OBJ_DIR" -mkdir -p "$OBJ_DIR" "$OUT_DIR" - -# zp_config.s is the single point of truth for the library's ZP equates. -# We assemble it with `-D` overrides so the sibling's defaults are -# replaced by c64-https's canonical ZP map (with the Phase 1.5 SHA-384 -# slot moves). The other source files use `.importzp` to pull these -# equates from the linker-resolved zp_config.o. -# `-g` embeds cc65 debug info into each .o; the overlay ld65 invocations -# in build_nistcurves_p384_bin.sh merge it into build/lib/overlay-p384-*.dbg -# sidecars. Does not change emitted code bytes. "$CA65" \ + --cpu 6502 \ -g \ - -I "$STAGING" \ - -I "$PROJECT_ROOT/src/crypto/shared" \ - "${ZP_DEFINES[@]}" \ - -o "$OBJ_DIR/zp_config.o" "$STAGING/zp_config.s" - -# Other files: NO -D. Let `.importzp` resolve through the linker to -# zp_config.o's `.exportzp` declarations. If we passed -D here the -# assembler would treat the symbol as locally-defined absolute and -# conflict with the .importzp declaration. -for src in fp384_raw mod384_raw points384_raw curve384_raw \ - sha384_raw ecdsa384_raw ec_scalar_mul_384_shim_raw \ - data_curve_raw data_sha_raw; do - "$CA65" \ - -g \ - -I "$STAGING" \ - -I "$PROJECT_ROOT/src/crypto/shared" \ - -o "$OBJ_DIR/$src.o" "$STAGING/$src.s" -done + -I "$LIB_SRC" \ + -o "$STAGING/curve/ec_scalar_mul_384_shim.o" \ + "$STAGING/curve/ec_scalar_mul_384_shim.s" -# --- Archive: nistcurves-p384-sha384.a (SHA-384 hash overlay half) --- -# Members: zp_config + sha384_raw + data_sha_raw. -# The SHA archive does NOT contain ANY curve code; ld65 link resolves -# only the SHA exports + the resident SHA DATA buffers. +# --- 6. Re-archive both halves --- +# SHA archive: zp_config + lib_version + lib_manifest + sha384 + data_sha. +# Self-contained — no curve / mul code. rm -f "$ARCHIVE_SHA" "$AR65" a "$ARCHIVE_SHA" \ - "$OBJ_DIR/zp_config.o" \ - "$OBJ_DIR/sha384_raw.o" \ - "$OBJ_DIR/data_sha_raw.o" - -# --- Archive: nistcurves-p384-curve.a (curve / verify overlay half) --- -# Members: zp_config + fp384 + mod384 + points384 + curve384 + -# ecdsa384 (verify_384 only) + shim + data_curve_raw. -# The curve archive does NOT contain ANY SHA code or SHA DATA exports; -# ld65 link resolves only ecdsa_verify_384 + the resident curve DATA -# buffers. + "$STAGING/sha/lib_version.o" \ + "$STAGING/sha/lib_manifest.o" \ + "$STAGING/sha/zp_config.o" \ + "$STAGING/sha/sha384.o" \ + "$STAGING/sha/data_sha.o" + +# Curve archive: zp_config + lib_version + lib_manifest + constants + +# reu_config + fp384 + mod384 + curve384 + points384_core + ecdsa384 + +# shim + data_p384. mul_8x8 + data_shared dropped (c64-https owns). rm -f "$ARCHIVE_CURVE" "$AR65" a "$ARCHIVE_CURVE" \ - "$OBJ_DIR/zp_config.o" \ - "$OBJ_DIR/fp384_raw.o" \ - "$OBJ_DIR/mod384_raw.o" \ - "$OBJ_DIR/points384_raw.o" \ - "$OBJ_DIR/curve384_raw.o" \ - "$OBJ_DIR/ecdsa384_raw.o" \ - "$OBJ_DIR/ec_scalar_mul_384_shim_raw.o" \ - "$OBJ_DIR/data_curve_raw.o" - -# --- Per-source byte counts --- + "$STAGING/curve/lib_version.o" \ + "$STAGING/curve/lib_manifest.o" \ + "$STAGING/curve/zp_config.o" \ + "$STAGING/curve/constants.o" \ + "$STAGING/curve/reu_config.o" \ + "$STAGING/curve/fp384.o" \ + "$STAGING/curve/mod384.o" \ + "$STAGING/curve/curve384.o" \ + "$STAGING/curve/points384_core.o" \ + "$STAGING/curve/ecdsa384.o" \ + "$STAGING/curve/ec_scalar_mul_384_shim.o" \ + "$STAGING/curve/data_p384.o" + +# --- 7. Per-source byte counts --- { echo "# nistcurves-p384-sha384.a per-source byte counts (ca65 .o file sizes)" - for src in zp_config sha384_raw data_sha_raw; do - bytes=$(wc -c < "$OBJ_DIR/$src.o") - printf '%-32s %d bytes (.o)\n' "$src" "$bytes" + for src in lib_version lib_manifest zp_config sha384 data_sha; do + if [ -f "$STAGING/sha/$src.o" ]; then + bytes=$(wc -c < "$STAGING/sha/$src.o") + printf '%-32s %d bytes (.o)\n' "$src" "$bytes" + fi done } > "$SIZES_SHA" { echo "# nistcurves-p384-curve.a per-source byte counts (ca65 .o file sizes)" - for src in zp_config fp384_raw mod384_raw points384_raw curve384_raw \ - ecdsa384_raw ec_scalar_mul_384_shim_raw data_curve_raw; do - bytes=$(wc -c < "$OBJ_DIR/$src.o") - printf '%-32s %d bytes (.o)\n' "$src" "$bytes" + for src in lib_version lib_manifest zp_config constants reu_config \ + fp384 mod384 curve384 points384_core ecdsa384 \ + ec_scalar_mul_384_shim data_p384; do + if [ -f "$STAGING/curve/$src.o" ]; then + bytes=$(wc -c < "$STAGING/curve/$src.o") + printf '%-32s %d bytes (.o)\n' "$src" "$bytes" + fi done } > "$SIZES_CURVE" diff --git a/tools/integration/build_nistcurves_p384_bin.sh b/tools/integration/build_nistcurves_p384_bin.sh index 9ebe7a5..8d6e50d 100755 --- a/tools/integration/build_nistcurves_p384_bin.sh +++ b/tools/integration/build_nistcurves_p384_bin.sh @@ -185,22 +185,20 @@ link_one () { local dbg_out dbg_out="${bin_out%.bin}.dbg" + # Under c64-lib-contract / libs/nistcurves cfa9085+, the upstream + # archive includes `constants.o` which `.export`s every REU register + # equate (`reu_status` / `reu_command` / `reu_c64_lo`-`hi` / + # `reu_reu_lo`-`hi` / `reu_reu_bank` / `reu_len_lo`-`hi` / + # `reu_addr_ctrl`). Pre-contract the .a was missing those exports + # and consumers patched them in via `ld65 --define`. Post-contract + # `--define`-ing them duplicates the symbol and ld65 errors with + # "Duplicate external identifier". "$LD65" \ -C "$cfg" \ -o "$bin_out" \ -Ln "$labels_out" \ -m "$map_out" \ --dbgfile "$dbg_out" \ - --define reu_status=\$df00 \ - --define reu_command=\$df01 \ - --define reu_c64_lo=\$df02 \ - --define reu_c64_hi=\$df03 \ - --define reu_reu_lo=\$df04 \ - --define reu_reu_hi=\$df05 \ - --define reu_reu_bank=\$df06 \ - --define reu_len_lo=\$df07 \ - --define reu_len_hi=\$df08 \ - --define reu_addr_ctrl=\$df0a \ --define mul_cached_a="$DEF_MUL_CACHED_A" \ --define mul_dma_lo="$DEF_MUL_DMA_LO" \ --define mul_dma_hi="$DEF_MUL_DMA_HI" \ diff --git a/tools/integration/build_x25519.sh b/tools/integration/build_x25519.sh index bd52fc9..f6efe72 100644 --- a/tools/integration/build_x25519.sh +++ b/tools/integration/build_x25519.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # ============================================================================= -# tools/integration/build_x25519.sh - Build c64-x25519 v0.4.0 X25519 -# primitives as a resident .a archive linked into the main PRG. +# tools/integration/build_x25519.sh - Build c64-x25519 v0.6.0 +# X25519 primitives as a resident .a archive linked into the main PRG. # # Optional sibling-library integration (Phase C.5). Produces # build/lib/x25519.a containing: @@ -12,6 +12,16 @@ # - data buffers (x25_*, fe25519_tmp*, mul_*, sqr_*, a24_*, fe_p) # - util (vic_blank, vic_unblank, bench helpers — pulled in if referenced) # +# Submodule pin: v0.6.0 (95fdd70) — adopts c64-lib-contract §8.1 (the +# canonical shared 1 KB quarter-square table) plus RAM reclamation in +# x25519_init.s (bank-2 stash removed) and bench rehab (bench_start/stop +# php/plp shape so jiffy-based benches measure real cycles again). +# Earlier contract-§1/§2/§3/§5 adoption landed in v0.4.0-7-g4d1c752 and +# remains in place: every ZP slot is `.exportzp`-ed (zp_config.s), +# LIB_VERSION_*/LIB_ABI_VERSION absolute exports (lib_version.s), +# X25519_REU_BANK configurable REU base (reu_config.s), and the +# LIB_X25519_* aggregate manifest equates. +# # Activated only when `make USE_X25519_SIBLING=1`. Default is OFF; the # in-tree src/crypto/fe25519.s + src/crypto/x25519.s remain the # default implementation until the supervisor + validator sign off on @@ -20,14 +30,21 @@ # # Excluded (replaced by in-tree equivalents): # - src/mul_8x8.s: in-tree src/crypto/poly1305.s already exports -# mul_8x8 / sqtab_init / poly_prod_lo / poly_prod_hi. Including -# the sibling's would duplicate symbols. The two implementations -# are calling-convention-compatible (A=multiplicand, X=multiplier -# → poly_prod_lo/hi). The in-tree variant uses a small branch on -# the sum-page byte; the sibling's is CT-clean via SMC patching. -# Using in-tree's is a CT regression for the X25519 mul path; the -# supervisor's plan accepts this for the integration smoke and -# defers a CT clean-up to a follow-up. +# mul_8x8 / sqtab_init / poly_prod_lo / poly_prod_hi / sqtab_lo / +# sqtab_hi. Including the sibling's would duplicate symbols. The +# two implementations are calling-convention-compatible +# (A=multiplicand, X=multiplier → poly_prod_lo/hi). The in-tree +# variant uses a small branch on the sum-page byte; the sibling's +# is CT-clean via SMC patching. Using in-tree's is a CT regression +# for the X25519 mul path; the supervisor's plan accepts this for +# the integration smoke and defers a CT clean-up to a follow-up. +# Under v0.6.0 §8.1 the sibling's mul_8x8 + the mult66 path inside +# fe25519_sqr both resolve `sqtab_lo` / `sqtab_hi` against the +# LIB_SHARED_SQTAB_BASE equate set via -D below; the equate is +# `.ifndef`-guarded in libs/x25519/src/constants.s so passing +# SHARED_SQTAB_INIT collapses the duplicate init body but keeps +# the SHARED_SQTAB_BASE-derived loads pointing at c64-https's +# resident table. # - src/main.s: the sibling's BASIC stub / test harness entry. We # have our own boot.s entry point. # @@ -55,10 +72,11 @@ CA65="${CA65:-ca65}" AR65="${AR65:-ar65}" # --- Canonical ZP defines --- -# The sibling's constants.s wraps every library-owned ZP equate in -# `.ifndef ` (see libs/x25519/docs/LIBRARY.md §4.2). We use the -# sibling's defaults — they are byte-compatible with the in-tree map -# under the following time-sharing analysis: +# The sibling's src/zp_config.s now wraps every library-owned ZP equate +# in `.ifndef ` AND `.exportzp`-s the symbol (issue #44, closes +# c64-lib-contract §2). c64-https uses the sibling's defaults — they +# are byte-compatible with the in-tree map under the following +# time-sharing analysis: # # Sibling claim In-tree slot at same addr Time-share? # ------------- -------------------------- ----------- @@ -84,20 +102,80 @@ AR65="${AR65:-ar65}" # fe_wide pins here # via .assert # -# No -D overrides needed — sibling defaults are fine. +# No ZP -D overrides needed — sibling defaults match c64-https's map. ZP_DEFINES=() +# --- REU bank base pin --- +# c64-x25519 v0.4.0-7-g4d1c752 ships src/reu_config.s with a +# `.ifndef`-guarded X25519_REU_BANK equate (default $00) — issue #43, +# closes c64-lib-contract §3. The library claims six contiguous REU +# banks starting at X25519_REU_BANK (banks 0..5 at the default) for +# its precomputed mul / doubled / 17th-bit-carry tables. +# +# c64-https pins the base to bank 0, matching the in-tree layout in +# src/crypto/shared/reu_layout.inc: +# REU_X25519_MUL_TABLES_BASE = $00000 (bank 0) +# The in-tree comment block at the bottom of reu_layout.inc enumerates +# the theoretical bank-3/4/5 collision with P-256/P-384 precompute +# reservations under USE_X25519_SIBLING=1; that collision remains +# theoretical only under the current TLS path. Passing X25519_REU_BANK +# explicitly (rather than relying on the library default) defends +# against a future c64-x25519 release bumping its default base. +# +# ca65 takes `-D =` for assemble-time symbol definitions +# (the library docs use "--asm-define" in prose but ca65 only supports +# the `-D` short form per `ca65 --help`). +REU_DEFINES=(-D X25519_REU_BANK=0) + +# --- c64-lib-contract §8.1 shared sqtab adoption (v0.6.0) --- +# c64-https owns the canonical 1 KB quarter-square multiply table — +# `sqtab_lo` / `sqtab_hi` live at $BC00 / $BE00 in TABLES_BSS (see +# src/data.s), populated by src/crypto/poly1305.s::sqtab_init at boot. +# +# Pass LIB_SHARED_SQTAB_BASE so the sibling's mul_8x8.s + fe25519.s +# `mult66` path resolve `sqtab_lo` / `sqtab_hi` against the shared +# c64-https table rather than the sibling's $7800 default (which would +# fight c64-https's TABLES_BSS-resident copy at link time / runtime). +# The `.ifndef`-guarded equate in libs/x25519/src/constants.s +# (v0.6.0 §8.1 adoption) plus `.assert (sqtab_lo & $00ff) = 0` + +# `.assert sqtab_hi = sqtab_lo + $0200` catch a misconfigured base at +# assemble time rather than runtime. +# +# SHARED_SQTAB_INIT signals that the host program supplies the +# canonical `mul_tables_init` from a shared-primitives module +# (c64-https's poly1305.s::sqtab_init, aliased through +# src/crypto/shared/mul_tables.s). With the gate defined, the sibling's +# own `sqtab_init` body collapses to a no-op stub +# (libs/x25519/src/mul_8x8.s::sqtab_init .ifdef SHARED_SQTAB_INIT) so +# the two libs don't duplicate work. +SQTAB_DEFINES=( + -D 'LIB_SHARED_SQTAB_BASE=$BC00' + -D SHARED_SQTAB_INIT=1 +) + # --- Stage sources --- rm -rf "$STAGING" mkdir -p "$STAGING" cp "$LIB_SRC"/constants.s "$STAGING/" +# zp_config.s + reu_config.s — transitively .include'd from constants.s +# (v0.4.0-7-g4d1c752, contract §2 + §3). Both files set +# ZP_CONFIG_NO_EXPORTS / REU_CONFIG_NO_EXPORTS when included via +# constants.s, so the .exportzp / .export directives in them only fire +# once per archive (no duplicate-symbol risk). +cp "$LIB_SRC"/zp_config.s "$STAGING/" +cp "$LIB_SRC"/reu_config.s "$STAGING/" cp "$LIB_SRC"/fe25519.s "$STAGING/fe25519_raw.s" cp "$LIB_SRC"/x25519.s "$STAGING/x25519_raw.s" cp "$LIB_SRC"/x25519_init.s "$STAGING/x25519_init_raw.s" # util.s (bench_*, vic_blank/unblank) is NOT staged — c64-https has no # in-PRG user of those helpers; vic_blank-style display blanking is a # perf optimization for benchmarks, not a correctness requirement. +# lib_version.s (LIB_VERSION_* + LIB_X25519_*) is NOT staged for now — +# c64-https doesn't .import any of those symbols yet. Future +# assemble-time fit/collision checks against LIB_X25519_RESIDENT_BYTES +# / LIB_X25519_REU_BANKS_USED would require staging lib_version.s and +# adding the assertions in a cfg or include file. # Route all sibling data (BSS buffers + initialized rodata tables) to # the page-aligned TABLES_BSS segment. TABLES_BSS has `align = $100` @@ -333,6 +411,8 @@ for src in fe25519_raw x25519_raw x25519_init_raw data_x25519_bss_raw data_x2551 -g \ -I "$STAGING" \ "${ZP_DEFINES[@]}" \ + "${REU_DEFINES[@]}" \ + "${SQTAB_DEFINES[@]}" \ -o "$OBJ_DIR/$src.o" "$STAGING/$src.s" done @@ -356,3 +436,124 @@ rm -f "$ARCHIVE" echo "built $ARCHIVE" cat "$SIZES" + +# ============================================================================= +# W3 (library-ingestion architecture) -- emit a padded overlay .bin +# image of the sibling for documentation / CI parity checking. +# +# The .bin is the same byte image the linker places into CRYPTO_OVERLAY +# under c64-https's main UCI cfg + USE_X25519_SIBLING=1. Producing it +# as a standalone artefact: +# * lets a CI bot diff the in-PRG slot bytes against the .bin to +# detect cfg drift, +# * gives W1 a ready-to-DMA staging image when the cold-path overlay +# wiring lands (REU bank 3, REU_OVERLAY_X25519), +# * documents the sibling's PRG-load-time bytes in `git status`. +# +# Output: +# build/lib/x25519-scalarmult.bin (7,680 B padded) +# build/lib/x25519-scalarmult.sizes.txt +# +# Pad / truncate to exactly $SLOT_BYTES so the .bin matches the live +# UCI CRYPTO_OVERLAY slot. +# ============================================================================= +BIN_OUT="$OUT_DIR/x25519-scalarmult.bin" +SIZES_BIN_OUT="$OUT_DIR/x25519-scalarmult.sizes.txt" +LABELS_BIN_OUT="$PROJECT_ROOT/build/labels-x25519-scalarmult.txt" +MAP_BIN_OUT="$OUT_DIR/x25519-scalarmult.map" +DBG_BIN_OUT="${BIN_OUT%.bin}.dbg" +CFG_BIN="$PROJECT_ROOT/cfg/x25519-overlay-scalarmult.cfg" +SLOT_BYTES=7680 + +if [ ! -f "$CFG_BIN" ]; then + echo "WARN: $CFG_BIN missing -- skipping .bin emission" >&2 +else + LD65="${LD65:-ld65}" + + # ld65 needs the archive members as plain .o files. We already + # have them in $OBJ_DIR from the archive step above -- pass them + # directly. + OBJ_BIN_ARGS=( + "$OBJ_DIR/fe25519_raw.o" + "$OBJ_DIR/x25519_raw.o" + "$OBJ_DIR/x25519_init_raw.o" + "$OBJ_DIR/data_x25519_bss_raw.o" + "$OBJ_DIR/data_x25519_rodata_raw.o" + ) + + # Resolve sibling imports against the main PRG's labels.txt when + # available (mirrors the P-384 overlay .bin script's pattern). + MAIN_LABELS="$PROJECT_ROOT/build/labels.txt" + + bin_lookup_label () { + local name="$1" + local fallback="$2" + if [ ! -f "$MAIN_LABELS" ]; then + echo "$fallback" + return + fi + local hex + hex=$(grep -E " \.${name}\$" "$MAIN_LABELS" | head -n1 | awk '{print $2}' | sed 's|^C:||') + if [ -z "$hex" ]; then + echo "$fallback" + else + printf '$%s' "$hex" + fi + } + + DEF_MUL_8X8=$(bin_lookup_label mul_8x8 '$0000') + DEF_SQTAB_LO=$(bin_lookup_label sqtab_lo '$0000') + DEF_SQTAB_HI=$(bin_lookup_label sqtab_hi '$0000') + DEF_POLY_PROD_LO=$(bin_lookup_label poly_prod_lo '$CFFE') + DEF_POLY_PROD_HI=$(bin_lookup_label poly_prod_hi '$CFFF') + + "$LD65" \ + -C "$CFG_BIN" \ + -o "$BIN_OUT" \ + -Ln "$LABELS_BIN_OUT" \ + -m "$MAP_BIN_OUT" \ + --dbgfile "$DBG_BIN_OUT" \ + --define reu_status=\$df00 \ + --define reu_command=\$df01 \ + --define reu_c64_lo=\$df02 \ + --define reu_c64_hi=\$df03 \ + --define reu_reu_lo=\$df04 \ + --define reu_reu_hi=\$df05 \ + --define reu_reu_bank=\$df06 \ + --define reu_len_lo=\$df07 \ + --define reu_len_hi=\$df08 \ + --define reu_addr_ctrl=\$df0a \ + --define mul_8x8="$DEF_MUL_8X8" \ + --define sqtab_lo="$DEF_SQTAB_LO" \ + --define sqtab_hi="$DEF_SQTAB_HI" \ + --define poly_prod_lo="$DEF_POLY_PROD_LO" \ + --define poly_prod_hi="$DEF_POLY_PROD_HI" \ + "${OBJ_BIN_ARGS[@]}" \ + 2>"$OUT_DIR/x25519-bin-ld.err" \ + || { + # Standalone .bin link is best-effort -- if it fails (e.g. + # missing symbol on a sibling bump), surface a warning but + # don't break the archive build that the main PRG actually + # needs. The W1 follow-on tightens this when the cold-path + # overlay slot lands. + echo "WARN: standalone x25519-scalarmult.bin link failed:" >&2 + cat "$OUT_DIR/x25519-bin-ld.err" >&2 || true + rm -f "$BIN_OUT" + } + + if [ -f "$BIN_OUT" ]; then + # Pad / truncate to exactly $SLOT_BYTES. + truncate -s "$SLOT_BYTES" "$BIN_OUT" + sed -i '' 's/^al 00\([0-9a-fA-F]\{4\}\) /al C:\1 /' "$LABELS_BIN_OUT" 2>/dev/null || true + + size=$(wc -c < "$BIN_OUT") + { + echo "# x25519-scalarmult overlay image (W3)" + echo "# slot size: $SLOT_BYTES B (\$1E00 -- UCI CRYPTO_OVERLAY)" + echo "# padded .bin: $size B" + } > "$SIZES_BIN_OUT" + + echo "built $BIN_OUT ($size B padded)" + cat "$SIZES_BIN_OUT" + fi +fi