Skip to content

fix(p384): un-break the P-384 archive build + upstream contract-alignment doc corrections - #84

Merged
JC-000 merged 2 commits into
docs/benchmark-refreshfrom
chore/upstream-contract-alignment
Aug 13, 2026
Merged

fix(p384): un-break the P-384 archive build + upstream contract-alignment doc corrections#84
JC-000 merged 2 commits into
docs/benchmark-refreshfrom
chore/upstream-contract-alignment

Conversation

@JC-000

Copy link
Copy Markdown
Owner

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-curves and c64-lib-contract.

No submodule bump.libs/nistcurves stays at v0.6.0. See "Why no bump" below — the
bump does not link under UCI today, and an unverified bump is worse than none.


Fix — tools/integration/build_nistcurves_p384.sh stages the wrong member name

Defect (one sentence): the wrapper hardcoded the archive member ecdsa384.o, but
upstream renamed it to ecdsa384_nocomb.o in c64-nist-curves 64b313d (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), so ar65 x never produces ecdsa384.o. Confirmed by listing the
staging dir at the moment of failure: ecdsa384_nocomb.o present, ecdsa384.o absent.
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):

bash tools/integration/build_nistcurves_p384.sh
[p384] building libs/nistcurves lib-p384-sha384 + lib-p384-verify (upstream defaults)...
ar65: Error: Could not open '/…/build/lib/nistcurves_p384_staging/curve/ecdsa384.o': No such file or directory
make: *** [build/lib/nistcurves-p384-sha384.a] Error 1

After (fixed, verbatim tail):

built /…/build/lib/nistcurves-p384-sha384.a
built /…/build/lib/nistcurves-p384-curve.a
…
ecdsa384_nocomb 10658 bytes (.o)
ec_scalar_mul_384_shim 898 bytes (.o)
data_p384 15166 bytes (.o)
bash tools/integration/build_nistcurves_p384_bin.sh
ld65: Warning: /…/cfg/p384-overlay-sha384.cfg(29): Segment 'LIB_NISTCURVES_SHA384_TABLES' overflows memory area 'OVERLAY_REGION' by 1536 bytes
ld65: Error: Cannot generate most of the files due to memory area overflow

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:

targetresult
make (ip65)links, 47,105 B
make BACKEND=ucilinks, 62,977 B

Two further P-384 blockers, documented not fixed

  1. LIB_NISTCURVES_SHA384_TABLES overflows OVERLAY_REGION by 1536 B
    (cfg/p384-overlay-sha384.cfg:29; slot is $4200-$5FFF = 7,680 B).
  2. make BACKEND=uci USE_OVERLAY_P384_EMBED=1 from clean dies with
    No 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 side
    effect 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-imports shows ecdsa384_nocomb.o imports
ec_scalar_mul_var_384, not ec_scalar_mul_384 — the ECDSA_NO_COMB variant already
performs the variable-base fallback the shim was written to supply, so ld65 never pulls it.


Doc corrections (each verified, not inferred)

claim in CLAUDE.mdcorrectionhow verified
libs/nistcurves@v0.3.0 siblingpin is v0.6.0fresh clone + git fetch --tags --force; git describe returns an exact tag, so the 2026-05 history rewrite is not misleading us
segments follow SPEC §8.1 namingsegment naming is SPEC §4; §8.1 is the shared sqtab tableread SPEC.md on contract main
c64-nist-curves#54 "remains open as optional headroom" (2 places)CLOSED as COMPLETED 2026-07-16; the 261 B trim shipped in 7cb59f7 before upstream v0.4.0, so it has been inside our pin all alonggh issue view 54 --json closedAt,stateReason + issue timeline
P-384 fails because "the upstream layout drifted"true but not actionable as written; the operative half was our hardcoded member name (see above)upstream Makefile, git log -S, staging-dir listing
"Under the currentlibs/nistcurves@v0.3.0 pin … measures 82.1 s"historical (2026-05-20); reworded to past tense with the current pin namedpin check above

Plus two additions:

  • The contract's git tags lag main by four minor versions — newest tag is v0.4.0
    while SPEC.md on main is v0.7.2 (dated 2026-08-13). Anyone reading gh release list / git tag gets a stale answer. CLAUDE.md now says to read main, and names the
    post-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-abi branch as motivation; our intake issue
    Align 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 the APP_OWNED
    case), 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.o in
    tools/integration/build_x25519.sh:174). c64-https imports no contract manifest
    equate today, so none of it is enforced here yet.
  • A fresh-submodule build trap: right after git submodule update --init ip65, plain
    make tries to relink the ip65 blob (the checked-out ip65_stub.s is newer than the
    committed ip65-c64.bin) and dies on a missing ip65_tcp.lib. touch ip65-build/ip65-c64.bin restores 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:

