feat: add bindings for level 2 and 4; generate randomness from provided CSRNG

This commit is contained in:
2025-03-19 11:07:26 +01:00
parent 6f3e5163b6
commit 7d0667d1c0
8 changed files with 285 additions and 100 deletions

60
Cargo.lock generated
View File

@@ -69,18 +69,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi",
"windows-targets",
]
[[package]]
name = "glob"
version = "0.3.2"
@@ -141,9 +129,9 @@ name = "mlkem-native-rs"
version = "0.1.0"
dependencies = [
"bindgen",
"getrandom",
"libc",
"make-cmd",
"rand_core",
"thiserror",
]
[[package]]
@@ -184,6 +172,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
[[package]]
name = "regex"
version = "1.11.1"
@@ -236,21 +230,32 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -314,12 +319,3 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]

View File

@@ -5,8 +5,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
getrandom = "0.3.1"
libc = "0.2.171"
rand_core = "0.9.3"
thiserror = "2.0.12"
[build-dependencies]
bindgen = "0.71.1"

View File

@@ -2,25 +2,19 @@ use std::env;
use std::path::PathBuf;
fn main() {
// This is the directory where the `c` library is located.
let libdir_path = PathBuf::from("mlkem-native")
// Canonicalize the path as `rustc-link-search` requires an absolute
// path.
.canonicalize()
.expect("cannot canonicalize path");
// This is the path to the `c` headers file.
let headers_path = libdir_path.join("mlkem/mlkem_native.h");
let headers_path_str = headers_path.to_str().expect("Path is not a valid string");
// Tell cargo to look for shared libraries in the specified directory
println!(
"cargo:rustc-link-search={}",
libdir_path.join("test/build").to_str().unwrap()
);
// Tell cargo to tell rustc to link our `hello` library. Cargo will
// automatically know it must look for a `libhello.a` file.
println!("cargo:rustc-link-lib=mlkem512");
println!("cargo:rustc-link-lib=mlkem768");
println!("cargo:rustc-link-lib=mlkem1024");
@@ -37,24 +31,39 @@ fn main() {
panic!("could not compile mlkem-native");
}
// The bindgen::Builder is the main entry point
// to bindgen, and lets you build up options for
// the resulting bindings.
let bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
let bindings_level2 = bindgen::Builder::default()
.header(headers_path_str)
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.clang_arg("-DMLKEM_K=2")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
bindings
let bindings_level3 = bindgen::Builder::default()
.header(headers_path_str)
.clang_arg("-DMLKEM_K=3")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
let bindings_level4 = bindgen::Builder::default()
.header(headers_path_str)
.clang_arg("-DMLKEM_K=4")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings_level2.rs");
bindings_level2
.write_to_file(out_path)
.expect("Couldn't write bindings!");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings_level3.rs");
bindings_level3
.write_to_file(out_path)
.expect("Couldn't write bindings!");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings_level4.rs");
bindings_level4
.write_to_file(out_path)
.expect("Couldn't write bindings!");
}

View File

