Skip to content

Retire the P-384 lane; only P-256 is verified (incl. L5) - #294

Merged
JC-000 merged 7 commits into
masterfrom
retire/p384-lane
Oct 14, 2026
Merged

JC-000 merged 7 commits into
masterfrom
retire/p384-lane

Conversation

@JC-000

@JC-000 JC-000 commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Retires the P-384 lane (decision 2026-10-08): only P-256 is verified. Includes layout pass L5.

  • Removed: ecdsa_verify_384, p384_force_link, crypto_swap, overlay ids, reu_layout.inc, OVERLAY_BLOB_CURVE_RAM, the P-384 tests and rig; knobs ENABLE_P384_VERIFY / USE_OVERLAY_P384_EMBED are retired and $(error)-guarded.
  • Size: UCI PRG 62,977 -> 55,297 B (the $E000 blob no longer drags the $C000-$DFFF pad into the file).
  • Refusal: P-384 / RSA / Ed25519 leaves and any CertificateVerify scheme but 0x0403 are refused, with one characterised exception (the extractor scans, so a leaf that also carries a P-256 SPKI pattern has that key verified against; [1d]).
  • Test: tools/test_curve_refusal.py 6/6, including [1b], which checks tls_recv_sub_progress for 0x0401/0x0402/0x04FF and was mutated until it could go red. Green after the rebase: ip65 c270ae4e…, onchip-store 7059930a… (no REU), comb-store 936884e9… (REU). (Pre-rebase: ip65 a0e9dfce…, onchip-store a3fdc772…, comb-store 6a5a2993….)

Rebased on 75fe1b4 (master with #289-#293). Conflicts, resolved keeping both sides:

Every profile tools/measure_margins.py --build covers links. PRG sha256 and free bytes vs master 75fe1b4 (tail free; the PRG file is fixed-size, so it is not the gauge):

profile master PRG this PR PRG size freed vs master
ip65 3caf4526 c270ae4e 47,105 (=) LOADER +4, CRYPTO_OVERLAY +116, CRYPTO_RESIDENT +2; COLD_SHADOW hole 71 -> 168
ip65-onchip e1ad289d 810f1158 47,105 (=) LOADER +4, CRYPTO_OVERLAY +116, CRYPTO_RESIDENT +2; hole 75 -> 172
ip65-wiki 74fc00b8 5f363110 47,105 (=) as ip65
ip65-onchip-wiki c7297617 00bdf485 47,105 (=) as ip65-onchip
uci b4e4e6fb ed6871b2 62,977 -> 55,297 LOADER +4, NET_CODE +116, CRYPTO_HOT +22
uci-onchip 4ab553bf eaa43a6a 62,977 -> 55,297 LOADER +4, NET_CODE +116, CRYPTO_HOT +22
uci-comb 4281d786 8f8a0d05 62,977 -> 55,297 LOADER +4, NET_CODE +116, CRYPTO_HOT +22
uci-onchip-store 375f9480 7059930a 62,977 -> 55,297 LOADER +4, NET_CODE +116, CRYPTO_HOT +22
uci-comb-store 74a7d358 936884e9 62,977 -> 55,297 LOADER +4, NET_CODE +116, CRYPTO_HOT +22
uci-comb-demo-store 335c481f d499a112 62,977 -> 55,297 LOADER +4, NET_CODE +116, CRYPTO_HOT +22

(The commit messages below quote +17/+22 "vs L5" and +99 for L5 itself; vs master the two together are the +116.)

Verified after the rebase:

  • test_curve_refusal.py 6/6 on ip65 c270ae4e, onchip-store 7059930a (C64_VICE_NO_REU=1), comb-store 936884e9 (REU).
  • test_ecdsa_kat_oracle.py 10/10 incl. [3k]-[3n] on onchip-store 7059930a (no REU) and comb-store 936884e9 (REU).
  • test_cold_bank.py 117/117; test_rig_scratch.py 17/17 and --build matrix PASS; test_build_flags_stamp.py 17/17; test_runner_coverage.py PASS; check_upstream_pins.py --strict 0 (nistcurves v0.15.0, x25519 v0.16.0).
  • Root pytest on a BACKEND=uci build: 476 passed, 1 failed — test_ip65_hw_checks_unit.py::test_the_build_exports_every_symbol_the_rig_reads, which requires an ip65 build in build/; on BACKEND=ip65 USE_NISTCURVES_ONCHIP=1 that file is 49/49.
  • U64E @ 48 MHz via DeviceLock: boot_check.py PASS on onchip-store 7059930a and comb-store 936884e9; rig_https_local.py PASS (HTTP 200 body, listener decrypted the GET) on uci-onchip+SNI 218a337b and uci-comb+SNI 5800da18. rig_https_local.py does NOT pass on a TRUST_STORE build: onchip-store+SNI 572b3c76 stops at Certificate (tls_last_state=$04), and master's onchip-store+SNI 4544a0c2 stops at the same point — the trampoline never runs trust_pre, so it is the rig, not this PR.

Commits (messages verbatim; each carries its red-green evidence)

8a92147 p384: gate the leftovers nothing reaches without ENABLE_P384_VERIFY

Everything below already assembled into every shipped image while only
an ENABLE_P384_VERIFY build could use it. Each is gated, not deleted, so
the armed link keeps all of it (CLAUDE.md: that link is the only thing
that keeps ecdsa_verify_384.s honest):

src/data.s ecdsa_pubkey_{x,y}_384 (96 B BSS) and their exports
src/tls_cert.s the P-384 curve-OID check, the 48 B pubkey copies, and
the CertificateVerify 0x0503 short-circuit
src/boot.s reu_p384_overlay_init: label, export and boot call,
under crypto_swap.s's own gate (KEEP_CRYPTO_SWAP /
ENABLE_P384_VERIFY / either overlay embed); it was an
RTS without the P-384 embed anyway

