Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "libs/x25519"]
path = libs/x25519
url = https://github.com/JC-000/c64-x25519.git
[submodule "libs/mlkem"]
path = libs/mlkem
url = https://github.com/JC-000/c64-mlkem.git
42 changes: 40 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ Variables:
and an `$(error)` with `TRUST_STORE=1`) —
both exist for uci only, so there is no ip65 comb build. Confirm from
the `ld65 -C` line, not from the profile name.
- `PQ_HYBRID=1` — links `libs/mlkem` (c64-mlkem v0.6.0, ABI 3) for the
planned X25519MLKEM768 hybrid; **placement only, nothing calls it**. uci
+ `USE_NISTCURVES_ONCHIP=1` or `_COMB=1`, with or without `TRUST_STORE`;
`$(error)` on ip65, uci-m3 and the uci REU profile. Unset = byte-identical
PRG on every profile. Layout in Memory layout; asserts in
`src/crypto/mlkem_link.s`, guards `tools/test_mlkem_placement.py` and
`tools/test_mlkem_linked.py`. The code **loads at $A000** (the shadow
BSS's file bytes) and `mlkem_boot_init` copies it to $E000 before boot's
zeroing (#288 stays satisfied); a tail marker refuses a cut-short LOAD. The
only entries are `pq_mlkem_keygen` / `pq_mlkem_decaps_seed` (`pq_mlkem_call`).
- `USE_X25519_SIBLING` — **retired, `$(error)`-guarded (#245)**: every
build links the `libs/x25519` sibling; any value is refused.
- `ENABLE_P384_VERIFY`, `USE_OVERLAY_P384_EMBED`, `KEEP_CRYPTO_SWAP`,
Expand Down Expand Up @@ -234,6 +244,19 @@ fixed buffers in crypto BSS.
`$1E-$2F`, `fe_wide` at ZP_WIDE `$40-$7F` — all time-shared with
per-call slots (table in `build_x25519.sh`); they are not in
`labels.txt`.
- ML-KEM (`libs/mlkem`, PQ_HYBRID only) zero page `$40-$4F`
(`mlkem_zp_src $40`, `_dst $42`, `_len $44`, `_tmp $46`, `_mul $48-$4F`;
-D values in `build_mlkem.sh`, pinned in `mlkem_link.s`). Upstream's
`$30-$3F` default hits `tls_hs_ptr $3E-$3F` and ECDSA/nistcurves. Only
co-tenant is X25519's per-call `fe_wide` ($40-$7F); ML-KEM never nests
in X25519, fp_mul or ECDSA, nor they in it. Other users of `$40-$7F`: none.

| range | owner |
|---|---|
| `$22-$3C`, `$39/$3A/$3D` | ECDSA bignum; nistcurves fp_mul_i/j, ptr2 |
| `$3E-$3F` | `tls_hs_ptr` (live across handlers) |
| `$14-$16`,`$1C`,`$1E-$2F` | X25519 / ChaCha / Poly1305, time-shared |
| `$40-$7F` | X25519 `fe_wide`; ML-KEM `$40-$4F` |
- c64-lib-contract is a **prose dependency, not a submodule** — nothing
in `make` reads it, so a contract release can never break a build.
Always cite a **tag** (newest: v1.1.0). v1.0.0 cut the SPEC by 7/8 and
Expand Down Expand Up @@ -790,14 +813,29 @@ UCI (`cfg/c64-https-uci.cfg`, W1 hot/cold split — the reference):
at $BA00 so sqtab lands at $BC00
(LIB_SHARED_SQTAB_BASE, asserted
post-link)
$C000-$CFFF (no region) TCP ring by equate; not in the PRG,
$C000-$CFFF (no region) TCP ring by equate (PQ_HYBRID: first 2,400 B = ML-KEM dk scratch); not in the PRG,
except comb's COLD_IMAGE (cold bank)
— the PRG ends $BFFF; comb with the
cold bank (COLD_BANK=1) ends $CFFF
$D000-$DFFF I/O never in the PRG: a KERNAL LOAD would
$D000-$DFFF I/O (PQ_HYBRID: LIB_MLKEM_POLY_BSS in the RAM
under it, $01=$34, file="")
never in the PRG: a KERNAL LOAD would
store into the registers (#288,
`tools/test_prg_io_range.py`)

PQ_HYBRID=1 adds three `file=""` areas to the uci cfgs (`cfg/c64-https-uci.cfg`
serves uci-onchip; `-onchip-cold.cfg` comb; `-onchip.cfg` comb COLD_BANK=0):
`MLKEM_CODE_RAM` $E000-$FDFF (code+rodata; KERNAL out),
`MLKEM_POLY_RAM` $D000-$DFFF (4,096 B polys), `MLKEM_BSS_RAM` $B100-$BBFF
(over X25519_TABLES + the $B900 hole + mul_dma). Rules:
ML-KEM never interleaves with X25519 or fp_mul, and **every ML-KEM call group
needs `mlkem_arith_init` first** (its 1 KB of mod-q tables live in that
window, which X25519 rebuilds over): the wrappers do both, and
`test_mlkem_placement.py` fails on any source outside `mlkem_link.s` naming a
symbol the map places in $E000-$FDFF.
The `mlkem_arg_*` block is in that window too: fill it after the last X25519.
All pinned in `src/crypto/mlkem_link.s`; scrub spans in `session_scrub.s`.

ip65 (`cfg/c64-https-ip65.cfg`):

$0801-$1FFF LOADER
Expand Down
54 changes: 53 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ ifeq ($(BACKEND),uci-m3)
M3_REFUSED_KNOBS := USE_NISTCURVES_ONCHIP USE_NISTCURVES_ONCHIP_COMB \
COLD_BANK TRUST_STORE TLS_STREAM_DEFRAME \
KEEP_X509_PARSE_CERT KEEP_WORD32_ALL HTTPS_PIN_WARN \
HTTPS_SNI VIC_BLANK
HTTPS_SNI VIC_BLANK PQ_HYBRID
$(foreach k,$(M3_REFUSED_KNOBS),$(if $(strip $($(k))),$(error $(k)=$($(k)) does not apply to BACKEND=uci-m3: it links no 6510 TLS or crypto (the ESP32 does the TLS); drop the variable)))
ifneq ($(filter-out 0 1,$(M3_ALLOW_TLS12)),)
$(error M3_ALLOW_TLS12 must be 1 (offer TLS 1.3 and hardened 1.2) or 0/unset (TLS 1.3 only))
Expand Down Expand Up @@ -432,6 +432,46 @@ SIBLING_LIB_ARCHIVES += build/lib/x25519.a

CRYPTO_SRCS_EFFECTIVE := $(CRYPTO_SRCS_ALL)

# PQ_HYBRID=1 - link libs/mlkem (c64-mlkem v0.6.0 ML-KEM-768) for the
# planned X25519MLKEM768 hybrid key exchange (PQC plan, phase P0: placement
# only). Nothing calls ML-KEM and the protocol is unchanged. Unset, the
# archive, src/crypto/mlkem_link.s, the scrub spans and the -D are all gated
# here and the cfg segment lines are `optional = yes`, so every profile's PRG
# is byte-identical to a tree that has never heard of the knob.
#
# Allowed on exactly the profiles the product list could ship it on:
# BACKEND=uci with USE_NISTCURVES_ONCHIP=1 or USE_NISTCURVES_ONCHIP_COMB=1,
# with or without TRUST_STORE=1. Refused on:
# ip65 ~41 B of overlay, no streaming deframer: it cannot carry the
# ~1.2 KB multi-record hybrid ServerHello at all.
# uci-m3 links no 6510 TLS (M3_REFUSED_KNOBS above).
# uci REU (no USE_NISTCURVES_ONCHIP): not a shipped profile, and its
# fp_mul row-fetch DMA lands in mul_dma_lo/hi, which the ML-KEM
# BSS union claims; the "ML-KEM never interleaves with fp_mul"
# rule is cheaper to keep by not building it.
# The placement, the asserts that pin it and the zero-page map are in
# src/crypto/mlkem_link.s, cfg/c64-https-uci-onchip*.cfg (MLKEM_*_RAM) and
# tools/integration/build_mlkem.sh.
PQ_HYBRID ?=
ifneq ($(filter-out 0 1,$(PQ_HYBRID)),)
$(error PQ_HYBRID must be 1 (link libs/mlkem) or 0/unset)
endif
ifeq ($(PQ_HYBRID),1)
ifeq ($(BACKEND),ip65)
$(error PQ_HYBRID=1 is UCI-only: ip65 has no streaming deframer and ~41 B of overlay, so it cannot carry a hybrid ServerHello)
endif
ifneq ($(BACKEND),uci)
$(error PQ_HYBRID=1 needs BACKEND=uci (got BACKEND=$(BACKEND)))
endif
ifneq ($(USE_NISTCURVES_ONCHIP),1)
$(error PQ_HYBRID=1 needs USE_NISTCURVES_ONCHIP=1 or USE_NISTCURVES_ONCHIP_COMB=1: the uci REU profile does fp_mul row-fetch DMA into mul_dma_lo/hi, which the ML-KEM BSS union claims, and it is not a shipped product)
endif
CA65FLAGS += -D PQ_HYBRID=1
SIBLING_LIB_ARCHIVES += build/lib/mlkem.a
else
CRYPTO_SRCS_EFFECTIVE := $(filter-out src/crypto/mlkem_link.s,$(CRYPTO_SRCS_EFFECTIVE))
endif

# #155 phase 2 memory reclaims: code no shipped image reaches is assembled
# out by default. Nothing is deleted; each knob re-arms its piece, and with
# the knobs set the PRG is byte-identical to the pre-reclaim one on every
Expand Down Expand Up @@ -862,6 +902,11 @@ $(eval $(call SIBLING_SRC_CHECK,build/lib/x25519.src.stamp,libs/x25519,\
$(eval $(call SIBLING_SRC_CHECK,build/lib/nistcurves.src.stamp,libs/nistcurves,\
tools/integration/build_nistcurves_p256.sh,\
build/lib/nistcurves-*))
ifeq ($(PQ_HYBRID),1)
$(eval $(call SIBLING_SRC_CHECK,build/lib/mlkem.src.stamp,libs/mlkem,\
tools/integration/build_mlkem.sh,\
build/lib/mlkem.a build/lib/mlkem.sizes.txt build/lib/mlkem_staging))
endif
endif

# src/boot.s pulls the HTTPS target hostname from this generated include
Expand Down Expand Up @@ -993,6 +1038,13 @@ build/lib/x25519.a: tools/integration/build_x25519.sh $(FLAGS_STAMP)
@mkdir -p build/lib
bash tools/integration/build_x25519.sh

# libs/mlkem ML-KEM-768 archive (PQ_HYBRID=1 only). Rebuilt when the wrapper
# or the flags stamp changes; the SIBLING_SRC_CHECK above deletes it on any
# libs/mlkem source or pin change.
build/lib/mlkem.a: tools/integration/build_mlkem.sh $(FLAGS_STAMP)
@mkdir -p build/lib
bash tools/integration/build_mlkem.sh

# Build ip65 object libraries from the submodule. Required once per fresh
# clone — the submodule ships sources, and the ip65-blob link below needs
# the .lib archives this target produces. Re-run when the submodule moves.
Expand Down
45 changes: 43 additions & 2 deletions cfg/c64-https-uci-onchip-cold.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ MEMORY {
ZP_CRYPTO: start = $0022, size = $001E, type = rw, define = yes;
ZP_WIDE: start = $0040, size = $0040, type = rw, define = yes;

LOADADDR: start = $07FF, size = $0002, file = %O;
LOADADDR: start = $07FF, size = $0002, file = %O, define = yes;
LOADER: start = $0801, size = $17FF, file = %O, define = yes, fill = yes, fillval = $00;
# W1 partial: NET_CODE sized to fit its content, and its tail reclaimed
# as NET_BSS_TAIL to spill BSS that does not fit in CRYPTO_HOT under the
Expand Down Expand Up @@ -222,7 +222,18 @@ MEMORY {
# 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;
# PQ_HYBRID (libs/mlkem): the shadow's FILE bytes and its RAM bytes are
# two areas over the same $A000-$BFFF. CRYPTO_COLD_SHADOW is the RAM
# view every BSS segment is placed in (unchanged names, symbols and
# addresses); COLD_FILE is the file view, a zero-filled 8 KB image that
# is byte-for-byte what CRYPTO_COLD_SHADOW alone used to emit. With no
# staged segment the PRG is identical to before. The ML-KEM code+rodata
# LOADS here (the file bytes under the BSS exist anyway: boot zeroes
# them) and RUNS at $E000, copied by boot before the zeroing. A BSS
# segment cannot share an area with a ro one at the same addresses, which
# is why the two views are separate areas rather than one.
CRYPTO_COLD_SHADOW: start = $A000, size = $2000, type = rw, file = "", define = yes;
COLD_FILE: start = $A000, size = $2000, file = %O, define = yes, fill = yes, fillval = $00;
# X25519_SCRATCH overlaps tls_rec_buf (BSS_TAIL, 548 B) ON PURPOSE
# (issue #245). It carries the X25519 sibling's 17 field buffers
# (X25519_BSS, 544 B), which are live only inside the two scalar mults
Expand All @@ -234,6 +245,23 @@ MEMORY {
# asserted equal to tls_rec_buf in src/crypto/x25519_tables.s.
X25519_SCRATCH: start = $A000, size = $0224, type = rw, define = yes;

# PQ_HYBRID=1 only (libs/mlkem, X25519MLKEM768; see PQ_HYBRID in the
# Makefile and src/crypto/mlkem_link.s, which asserts every number
# here). All three are file="": no PRG byte is spent on them, and with no
# LIB_MLKEM_* segment linked they are empty, so an unset build is
# byte-identical.
# MLKEM_CODE_RAM $E000-$FDFF, RAM under the KERNAL ROM (callers run
# ML-KEM with the KERNAL banked out, under sei). Not
# file-backed (#288: no PRG byte may reach $D000+): the
# code LOADS in COLD_FILE at $A000 and boot copies it up.
# MLKEM_BSS_RAM $B100-$BBFF = X25519_TABLES + the $B900 hole +
# mul_dma_lo/hi, all dead while ML-KEM runs (ML-KEM
# never interleaves with X25519 or fp_mul).
# MLKEM_POLY_RAM $D000-$DFFF, RAM under I/O ($01 banked to all-RAM).
MLKEM_CODE_RAM: start = $E000, size = $1E00, type = rw, file = "", define = yes;
MLKEM_BSS_RAM: start = $B100, size = $0B00, type = rw, file = "", define = yes;
MLKEM_POLY_RAM: start = $D000, size = $1000, type = rw, file = "", define = yes;

# Cold bank: the PRG's copy of the cold groups (src/net/uci/cold_bank.s),
# back to back; boot stashes it into the REU before the first connect
# reuses the range as the TCP ring.
Expand Down Expand Up @@ -501,4 +529,17 @@ SEGMENTS {
# anchor must be explicit. $BA00 + $600 = $C000 — flush to the
# region end, exactly the historical placement.
TABLES_BSS: load = CRYPTO_COLD_SHADOW, type = bss, start = $BA00;

# PQ_HYBRID=1 only (all optional). align = $40 on rodata and $100 on the
# BSS are the library's own rules (cfg/mlkem-example.cfg in libs/mlkem).
# load = COLD_FILE ($A000, file bytes), run = MLKEM_CODE_RAM ($E000).
# align_load keeps the load image the same shape as the run image, so
# one block copy moves both (asserted in src/crypto/mlkem_link.s).
LIB_MLKEM_CODE: load = COLD_FILE, run = MLKEM_CODE_RAM, type = ro, optional = yes, define = yes;
LIB_MLKEM_RODATA: load = COLD_FILE, run = MLKEM_CODE_RAM, type = ro, optional = yes, align = $40, align_load = $40, define = yes;
# One byte, last in the load image: boot checks it before copying, so a
# LOAD cut short (or a stage already zeroed) is refused, never half-copied.
LIB_MLKEM_MARK: load = COLD_FILE, run = MLKEM_CODE_RAM, type = ro, optional = yes, define = yes;
LIB_MLKEM_BSS: load = MLKEM_BSS_RAM, type = bss, optional = yes, align = $100, define = yes;
LIB_MLKEM_POLY_BSS: load = MLKEM_POLY_RAM, type = bss, optional = yes, align = $100, define = yes;
}
45 changes: 43 additions & 2 deletions cfg/c64-https-uci-onchip.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ MEMORY {
ZP_CRYPTO: start = $0022, size = $001E, type = rw, define = yes;
ZP_WIDE: start = $0040, size = $0040, type = rw, define = yes;

LOADADDR: start = $07FF, size = $0002, file = %O;
LOADADDR: start = $07FF, size = $0002, file = %O, define = yes;
LOADER: start = $0801, size = $17FF, file = %O, define = yes, fill = yes, fillval = $00;
# W1 partial: NET_CODE sized to fit its content, and its tail reclaimed
# as NET_BSS_TAIL to spill BSS that does not fit in CRYPTO_HOT under the
Expand Down Expand Up @@ -201,7 +201,18 @@ MEMORY {
# 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;
# PQ_HYBRID (libs/mlkem): the shadow's FILE bytes and its RAM bytes are
# two areas over the same $A000-$BFFF. CRYPTO_COLD_SHADOW is the RAM
# view every BSS segment is placed in (unchanged names, symbols and
# addresses); COLD_FILE is the file view, a zero-filled 8 KB image that
# is byte-for-byte what CRYPTO_COLD_SHADOW alone used to emit. With no
# staged segment the PRG is identical to before. The ML-KEM code+rodata
# LOADS here (the file bytes under the BSS exist anyway: boot zeroes
# them) and RUNS at $E000, copied by boot before the zeroing. A BSS
# segment cannot share an area with a ro one at the same addresses, which
# is why the two views are separate areas rather than one.
CRYPTO_COLD_SHADOW: start = $A000, size = $2000, type = rw, file = "", define = yes;
COLD_FILE: start = $A000, size = $2000, file = %O, define = yes, fill = yes, fillval = $00;
# X25519_SCRATCH overlaps tls_rec_buf (BSS_TAIL, 548 B) ON PURPOSE
# (issue #245). It carries the X25519 sibling's 17 field buffers
# (X25519_BSS, 544 B), which are live only inside the two scalar mults
Expand All @@ -213,6 +224,23 @@ MEMORY {
# asserted equal to tls_rec_buf in src/crypto/x25519_tables.s.
X25519_SCRATCH: start = $A000, size = $0224, type = rw, define = yes;

# PQ_HYBRID=1 only (libs/mlkem, X25519MLKEM768; see PQ_HYBRID in the
# Makefile and src/crypto/mlkem_link.s, which asserts every number
# here). All three are file="": no PRG byte is spent on them, and with no
# LIB_MLKEM_* segment linked they are empty, so an unset build is
# byte-identical.
# MLKEM_CODE_RAM $E000-$FDFF, RAM under the KERNAL ROM (callers run
# ML-KEM with the KERNAL banked out, under sei). Not
# file-backed (#288: no PRG byte may reach $D000+): the
# code LOADS in COLD_FILE at $A000 and boot copies it up.
# MLKEM_BSS_RAM $B100-$BBFF = X25519_TABLES + the $B900 hole +
# mul_dma_lo/hi, all dead while ML-KEM runs (ML-KEM
# never interleaves with X25519 or fp_mul).
# MLKEM_POLY_RAM $D000-$DFFF, RAM under I/O ($01 banked to all-RAM).
MLKEM_CODE_RAM: start = $E000, size = $1E00, type = rw, file = "", define = yes;
MLKEM_BSS_RAM: start = $B100, size = $0B00, type = rw, file = "", define = yes;
MLKEM_POLY_RAM: start = $D000, size = $1000, type = rw, file = "", define = yes;

# The PRG ends at $BFFF, CRYPTO_COLD_SHADOW's last byte (#288).
# Nothing above it is file-backed. A zero pad used to run on to $DFFF
# (it put the retired P-384 curve blob at $E000), so a KERNAL LOAD
Expand Down Expand Up @@ -464,4 +492,17 @@ SEGMENTS {
# anchor must be explicit. $BA00 + $600 = $C000 — flush to the
# region end, exactly the historical placement.
TABLES_BSS: load = CRYPTO_COLD_SHADOW, type = bss, start = $BA00;

# PQ_HYBRID=1 only (all optional). align = $40 on rodata and $100 on the
# BSS are the library's own rules (cfg/mlkem-example.cfg in libs/mlkem).
# load = COLD_FILE ($A000, file bytes), run = MLKEM_CODE_RAM ($E000).
# align_load keeps the load image the same shape as the run image, so
# one block copy moves both (asserted in src/crypto/mlkem_link.s).
LIB_MLKEM_CODE: load = COLD_FILE, run = MLKEM_CODE_RAM, type = ro, optional = yes, define = yes;
LIB_MLKEM_RODATA: load = COLD_FILE, run = MLKEM_CODE_RAM, type = ro, optional = yes, align = $40, align_load = $40, define = yes;
# One byte, last in the load image: boot checks it before copying, so a
# LOAD cut short (or a stage already zeroed) is refused, never half-copied.
LIB_MLKEM_MARK: load = COLD_FILE, run = MLKEM_CODE_RAM, type = ro, optional = yes, define = yes;
LIB_MLKEM_BSS: load = MLKEM_BSS_RAM, type = bss, optional = yes, align = $100, define = yes;
LIB_MLKEM_POLY_BSS: load = MLKEM_POLY_RAM, type = bss, optional = yes, align = $100, define = yes;
}
Loading