fix(p384): un-break the P-384 archive build + upstream contract-alignment doc corrections - #84
Conversation
`tools/integration/build_nistcurves_p384.sh` hardcoded the archive member name `ecdsa384.o`. Upstream renamed it to `ecdsa384_nocomb.o` in c64-nist-curves commit 64b313d (their issue #61), released in v0.5.0 — i.e. before our own v0.6.0 pin, and unchanged by v0.7.0 and v0.8.0. Every P-384 target therefore died immediately at `ar65`, which CLAUDE.md recorded as opaque "upstream layout drift". Upstream builds every P-384 archive from LIB_P384_VERIFY_OBJS = $(LIB_P384_VERIFY_BASE_OBJS) \ $(BUILD_DIR)/ecdsa384_nocomb.o so `ar65 x` never produces `ecdsa384.o`; the staging dir at the point of failure contains `ecdsa384_nocomb.o` and no `ecdsa384.o`. A/B at the unchanged v0.6.0 pin, `make p384-overlay`: before: [p384] building libs/nistcurves lib-p384-sha384 + lib-p384-verify ... ar65: Error: Could not open '.../nistcurves_p384_staging/curve/ecdsa384.o': No such file or directory make: *** [build/lib/nistcurves-p384-sha384.a] Error 1 after: built .../build/lib/nistcurves-p384-sha384.a built .../build/lib/nistcurves-p384-curve.a ... ecdsa384_nocomb 10658 bytes (.o) ... ld65: Warning: cfg/p384-overlay-sha384.cfg(29): Segment 'LIB_NISTCURVES_SHA384_TABLES' overflows memory area 'OVERLAY_REGION' by 1536 bytes Both archives now build and the chain advances to the next, separate blocker — exactly the SHA-384 LUT overflow CLAUDE.md predicted, now confirmed at 1536 B. Two further P-384 blockers are documented rather than fixed here: the `USE_OVERLAY_P384_EMBED=1` build cannot resolve its order-only `build/labels.txt` prerequisite from clean, and the wrapper's `ec_scalar_mul_384_shim` is now dead (od65 shows `ecdsa384_nocomb.o` imports `ec_scalar_mul_var_384`, not `ec_scalar_mul_384`). Blast radius: none on the shipped builds. The script runs only under `make p384-overlay` / `USE_OVERLAY_P384_EMBED=1`. Re-verified after the change: `make` links at 47,105 B and `make BACKEND=uci` at 62,977 B, both unchanged. Also corrects documentation drift found while auditing our alignment against upstream c64-nist-curves and c64-lib-contract: - the `libs/nistcurves` pin is v0.6.0, not v0.3.0 (verified by fresh clone + `git fetch --tags --force`; `git describe` gives an exact tag, so the 2026-05 history rewrite is not confusing us) - segment naming is c64-lib-contract SPEC §4, not §8.1 (§8.1 is the shared `sqtab` table) - c64-nist-curves#54 is CLOSED as COMPLETED (2026-07-16) and shipped before upstream v0.4.0 — it has been inside our pin all along, so it is not headroom held in reserve. Two places said otherwise. - the contract's git tags lag `main` by four minor versions (newest tag v0.4.0, `main` is SPEC v0.7.2). Records which post-v0.4.0 clauses bind a *consumer*: §13 network backend ABI (our intake issue #70 is open), §8.0 `LIB_<X>_SHARED_CONSUMES`, and §1/§5 prefixed manifest exports with `-D LIB_NO_BARE_EXPORTS=1`. - records why a v0.7.0/v0.8.0 bump does not link under UCI today: `LIB_NISTCURVES_P256_RODATA` overflows `CRYPTO_HOT` by 207 B, because CRYPTO_HOT is one byte from full at the current pin (map: 009E1F..009FFE, region ends $9FFF) and v0.7.0's FIPS 186-5 §3.3 public-key validation gate adds +512 B. ip65 links at v0.8.0 and both VICE suites pass there (KAT 3/3, x509 11/11). - notes the fresh-submodule ip65 blob relink trap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JC-000
commented
Aug 13, 2026
Spot-checked by the supervising session. Two claims verified directly: The P-384 break was ours, not upstream drift. Confirmed the naming asymmetry in the tree: upstream c64-nist-curves#54 is CLOSED as completed (2026-07-16), while CLAUDE.md describes it in two places as open headroom — Not bumping was right, and the measurement proves it rather than merely arguing it: v0.8.0 links clean under ip65 at an identical 47,105 B but fails Two items here deserve their own tracking rather than living in a PR body:
Also noted: the contract's |
JC-000
commented
Aug 13, 2026
Merge-order note from the supervising session — dry-ran the whole stack onto Seven of the eight branches merge clean in sequence. This one conflicts, and only in Two conflicting regions, both in the Known-issues area, where #81's x25519 rewrite and this branch's contract-alignment rewrite replace the same block. Neither side is wrong and the content is complementary — #81 documents that Suggested order: land the doc-touching PRs first (#80, then #81), take this one last, and merge the two Known-issues blocks rather than choosing between them. If you would rather not hand-resolve at merge time, say so and I will have this branch rebased with the resolution pre-applied. Worth noting the one that did not conflict: #81 and #83 both modify |
Resolves the two CLAUDE.md conflicts by taking the better side of each rather than picking a branch: - Known-issues x25519 block: took the merged-stack version. It is a strict superset and this branch's side still asserted "UCI remains the supported sibling-on path", which #81 disproved by measurement (UCI overflows CRYPTO_HOT by 381 B; nothing links the sibling today). It also still cited a `make -C libs/x25519 lib-x25519-scalarmult` target that exists at no tag. - ECDSA wall-clock note: took this branch's version, which adds the "(the pin is v0.6.0 today)" clarification correcting the file's own v0.3.0-vs-v0.6.0 self-contradiction. The c64-nist-curves#54 CLOSED correction that lived in the dropped hunk survives via this branch's two non-conflicting hunks; verified both remaining mentions state CLOSED and no "optional headroom" phrasing is left. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lane F (upstream contract alignment). Two things: a verified build fix that removes the
first link in the long-broken P-384 chain, and documentation corrections found while
auditing c64-https against upstream
c64-nist-curvesandc64-lib-contract.No submodule bump.
libs/nistcurvesstays at v0.6.0. See "Why no bump" below — thebump does not link under UCI today, and an unverified bump is worse than none.
Fix —
tools/integration/build_nistcurves_p384.shstages the wrong member nameDefect (one sentence): the wrapper hardcoded the archive member
ecdsa384.o, butupstream renamed it to
ecdsa384_nocomb.oin c64-nist-curves64b313d(their issue #61),released in v0.5.0 — before our own v0.6.0 pin — so every P-384 target died immediately
at
ar65.Upstream builds every P-384 archive from
LIB_P384_VERIFY_OBJS = $(LIB_P384_VERIFY_BASE_OBJS) $(BUILD_DIR)/ecdsa384_nocomb.o(their
Makefile:221), soar65 xnever producesecdsa384.o. Confirmed by listing thestaging dir at the moment of failure:
ecdsa384_nocomb.opresent,ecdsa384.oabsent.CLAUDE.md attributed this to "the upstream layout drifted", which reads as something to
wait out. It was two tokens on our side.
Acceptance A/B
Both runs on this branch, submodule at the unchanged v0.6.0 pin,
make p384-overlay.Before (pristine, verbatim):
After (fixed, verbatim tail):
Both archives now build. The chain advances to the next, separate blocker — precisely
the one CLAUDE.md predicted ("likely still lurk the earlier v0.3.0-era SHA-384 LUT overlay
overflow (1536 B over the 7.5 KB slot)"), now confirmed at exactly 1536 B and located at
cfg/p384-overlay-sha384.cfg:29.Blast radius
None on shipped builds — the script runs only under
make p384-overlay/USE_OVERLAY_P384_EMBED=1. Re-verified with the patch applied:make(ip65)make BACKEND=uciTwo further P-384 blockers, documented not fixed
LIB_NISTCURVES_SHA384_TABLESoverflowsOVERLAY_REGIONby 1536 B(
cfg/p384-overlay-sha384.cfg:29; slot is $4200-$5FFF = 7,680 B).make BACKEND=uci USE_OVERLAY_P384_EMBED=1from clean dies withNo rule to make target 'build/labels.txt', needed by 'build/labels-p384-sha384.txt'—the overlay rules take an order-only
| build/labels.txt, but that file is only a sideeffect of the main link, whose bootstrap rule is gated off under this flag.
Also now dead weight (left in place to keep the diff minimal): the wrapper's
ec_scalar_mul_384_shim.od65 --dump-importsshowsecdsa384_nocomb.oimportsec_scalar_mul_var_384, notec_scalar_mul_384— theECDSA_NO_COMBvariant alreadyperforms the variable-base fallback the shim was written to supply, so ld65 never pulls it.
Doc corrections (each verified, not inferred)
libs/nistcurves@v0.3.0siblinggit fetch --tags --force;git describereturns an exact tag, so the 2026-05 history rewrite is not misleading ussqtabtableSPEC.mdon contractmainc64-nist-curves#54"remains open as optional headroom" (2 places)7cb59f7before upstream v0.4.0, so it has been inside our pin all alonggh issue view 54 --json closedAt,stateReason+ issue timelineMakefile,git log -S, staging-dir listinglibs/nistcurves@v0.3.0pin … measures 82.1 s"Plus two additions:
mainby four minor versions — newest tag is v0.4.0while
SPEC.mdonmainis v0.7.2 (dated 2026-08-13). Anyone readinggh release list/git taggets a stale answer. CLAUDE.md now says to readmain, and names thepost-v0.4.0 clauses that bind a consumer rather than an adopter: §13 network backend
ABI (v0.6.0 — written from c64-https's own net surface, citing our PR fix: remove 255-byte clamp in ip65 TCP RX callback #27 RX-clamp
defect and our abandoned
feat/net-drain-abibranch as motivation; our intake issueAlign networking surface with c64-lib-contract SPEC §13 (network backend ABI) #70 is open), §8.0
LIB_<X>_SHARED_CONSUMES(v0.5.0 — we are theAPP_OWNEDcase), and §1/§5 prefixed manifest exports with
-D LIB_NO_BARE_EXPORTS=1(v0.7.0 —the sanctioned replacement for the hand-dropped
lib_version.ointools/integration/build_x25519.sh:174). c64-https imports no contract manifestequate today, so none of it is enforced here yet.
git submodule update --init ip65, plainmaketries to relink the ip65 blob (the checked-outip65_stub.sis newer than thecommitted
ip65-c64.bin) and dies on a missingip65_tcp.lib.touch ip65-build/ip65-c64.binrestores the documented "committed blob is reused" path.Why no submodule bump
Deliverable gate was: ip65 links, UCI links, KAT passes, x509 passes. Measured all four
against
libs/nistcurvesv0.8.0 on this branch:make(ip65) linksmake BACKEND=ucilinkstools/test_ecdsa_kat_oracle.py(VICE,-reu)tools/test_x509.pyBisected to v0.7.0 (v0.7.0 fails identically; v0.8.0 adds nothing to it). The cause is
v0.7.0's +512 B FIPS 186-5 §3.3 public-key validation gate landing in a region that is
one byte from full at the current pin —
build/c64-https.map:LIB_NISTCURVES_P256_RODATA 009E1F 009FFE 0001E0, andCRYPTO_HOTends at$9FFF.The bump is worth taking eventually: that gate validates the attacker-supplied
certificate public key
src/tls_cert.sfeeds toecdsa_verify_256, which c64-https doesnot check itself. A link-verified remedy exists — routing
cfg/c64-https-uci.cfg:246 LIB_NISTCURVES_P256_RODATAto the otherwise-unusedCRYPTO_OVERLAYmakes v0.8.0 UCI link at the same 62,977 B with 7,200 B of the slot stillfree — but it is link-verified only, it permanently occupies a slot three mutually
exclusive flags claim, and it changes what
tools/uci/_memory_policy.pysees at$4200-$5FFF. That needs a UCI e2e behind it, so it is a follow-up PR, not this one.
Note for the ECDSA-coverage lane: no export we depend on was renamed or removed in
v0.7.0, v0.8.0, or upstream master.
LIB_ABI_VERSIONis still0, and master's"prefixed exports" work is additive (bare
LIB_VERSION_*still emitted by default unlessthe consumer passes
-D LIB_NO_BARE_EXPORTS=1). So this bump would not have triggered thesilent-skip failure mode — that fix is prudent rather than urgent on this evidence.
🤖 Generated with Claude Code