Skip to content

ML-KEM: K-PKE key generation, encryption and decryption #9

Closed

Description

@Zikithezikit

First half of the ML-KEM core, in src/mlkem/. This is the K-PKE layer from FIPS 203 (Algorithms 13 to 15).

  • Keygen with (rho, sigma) = G(d || k). The k byte is new compared to Kyber, so don't copy the legacy keygen as is
  • Encrypt and decrypt, generic over the three parameter sets
  • Cite the algorithm and step numbers in comments
  • Follow the constant-time rules in AGENTS.md. Nothing here should branch on or divide a secret

Done when: unit tests pass, and the K-PKE output for at least one known vector matches (pull a keygen vector from ACVP if #11 isn't done yet).

Depends on: #8. Plan: WP-3.

Part of #3.

Activity

  1. added this to the 2.0.0 milestone on Oct 29, 2026
  2. Zikithezikit commented on Oct 30, 2026

    @Zikithezikit
    OwnerAuthor

    Notes for whoever picks this up, from the WP-2 audit (#8, merged in #28). Read the module docs at the top of src/mlkem/mod.rs first; this is the short list of what you will run into.

    What exists in src/mlkem/ (crate-private, unused until now, so drop the #[allow(dead_code)] on the module in src/lib.rs once something uses it):

    • params.rs: ParamSet with K, ETA1, ETA2, DU, DV and the Table 3 sizes, plus MlKem512/768/1024 aliases. Stable Rust cannot use these constants as array lengths in generic code, so byte buffers are slices or Vec and only [Poly; K] is a plain array.
    • polyvec.rs: PolyVec with ntt, invntt_tomont, reduce, add, basemul_acc, to_bytes and from_bytes. from_bytes reduces mod q like ByteDecode12.
    • sample.rs: sample_ntt (Algorithm 7). Its input is rho || j || i, so Algorithm 13's A[i][j] is sample_ntt(rho, j, i).
    • compress.rs: an exact Compress_11 + ByteEncode_11 for du = 11.

    Things that will bite:

    • The legacy ntt carries Montgomery factors. ntt_inverse is Algorithm 10 times 2^16 and poly_base_multiply is Algorithm 11 times 2^-16, so compose them the way the legacy indcpa does and the factors cancel. poly_add and poly_sub are plain i16 operations, so reduce between steps.
    • Compression for du has a gap. The legacy d = 10 and d = 11 code exists only inside the per-level polyvec_compress*/polyvec_decompress* functions, so PolyVec cannot call it. You either extract poly-level helpers from the legacy loops (a pure move, golden vectors must stay green) or write generic ones in src/mlkem. Do not reuse the legacy d = 11 compression: it is wrong at two residues. The d = 4, 5 and 1 paths (poly_compress, poly_compress_1024, poly_tomsg) and the decompress functions are fine to call.
    • Do not reuse poly_frommsg, verify, cmov or poly_getnoise_*. frommsg builds its mask with wrapping_neg() on a message bit (the Clangover pattern; it was not turned into a branch on rustc 1.95, but nothing guarantees that), verify/cmov have no optimization barrier, and getnoise hard-codes eta. Use the PRF (shake256 over seed || nonce, 64 * eta bytes) with cbd2 or cbd3 and ParamSet::ETA1/ETA2.
    • poly_frombytes does not reduce mod q. Use PolyVec::from_bytes, otherwise the section 7.2 modulus check will always pass.
    • Keygen is (rho, sigma) = G(d || k): the single k byte is the difference from Kyber.

    Process reminders: a new section in AGENTS.md asks for named constants instead of magic numbers. CI runs clippy on the latest stable toolchain (1.99.1 when I checked), which has lints your local one may not, so run cargo +stable clippy --all-targets -- -D warnings before pushing. scripts/check.sh is the local gate for every commit.

    Known gap that belongs to #18 and #16, not to this issue: the Keccak permutation spills temporaries to its own stack frame and the zeroize of the state cannot clear them.

  3. Zikithezikit commented on Oct 30, 2026

    @Zikithezikit
    OwnerAuthor

    Done in #29, merged into dev-v2. K-PKE KeyGen, Encrypt and Decrypt (Algorithms 13 to 15) are in src/mlkem/kpke.rs, generic over K. ek_PKE and dk_PKE match all 75 ACVP keyGen cases, and Encrypt and Decrypt match the ACVP encapsulation and decapsulation vectors for all three sets. Notes for #10 are in a comment there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestml-kemML-KEM (FIPS 203) work

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions