ML-KEM-768 Encapsulation Design
Overview
ML-KEM-768 in MetaMUI Crypto Primitives is FIPS 203 ML-KEM, and nothing else.
Encapsulation is ML-KEM.Encaps (FIPS 203 Algorithm 20): 32 bytes m from a
cryptographically secure RNG, then Encaps_internal. No binding mixes a
timestamp, a domain string or any other input into m.
Entry points (Rust, metamui_mlkem::mlkem768)
| Function | FIPS 203 | Use |
|---|---|---|
encapsulate(pk, rng) |
Alg 20 ML-KEM.Encaps |
every application |
decapsulate(sk, ct) |
Alg 21 ML-KEM.Decaps (implicit rejection) |
every application |
generate_keypair(rng) |
Alg 19 ML-KEM.KeyGen |
every application |
encapsulate_deterministic(pk, m) |
Alg 17 Encaps_internal |
known-answer tests only |
generate_keypair_from_dz(d, z) |
Alg 16 KeyGen_internal |
known-answer tests only |
generate_keypair_from_seed(seed) |
none — MetaMUI single-seed convention, d := seed, z := SHA3-256(seed ‖ "z_derivation") |
cross-language fixtures only |
FIPS 203 §6 describes the _internal functions as a testing interface, not as
an application’s source of keys or shared secrets; the seeded convention above
inherits the same limit. The release
facade (metamui-crypto) offers none of them; its tests reproduce the NIST
ACVP answers by passing a scripted CryptoRng to encapsulate and
generate_keypair.
The last three rows, on mlkem512 and mlkem1024 as well, are compiled only
with the crate’s fips203-internal feature. A dependant that does not name the
feature cannot call them. The crate’s own tests enable it on themselves through
a path = "." dev-dependency, so the ACVP and KAT gates are always built and
nothing published carries the feature. generate_keypair and encapsulate
draw (d, z) and m and then run the same KeyGen_internal and
Encaps_internal bodies. Before 2026-09-16 they were copies of those bodies,
and the vectors verified the copies.
Feature fips203-internal (2026-09-16)
| Who enables it | Why |
|---|---|
metamui-mlkem’s own tests, examples and benches |
ACVP keyGen/encapDecap, the upstream KAT gate, emit_kat_fixtures |
metamui-crypto-wasm/mlkem |
the KAT exports behind @metamui/mlkem/testing |
| nothing else | the facade, metamui-hybrid, metamui-pqc, the interop driver and metamui-did-pqc call only the randomized functions |
cargo test never skips the gates when the feature is off. A required-features
entry on the test targets would do that, so crate_metadata.py --check refuses
one on any exported crate. The crate’s test internal_entry_points_are_gated
fails if any public function or re-export named *from_dz*, *from_seed* or
*deterministic* is compiled without the feature. It also fails if the
self-dependency disappears.
Evidence: documents/public-release/evidence/202-api-tests.md §7.
What was removed, and why
Until 2026-09-15 the Rust mlkem768 module also exported
encapsulate_production, encapsulate_test and
encapsulate_raw_compatibility, and an earlier version of this page described
them as a “three-tier” design:
encapsulate_productionsetm = SHA3-256("MLKEM768-ENCAPS-V1.0-" ‖ timestamp ‖ rng bytes). That is notML-KEM.Encaps: a scripted RNG no longer reproduces the standard’s answers, it adds a hash composition FIPS 203 does not specify, and it panics on a clock set before 1970 and onwasm32-unknown-unknown, whereSystemTime::nowis unavailable. Hashing a clock value in adds no security to an output that is already 32 bytes from a CSPRNG.encapsulate_testsetm = SHA3-256("MLKEM768-TEST-V1.0" ‖ seed), a MetaMUI-only derivation that no vector file and no other binding uses.encapsulate_raw_compatibilitywasencapsulate_deterministicunder a second name.
None of the three had a caller in this repository, in any other language
binding, or in metamui-did-pqc (which uses the MLKem768 type’s
encapsulate). The Python module that once carried the same names is gone.
Evidence: documents/public-release/evidence/202-api-tests.md §6.