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
34 changes: 34 additions & 0 deletions Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -116,6 +116,30 @@ SIBLING_LIB_ARCHIVES += build/lib/x25519.a
# Propagate the flag to ca65 so src/data.s suppresses the in-tree X25519
# buffer declarations (the sibling's data_x25519_raw.s provides them).
CA65FLAGS += -D USE_X25519_SIBLING=1

# Split the sibling's code across CRYPTO_HOT and CRYPTO_OVERLAY. This is
# what makes USE_X25519_SIBLING=1 link at all under UCI, so it is a
# default rather than something the operator has to know.
#
# The sibling's 4,226 B of code does not fit either region whole:
# CRYPTO_HOT has ~3.3 KB of room for it (measured), and CRYPTO_OVERLAY
# has ~3.7 KB once the sibling's own 3,840 B of tables and BSS are in
# there. Leaving the ladder (717 B) and the boot-only table init (666 B)
# in CRYPTO_OVERLAY satisfies both. Under USE_X25519_SIBLING=1 that
# region is not a paged overlay -- both embed flags that page it are
# mutually exclusive with this one -- so it is plain resident RAM.
#
# X25519_RODATA is the wrong NAME for executable code; it is used only
# because it is the one segment both backend cfgs already route into
# CRYPTO_OVERLAY. The clean form is a cfg declaring the SPEC §4 names
# (LIB_X25519_CODE / LIB_X25519_INIT_CODE / LIB_X25519_DATA); at that
# point these two lines and the wrapper's sed both go away.
#
# ip65 does not link either way -- its CRYPTO_OVERLAY is 4,212 B and
# already holds TLS_CODE + CRYPTO_AUX_CODE, so X25519_RODATA overflows
# it by 2,048 B (2,816 B with these settings). See build_x25519.sh.
export X25519_INIT_SEGMENT ?= X25519_RODATA
export X25519_SEG_LADDER ?= X25519_RODATA
endif

# Phase C.5: under USE_X25519_SIBLING=1, evict the in-tree X25519
Expand DownExpand Up@@ -279,6 +303,16 @@ ifeq ($(USE_NISTCURVES_ONCHIP),1)
@grep -q '^al C:BC00 \.sqtab_reserved' $(LABELS) || \
{ echo 'ERROR: sqtab_reserved is not at $$BC00 — TABLES_BSS layout drifted; realign LIB_SHARED_SQTAB_BASE in tools/integration/build_nistcurves_p256.sh'; exit 1; }
endif
ifeq ($(USE_X25519_SIBLING),1)
# Same invariant, other sibling: build_x25519.sh bakes sqtab_lo/hi
# into the sibling at X25519_SQTAB_BASE while in-tree sqtab_init
# fills the real table wherever ld65 put it. Disagreement is neither
# a link nor a boot failure, just a wrong shared secret. Rationale
# and the $$B800-vs-$$BC00 history: tools/integration/build_x25519.sh.
@grep -q '^al C:B800 \.sqtab_lo' $(LABELS) || \
{ echo 'ERROR: sqtab_lo is not at $$B800 — TABLES_BSS layout drifted; realign X25519_SQTAB_BASE in tools/integration/build_x25519.sh'; \
grep ' \.sqtab_lo$$' $(LABELS); exit 1; }
endif