gatev0.6.0 (current pin)v0.8.0
make (ip65) linksPASS — 47,105 BPASS — 47,105 B
make BACKEND=uci linksPASS — 62,977 BFAIL
tools/test_ecdsa_kat_oracle.py (VICE, -reu)PASS 3/3
tools/test_x509.pyPASS 11/11
ld65: Warning: cfg/c64-https-uci.cfg(128): Segment 'LIB_NISTCURVES_P256_RODATA'
overflows memory area 'CRYPTO_HOT' by 207 bytes
ld65: Error: Cannot generate most of the files due to memory area overflow

Bisected 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, and CRYPTO_HOT ends at $9FFF.

The bump is worth taking eventually: that gate validates the attacker-supplied
certificate public key src/tls_cert.s feeds to ecdsa_verify_256, which c64-https does
not check itself. A link-verified remedy exists — routing
cfg/c64-https-uci.cfg:246 LIB_NISTCURVES_P256_RODATA to the otherwise-unused
CRYPTO_OVERLAY makes v0.8.0 UCI link at the same 62,977 B with 7,200 B of the slot still
free — but it is link-verified only, it permanently occupies a slot three mutually
exclusive flags claim, and it changes what tools/uci/_memory_policy.py sees 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_VERSION is still 0, and master's
"prefixed exports" work is additive (bare LIB_VERSION_* still emitted by default unless
the consumer passes -D LIB_NO_BARE_EXPORTS=1). So this bump would not have triggered the
silent-skip failure mode — that fix is prudent rather than urgent on this evidence.

🤖 Generated with Claude Code

`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

Copy link
Copy Markdown
OwnerAuthor

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 libs/nistcurves/Makefile:58 builds ecdsa384_nocomb.o from src/ecdsa384.s, exactly mirroring ecdsa256_nocomb.o — and our P-256 wrapper already stages the nocomb name correctly (it appears in every build log). Only build_nistcurves_p384.sh:206 stages the bare ecdsa384.o. So the "all P-384 targets are broken at the v0.6.0 pin" story, which has stood for months and got two issues closed as stale, was a one-line wrapper bug against a rename that shipped in v0.5.0 — before our own pin. The A/B advancing to precisely the LIB_NISTCURVES_SHA384_TABLES overflows OVERLAY_REGION by 1536 bytes blocker that CLAUDE.md predicted is good corroboration that the fix is real and the next obstacle is the documented one.

c64-nist-curves#54 is CLOSED as completed (2026-07-16), while CLAUDE.md describes it in two places as open headroom — :973 "remains open as optional headroom" and :1048 "is now optional headroom rather than a blocker". Worse than stale: it is counted on as reserve capacity that was in fact already spent inside our current pin.

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 BACKEND=uci with LIB_NISTCURVES_P256_RODATA overflows CRYPTO_HOT by 207 bytes, bisected to v0.7.0. Holding back the CRYPTO_OVERLAY remedy because it is link-only evidence, permanently claims a slot three mutually exclusive flags contend for, and moves what tools/uci/_memory_policy.py sees at $4200-$5FFF — that is the correct call at this evidence level.

Two items here deserve their own tracking rather than living in a PR body:

  1. v0.7.0's FIPS 186-5 public-key validation gate. The Q we pass to ecdsa_verify_256 comes from an attacker-supplied certificate and we perform no on-curve check ourselves. That reframes the bump from "housekeeping" to "closes a validation gap", at +512 B.
  2. CRYPTO_HOT has one byte of slack (LIB_NISTCURVES_P256_RODATA ends $9FFE, region ends $9FFF). Any future growth in that segment fails the link, which is the mechanism behind the v0.7.0 overflow above.

Also noted: the contract's SPEC.md on main is v0.7.2 while its latest tag is v0.4.0 — so reading tags understates the spec by four minor versions, and §13's Network backend ABI was written from this project's net surface with our intake issue #70 still open.

@JC-000

Copy link
Copy Markdown
OwnerAuthor

Merge-order note from the supervising session — dry-ran the whole stack onto docs/benchmark-refresh in a scratch clone.

Seven of the eight branches merge clean in sequence. This one conflicts, and only in CLAUDE.md:

 OK fix/audit-f1-aead-oracle
OK fix/audit-f10-handshake-oracle
OK fix/audit-f4-f8-f9-harness-docs
OK chore/x25519-alignment
OK fix/audit-f5-f6-e2e-oracle
OK fix/audit-f3-f7-ecdsa-coverage
CONFLICT chore/upstream-contract-alignment -- CLAUDE.md
OK fix/audit-f2-bad-finished

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 USE_X25519_SIBLING=1 links on neither backend plus the pinned-v0.6.0 correctness bug; this branch documents the contract §13 gap, the closed-not-open state of c64-nist-curves#54, and the P-384 wrapper fix. The resolution is keep-both, not pick-a-side.

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 tools/run_all_tests.py — different hunks (x25519 import branch vs suite-list and summary block) — and auto-merged, exactly as both lanes predicted in their PR bodies.

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>
@JC-000
JC-000 merged commit e597b70 into docs/benchmark-refreshAug 13, 2026
@JC-000
JC-000 deleted the chore/upstream-contract-alignment branch August 13, 2026 13:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JC-000