@@ -1,10 +1,22 @@
use rand_core::TryCryptoRng;
use std::default::Default;
use thiserror::Error;
mod unsafe_bindings;
mod unsafe_bindings_level2;
mod unsafe_bindings_level3;
mod unsafe_bindings_level4;
#[derive(Error, Debug)]
pub enum MLKEMNativeError {
#[error("the CSRNG failed due to insufficent entropy")]
InsufficentEntropy,
#[error("the library function encountered an internal error")]
LibraryError,
}
macro_rules! reexport_const {
( $type_name:ident, $struct_name:ident ) => {
pub const $type_name: usize = unsafe_bindings::$type_name as usize;
pub const $type_name: usize = unsafe_bindings_level2::$type_name as usize;
#[derive(Debug, PartialEq)]
pub struct $struct_name([u8; $type_name]);
@@ -17,7 +29,7 @@ macro_rules! reexport_const {
};
}
reexport_const!(MLKEM512_SECRETKEYBYTES, MLKEM512Secretkey);
reexport_const!(MLKEM512_SECRETKEYBYTES, MLKEM512SecretKey);
reexport_const!(MLKEM512_PUBLICKEYBYTES, MLKEM512PublicKey);
reexport_const!(MLKEM512_CIPHERTEXTBYTES, MLKEM512Ciphertext);
reexport_const!(MLKEM512_BYTES, MLKEM512SharedSecret);
@@ -32,48 +44,66 @@ reexport_const!(MLKEM1024_PUBLICKEYBYTES, MLKEM1024PublicKey);
reexport_const!(MLKEM1024_CIPHERTEXTBYTES, MLKEM1024Ciphertext);
reexport_const!(MLKEM1024_BYTES, MLKEM1024SharedSecret);
pub fn mlkem768_keypair() -> Option<(MLKEM768SecretKey, MLKEM768PublicKey)> {
let mut sk = MLKEM768SecretKey::default();
let mut pk = MLKEM768PublicKey::default();
pub fn mlkem512_keypair(
rng: &mut impl TryCryptoRng,
) -> Result<(MLKEM512SecretKey, MLKEM512PublicKey), MLKEMNativeError> {
let mut sk = MLKEM512SecretKey::default();
let mut pk = MLKEM512PublicKey::default();
let mut coins = [0u8; 2 * (unsafe_bindings_level2::MLKEM512_SYMBYTES as usize)];
rng.try_fill_bytes(&mut coins)
.map_err(|_| MLKEMNativeError::InsufficentEntropy)?;
let success = unsafe {
unsafe_bindings::PQCP_MLKEM_NATIVE_MLKEM768_keypair(pk.0.as_mut_ptr(), sk.0.as_mut_ptr())
};
if success == 0 {
Some((sk, pk))
} else {
None
}
}
pub fn mlkem768_enc(pk: &MLKEM768PublicKey) -> Option<(MLKEM768Ciphertext, MLKEM768SharedSecret)> {
let mut ct = MLKEM768Ciphertext::default();
let mut ss = MLKEM768SharedSecret::default();
let success = unsafe {
unsafe_bindings::PQCP_MLKEM_NATIVE_MLKEM768_enc(
ct.0.as_mut_ptr(),
ss.0.as_mut_ptr(),
pk.0.as_ptr(),
unsafe_bindings_level2::PQCP_MLKEM_NATIVE_MLKEM512_keypair_derand(
pk.0.as_mut_ptr(),
sk.0.as_mut_ptr(),
coins.as_ptr(),
)
};
if success == 0 {
Some((ct, ss))
Ok((sk, pk))
} else {
None
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem768_dec(
pub fn mlkem512_enc(
rng: &mut impl TryCryptoRng,
pk: &MLKEM768PublicKey,
) -> Result<(MLKEM768Ciphertext, MLKEM768SharedSecret), MLKEMNativeError> {
let mut ct = MLKEM768Ciphertext::default();
let mut ss = MLKEM768SharedSecret::default();
let mut coins = [0u8; unsafe_bindings_level2::MLKEM768_SYMBYTES as usize];
rng.try_fill_bytes(&mut coins)
.map_err(|_| MLKEMNativeError::InsufficentEntropy)?;
let success = unsafe {
unsafe_bindings_level2::PQCP_MLKEM_NATIVE_MLKEM512_enc_derand(
ct.0.as_mut_ptr(),
ss.0.as_mut_ptr(),
pk.0.as_ptr(),
coins.as_ptr(),
)
};
if success == 0 {
Ok((ct, ss))
} else {
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem512_dec(
sk: &MLKEM768SecretKey,
ct: &MLKEM768Ciphertext,
) -> Option<MLKEM768SharedSecret> {
) -> Result<MLKEM768SharedSecret, MLKEMNativeError> {
let mut ss = MLKEM768SharedSecret::default();
let success = unsafe {
unsafe_bindings::PQCP_MLKEM_NATIVE_MLKEM768_dec(
unsafe_bindings_level2::PQCP_MLKEM_NATIVE_MLKEM512_dec(
ss.0.as_mut_ptr(),
ct.0.as_ptr(),
sk.0.as_ptr(),
@@ -81,8 +111,154 @@ pub fn mlkem768_dec(
};
if success == 0 {
Some(ss)
Ok(ss)
} else {
None
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem768_keypair(
rng: &mut impl TryCryptoRng,
) -> Result<(MLKEM768SecretKey, MLKEM768PublicKey), MLKEMNativeError> {
let mut sk = MLKEM768SecretKey::default();
let mut pk = MLKEM768PublicKey::default();
let mut coins = [0u8; 2 * (unsafe_bindings_level2::MLKEM768_SYMBYTES as usize)];
rng.try_fill_bytes(&mut coins)
.map_err(|_| MLKEMNativeError::InsufficentEntropy)?;
let success = unsafe {
unsafe_bindings_level3::PQCP_MLKEM_NATIVE_MLKEM768_keypair_derand(
pk.0.as_mut_ptr(),
sk.0.as_mut_ptr(),
coins.as_ptr(),
)
};
if success == 0 {
Ok((sk, pk))
} else {
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem768_enc(
rng: &mut impl TryCryptoRng,
pk: &MLKEM768PublicKey,
) -> Result<(MLKEM768Ciphertext, MLKEM768SharedSecret), MLKEMNativeError> {
let mut ct = MLKEM768Ciphertext::default();
let mut ss = MLKEM768SharedSecret::default();
let mut coins = [0u8; unsafe_bindings_level2::MLKEM768_SYMBYTES as usize];
rng.try_fill_bytes(&mut coins)
.map_err(|_| MLKEMNativeError::InsufficentEntropy)?;
let success = unsafe {
unsafe_bindings_level3::PQCP_MLKEM_NATIVE_MLKEM768_enc_derand(
ct.0.as_mut_ptr(),
ss.0.as_mut_ptr(),
pk.0.as_ptr(),
coins.as_ptr(),
)
};
if success == 0 {
Ok((ct, ss))
} else {
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem768_dec(
sk: &MLKEM768SecretKey,
ct: &MLKEM768Ciphertext,
) -> Result<MLKEM768SharedSecret, MLKEMNativeError> {
let mut ss = MLKEM768SharedSecret::default();
let success = unsafe {
unsafe_bindings_level3::PQCP_MLKEM_NATIVE_MLKEM768_dec(
ss.0.as_mut_ptr(),
ct.0.as_ptr(),
sk.0.as_ptr(),
)
};
if success == 0 {
Ok(ss)
} else {
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem1024_keypair(
rng: &mut impl TryCryptoRng,
) -> Result<(MLKEM768SecretKey, MLKEM768PublicKey), MLKEMNativeError> {
let mut sk = MLKEM768SecretKey::default();
let mut pk = MLKEM768PublicKey::default();
let mut coins = [0u8; 2 * (unsafe_bindings_level2::MLKEM1024_SYMBYTES as usize)];
rng.try_fill_bytes(&mut coins)
.map_err(|_| MLKEMNativeError::InsufficentEntropy)?;
let success = unsafe {
unsafe_bindings_level4::PQCP_MLKEM_NATIVE_MLKEM1024_keypair_derand(
pk.0.as_mut_ptr(),
sk.0.as_mut_ptr(),
coins.as_ptr(),
)
};
if success == 0 {
Ok((sk, pk))
} else {
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem1024_enc(
rng: &mut impl TryCryptoRng,
pk: &MLKEM768PublicKey,
) -> Result<(MLKEM768Ciphertext, MLKEM768SharedSecret), MLKEMNativeError> {
let mut ct = MLKEM768Ciphertext::default();
let mut ss = MLKEM768SharedSecret::default();
let mut coins = [0u8; unsafe_bindings_level2::MLKEM1024_SYMBYTES as usize];
rng.try_fill_bytes(&mut coins)
.map_err(|_| MLKEMNativeError::InsufficentEntropy)?;
let success = unsafe {
unsafe_bindings_level4::PQCP_MLKEM_NATIVE_MLKEM1024_enc_derand(
ct.0.as_mut_ptr(),
ss.0.as_mut_ptr(),
pk.0.as_ptr(),
coins.as_ptr(),
)
};
if success == 0 {
Ok((ct, ss))
} else {
Err(MLKEMNativeError::LibraryError)
}
}
pub fn mlkem1024_dec(
sk: &MLKEM768SecretKey,
ct: &MLKEM768Ciphertext,
) -> Result<MLKEM768SharedSecret, MLKEMNativeError> {
let mut ss = MLKEM768SharedSecret::default();
let success = unsafe {
unsafe_bindings_level4::PQCP_MLKEM_NATIVE_MLKEM1024_dec(
ss.0.as_mut_ptr(),
ct.0.as_ptr(),
sk.0.as_ptr(),
)
};
if success == 0 {
Ok(ss)
} else {
Err(MLKEMNativeError::LibraryError)
}
}

View File

@@ -1,14 +0,0 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use std::slice;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[no_mangle]
pub unsafe extern "C" fn randombytes(buf: *mut u8, len: libc::size_t) -> libc::c_int {
let buf = slice::from_raw_parts_mut(buf, len);
getrandom::fill(buf).expect("RNG Failed");
0
}

View File

@@ -0,0 +1,6 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bindings_level2.rs"));

View File

@@ -0,0 +1,6 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bindings_level3.rs"));

View File

@@ -0,0 +1,6 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bindings_level4.rs"));