# Phase 5 Fix D: $(LABELS) is normally a side-effect of the $(PRG)
# link recipe; we don't add an explicit rule. The overlay-bin rule
Expand Down
2 changes: 1 addition & 1 deletion libs/x25519
Submodule x25519 updated 158 files
47 changes: 42 additions & 5 deletions src/boot.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@
; imports `reu_mul_init` from the sibling archive instead.
.ifdef USE_X25519_SIBLING
.import reu_mul_init
; Needed for its autoload-latch restore tail, not for the ZP
; clear — see the X25519 slot stash near the end of this file.
.import reu_clear_wide
.else
.export reu_mul_init
; Under USE_NISTCURVES_ONCHIP the sibling's rebuilt
Expand DownExpand Up@@ -994,11 +997,19 @@ reu_p384_overlay_init:
;
; 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.
; STASHes the slot bytes to REU_OVERLAY_X25519 (bank 6, $60000 under
; this flag) 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.
;
; THIS WRITE IS THE ONE THAT MATTERS FOR THE REU BANK MAP. It runs
; unconditionally at boot whether or not any swap ever happens, so it
; is not covered by the "no TLS caller invokes crypto_swap_to_x25519"
; argument that reu_layout.inc used to declare the bank-3 overlap
; theoretical. At $30000 it destroyed the sibling's 17th-bit-carry
; table right after reu_mul_init built it, breaking fe25519_sqr (and
; only fe25519_sqr) -- see the relocation note in reu_layout.inc.
;
; NB: this stashes the *initialized* portion of CRYPTO_OVERLAY (the
; sibling's rodata tables) plus any zero-init BSS bytes that fall in
Expand DownExpand Up@@ -1028,6 +1039,32 @@ reu_p384_overlay_init:
lda #$90 ; execute + STASH (C64->REU)
sta reu_command
plp

; RESTORE THE MUL-ROW AUTOLOAD LATCH. The stash above is a full
; six-register REU setup (c64 addr, reu addr, bank, len=$2000,
; addr_ctrl) and it runs AFTER `jsr reu_mul_init` in the boot
; sequence. `reu_fetch_mul_row` is a three-register primitive —
; it writes only reu_reu_hi / reu_reu_bank / reu_command and
; trusts the latch for everything else — so leaving it stomped
; makes the next fetch pull $2000 bytes into $4200 instead of
; $0200 bytes into mul_dma_lo.
;
; The blast radius is asymmetric and that is what made this hard
; to see: fe25519 re-establishes the latch itself on every op
; (reu_clear_wide's tail), so X25519 is unaffected and both
; RFC 7748 vectors pass. libs/nistcurves' fp_mul does NOT — it
; relies on the boot-time latch — so ECDSA P-256 verify is the
; only visible casualty: tools/test_ecdsa_kat_oracle.py went
; 3/6, all three VALID vectors rejected, which reads exactly
; like the "missing -reu" garbage-fp_mul failure documented in
; CLAUDE.md and would have been misdiagnosed as one.
;
; reu_clear_wide is the library's own canonical restorer (its
; tail is documented as one of the two establishers of this
; latch), so we call it rather than open-coding the register
; writes and drifting from it later. Its ZP clear of fe_wide is
; incidental and harmless at boot.
jsr reu_clear_wide
.endif ; .ifdef USE_X25519_SIBLING
rts

Expand Down
20 changes: 18 additions & 2 deletions src/crypto/poly1305.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -201,7 +201,7 @@ sqtab_init:
; Uses identity: a*b = sqtab[a+b] - sqtab[|a-b|]
; Clobbers: A, X, Y
; =============================================================================
.ifdef USE_NISTCURVES_ONCHIP
.if .defined(USE_NISTCURVES_ONCHIP) .or .defined(USE_X25519_SIBLING)
; --- c64-lib-contract §8.3 canonical body (issue #69 integration) ---
; The sibling's FP_ONCHIP_MUL row generator (og_common, rebuilt with
; SHARED_CT_MUL_8X8) imports ct_mul_8x8 + the SMC bake sites from the
Expand All@@ -210,6 +210,22 @@ sqtab_init:
; smc_sum_a_imm+1 / smc_diff_a_imm+1 once per row, passes b in Y.
; The legacy in-tree convention (A=a, X=b, re-baked per call) is kept
; as the thin `mul_8x8` shim for poly1305/fe25519 call sites.
;
; USE_X25519_SIBLING selects this body too, as of the c64-x25519
; v0.10.0 bump. From v0.7.0 the sibling's x25519_init.s imports
; ct_mul_8x8 + both SMC bake sites UNCONDITIONALLY (it used to carry
; its own copy in mul_8x8.s, which this wrapper does not stage because
; that file's other exports collide with in-tree ones). Without this
; arm the sibling link dies on three unresolved externals — and it
; dies *behind* the memory-area overflows, so it only becomes visible
; once the placement problem is solved.
;
; The two arms are functionally equivalent implementations of the same
; a*b -> poly_prod_lo/hi contract, so widening the gate is a strict
; no-op for every configuration that does not set one of these flags:
; the default REU builds on both backends stay byte-identical (verified
; by PRG sha256, not by inspection). The two flags are mutually
; exclusive at Makefile:90, so this arm is never selected twice.
.export ct_mul_8x8
.export smc_sum_a_imm, smc_diff_a_imm

Expand DownExpand Up@@ -306,7 +322,7 @@ mul_8x8:
sbc sqtab_hi,y
sta poly_prod_hi
rts
.endif ; USE_NISTCURVES_ONCHIP
.endif ; USE_NISTCURVES_ONCHIP / USE_X25519_SIBLING

; =============================================================================
; poly1305_multiply - Multiply h (17 bytes) by r (16 bytes), reduce mod 2^130-5
Expand Down
63 changes: 59 additions & 4 deletions src/crypto/shared/reu_layout.inc
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,13 +71,55 @@ REU_OVERLAY_P256_VERIFY = REU_OVERLAY_P256
; 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).
;
; *** RELOCATED OFF BANK 3 — THE COLLISION BELOW IS NOT THEORETICAL ***
;
; The "collision note" at the bottom of this file called the bank-3/4/5
; overlap with the sibling's tables theoretical, on the grounds that no
; TLS caller ever invokes `crypto_swap_to_x25519`. That reasoning misses
; the write that actually happens: `src/boot.s` STASHes the whole 8 KB
; CRYPTO_OVERLAY slot to REU_OVERLAY_X25519 unconditionally under
; `.ifdef USE_X25519_SIBLING`, at boot, whether or not a swap is ever
; performed. At $30000 that landed exactly on bank 3 =
; X25519_REU_BANK + 3, the sibling's 17th-bit-carry table, after
; `reu_mul_init` had filled it.
;
; Measured symptom, once the sibling first linked (see the integrator's
; report): `fe25519_mul` correct, `fe25519_add`/`sub`/`mul_a24` correct,
; `fe25519_sqr` WRONG, and therefore both RFC 7748 §5.2 vectors wrong.
; That signature is diagnostic — banks 0/1 (mul lo/hi) are untouched by
; the stash, bank 3 (carry, read only by fe25519_sqr's SQR_DMA_K>0
; path) is destroyed. Moving this base to bank 6 makes all seven field
; ops and both RFC 7748 vectors pass at the default SQR_DMA_K=22.
;
; Bank 6 is safe here specifically: it belongs to the P-384 split
; overlays below, and `USE_OVERLAY_P384_EMBED` is forced off whenever
; USE_X25519_SIBLING=1 (Makefile, "ifneq ($(USE_X25519_SIBLING),1)"),
; so the two never coexist. The sibling owns banks 0-5 (six contiguous
; from X25519_REU_BANK=0), which is why 6 is the first free one.
;
; Scoped to the flag rather than applied globally, deliberately. The
; symbol is referenced UNCONDITIONALLY by
; `src/crypto/shared/crypto_swap.s::crypto_swap_to_x25519` — a routine
; with no caller in any build — so changing it outright moves an
; immediate operand in every PRG, including the two that ship. There is
; no behavioural reason for that: bank 3 is only wrong when the sibling
; is actually linked and actually owns the bank. Keeping the default
; arm at $30000 leaves both default PRGs byte-identical (verified by
; sha256), which is what makes this diff reviewable.
.ifndef REU_OVERLAY_X25519
.ifdef USE_X25519_SIBLING
REU_OVERLAY_X25519 = $60000
.else
REU_OVERLAY_X25519 = $30000
.endif
.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
; NB: REU_OVERLAY_X25519 shares this bank ONLY in the non-sibling arm
; above; under USE_X25519_SIBLING=1 it now sits at bank 6, because the
; sibling owns bank 3 for real. 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
Expand DownExpand Up@@ -125,8 +167,21 @@ REU_OVERLAY_P384_CURVE = $70000
; The sibling c64-x25519 v0.4.0 reu_mul_init populates banks 3, 4, and 5
; with its own doubled-product and 17th-bit-carry tables for fe25519_sqr.
; Banks 3 / 4 / 5 are also nominally reserved here for P-256 precompute
; (bank 3) and P-384 precompute (banks 4-5). The collision is THEORETICAL
; under the current TLS path:
; (bank 3) and P-384 precompute (banks 4-5).
;
; UPDATE — one of these was NOT theoretical, and the reasoning below is
; the reason it was missed. It asks only "does anything READ the
; reservation?", and every answer was no. The write nobody looked for
; was boot.s's unconditional STASH of CRYPTO_OVERLAY into
; REU_OVERLAY_X25519, which sat on bank 3 and silently destroyed the
; sibling's carry table. It was invisible because the sibling had never
; linked on either backend, so no build existed in which the collision
; could be observed. Fixed by relocating REU_OVERLAY_X25519 to bank 6
; (see the block at that definition). Treat the remaining reservations
; below as unverified rather than safe: a reservation with no reader can
; still have a writer.
;
; The rest of the collision analysis, for the reservations that remain:
; - P-256 always uses ec_scalar_mul_var (variable-base) which does not
; touch REU_P256_PRECOMPUTE_BASE.
; - P-384 is stubbed at the TLS layer (see project_p384_stubbed memory
Expand Down
Loading