Repository navigation
PQ P4: hybrid ServerHello reader, KeyGen in tls_connect, Decaps after SH - #314
Merged
Merged
Conversation
…ter SH (WIP) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Streaming ServerHello machine (any record fragmentation), KeyGen + hybrid key share armed inside tls_connect, Decaps before the X25519 mult, seed/ss homes with a scrub span, red/green suites, 44-mutant harness, hardware oracle against openssl s_server -keylogfile. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…, one SH timeout budget, fail fast on oversize SH record, oracle/listener/test fixes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…Y4 mutants), mul_dma wording; tests and docs only Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scope (PQC phase P4, everything under
PQ_HYBRID=1)tls_connectdraws a 64 B d||z seed from the DRBG, runs ML-KEM KeyGen, zeroes the 2,400 B dk at once, and arms the sectioned ClientHello with the ek (11EC = ek || x25519, then 001D also offered, the shape PQ P3: sectioned ClientHello (16-bit lengths) + HelloRetryRequest refusal #312 sent to OpenSSL). The "arm beforejsr tls_connect" caller contract is retired.src/crypto/pq_hybrid.s): any record fragmentation, a 16-bit field/message counter, nothing buffered, every record folded into the transcript exactly once and in order. 11EC needs exactly 1,120 B = ct 1,088 (intocert_buf) || x25519 32; 001D needs 32. Refusals have their owntls_sh_errcodes$01-$0E(src/constants.inc).pq_sh_decapsruns after the drain and BEFORE the X25519 mult;tls_kex_group($1D/$EC) andtls_mlkem_ssare recorded for P5.Round 1 (adversary findings, all fixed in 71f4bfe)
tls_state < 3, never true for a failed run ($FF); now gates ontls_last_statewhentls_state == $FFpq_keylog_check.py --selftestandtest_pq_keylog_check.pyopenssl_listener.pyreported LISTENING after probing a port a foreign process held; now only when s_server printsACCEPT, non-zero exit on bind failure/early deathtools/test_openssl_listener.py(pytest): held port must exit non-zero. Red shown: the old launcher fails it (1/2), the new one passes (2/2)jsr pq_secrets_zero) killedtest_pq_kem.py timeout: budget nearly spent after record 1, the next failures must end it (poll count 2..16). M46 (re-add the reset) killed (it wraps to 1)SH_ERR_EXT $0D); SS_SKIP/h_skip deletedlast_state == 3 or carry == 1andpq_sh_init's reset oftls_kex_groupunpinnedbuild/crashed with KeyErrorrequire_pq_image()->cannot_run(exit 2, CANNOT RUN). Shown: master's PQ image + the new suites withC64_SKIP_BUILD=1-> rc 2. A behavioural red against master's single-record parser is not meaningful: it cannot carry any of these fixtures (the 1,178 B SH does not fit its buffer), so the mutant table is the primary redpq_mlkem_scrub(next topq_mlkem_call, sei +$01=$34): zeroes$D000-$DFFF(s_hat / y,e1,e2) and the whole$B100-$BBFFwindow (m', K', r', Keccak state, X25519_TABLES, mul_dma) after KeyGen and after Decaps, on every outcome. 6,912 stores, about 76K cycles (11 cycles/store, estimated from the loop, not measured on a clock)$01restored); prep and Decaps tests read both areas; M49-M52 killedtls_rec_frame_fail(PQ only) andtls_recv_server_hellofails at once withSH_ERR_RECORD $0Etest_pq_kem.py oversize(ERROR-latched, code $0E, < 8 polls); M53, M54, M56 killed. It still cannot READ such a helloRound 2 (tests + docs only; no .s/.cfg/Makefile byte change)
pq_mlkem_scrub's$01and I-flag restore are now asserted: directly (test_pq_sh.py 5, entered with I=1 then I=0), afterpq_connect_prepand afterpq_sh_decaps(test_pq_kem.py, first call entered with I=1, the second with I=0). Entry order matters: a leaked$01=$34with I clear livelocks on the next IRQ (the RTI vector acknowledges nothing), so$01is checked underseifirst. New mutants, each killed BY ASSERTION in seconds, not by a hang: Y3 (no$01restore) 4 s, Y4 (no php/plp) 4 s, Y3p (leak after KeyGen) 6 s, Y4d (I flag leaks after Decaps) 8 s. Y3 and Y4 first died only by timeout against the round-1 tests (no FAIL line); that is what this fixes. Green:test_pq_sh.py 58/8,test_pq_kem.py prep decaps26/26.x25519_*_fresh(engineering-notes, comment inmlkem_link.s).493cee70bb40957bb9ba8172d6dc91ab5b2cb4b1ec917b8919f682ba32751ceb.Notes for P5
tls_reached_connected(tls_state has no high-water latch, so the rig's CONNECTED oracle is outrun at turbo #204), not ontls_state/tls_last_state(a CONNECTED-then-closed run reads IDLE).pq_sh_decapswraps the scrub in a redundant outerphp/plp(2 B, harmless); left for P5.Decisions (and why)
cert_buf(ek dead once the CH is sent; ct dead once Decaps returns; Decaps precedes anything that can writecert_buf; the comb cold bank refuses to run during a session). Seed (64) + ss (32) are one adjacent pair inNET_BSS_TAIL(below$4200), asserted at link, one 96 Bsession_scrubspan.tls_parse_server_hello(~280 B of LOADER) is assembled out under PQ; two optional segmentsPQ_CODE/PQ_GLUE, LOADER/NET_CODE oncfg/c64-https-uci.cfg, CRYPTO_HOT on both comb cfgs.TLS_HS_SEQ_CHECKnot reused: the header state (type 2), the length bound and DONE give the same guarantee for the first message.tls_connect's error exit; ML-KEM residue scrubbed as above;tls_mlkem_ssis KEPT on error liketls_shared_secret(the hardware oracle reads it; 'Q' or the next prep scrubs it).Memory (
tools/measure_margins.py --build, free bytes, v1 -> now)Knob-off PRGs byte-identical to master 10ccf25 (sha256, before == after, re-verified after round 1)
(also identical: ip65-wiki, ip65-onchip-wiki, uci-comb-demo-store). PQ images (non-SNI): uci-onchip-pq 208f3967..., uci-comb-pq e97ab54a..., uci-onchip-store-pq eeeb8b0f..., uci-comb-store-pq 0efe0803....
Tests (exact command for every count; all on this head)
python3 tools/test_pq_sh.py(builds the PQ uci-onchip image): 186/186.python3 tools/test_pq_kem.py: 71/71 (KeyGen/Decaps vs the libs/mlkem reference, CH wire,tls_connectend to end with breakpoints, refused SH, refused KeyGen, oversize record, shared timeout budget, residue zero).python3 tools/test_pq_keylog_check.pyandpython3 tools/pq_keylog_check.py --selftest;python3 tools/test_openssl_listener.py(2/2).python3 tools/test_mlkem_placement.py: 194/194.python3 tools/test_mlkem_linked.py --no-mutants: 25/25; with mutants (python3 tools/test_mlkem_linked.py): 34/34.BACKEND=uci python3 tools/test_hs_sequence.py: 62/62.BACKEND=uci python3 tools/test_tls_handshake.py: 36/36.test_prg_load.py13/13,test_runner_coverage.py,test_pytest_boundary.py,test_rig_scratch.py,test_build_flags_stamp.py21/21 pass. Barepython3 -m pytest -q: 507 passed.Mutants (
python3 tools/mutate_pq_sh.py, 56, all killed, green controls first)Round 0: 44 mutants (39 killed first pass, 5 survivors strengthened and re-killed). Round 1 adds M45-M56 (table above), plus re-targeted M20 for the scrub call now in the KeyGen path. Full table:
python3 tools/mutate_pq_sh.py --list(mutant, file, edit, suite). One mutant (M17) removesbcsandcmp #0together on purpose: either alone is equivalent for a wrapper refusal (A=$FF).Hardware rerun after the parser changes (U64E 10.43.23.81, DeviceLock, OpenSSL 3.6.5
s_server, free ports 24510-24513, port 4433 untouched)make BACKEND=uci USE_NISTCURVES_ONCHIP=1 PQ_HYBRID=1 HTTPS_SNI=www.foo.invalid, sha256493cee70bb40957bb9ba8172d6dc91ab5b2cb4b1ec917b8919f682ba32751ceb, 48 MHz.-groups X25519: full HTTPS PASS (95 s).-groups X25519MLKEM768: SH accepted (kex $EC), Decaps ran, expected failure at the first encrypted record; oracle (pq_keylog_check.py --state) exit 0 MATCH.make BACKEND=uci USE_NISTCURVES_ONCHIP_COMB=1 PQ_HYBRID=1 HTTPS_SNI=www.foo.invalid, sha256d9fe6881fbc905221b645e73b7e9a79323e09ca639839f2901705d860397e73b, 48 MHz, REU 16 MB. X25519: PASS (191 s). Hybrid: expected failure, oracle MATCH.Known gaps
SH_ERR_RECORD) but cannot be read (tls_rec_bufis 548 B).tls_mlkem_sssurvives a failed connect (deliberate).🤖 Generated with Claude Code