Skip to content

Commit ea44681

Browse files
authored
Add an ML-KEM implementation (#2)
This PR adds an implementation of ML-KEM variant of the Kyber KEM as described in the initial public draft of FIPS 203. This implementation covers all three parameter sets described in the specification by making heavy use of Rust generics and the typenum crate. In addition to self-compatibility testing, we test correctness by verifying the test vectors supplied by NIST; see tests/nist.rs.
1 parent 108ef82 commit ea44681

14 files changed

+3690
-0
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[workspace]
2+
resolver = "2"
3+
members = [
4+
"ml-kem",
5+
]

ml-kem/Cargo.toml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "ml-kem"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[features]
7+
default = []
8+
deterministic = [] # Expose deterministic generation and encapsulation functions
9+
10+
[dependencies]
11+
const-default = "1.0.0"
12+
crypto-common = { version = "0.1.6", features = ["getrandom"] }
13+
generic-array = { version = "1.0.0", features = ["const-default"] }
14+
hybrid-array = { version = "0.2.0-rc.6" }
15+
sha3 = "0.10.8"
16+
17+
[dev-dependencies]
18+
criterion = "0.5.1"
19+
hex = "0.4.3"
20+
hex-literal = "0.4.1"
21+
rand = "0.8.5"
22+
23+
[profile.bench]
24+
debug = true
25+
26+
[[bench]]
27+
name = "mlkem"
28+
harness = false

ml-kem/benches/mlkem.rs

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use crypto_common::rand_core::CryptoRngCore;
3+
use hybrid_array::{Array, ArraySize};
4+
use ml_kem::*;
5+
6+
pub fn rand<L: ArraySize>(rng: &mut impl CryptoRngCore) -> Array<u8, L> {
7+
let mut val = Array::<u8, L>::default();
8+
rng.fill_bytes(&mut val);
9+
val
10+
}
11+
12+
fn criterion_benchmark(c: &mut Criterion) {
13+
let mut rng = rand::thread_rng();
14+
let d: B32 = rand(&mut rng);
15+
let z: B32 = rand(&mut rng);
16+
let m: B32 = rand(&mut rng);
17+
18+
let (dk, ek) = MlKem768::generate_deterministic(&d, &z);
19+
let dk_bytes = dk.as_bytes();
20+
let ek_bytes = ek.as_bytes();
21+
let (ct, _sk) = ek.encapsulate(&mut rng).unwrap();
22+
23+
// Key generation
24+
c.bench_function("keygen", |b| {
25+
b.iter(|| {
26+
let (dk, ek) = <MlKem768 as KemCore>::generate_deterministic(&d, &z);
27+
let _dk_bytes = dk.as_bytes();
28+
let _ek_bytes = ek.as_bytes();
29+
})
30+
});
31+
32+
// Encapsulation
33+
c.bench_function("encapsulate", |b| {
34+
b.iter(|| {
35+
let ek = <MlKem768 as KemCore>::EncapsulationKey::from_bytes(&ek_bytes);
36+
ek.encapsulate_deterministic(&m).unwrap();
37+
})
38+
});
39+
40+
// Decapsulation
41+
c.bench_function("decapsulate", |b| {
42+
b.iter(|| {
43+
let dk = <MlKem768 as KemCore>::DecapsulationKey::from_bytes(&dk_bytes);
44+
dk.decapsulate(&ct).unwrap();
45+
})
46+
});
47+
48+
// Round trip
49+
c.bench_function("round_trip", |b| {
50+
b.iter(|| {
51+
let (dk, ek) = <MlKem768 as KemCore>::generate_deterministic(&d, &z);
52+
let (ct, _sk) = ek.encapsulate(&mut rng).unwrap();
53+
dk.decapsulate(&ct).unwrap();
54+
})
55+
});
56+
}
57+
58+
criterion_group!(benches, criterion_benchmark);
59+
criterion_main!(benches);

0 commit comments

Comments
 (0)