Skip to content

Specs for ML-KEM#1480

Open
abentkamp wants to merge 9 commits into
mainfrom
alex/mlkem-specs
Open

Specs for ML-KEM#1480
abentkamp wants to merge 9 commits into
mainfrom
alex/mlkem-specs

Conversation

@abentkamp

@abentkamp abentkamp commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

This PR adds a hacspec-style reference implementation for ML-KEM. It includes cross-tests with the actual ML-KEM implementation, an F* and a Lean extraction. The PR builds on top of the SHA3-spec PR #1399.

The PR is structured into four clean commits:

  • just the spec
  • cross-spec tests
  • F*
  • Lean

This is the basis for the Lean verification of ML-KEM in libcrux-iot.

@abentkamp abentkamp force-pushed the alex/mlkem-specs branch 5 times, most recently from 3bb7365 to fcefc79 Compare June 16, 2026 15:23
@abentkamp abentkamp marked this pull request as ready for review June 16, 2026 15:25
@abentkamp abentkamp requested a review from a team as a code owner June 16, 2026 15:25
@abentkamp abentkamp requested review from franziskuskiefer, jschneider-bensch and robinhundt and removed request for a team and franziskuskiefer June 16, 2026 15:25
Base automatically changed from sha3-spec-upstream to main June 22, 2026 11:53

@jschneider-bensch jschneider-bensch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving some comments already before the rest of the review.

Comment thread CHANGELOG.md
Comment thread specs/sha3/hax_aeneas.py
Comment thread specs/ml-kem/src/ind_cca.rs
Comment thread specs/ml-kem/src/ind_cca.rs
Comment thread specs/ml-kem/src/ind_cca.rs
Comment thread specs/ml-kem/src/ind_cca.rs
Comment thread specs/ml-kem/src/ind_cca.rs

@jschneider-bensch jschneider-bensch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left more comments, mostly around documentation and naming of functions. Is renaming a function okay in terms of updating the proofs, would you say?

  • The other set of suggestions concern the use of FieldElement and its operations, where we could use the Rust type system more effectively to maintain the boundedness invariant, instead of mixing up the type's own operations with direct field accesses and allowing construction of the type independent of its new method.

  • I think some of the documentation issues are inherited from the impl, where some documentation has outdated references that refer to the initial public draft of FIPS203, instead of the final version. While the documentation deviated from the published standard in some ways I found no issues with the implementation code.

  • Can you add test vector testing for the spec here, using libcrux-kats, so we have the same test vector coverage here as on the impl side?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes cause a rustfmt error.

Comment thread CHANGELOG.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file does not need to be changed at all.

/// Output: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}.
/// Output: decryption key dkₚₖₑ ∈ 𝔹^{384k}.
///
/// d ←$ B

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the key_generation_seed and is not sampled internally, but is an input.

Suggested change
/// d ←$ B
Input: randomness d ∈ 𝔹^{32}

/// Output: decryption key dkₚₖₑ ∈ 𝔹^{384k}.
///
/// d ←$ B
/// (ρ,σ) ← G(d)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rank is part of the input to G here.

Suggested change
/// (ρ,σ) ← G(d)
/// (ρ,σ) ← G(d‖k)

/// N ← 0
/// for (i ← 0; i < k; i++)
/// for(j ← 0; j < k; j++)
/// Â[i,j] ← SampleNTT(XOF(ρ, j, i))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line deviates from the presentation in the standard.

Suggested change
/// Â[i,j] ← SampleNTT(XOF(ρ, j, i))
/// Â[i,j] ← SampleNTT(ρ‖j‖i)

While the current state is what actually happens at the beginning of SampleNTT, I prefer the presentation of the standard since it makes clear how (in particular in which order) the function arguments are to be combined to one input for SampleNTT.


#[hax_lib::attributes]
impl FieldElement {
#[hax_lib::requires(val < FIELD_MODULUS)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why enforce this with a pre-condition here, instead of doing the reduction % FIELD_MODULUS in the constructor for FieldElement? The bound could be post-condition instead and making the val field private instead of pub would ensure that no out-of-bounds field element can be created in the Rust domain already as long as the operations preserve this too.

/// `deserialize_then_decompress_u` function (which fuses the NTT into
/// the per-element decompress loop).
#[hax_lib::requires(RANK <= 4 && (du == 10 || du == 11) && ciphertext.len() == (RANK * COEFFICIENTS_IN_RING_ELEMENT * du) / 8)]
pub fn deserialize_then_decompress_u_then_ntt<const RANK: usize>(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this function is not used in ind_cpa::decrypt_unpacked?

let ek: [u8; EK_SIZE] = serialize_public_key::<RANK, EK_SIZE>(&t_as_ntt, &seed_for_A);
let public_key_hash: [u8; 32] = H(&ek);

let mut implicit_rejection_value = [0u8; 32];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unnecessary, since z is already a reference to a [u8;32] and could be cloned below instead.

Comment thread specs/ml-kem/Readme.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a section on the F* extraction as well? I think there is no script to drive it and it's not clear what it should prove.

Comment thread specs/ml-kem/Readme.md

## Extraction via HAX

### Lean

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything that is proven in lean about this spec on its own, or is the extraction just used for the functional correctness proof of the impl?

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.

3 participants