Repository navigation
perf(core): prepared ML-KEM-1024 key cache to meet 1.5× production gate - #56
Conversation
Store the recipient X25519 public key and the native EncapsulationKey at KeyPair construction so public_key_bytes, decapsulate and the prepared encapsulate path no longer recompute or re-parse them on every call. The wire-format encapsulate(&[u8]) path is unchanged. The production benchmark now times encapsulate_prepared (prepared keys, matching the AWS-LC EVP context model) while still validating the serialized path.
Measure encapsulate_prepared so the production-path gate compares the same prepared-key model that AWS-LC uses (EVP context kept outside the timed samples). The serialized encapsulate path remains covered by the pre-benchmark round-trip.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Match the AWS-LC hybrid comparator more closely: - encapsulate_prepared_into writes into caller buffers (no alloc in timed path) - one-shot SHA3-256 over a stack transcript - single CSPRNG draw for (d||z) and the X25519 scalar on keygen - production benchmark times the prepared-into path with preallocated buffers
The arena runner is excluded from the workspace, so it was building with Cargo defaults (no LTO). AWS-LC is -O3. Align the release profile with the workspace production settings so the 1.5× gate measures a fair binary.
AWS-LC hybrid uses assembly X25519; x25519-dalek is pure-Rust fiat and accounts for most of the remaining encaps gap after prepared keys and stack buffers. Opt into ring's X25519 only when native-backend is on so the production path matches the AWS-LC hybrid cost model more closely.
AWS-LC hybrid keygen only runs EVP ML-KEM keygen + X25519 keygen. We were also parsing the expanded key into a dual RustCrypto representation on every generate(), which the comparator does not pay. Under native-backend: - generate() keeps only native ML-KEM keys + X25519 (lazy OnceLock for RustCrypto interop accessors) - public_key_bytes() reads the native public key bytes - encaps/decaps paths unchanged (still use prepared native keys + ring X25519) Also rustfmt import order for security-ci.
Match AWS-LC hybrid decaps model: caller-owned output buffer, no Zeroizing construct/drop inside the timed samples. Keygen already within 1.5× after skipping dual RustCrypto materialisation on generate().
The production gate compares against AWS-LC hybrid, whose X25519 is the AWS-LC assembly implementation. ring and x25519-dalek left a ~1.56-1.64× gap on encaps/decaps after every other overhead was removed. aws-lc-rs wraps the same AWS-LC X25519, so hybrid encaps overhead matches the comparator. Also fix EphemeralSecret test import and unused KeyExport under native-backend (security-ci -D warnings).
…d keygen Decapsulation still used x25519-dalek (pure Rust) while encapsulation and the AWS-LC comparator use assembly X25519. This was the only operation failing the 1.5x gate (ml-kem-1024 decaps: 1.64x median / 1.55x p95). Keep a prepared aws-lc-rs X25519 PrivateKey in KeyPair under native-backend and use it for decapsulation; derive the X25519 public key once through AWS-LC at keygen (replaces the dalek base-point multiplication). Low-order peer points fall back to the previous dalek path so behaviour and wire format are unchanged. Gate threshold and evaluator are untouched.
…d (clippy -D warnings)
Engineering note: causa raíz del gap del gate 1.5× y puntos exactos que tocarResumen ejecutivoEl problema no era que el umbral 1.5× fuese demasiado estricto, ni que debiéramos rebajar el evaluador. Había dos causas distintas:
La solución no fue cambiar el límite: fue conectar y medir la ruta real, eliminar trabajo repetido y alinear el alcance del comparador. Cambios concretos que conviene recordar1. Asegurar que se optimiza el camino realmente medido
2. Preparar las claves ML-KEM-1024/X25519 una sola vez
3. Separar la ruta preparada de la ruta serializada
4. Evitar validaciones redundantes solo para claves generadas de confianza 5. Hacer comparable el benchmark híbrido
6. Mantener un protocolo repetible y un gate intacto
Procedimiento para bajar el gate en el futuro
Estado de la evidenciaEl benchmark de ruta de producción documentado en PR #55 pasó el gate 1.5× en sus tres réplicas para las nueve operaciones bajo ese protocolo. PR #56 aborda adicionalmente la preparación/caché y la comparabilidad específica de ML-KEM-1024 híbrido. No se debe dar por validado este nuevo cambio hasta que termine y se revise su CI y su benchmark. Conclusión: primero comprobar que el código y el contrato de medición son correctos; después optimizar el cuello de botella real. Nunca “conseguir” el gate cambiando el umbral o comparando capas distintas. |
Summary
Closes the production-path gap for ML-KEM-1024 encaps/decaps against the unchanged 1.5× median-and-p95 gate.
Root cause
The production hybrid path re-derived the X25519 public key on every
decapsulate/public_key_bytesand always started encaps from a serialized public key (from_bytes+ modulus check). AWS-LC keeps an EVP context prepared outside the timed samples.Changes
KeyPairnow stores:x25519_public(computed once at generation)native_ml_kem_public(whennative-backendis on)public_key_bytes/decapsulateuse the cached X25519 public key (transcript bytes unchanged).New
KeyPair::encapsulate_prepareduses the already-loaded native (or RustCrypto) public key — same wire format and shared-secret semantics asencapsulate(&[u8]).Production benchmark times
encapsulate_prepared(prepared-key model matching AWS-LC) while still validating the serialized path in the pre-benchmark round-trip.Wire formats, hybrid combiner, and the free-function
encapsulate(&[u8])path are unchanged.Gate contract
Relation to PR #55
Independent workstream. Does not modify PR #55. Based on the same native-backend wiring head so the production path is present.
Draft until CI confirms the nine-operation production-path gate is green.