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
44 changes: 40 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -162,7 +162,7 @@ Variables:
(`cfg/c64-https-$(BACKEND).cfg`; default ip65).
Changing it requires `make clean` — see above.
- `USE_X25519_SIBLING=1` — swap the in-tree X25519 for the
`libs/x25519@v0.11.1` sibling. **UCI links;
`libs/x25519@v0.11.2` sibling. **UCI links;
ip65 overflows CRYPTO_OVERLAY** — see
"Known issues". Off by default either way.
- `EMBED_P256_OVERLAY=1` — stage the P-256 verify image into the
Expand DownExpand Up@@ -218,7 +218,7 @@ buffers in the crypto BSS — see per-module headers for details):

X25519 / field arithmetic
Default: in-tree `src/crypto/{x25519,fe25519}.s`.
Opt-in: sibling `libs/x25519@v0.11.1` via `make USE_X25519_SIBLING=1`
Opt-in: sibling `libs/x25519@v0.11.2` via `make USE_X25519_SIBLING=1`
— links under UCI from the v0.10.1/v0.11.0 wave pins, still
overflows under ip65, and ships in nothing; see Known issues.
The v0.6.0 pin is c64-lib-contract-aligned (SPEC §8.1) and adds the
Expand All@@ -235,7 +235,7 @@ buffers in the crypto BSS — see per-module headers for details):
SHA-256 (in-tree; no sibling)
sha256_init, sha256_update, sha256_final

ECDSA P-256 (`libs/nistcurves@v0.11.0` sibling,
ECDSA P-256 (`libs/nistcurves@v0.11.2` sibling,
c64-lib-contract SPEC §1-§8.1 aligned)
ecdsa_verify_256 — TLS dispatcher in src/crypto/ecdsa_verify.s
packs the BE struct + calls the sibling entry
Expand DownExpand Up@@ -901,7 +901,7 @@ Five latent bugs and three new ones were cleared to get here:
`tools/test_x25519.py` RFC 7748 vector 2 — whose u ends `0x93` —
passes 73/73 on the in-tree build.)