Behaviour, unarmed: a P-384 leaf is now refused AT Certificate (the scan
finds no supported key, C=1) instead of being extracted as curve 1 and
refused at CertificateVerify; a 0x0503 CertificateVerify is refused in
the handler without routing curve_id = 1 into the dispatcher. Same
verdict, earlier, with less code; ecdsa_curve_id can no longer be 1.

Freed (tools/measure_margins.py --build), every profile:
code ip65 CRYPTO_OVERLAY 45 -> 144 (+99), ip65-onchip 11 -> 110;
uci NET_CODE 176 -> 275 (+99), uci-onchip 184 -> 283,
uci-comb 176 -> 275, uci-onchip-store 184 -> 283,
uci-comb-store 38 -> 137, comb demo 38 -> 137
LOADER +4 B everywhere (the jsr and the rts)
BSS 96 B: ip65's hole below TABLES_BSS 71 -> 167; on UCI the gap
below X25519_TABLES ($B100, page-aligned) 14 -> 110 -- alignment
padding, not tail, on both

Armed builds fail exactly as on master (NET_CODE overflow 327/319/327 B
on uci/onchip/comb, ip65 CRYPTO_OVERLAY 63/97 B -- the same bytes, so the
gates restore everything). That link was already broken at 75dd519;
KEEP_CRYPTO_SWAP=1 and uci-m3 link.

tools/test_tls_p384_negotiation.py: [1b] is build-aware (armed =
ecdsa_verify_384_tls linked, which tracks the flag on master and here);
new [1c] runs a P-384 leaf, SAN matching tls_hostname so only the curve
can refuse it, through the live x509_extract_pubkey.
L5 ip65 23b843bc..., uci-onchip d5202b48...: [1a][1b][1c] PASS
master tls_cert.s/data.s, uci-onchip 60678588...: [1b] FAIL
(curve_id = 1), [1c] FAIL (C=0: leaf extracted as P-384)
test_p384_overlay_hazard.py PASS on both (C=1, 0/7680 overlay bytes
changed). test_cert_pin.py 44 PASS.

aec540f p384: qualify "refused at Certificate", and characterise the exception ([1d])

Review finding: the previous commit said a P-384 leaf is refused AT
Certificate. That holds for a plain P-384 leaf only. x509_extract_pubkey
scans for the first supported curve OID rather than parsing the TBS, so
with the P-384 OID gated out a P-384 leaf that also carries a P-256 SPKI
byte pattern later (inside an unknown extension, say) gets C=0 with that
trailing P-256 key extracted -- as RSA leaves already do at 75dd519.
CertificateVerify is then checked against the extracted key, one the
server chose; the same position as any self-chosen leaf, since nothing
validates a chain, and a pin binds to the extracted key.

