Repository navigation
Description
Heads-up from the c64-nist-curves side, with something you could not have seen yet.
The link failure you reported is fixed
c64-nist-curves#149 is fixed on master and ships in v0.13.0. The §8.2 settle state no longer shares a translation unit with the multiply-row landing buffers, so resolving it does not drag data_shared.o and its nistcurves_mul_dma_lo/hi definitions into your link.
Your preference order was right and option 1 is what landed — the split, no new knob, no configuration needed on your side.
Two things worth knowing:
- The first split was half a fix. Moving only the settle state left the multiply operand cache (
nistcurves_mul_cached_a/_src2_buf) in the buffers' TU, andfp256.o/fp384.o/mul_8x8.oall import it — so a consumer that owned the buffers and called any field op still hit the identical error. Adversarial review caught that our own regression probe referenced only the settle state, i.e. it stood in for a consumer that links nothing. The probe now callsfp_mul, and there are three files:data_reu_wait.s,data_mul_stage.s,data_shared.s. - Your report became contract law. I raised the general rule as c64-lib-contract#179 and it is now normative as SPEC 1.2.0 §6.1 member isolation, refined by 1.2.1's counterpart carve-out. Your incident is cited in the release entry.
The second blocker, which you have not hit yet because the link failed first
I built your tree against our master to verify the fix end-to-end. The duplicate-external error is gone. The build then fails at a different point:
ld65: Warning: cfg/c64-https-ip65.cfg(106): Segment 'LIB_NISTCURVES_MUL_CODE'
overflows memory area 'CRYPTO_RESIDENT' by 57 bytes
ld65: Error: Cannot generate most of the files due to memory area overflow
Config: USE_NISTCURVES_ONCHIP=1, ip65. LIB_NISTCURVES_MUL_CODE grew 366 → 408 bytes between v0.11.2 and now — that is issue #130's nistcurves_reu_dma_wait, the bounded $DF00 bit-6 confirm and post-execute settle, which is resident in both profiles because the hot path calls it. It arrived in v0.12.0, not in this fix.
So moving to v0.13.0 needs 57 bytes more in CRYPTO_RESIDENT (or the segment placed elsewhere). I have not proposed a patch because your memory map is yours.
Related: our §5 figures were understating, including the archive you link
While auditing, the RESIDENT_BYTES / COLD_BYTES pins turned out to be comparing our manifest against a hard-coded copy of the same numbers — so they checked nothing about the archive. Replacing that with a real measurement found six of twelve archives understating, in the direction where your §5 fit check passes while we overrun your region. nistcurves-p256-verify-onchip.a — which your build stages as nistcurves-p256-onchip.a — was short by 12 bytes; p384-verify-onchip by 72.
Every RESIDENT_BYTES moved in v0.13.0, and they now include worst-case page-alignment padding. If you bind our §5 equates to a budget assert, re-read the API.md §8.4.1 table — the numbers are larger and they are now measured rather than asserted.
Also in v0.13.0, and it affects you directly
LIB_NISTCURVES_ABI_VERSION moves 2 → 3. Any .assert LIB_NISTCURVES_ABI_VERSION = 2 gate needs updating.
The reason is c64-nist-curves#148, the comb issue you filed: ec_scalar_mul[_384] now return C=1 with a zeroed output when the evaluation ends at infinity having seeded from a table slot, where they previously documented no carry at all. ecdsa_verify_* reject on it internally, so if you only call the packaged verifiers there is no code change — just the gate.
Your instinct on #148 was right and the fix is in, but please read the scope note before treating it as closed on your side: it covers collapse-to-infinity from accidental corruption, which is the shape your "REU too small / precompute never ran / another tenant" leads describe. It is not a defence against an attacker who can write REU bank 2, and no post-condition on the result can be — an attacker who picks the planted point reaches a forged R with Z != 0 throughout, no collapse involved. Your decision to reject the 90-110 byte boot-time table probe still looks right to me; the honest answer for that threat is table integrity, not a probe.
Activity
JC-000 commentedon Sep 6, 2026
v0.13.0 is published. https://github.com/JC-000/c64-nist-curves/releases/tag/v0.13.0
Tarball SHA256
ef49c54ebcc7bbab29b3197d9c91886960f5d47e62a55c084296dcd63b3b8568, and there is now a.sha256sidecar asset — verify withshasum -a 256 -c. I re-downloaded the published asset and confirmed the hash matches before announcing it, which is the check v0.12.0 failed.Three things to action on your side, in order of how quickly they will bite:
LIB_NISTCURVES_ABI_VERSIONis now 3. Any.assert LIB_NISTCURVES_ABI_VERSION = 2gate needs updating or your link fails. If you only call the packaged verifiers there is no other change — the uci: capture the firmware's STATUS line instead of discarding it (#147) #148 rejection is internal toecdsa_verify_*.The 57-byte
CRYPTO_RESIDENToverflow is still there and is yours to place. It is not from this fix —LIB_NISTCURVES_MUL_CODEgrew 366 → 408 bytes in v0.12.0 when issue test(uci): menu-driven rig proving the HTTPS banner names the real host (#128) #130 added the §8.2 DMA settle, which is resident in both profiles. You will hit it immediately after the duplicate-external error disappears.Every
RESIDENT_BYTESvalue moved. If you bind our §5 equates to a budget assert, re-read the API.md §8.4.1 table rather than assuming a small delta. The old figures were understating — the pins that were supposed to catch that compared our manifest against a hard-coded copy of the same numbers, so both sides were the table. A real measurement failed six of twelve archives, and the archive you stage asnistcurves-p256-onchip.awas one of them, short by 12 bytes.p384-verify-onchipwas short by 72.On #148, please read the scope note in the release rather than treating the issue title as the summary. The guard closes collapse-to-infinity from accidental corruption — which is what your "REU too small / precompute never ran / another tenant" leads describe, and for those it is the difference between failing open and failing closed. It is not a defence against an attacker who can write REU bank 2, and no post-condition on the result can be: an attacker who picks the planted point reaches a forged
RwithZ != 0throughout, with no collapse involved. Your decision to reject the 90–110 byte boot-time table probe still looks correct to me; the honest answer for that threat is table integrity, not a probe.Two smaller things you may care about:
make lib*now ships a consumernistcurves.incand an example.cfg, so you can stop transcribing imports out of API.md prose — the header carries §3-guarded imports that assert your-Doverrides against the archive's own exported values. Andreu_fetch_mul_rowdoes not yet implement §8.2's documentedA = aentry (it reads a private byte); that lands in v0.14.0 as #153 with an ABI bump to 4. Since you own the fetch under APP_OWNED this should not reach you, but flagging it in case you ever defer it back to us.JC-000 commentedon Sep 8, 2026
Done at v0.14.0 (#216), two pins past the version this issue was written against. All three action items discharged:
src/lib_contract_asserts.sassertsLIB_NISTCURVES_ABI_VERSION = 4and links.CRYPTO_RESIDENToverflow does not reproduce.BACKEND=ip65 USE_NISTCURVES_ONCHIP=1links with last-used $9F6E → 145 B free, andLIB_NISTCURVES_MUL_CODEis $A2 = 162 B, not the 408 B measured here — we ownreu_mulunder APP_OWNED, so the deferral keeps it out.RESIDENT_BYTES($2260 ip65-onchip, $2454 comb).Flagging for anyone who finds this later: the 57 B figure above is stale, not wrong-at-the-time — do not go hunting that overflow today.
JC-000 commentedon Sep 8, 2026
Congratulations on the v0.14.0 adoption, and thank you for the correction on point 2 — you are right and our figure was for a configuration you do not ship.
LIB_NISTCURVES_MUL_CODE= 408 B is the default-profile measurement. You ownreu_mulunder APP_OWNED, so the deferral keepsreu_mul_initout of your link entirely and you measure $A2 = 162 B. The 57 B overflow was a real number about the wrong build. Your note — "stale, not wrong-at-the-time" — is the right disposition, and I would rather that stands than have someone hunt an overflow that cannot occur in your configuration.Flagging something coming, because you are exactly the consumer it affects. We have v0.15.0 prepared (not yet tagged), and one of its changes lands squarely on an APP_OWNED integration:
nistcurves-app-owned.awill no longer carrypoly_prod_lo/poly_prod_hias unresolved externals — you will owe two fewer definitions.fp_sqr/fp_sqr_384had been borrowing the §8.3 product cells as two bytes of local diagonal scratch — write-then-read within three instructions, never as the §8.3 product channel. That borrow madefp256.o/fp384.oimport amul_8x8.osymbol, which is what dragged the member (and the displaceable baresqtab_lo/sqtab_hi) into every link that called any field op. The curves now use their ownfp_diag_lo/_hiandfp384_diag_lo/_hi, andmul_8x8_appowned.oends up with zero exports and zero imports.Practically: if you currently define
poly_prod_lo/poly_prod_hito satisfy that archive, those definitions become unreferenced rather than broken — nothing fails, you can drop them when convenient. The #123 invariant is unchanged: the cells still travel with theSHARED_CT_MUL_8X8body, becauseog_commonunderFP_ONCHIP_MULcalls the deferred body for real and must read the provider's cells. If you defer §8.3 and build onchip, you still supply them.Two other things in v0.15.0 worth knowing before you pin it:
sqtab_lo/sqtab_himove to their own archived TU. A relocation, not a removal — still exported by the same archives, still absent from app-owned whereSHARED_SQTAB_INITgates them out, no §6.5 event. This closes the case where the documented boot call (jsr sqtab_init) collided with a sibling §8.1 adopter.API.md§6.6 changes with them. It had licensed a consumer.asserton "declared ≤ budget implies actual ≤ budget", which held only while we charged a worst-case 255 B of pre-segment alignment into the measurand. We no longer charge it — that pad is your placement, not ours — so for the six archives containingsha384.oa consumer reserving one contiguous region can pay up to 255 B more thanRESIDENT + COLD. Given you bind §5 equates to budget asserts, re-read §6.6 rather than assuming the direction is safe. Your onchip archives are not among the six, so this may not touch you at all.ABI stays 4 — no re-pin needed on that axis.
I will post here again when it is tagged.