You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PyO3 functions for keygen, encaps and decaps per parameter set; Rust errors become KyberError (or a subclass), not a bare Exception
Classes that work exactly like the Kyber* ones: an instance is a keypair, static encapsulate/decapsulate/from_keys, hex/b64/dict, tuple unpacking
Seed is 64 bytes, d then z (FIPS 203 Algorithm 19 order)
Kyber and ML-KEM keys are the same size, so it's easy to mix them up. An ML-KEM method should refuse a Kyber keypair object (we can't tell raw bytes apart, so the docs need to say that)
Update __all__, regenerate the .pyi stubs
Done when: Python tests cover all three classes, and the ACVP tests from #11 also run through the Python API for at least one vector per set.
Handoff for whoever picks this up (the Rust side is finished in #30; read src/mlkem/kem.rs first).
Rust surface, all pub(crate) and under the dead_code allow in src/lib.rs, which you should drop once this wires them in:
kem::keygen::() -> Result<(Vec, Zeroizing<Vec>), KyberError>, kem::keygen_from_seed::(d, z) with d and z as &[u8; 32] (no error, it cannot fail), kem::encaps::(ek) -> Result<(SharedSecret, Vec)> which returns (K, c) in the order of the standard, and kem::decaps::(dk, c) -> Result. Call them as kem::keygen::<{ MlKem768::K }>().
The seed for Python is 64 bytes, d then z. Split it and check the length before calling.
decaps never errors on a well-formed but wrong ciphertext; it returns the implicit-rejection secret. It does error on a wrong ciphertext length, a wrong dk length, and a dk whose stored H(ek) does not match.
Errors are KyberError: InvalidInput { param, expected, actual }, RandomBytesGeneration, and two new variants, InvalidEncapsulationKey (modulus check) and InvalidDecapsulationKey (hash check). Map each to its own KyberError subclass in Python. The legacy functions in src/lib.rs raise a bare PyException; do not copy that for the new ones and do not change the legacy ones.
Do not use the acvp feature or kem::acvp from the bindings. Those exist for vector tests only (FIPS 203 section 3.3).
Things that will bite:
ML-KEM and Kyber keys have identical sizes, so a Kyber keypair object passed to an ML-KEM method must be refused by type, not by length.
Secrets that cross into Python become bytes objects that cannot be wiped; say so in the docs. The Rust side zeroizes its own copies, but converting a Zeroizing<Vec> with to_vec() makes an unwiped copy before it is handed over, so build the Python bytes straight from the slice.
Panics must not cross the boundary; the new Rust code has none on untrusted input, keep it that way in the glue.
Run cargo +stable clippy as well as the default one; CI uses the newest stable.
For the "ACVP through the Python API" item: scripts/fetch_vectors.py already downloads the pinned files into tests/vectors/external/acvp/. The internalProjection decapsulation cases use keys that pass the hash check, so they work through the public decaps. Encapsulation cases fix m, which the public API does not allow, so for those check only that the Python encaps output decapsulates to the same secret, or use a keyGen case through the seed path (seed = d || z gives the exact ek and dk of the keyGen vectors).
Done in #31 (rebase-merged into dev-v2 as 3f3aa86, f495537, c406cad and 6e72926). The ML_KEM_512/768/1024 classes, the nine private Rust functions, the KyberError subclasses, the Kyber/ML-KEM refusal by type and the ACVP vectors through the Python API are in. Left open on purpose: ML-KEM from_keys checks sizes only, like the Kyber one (see the comment on #18).
Activity
Zikithezikit commentedon Oct 30, 2026
Handoff for whoever picks this up (the Rust side is finished in #30; read src/mlkem/kem.rs first).
Rust surface, all pub(crate) and under the dead_code allow in src/lib.rs, which you should drop once this wires them in:
Things that will bite:
For the "ACVP through the Python API" item: scripts/fetch_vectors.py already downloads the pinned files into tests/vectors/external/acvp/. The internalProjection decapsulation cases use keys that pass the hash check, so they work through the public decaps. Encapsulation cases fix m, which the public API does not allow, so for those check only that the Python encaps output decapsulates to the same secret, or use a keyGen case through the seed path (seed = d || z gives the exact ek and dk of the keyGen vectors).
Zikithezikit commentedon Oct 30, 2026
Done in #31 (rebase-merged into dev-v2 as 3f3aa86, f495537, c406cad and 6e72926). The ML_KEM_512/768/1024 classes, the nine private Rust functions, the KyberError subclasses, the Kyber/ML-KEM refusal by type and the ACVP vectors through the Python API are in. Left open on purpose: ML-KEM
from_keyschecks sizes only, like the Kyber one (see the comment on #18).