src/tls_cert.s the gate's comment says so
tools/test_tls_p384_negotiation.py [1c] docstring narrowed to a PLAIN
P-384 leaf; new [1d] (characterisation, not a guarantee): P-384 leaf,
SAN = tls_hostname, real P-384 SPKI first, a P-256 SPKI inside an
unknown extension (1.3.6.1.4.1.99999.1) after it. Unarmed: C=0,
curve 0, the trailing P-256 point extracted. Armed: curve 1.

Green: ip65 23b843bc..., uci-onchip d5202b48... (PRGs unchanged: comment
only): [1a]-[1d] PASS.
Red / characterising: master's tls_cert.s + data.s, uci-onchip 60678588...:
[1b] [1c] FAIL as before, [1d] FAIL (C=0 but curve_id=1: master takes the
P-384 key first), so the [1d] behaviour is this branch's, now documented.

eda8bb8 Retire the P-384 lane; only P-256 is verified

ENABLE_P384_VERIFY=1 no longer linked on any of the five profiles at
75dd519 (NET_CODE / CRYPTO_OVERLAY overflows), and no P-384 build target
ever completed. The user decided to retire the lane. Removed:

src/crypto/ecdsa_verify_384.s, src/crypto/p384_force_link.s, the
P-384 arm of ecdsa_verify.s (any non-zero ecdsa_curve_id is now a clean
C=1 before the VIC is blanked or the library entered), the L5-gated
code in tls_cert.s / data.s / boot.s (P-384 OID check, 48 B copies,
0x0503 short-circuit and cv_sig_scheme -- the handler now accepts
0x0403 only), reu_p384_overlay_init and its stash;
the overlay machinery that existed only to page P-384 (and the retired
P-256 embed): crypto_swap.s, overlay_ids.inc, p384_overlay_blobs.s,
p256_overlay_blobs.s, cfg/p384-overlay-{curve,sha384}.cfg,
cfg/p256-overlay-verify.cfg, build_nistcurves_p384{,bin}.sh,
build_nistcurves_p256_bin.sh, gen_p384_overlay_equates.sh, the unused
build_nistcurves.sh placeholder, the p384-overlay / p256-overlay make
targets, and every cfg's OVERLAY_P256/P384, OVERLAY_BLOB
,
LIB_NISTCURVES_P384_
/ SHA384_* routing and OVERLAY_BLOB_CURVE_RAM;
src/crypto/shared/reu_layout.inc (no consumer left), the REU banks 6-7
reservation (lib_contract_asserts.s, http.s), so the HTTP body sink's
region is now [base, REU end) for any base at or above bank 3;
tools/test_p384_overlay_hazard.py, test_p384_symbols.py,
test_ecdsa_p384_kat.py, tools/uci/rig_https_local_p384.py, and the
skip-contract case that spawned the P-384 KAT.

Kept, and why: crypto_swap.s / P-256 overlay plumbing had no importer
outside the P-384 path and the already-retired embed, so they went too.
Kept: OVERLAY_FILE_PAD (dropping it would stop the UCI PRG zero-filling
$C000-$DFFF at load, a separate behaviour change); the listener's P-384
cert profile (the fixture a hardware refusal test would need);
test_x509.py's P-384 DER-parse group (x509_parse_cert, test-only knob);
ecdsa_curve_id (the pin hook and trust_bundle use it); the dead overlay
segment names in tools/uci/_memory_policy.py's blocker list (harmless,
harness logic untouched). LIB_NISTCURVES_REU_BANKS_USED keeps an import
(asserted < bank 3, the sink floor): the REU preflight reads it from
labels.txt.

Retired knobs ENABLE_P384_VERIFY, USE_OVERLAY_P384_EMBED,
KEEP_CRYPTO_SWAP, EMBED_P256_OVERLAY, USE_OVERLAY_P256_EMBED are a
Makefile $(error) for any value (=0 included), like USE_X25519_SIBLING.

tools/test_curve_refusal.py replaces test_tls_p384_negotiation.py:
[1a] ClientHello offers 0x0403 only; [1b] 0x0503 CertificateVerify
refused, curve_id untouched; [1c] P-384, [1e] RSA-2048, [1f] Ed25519
leaves refused (C=1, slots untouched); [1d] P-384 leaf + trailing P-256
pattern characterised. 6/6 on ip65 a0e9dfce, uci-onchip d5fe4a54
(no REU), uci-comb 662d7fd9. ECDSA KAT 6/6 on uci-onchip and uci-comb.