The pin is now **v0.11.1**. The v0.6.0 -> v0.10.0 migrations the
The pin is now **v0.11.2**. The v0.6.0 -> v0.10.0 migrations the
wrapper had to absorb (the §4 `LIB_X25519_CODE` /
`LIB_X25519_INIT_CODE` segment renames, the `x25_x1` buffer added
by v0.7.0's #64 fix) are done and documented in
Expand DownExpand Up@@ -980,6 +980,42 @@ Five latent bugs and three new ones were cleared to get here:
Both P-384 wrappers now **discover** those member names instead, so
the chain again stops exactly at the SHA-384 `OVERLAY_REGION`
overflow above — verified, same 1536 B, at the v0.9.1 pin.
- **§6.1 IS SATISFIED: no archive member is edited.** From the
`libs/nistcurves` **v0.11.2** pin the wrapper requests the §8.0
APP_OWNED shape through `CONTRACT_DEFINES` (SPEC §6.2) instead of
deleting members: four `SHARED_*` switches, `LIB_NO_BARE_EXPORTS=1`
and `LIB_SHARED_SQTAB_BASE=0xBC00`. The manifest then *attests* the
deferral — `SHARED_PRIMITIVES=$0000`, `SHARED_CONSUMES=$0007` — so
the §8.0 disjointness and coverage asserts are live in
`src/lib_contract_asserts.s`, profile-independently, where they were
previously recorded as "not writable today".

Three ownership questions moved to c64-https as a result, all in the
direction §8.0 always implied:
- `poly_prod_lo/hi` — we provide the §8.3 `ct_mul_8x8` body, so we
own its product scratch. `poly1305.s` exports them in every
profile now (it used to import under onchip).
- `sqtab_lo/hi` — we provide `sqtab_init`, so we own the table.
`data.s` defines them in every profile; the `sqtab_reserved`
placeholder is gone. The sibling still reads through its own
equates derived from `LIB_SHARED_SQTAB_BASE`, deriving
`sqtab_hi = base+$0200`, so the labels MUST land at
`$BC00`/`$BE00` — the Makefile's post-link check asserts it.
- the ABI import is the prefixed `LIB_NISTCURVES_ABI_VERSION`; the
bare name is suppressed by the export gate.

**Two upstream fixes made this possible, and the second is the one
that cost a day.** v0.11.1 made `SHARED_CT_MUL_8X8` assemble against
the on-chip TU (c64-nist-curves#123, filed from here) — before that
the wrapper needed a glue TU that `.include`d pristine library
source. v0.11.2 added the **knob-staleness guard**: a changed
`CONTRACT_DEFINES` used to reuse stale objects and exit 0 with a
*different archive than requested*. An attempt at this change before
that guard produced a comb image that would not boot, and the cause
looked like our ownership changes; it was mixed objects. If a
`libs/nistcurves` build ever behaves inexplicably after a define
change, check the pin is >= v0.11.2 before debugging anything else.

- **Sibling-archive member names are DISCOVERED, never hardcoded —
and this is load-bearing.** Upstream v0.9.0 gave each of its nine
archives its own per-variant `zp_config_*.o` /
Expand Down
11 changes: 7 additions & 4 deletions Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -345,10 +345,13 @@ $(PRG): $(PRG_DEPS)
ifeq ($(USE_NISTCURVES_ONCHIP),1)
# Onchip-profile invariant: the sibling's sqtab_lo/hi equates are
# BAKED to $$BC00/$$BE00 (LIB_SHARED_SQTAB_BASE in the wrapper).
# data.s's sqtab_reserved placeholder must still land exactly there —
# any TABLES_BSS layout drift silently corrupts every multiply.
@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; }
# src/data.s owns sqtab_lo/hi, but the sibling reads the table through
# its OWN equates derived from that base, so the two must agree on the
# address. Drift is neither a link nor a boot failure — just every
# multiply reading the wrong memory.
@grep -q '^al C:BC00 \.sqtab_lo' $(LABELS) || \
{ echo 'ERROR: sqtab_lo is not at $$BC00 — TABLES_BSS layout drifted; realign LIB_SHARED_SQTAB_BASE in tools/integration/build_nistcurves_p256.sh'; \
grep ' \.sqtab_lo$$' $(LABELS); exit 1; }
endif
ifeq ($(USE_X25519_SIBLING),1)
# Same invariant, other sibling: build_x25519.sh bakes sqtab_lo/hi
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,9 +294,9 @@ Progress:

### Known Issues

- **The handshake is slow, and the ECDSA P-256 verify dominates it.** Every figure here is quoted from the measurement record in `CLAUDE.md`. Except where noted they were taken at the **`libs/nistcurves` v0.6.0 pin**, and the pin is now v0.11.0, so treat them as a baseline rather than as current. The one profile re-measured at the current pin is comb: 46.986 / 24.440 / 16.402 s verify at 16 / 32 / 48 MHz (U64E, n=3, VIC blanking active). End-to-end handshake + GET against the local listener, U64E, master 2ceb5b1: **80.8 s** (REU profile, 48 MHz), **45.5 s** (onchip profile, 48 MHz), **1,157.7 s** (REU, stock 1 MHz). One point of that sweep has been carried forward: 48 MHz REU measures **82.1 s** at v0.9.1 and **82.4 s** at v0.10.1 (n=1 each, so the 0.4% step between them is noise; the 1.6% from v0.6.0 is the FIPS 186-5 public-key validation gate v0.7.0 added). No other clock or profile has been re-measured. On the REU-less stock-C64 path (ip65 + onchip, no REU, honest 1 MHz in VICE) the whole run measured **2,159.7 s = 36.0 min**, of which the verify stretch alone was 1,416.7 s. That is fine for the local listener, which holds the connection open; it exceeds a typical 10-30 s real-world server handshake window.
- **The handshake is slow, and the ECDSA P-256 verify dominates it.** Every figure here is quoted from the measurement record in `CLAUDE.md`. Except where noted they were taken at the **`libs/nistcurves` v0.6.0 pin**, and the pin is now v0.11.2, so treat them as a baseline rather than as current. The one profile re-measured at the current pin is comb: 46.986 / 24.440 / 16.402 s verify at 16 / 32 / 48 MHz (U64E, n=3, VIC blanking active). End-to-end handshake + GET against the local listener, U64E, master 2ceb5b1: **80.8 s** (REU profile, 48 MHz), **45.5 s** (onchip profile, 48 MHz), **1,157.7 s** (REU, stock 1 MHz). One point of that sweep has been carried forward: 48 MHz REU measures **82.1 s** at v0.9.1 and **82.4 s** at v0.10.1 (n=1 each, so the 0.4% step between them is noise; the 1.6% from v0.6.0 is the FIPS 186-5 public-key validation gate v0.7.0 added). No other clock or profile has been re-measured. On the REU-less stock-C64 path (ip65 + onchip, no REU, honest 1 MHz in VICE) the whole run measured **2,159.7 s = 36.0 min**, of which the verify stretch alone was 1,416.7 s. That is fine for the local listener, which holds the connection open; it exceeds a typical 10-30 s real-world server handshake window.
- **P-384** ECDSA is stubbed at the TLS layer. The dispatcher advertises `ecdsa_secp384r1_sha384` (0x0503) and routes to `src/crypto/ecdsa_verify_384.s`, but no P-384 build target completes. Measured 2026-08-14: `make p384-overlay` from a clean tree stops at `No rule to make target 'build/labels.txt'`, and after a main build has produced that file it stops at `Segment 'LIB_NISTCURVES_SHA384_TABLES' overflows memory area 'OVERLAY_REGION' by 1536 bytes`. Cert chains requiring P-384 will not verify.
- **`USE_X25519_SIBLING=1` now links under UCI, and still does not under ip65.** The duplicate-symbol failure this entry used to record — `ld65: Error: Duplicate external identifier: 'reu_mul_tables_init'`, on **both** backends — was closed by the `libs/nistcurves` v0.10.1 / `libs/x25519` v0.11.0 bump plus one line of archive surgery: `tools/integration/build_nistcurves_p256.sh` now also drops `reu_mul_init.o`, the SPEC §8.2 `reu_mul` provider that `src/boot.s` supplies itself. Re-measured at the current v0.11.0 / v0.11.1 pins, unchanged: `make clean && make BACKEND=uci USE_X25519_SIBLING=1` produces a 62,977 B PRG, and ip65 stops instead at `Segment 'X25519_RODATA' overflows memory area 'CRYPTO_OVERLAY' by 3584 bytes` — a placement problem (ip65's overlay slot is 4,212 B against UCI's 7,680 B), not a symbol collision. The flag remains **off by default** and no shipped artifact contains the sibling; the in-tree X25519 in `src/crypto/{x25519,fe25519}.s` is what every release PRG is built from. Flipping the default is a separate decision that wants a hardware handshake behind it.
- **`USE_X25519_SIBLING=1` now links under UCI, and still does not under ip65.** The duplicate-symbol failure this entry used to record — `ld65: Error: Duplicate external identifier: 'reu_mul_tables_init'`, on **both** backends — was closed by the `libs/nistcurves` v0.10.1 / `libs/x25519` v0.11.0 bump plus one line of archive surgery: `tools/integration/build_nistcurves_p256.sh` now also drops `reu_mul_init.o`, the SPEC §8.2 `reu_mul` provider that `src/boot.s` supplies itself. Re-measured at the current v0.11.2 pins, unchanged: `make clean && make BACKEND=uci USE_X25519_SIBLING=1` produces a 62,977 B PRG, and ip65 stops instead at `Segment 'X25519_RODATA' overflows memory area 'CRYPTO_OVERLAY' by 3584 bytes` — a placement problem (ip65's overlay slot is 4,212 B against UCI's 7,680 B), not a symbol collision. The flag remains **off by default** and no shipped artifact contains the sibling; the in-tree X25519 in `src/crypto/{x25519,fe25519}.s` is what every release PRG is built from. Flipping the default is a separate decision that wants a hardware handshake behind it.
- **Live internet HTTP GET (UCI backend)** has not been re-verified since the FPGA-fence rework; only the local multi-segment listener is exercised regularly.
- **VICE 3.9** previously appeared to crash on chained HMAC-SHA256 calls (backend-independent — affects the crypto-only test suites), but this was caused by hardcoded port numbers bypassing the test harness port allocator. With proper `ViceInstanceManager` usage (no hardcoded ports), all N=1..10 chained calls succeed reliably.

Expand Down
9 changes: 4 additions & 5 deletions src/contract_footprint_asserts.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -99,11 +99,10 @@
; - the switch was PRG-NEUTRAL — the comb image is byte-identical
; (`63c7ce868cdd…`) built either way, so the archive change altered the
; manifest and nothing else;
; - the wrapper still drops three members for other reasons
; (`mul_8x8.o`, `data_shared.o`, `reu_mul_init.o` — the §8.0 APP_OWNED
; collision set, c64-https#119). That surgery does not affect the
; numbers this assert reads, because those are per-variant §6.4 values,
; but it is why the §8.0 disjointness asserts are still not writable.
; - the wrapper drops NO members at all from the v0.11.2 pin
; (c64-https#119 closed): the §8.0 deferral is requested through
; CONTRACT_DEFINES, so the manifest attests it and the §8.0
; disjointness/coverage asserts in src/lib_contract_asserts.s are live.


.import LIB_NISTCURVES_RESIDENT_BYTES
Expand Down
49 changes: 18 additions & 31 deletions src/crypto/poly1305.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,45 +15,34 @@
.import poly_h, poly_r, poly_s, poly_product, poly1305_tag
.import aead_scratch

; --- poly_prod_lo / poly_prod_hi ownership (c64-lib-contract v0.9.1
; "adopter-private buffer" rule) ---
; --- poly_prod_lo / poly_prod_hi: c64-https OWNS them, every profile ---
;
; These two bytes are the a*b -> 16-bit output of the §8.3 `ct_mul_8x8`
; body below, and ordinary scratch for anything else that wants a 16-bit
; product slot. Exactly ONE module in the link may define them, and every
; writer and reader must agree on which — they are a rendezvous, not
; private state, so a second copy is not a duplicate-symbol error but a
; silent wrong answer.
; Two bytes, and getting the direction wrong is silent wrong crypto with no
; link error: they are the a*b -> 16-bit output of the §8.3 `ct_mul_8x8`
; body below, written by that body and read back by the library's
; `og_common` row generator. Exactly ONE module may define them. If both
; sides defined a pair the link SUCCEEDS with two disjoint pairs and every
; on-chip-generated multiply row reads two bytes nothing ever wrote.
;
; Under USE_NISTCURVES_ONCHIP the owner is the sibling's
; `mul_8x8_onchip.o`, and it must be: from libs/nistcurves v0.10.0 that
; object defines poly_prod_lo/hi OUTSIDE the SHARED_CT_MUL_8X8 gate,
; because its own `fp_sqr` diagonal-squaring path writes them
; independently of ct_mul_8x8 (upstream's comment at
; libs/nistcurves/src/mul_8x8.s:207-222). Its `og_common` row generator
; then does `jsr ct_mul_8x8` — resolving to the body below — and reads
; the result back out of poly_prod_lo/hi. If this file kept its own pair,
; og_common would read two zero bytes on every product and every
; on-chip-generated multiply row would be wrong, with no link diagnostic
; anywhere. So we import instead: one pair of bytes, in
; LIB_NISTCURVES_MUL_CODE, written and read by both sides.
; SPEC §8.3: whoever provides the ct_mul_8x8 body owns the product scratch.
; c64-https provides that body, so it owns the pair — in every profile,
; from the libs/nistcurves v0.11.2 pin.
;
; Under every other profile the sibling's mul_8x8 object is dropped from
; the archive entirely, nothing else defines them, and this file owns
; them as before.
.ifdef USE_NISTCURVES_ONCHIP
.import poly_prod_lo
.import poly_prod_hi
.endif
; This was profile-split before, and both arms were workarounds. Under the
; REU profile the sibling's mul_8x8 object was DROPPED from the archive, so
; this file owned the pair by default. Under onchip the sibling exported
; them unconditionally (v0.10.0 moved them outside the SHARED_CT_MUL_8X8
; gate) and this file had to yield and import. v0.11.1 puts them inside the
; gate as IMPORTS on the library side (c64-nist-curves#123), so a deferring
; consumer provides them everywhere and the split disappears.


; --- Exports ---
.export poly1305_init
.export poly1305_clamp
.export sqtab_init
.ifndef USE_NISTCURVES_ONCHIP
.export poly_prod_lo
.export poly_prod_hi
.endif
.export mul_8x8
.export poly1305_multiply
.export poly1305_reduce
Expand All@@ -66,10 +55,8 @@
; =============================================================================
.segment "CRYPTO_BSS"

.ifndef USE_NISTCURVES_ONCHIP
poly_prod_lo: .res 1
poly_prod_hi: .res 1
.endif

mul_a: .res 1
mul_b: .res 1
Expand Down
32 changes: 19 additions & 13 deletions src/data.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -130,24 +130,30 @@ mul_dma_hi: .res 256 ; DMA target: hi bytes of a*b for current a
; These remain in-tree under both modes: sqtab_init is still served by
; src/crypto/poly1305.s (the sibling's mul_8x8.s is intentionally
; excluded from build/lib/x25519.a — see tools/integration/build_x25519.sh).
.ifndef USE_NISTCURVES_ONCHIP
; c64-https OWNS the quarter-square tables in every profile, because it
; owns the §8.0 SQTAB primitive: src/crypto/poly1305.s provides
; `sqtab_init`, and the table a primitive fills belongs to whoever fills it.
;
; This was profile-split before. Under USE_NISTCURVES_ONCHIP the in-tree
; labels yielded to the sibling, which exported sqtab_lo/hi as absolute
; equates baked to LIB_SHARED_SQTAB_BASE, and this file kept a same-size
; `sqtab_reserved` placeholder purely to hold the address still. That
; existed because the sibling's export was unconditional; from the v0.11.2
; pin the wrapper builds it with `-D LIB_NO_BARE_EXPORTS=1` (SPEC §6.5), so
; the bare names are suppressed and the split collapses in the direction
; §8.0 always implied.
;
; THE ADDRESS IS STILL LOAD-BEARING. The sibling reads the table through
; its OWN equates derived from LIB_SHARED_SQTAB_BASE=0xBC00, and derives
; sqtab_hi as base+$0200 (libs/nistcurves/src/mul_8x8.s:47, with its own
; `.assert sqtab_hi = sqtab_lo + $0200`). These labels must therefore land
; at $BC00/$BE00 or every multiply reads the wrong memory — silently, with
; no link error. The Makefile's post-link check asserts sqtab_lo's address.
.align 256
.export sqtab_lo
.export sqtab_hi
sqtab_lo: .res 512
sqtab_hi: .res 512
.else
; Under USE_NISTCURVES_ONCHIP the sibling's rebuilt mul_8x8_onchip.o
; exports sqtab_lo/hi as ABSOLUTE EQUATES at LIB_SHARED_SQTAB_BASE=$BC00
; (its .export is unconditional, so the in-tree labels must yield).
; Keep a same-size aligned placeholder so the TABLES_BSS layout — and
; therefore the $BC00 address the equate is baked to — stays put. The
; placeholder is exported solely so the Makefile's post-link check can
; assert it still sits at $BC00; sqtab_init fills it via the equates.
.align 256
.export sqtab_reserved
sqtab_reserved: .res 1024
.endif

.segment "BSS"

Expand Down
Loading