Skip to content

test: compare ML-KEM with RustCrypto ml-kem on random inputs (WP-4) - #36

Merged
Zikithezikit merged 1 commit into
dev-v2from
wp4-differential
Oct 30, 2026
Merged

Zikithezikit merged 1 commit into
dev-v2from
wp4-differential

Conversation

@Zikithezikit

Copy link
Copy Markdown
Owner

Part of #12 (WP-4), the differential item. The CCTV and Wycheproof items are separate PRs from the same base.

What this adds

One commit: src/mlkem/differential_tests.rs plus a dev-dependency on RustCrypto ml-kem. For each parameter set it runs 200 rounds over a fixed-seed generator (xorshift64*, seed in a named constant, the round number is in every failure message), comparing our internal functions with ml-kem on the same bytes:

  • KeyGen from a random seed d || z: same ek and same expanded dk.
  • Encaps with a random m: same ciphertext and same shared secret.
  • Decaps of the valid ciphertext, of one with a single flipped bit, and of a random ciphertext: same output. The last two are the implicit-rejection path, and the test also asserts that only the valid one gives the encapsulated secret.
  • Decaps with a dk whose dk_PKE or z byte was changed (both pass the section 7.3 checks): same output from both.
  • Key checks: after replacing one random byte of ek, both must agree on accept or reject (section 7.2); after forcing one coefficient into q..4095 both must reject; after changing one byte inside ek or H(ek) in dk, both must reject (section 7.3, and ours must equal theirs).

Result: no disagreement in 600 rounds. No finding.

Why ml-kem and not libcrux-ml-kem

I picked RustCrypto ml-kem 0.3.3 (crates.io, published 2026-10-17, Apache-2.0 OR MIT).

  • Its README says it implements FIPS 203 final, and I read the source rather than trusting that: pke.rs computes (rho, sigma) from G(d || k) (the final standard, see the CCTV PR for the draft difference), and encapsulation uses G(m || H(ek)) with no H(m) and no H(c). The test itself also rules out the round-3 and draft behavior: ours passes the ACVP vectors from Test ML-KEM against the NIST ACVP vectors #11, and the two agree byte for byte.
  • It declares rust-version 1.85 and is a stable 0.3 release line. libcrux-ml-kem is at 0.0.11 and declares no rust-version on crates.io. I did not evaluate libcrux beyond that, so this is a judgment on maturity and metadata, not a finding about its quality.
  • It is written independently of this crate: separate crates for the algebra (module-lattice) and the hashing (sha3), with no code shared with src/kyber, so a mistake in our fips202 or ntt cannot be mirrored in it. I read ml-kem's top-level source, not module-lattice or sha3.
  • It has the hook the test needs, behind a feature: ml-kem = { version = "=0.3.3", features = ["hazmat"] } turns on encapsulate_deterministic(m). The version is pinned with = so a bump is a reviewable diff.

Not used: its own zeroize, pkcs8, getrandom features. Nothing of its code is copied; the test only calls its public API.

Dev dependency footprint

[dev-dependencies] only. cargo tree -e normal is unchanged and contains no ml-kem, sha3, hybrid-array or kem, so nothing reaches the cdylib. New packages in Cargo.lock, all Apache-2.0 OR MIT or MIT OR Apache-2.0: ml-kem 0.3.3, kem 0.3.0, module-lattice 0.2.3, hybrid-array 0.4.15, sha3 0.12.0, shake 0.1.0, keccak 0.2.2, sponge-cursor 0.1.0, digest 0.11.3, crypto-common 0.2.2, typenum 1.20.1, cmov 0.5.4, ctutils 0.4.7, cpufeatures 0.3.1, num-traits 0.2.19, rand_core 0.10.1 (a second rand_core next to our 0.6), and pkcs8 0.11.0, spki 0.8.1, der 0.8.3, base64ct 1.8.3, const-oid 0.10.2 (optional dependencies of ml-kem that appear in the lockfile).

MSRV: ml-kem is edition 2024 and its dependency ctutils declares rust-version 1.87, so running the tests needs rustc 1.87 or newer. This repo declares no MSRV and CI installs the newest stable; I ran on 1.95.0 and 1.99.1. Dev-dependencies are not built for a normal build, so I expect an older toolchain to lose only cargo test and not the extension build; I did not try an older toolchain.

Verified (how)

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings on rustc 1.95.0 and 1.99.1, cargo test --locked, maturin develop and pytest python/tests: all clean. Run through git rebase --exec 'scripts/check.sh && cargo +1.99.1 clippy --all-targets -- -D warnings' origin/dev-v2.
  • Mutation checks, reverted and not committed: making check_dk always accept made all three tests fail; removing the implicit-rejection select in decaps_internal made all three fail.
  • The test takes about 11 s in the dev profile.
  • No ignored or skipped tests.

Believed, not verified

  • That the reference is stricter than the standard in one place: its dk parser (from_expanded) also runs the modulus check on the ek inside dk, which section 7.3 does not ask for. The test only mutates bytes where both must reject, so the difference never shows. I read this in its source; I did not construct a dk that separates the two.
  • That I have not looked at ml-kem's own test suite results beyond its README; the claim that it is correct rests on the agreement with ACVP-checked output here.

Size

226 changed lines excluding Cargo.lock (221 test file, 4 Cargo.toml, 1 mod.rs).

Sources checked (2026-10-30)

Checklist

  • Spec sections cited in code for new crypto logic (tests only; sections 7.2 and 7.3 and Algorithm 16 named in the test)
  • Legacy golden vectors pass unchanged
  • No secret-dependent branches, divisions, or indexing (asm checked: n.a., no production code changed)
  • Secret intermediates zeroized on all paths (n.a.)
  • Inputs validated before secret-dependent work (n.a.)
  • No new runtime deps; no unsafe; no panics on untrusted input (one new dev-dependency, confirmed absent from cargo tree -e normal)
  • No magic numbers: sizes, widths, multipliers and counts are named, documented constants
  • Tests added (vectors for crypto); fmt/clippy/test/pytest green on every commit (git rebase --exec)
  • Review fixes folded into the commits they fix (none yet); rebased on current dev-v2
  • CHANGELOG updated (n.a., tests only)
  • Sources checked: see the list above

Generated with Claude Code (https://claude.com/claude-code)

A dev-only differential test (ml-kem 0.3.3, FIPS 203 final) over a
fixed-seed generator: keygen from a seed, encaps with a given m, decaps
of valid, bit-flipped and random ciphertexts, decaps under a changed
dk_PKE or z, and the modulus and hash checks after random byte changes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Zikithezikit
Zikithezikit merged commit 0bf6007 into dev-v2 Oct 30, 2026
46 checks passed
@Zikithezikit
Zikithezikit deleted the wp4-differential branch October 30, 2026 14:41
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.

1 participant