Sink: test_reu_body_sink.py / test_http.py retargeted from the bank-6
edge to the REU's end (wrap to bank 0). Red: the new expectations
against the old sink code -> 11 failures, all the banks 6-7 cap; green
76/76 on b96e1a4a (comb demo).

Freed vs L5 (tools/measure_margins.py --build): ip65 CRYPTO_OVERLAY +17,
CRYPTO_RESIDENT +2; every UCI profile NET_CODE +17, CRYPTO_HOT +22. UCI
PRG file 62,977 -> 55,297 B (the 7,680 B curve-blob pad).

Also green: test_runner_coverage, test_rig_skip_contract +
test_skip_policy (37), cold-bank cfg delta, test_pytest_boundary,
_sni_precondition --selftest, root pytest (440 + 2 build-state).
CLAUDE.md / README: one line each; PR link TBD (not pushed).

9d0c4ab curve refusal: test the scheme gate itself; run on the store builds; docs

Review findings on d638333.

  1. [1b] could not go red. It sent 48 zero bytes and asserted only C=1;
    ecdsa_parse_der_sig rejects those whatever the scheme, so a handler
    with no scheme gate passed. [1b] now judges the GATE by
    tls_recv_sub_progress ($21 after the type check, $22 only once the
    scheme gate passes): 0x0503, 0x0603, 0x0804 and 0x0003 must stop at
    $21 with C=1 and ecdsa_curve_id untouched; the 0x0403 control must get
    past it. Red: the gate's cmp #$04 / bne removed from tls_cert.s
    (ip65 0cdff9ec...) -> 0x0503, 0x0603, 0x0003 reach $23 (C=1 on all of
    them, which is why the old check passed) -> 5/6. Restored a0e9dfce...
    -> 6/6; uci-onchip d5fe4a54... -> 6/6.

  2. TRUST_STORE builds: x509_extract_pubkey tail-calls the trust hook,
    which refuses while tp_mode is TP_M_NONE -- the bug L3 had. Every
    extraction now sets TP_M_FIRST (read from src/trust_policy.inc) first.
    Shipped onchip-store a3fdc772... (no REU) 6/6 and comb-store
    6a5a2993... 6/6. Red: the arming removed, onchip-store -> [1d] C=1,
    curve 0, trailing key extracted (the hook's refusal) -> 5/6.

  3. Runner: run_tests(transport, labels, seed) added, shared by main();
    "curve_refusal" is in run_all_tests.SUITE_ORDER with its dispatch arm
    (valid on the ip65 build the runner makes). test_runner_coverage and
    test_pytest_boundary pass.

  4. Docs: CLAUDE.md's two "every other key or scheme is refused"
    sentences deduplicated into one, qualified with the [1d] exception;
    README's "parked as roadmap work" -> retired; src/vic.s no longer
    names ecdsa_verify_384_tls; "32 or 48" comments in data.s and
    tls_cert.s -> 32 (P-256 only). PRGs unchanged (comment only).

  5. Stale overlay names: tools/rig_scratch_profiles.json regenerated from
    real links (test_rig_scratch.py --build --write-fixtures, 17/17); the
    OVERLAY_BLOB_SHA384/P256 and OVERLAY_P256/P384 blockers and the prose
    that cited them dropped from tools/uci/_memory_policy.py.

OVERLAY_FILE_PAD untouched (pending #288).

a32baf3 curve refusal: [1b] also tests the scheme gate's low byte

Review finding: every refused scheme in [1b] had a high byte != $04, so
the gate's low-byte check (cmp #$03 / beq) was never exercised; with it
replaced by jmp, [1b] still passed. Added 0x0401 (rsa_pkcs1_sha256),
0x0402 and 0x04FF, which only the low-byte check refuses.

Red: cmp #$03 / beq :+ -> jmp :+ in src/tls_cert.s, ip65 73cddfcd...
-> 0x0401, 0x0402, 0x04FF reach $23 -> 5/6.
Green, restored: ip65 a0e9dfce... 6/6, onchip-store a3fdc772... 6/6
(no REU), comb-store 6a5a2993... 6/6.

Also: src/data.s's note about reclaiming the P-384 slots "when
ecdsa_verify_384 is re-integrated" now says the lane is retired. Comment
only; PRGs unchanged.

🤖 Generated with Claude Code

JC-000 added a commit that referenced this pull request Oct 12, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000 and others added 7 commits October 12, 2026 14:02
Everything below already assembled into every shipped image while only
an ENABLE_P384_VERIFY build could use it. Each is gated, not deleted, so
the armed link keeps all of it (CLAUDE.md: that link is the only thing
that keeps ecdsa_verify_384.s honest):

  src/data.s       ecdsa_pubkey_{x,y}_384 (96 B BSS) and their exports
  src/tls_cert.s   the P-384 curve-OID check, the 48 B pubkey copies, and
                   the CertificateVerify 0x0503 short-circuit
  src/boot.s       reu_p384_overlay_init: label, export and boot call,
                   under crypto_swap.s's own gate (KEEP_CRYPTO_SWAP /
                   ENABLE_P384_VERIFY / either overlay embed); it was an
                   RTS without the P-384 embed anyway

Behaviour, unarmed: a P-384 leaf is now refused AT Certificate (the scan
finds no supported key, C=1) instead of being extracted as curve 1 and
refused at CertificateVerify; a 0x0503 CertificateVerify is refused in
the handler without routing curve_id = 1 into the dispatcher. Same
verdict, earlier, with less code; ecdsa_curve_id can no longer be 1.

Freed (tools/measure_margins.py --build), every profile:
  code  ip65 CRYPTO_OVERLAY 45 -> 144 (+99), ip65-onchip 11 -> 110;
        uci NET_CODE 176 -> 275 (+99), uci-onchip 184 -> 283,
        uci-comb 176 -> 275, uci-onchip-store 184 -> 283,
        uci-comb-store 38 -> 137, comb demo 38 -> 137
  LOADER +4 B everywhere (the jsr and the rts)
  BSS   96 B: ip65's hole below TABLES_BSS 71 -> 167; on UCI the gap
        below X25519_TABLES ($B100, page-aligned) 14 -> 110 -- alignment
        padding, not tail, on both

Armed builds fail exactly as on master (NET_CODE overflow 327/319/327 B
on uci/onchip/comb, ip65 CRYPTO_OVERLAY 63/97 B -- the same bytes, so the
gates restore everything). That link was already broken at 75dd519;
KEEP_CRYPTO_SWAP=1 and uci-m3 link.

tools/test_tls_p384_negotiation.py: [1b] is build-aware (armed =
ecdsa_verify_384_tls linked, which tracks the flag on master and here);
new [1c] runs a P-384 leaf, SAN matching tls_hostname so only the curve
can refuse it, through the live x509_extract_pubkey.
  L5 ip65 23b843bc..., uci-onchip d5202b48...: [1a][1b][1c] PASS
  master tls_cert.s/data.s, uci-onchip 60678588...: [1b] FAIL
    (curve_id = 1), [1c] FAIL (C=0: leaf extracted as P-384)
test_p384_overlay_hazard.py PASS on both (C=1, 0/7680 overlay bytes
changed). test_cert_pin.py 44 PASS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n ([1d])

Review finding: the previous commit said a P-384 leaf is refused AT
Certificate. That holds for a plain P-384 leaf only. x509_extract_pubkey
scans for the first supported curve OID rather than parsing the TBS, so
with the P-384 OID gated out a P-384 leaf that also carries a P-256 SPKI
byte pattern later (inside an unknown extension, say) gets C=0 with that
trailing P-256 key extracted -- as RSA leaves already do at 75dd519.
CertificateVerify is then checked against the extracted key, one the
server chose; the same position as any self-chosen leaf, since nothing
validates a chain, and a pin binds to the extracted key.

  src/tls_cert.s                     the gate's comment says so
  tools/test_tls_p384_negotiation.py [1c] docstring narrowed to a PLAIN
    P-384 leaf; new [1d] (characterisation, not a guarantee): P-384 leaf,
    SAN = tls_hostname, real P-384 SPKI first, a P-256 SPKI inside an
    unknown extension (1.3.6.1.4.1.99999.1) after it. Unarmed: C=0,
    curve 0, the trailing P-256 point extracted. Armed: curve 1.

Green: ip65 23b843bc..., uci-onchip d5202b48... (PRGs unchanged: comment
only): [1a]-[1d] PASS.
Red / characterising: master's tls_cert.s + data.s, uci-onchip 60678588...:
[1b] [1c] FAIL as before, [1d] FAIL (C=0 but curve_id=1: master takes the
P-384 key first), so the [1d] behaviour is this branch's, now documented.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ENABLE_P384_VERIFY=1 no longer linked on any of the five profiles at
75dd519 (NET_CODE / CRYPTO_OVERLAY overflows), and no P-384 build target
ever completed. The user decided to retire the lane. Removed:

  src/crypto/ecdsa_verify_384.s, src/crypto/p384_force_link.s, the
  P-384 arm of ecdsa_verify.s (any non-zero ecdsa_curve_id is now a clean
  C=1 before the VIC is blanked or the library entered), the L5-gated
  code in tls_cert.s / data.s / boot.s (P-384 OID check, 48 B copies,
  0x0503 short-circuit and cv_sig_scheme -- the handler now accepts
  0x0403 only), reu_p384_overlay_init and its stash;
  the overlay machinery that existed only to page P-384 (and the retired
  P-256 embed): crypto_swap.s, overlay_ids.inc, p384_overlay_blobs.s,
  p256_overlay_blobs.s, cfg/p384-overlay-{curve,sha384}.cfg,
  cfg/p256-overlay-verify.cfg, build_nistcurves_p384{,_bin}.sh,
  build_nistcurves_p256_bin.sh, gen_p384_overlay_equates.sh, the unused
  build_nistcurves.sh placeholder, the p384-overlay / p256-overlay make
  targets, and every cfg's OVERLAY_P256/P384, OVERLAY_BLOB_*,
  LIB_NISTCURVES_P384_* / SHA384_* routing and OVERLAY_BLOB_CURVE_RAM;
  src/crypto/shared/reu_layout.inc (no consumer left), the REU banks 6-7
  reservation (lib_contract_asserts.s, http.s), so the HTTP body sink's
  region is now [base, REU end) for any base at or above bank 3;
  tools/test_p384_overlay_hazard.py, test_p384_symbols.py,
  test_ecdsa_p384_kat.py, tools/uci/rig_https_local_p384.py, and the
  skip-contract case that spawned the P-384 KAT.

Kept, and why: crypto_swap.s / P-256 overlay plumbing had no importer
outside the P-384 path and the already-retired embed, so they went too.
Kept: OVERLAY_FILE_PAD (dropping it would stop the UCI PRG zero-filling
$C000-$DFFF at load, a separate behaviour change); the listener's P-384
cert profile (the fixture a hardware refusal test would need);
test_x509.py's P-384 DER-parse group (x509_parse_cert, test-only knob);
ecdsa_curve_id (the pin hook and trust_bundle use it); the dead overlay
segment names in tools/uci/_memory_policy.py's blocker list (harmless,
harness logic untouched). LIB_NISTCURVES_REU_BANKS_USED keeps an import
(asserted < bank 3, the sink floor): the REU preflight reads it from
labels.txt.

Retired knobs ENABLE_P384_VERIFY, USE_OVERLAY_P384_EMBED,
KEEP_CRYPTO_SWAP, EMBED_P256_OVERLAY, USE_OVERLAY_P256_EMBED are a
Makefile $(error) for any value (=0 included), like USE_X25519_SIBLING.

tools/test_curve_refusal.py replaces test_tls_p384_negotiation.py:
[1a] ClientHello offers 0x0403 only; [1b] 0x0503 CertificateVerify
refused, curve_id untouched; [1c] P-384, [1e] RSA-2048, [1f] Ed25519
leaves refused (C=1, slots untouched); [1d] P-384 leaf + trailing P-256
pattern characterised. 6/6 on ip65 a0e9dfce, uci-onchip d5fe4a54
(no REU), uci-comb 662d7fd9. ECDSA KAT 6/6 on uci-onchip and uci-comb.

Sink: test_reu_body_sink.py / test_http.py retargeted from the bank-6
edge to the REU's end (wrap to bank 0). Red: the new expectations
against the old sink code -> 11 failures, all the banks 6-7 cap; green
76/76 on b96e1a4a (comb demo).

Freed vs L5 (tools/measure_margins.py --build): ip65 CRYPTO_OVERLAY +17,
CRYPTO_RESIDENT +2; every UCI profile NET_CODE +17, CRYPTO_HOT +22. UCI
PRG file 62,977 -> 55,297 B (the 7,680 B curve-blob pad).

Also green: test_runner_coverage, test_rig_skip_contract +
test_skip_policy (37), cold-bank cfg delta, test_pytest_boundary,
_sni_precondition --selftest, root pytest (440 + 2 build-state).
CLAUDE.md / README: one line each; PR link TBD (not pushed).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…docs

Review findings on d638333.

1. [1b] could not go red. It sent 48 zero bytes and asserted only C=1;
   ecdsa_parse_der_sig rejects those whatever the scheme, so a handler
   with no scheme gate passed. [1b] now judges the GATE by
   tls_recv_sub_progress ($21 after the type check, $22 only once the
   scheme gate passes): 0x0503, 0x0603, 0x0804 and 0x0003 must stop at
   $21 with C=1 and ecdsa_curve_id untouched; the 0x0403 control must get
   past it. Red: the gate's `cmp #$04 / bne` removed from tls_cert.s
   (ip65 0cdff9ec...) -> 0x0503, 0x0603, 0x0003 reach $23 (C=1 on all of
   them, which is why the old check passed) -> 5/6. Restored a0e9dfce...
   -> 6/6; uci-onchip d5fe4a54... -> 6/6.

2. TRUST_STORE builds: x509_extract_pubkey tail-calls the trust hook,
   which refuses while tp_mode is TP_M_NONE -- the bug L3 had. Every
   extraction now sets TP_M_FIRST (read from src/trust_policy.inc) first.
   Shipped onchip-store a3fdc772... (no REU) 6/6 and comb-store
   6a5a2993... 6/6. Red: the arming removed, onchip-store -> [1d] C=1,
   curve 0, trailing key extracted (the hook's refusal) -> 5/6.

3. Runner: run_tests(transport, labels, seed) added, shared by main();
   "curve_refusal" is in run_all_tests.SUITE_ORDER with its dispatch arm
   (valid on the ip65 build the runner makes). test_runner_coverage and
   test_pytest_boundary pass.

4. Docs: CLAUDE.md's two "every other key or scheme is refused"
   sentences deduplicated into one, qualified with the [1d] exception;
   README's "parked as roadmap work" -> retired; src/vic.s no longer
   names ecdsa_verify_384_tls; "32 or 48" comments in data.s and
   tls_cert.s -> 32 (P-256 only). PRGs unchanged (comment only).

5. Stale overlay names: tools/rig_scratch_profiles.json regenerated from
   real links (test_rig_scratch.py --build --write-fixtures, 17/17); the
   OVERLAY_BLOB_SHA384/P256 and OVERLAY_P256/P384 blockers and the prose
   that cited them dropped from tools/uci/_memory_policy.py.

OVERLAY_FILE_PAD untouched (pending #288).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review finding: every refused scheme in [1b] had a high byte != $04, so
the gate's low-byte check (`cmp #$03 / beq`) was never exercised; with it
replaced by `jmp`, [1b] still passed. Added 0x0401 (rsa_pkcs1_sha256),
0x0402 and 0x04FF, which only the low-byte check refuses.

Red: `cmp #$03 / beq :+` -> `jmp :+` in src/tls_cert.s, ip65 73cddfcd...
-> 0x0401, 0x0402, 0x04FF reach $23 -> 5/6.
Green, restored: ip65 a0e9dfce... 6/6, onchip-store a3fdc772... 6/6
(no REU), comb-store 6a5a2993... 6/6.

Also: src/data.s's note about reclaiming the P-384 slots "when
ecdsa_verify_384 is re-integrated" now says the lane is retired. Comment
only; PRGs unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The capture predated #292's P256_SCRATCH_UNION and #290's sha256 roll, so
its five PRG hashes and labels were the pre-rebase links. Regenerated with
test_rig_scratch.py --build --write-fixtures; --build re-run: matrix PASS,
17/17, hashes uci ed6871b2, uci-onchip eaa43a6a, uci-comb 8f8a0d05,
uci-comb-trust 936884e9, uci-comb-demo-trust b66eaf0c.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to 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