diff --git a/.github/workflows/ml-kem.yml b/.github/workflows/ml-kem.yml index 1cea7bc..f54a276 100644 --- a/.github/workflows/ml-kem.yml +++ b/.github/workflows/ml-kem.yml @@ -79,6 +79,15 @@ jobs: - run: cargo test - run: cargo test --all-features + checkct: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: Ledger-Donjon/cargo-checkct@85271e5ce9eab9ba895c62c8b5d4129810a635a0 + with: + directory: ml-kem + timeout: 120 + cross: needs: set-msrv strategy: diff --git a/ml-kem/checkct/.cargo/config.toml b/ml-kem/checkct/.cargo/config.toml new file mode 100644 index 0000000..5f48358 --- /dev/null +++ b/ml-kem/checkct/.cargo/config.toml @@ -0,0 +1,20 @@ +[build] +target = [ + # TODO(tarcieri): investigate codegen problems on commented out platforms + #"riscv32imac-unknown-none-elf", + #"thumbv6m-none-eabi", + "thumbv7em-none-eabihf", + #"x86_64-unknown-linux-gnu", +] + +[target.x86_64-unknown-linux-gnu] +rustflags = [ + "-C", "linker=rust-lld", + "-C", "linker-flavor=ld.lld", + "-C", "relocation-model=static", +] + +[unstable] +unstable-options = true +build-std = ["core", "panic_abort"] +build-std-features = ["compiler-builtins-mem"] diff --git a/ml-kem/checkct/.gitignore b/ml-kem/checkct/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/ml-kem/checkct/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/ml-kem/checkct/Cargo.lock b/ml-kem/checkct/Cargo.lock new file mode 100644 index 0000000..e2a759d --- /dev/null +++ b/ml-kem/checkct/Cargo.lock @@ -0,0 +1,231 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "checkct_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "ctutils" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3e9d21d39e6f39ff46e03d5b621424f88ee23d8a1672c6bfc9ccc7f2da4f6a" +dependencies = [ + "cmov", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "crypto-common", +] + +[[package]] +name = "driver" +version = "0.0.0" +dependencies = [ + "checkct_macros", + "ml-kem", + "rand_core 0.10.1", + "rand_core 0.6.4", + "rand_core 0.9.5", +] + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "ctutils", + "typenum", +] + +[[package]] +name = "keccak" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" +dependencies = [ + "cfg-if", + "cpufeatures", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common", + "rand_core 0.10.1", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "ml-kem" +version = "0.3.3" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "rand_core 0.10.1", + "sha3", + "shake", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "sha3" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" diff --git a/ml-kem/checkct/Cargo.toml b/ml-kem/checkct/Cargo.toml new file mode 100644 index 0000000..079a13d --- /dev/null +++ b/ml-kem/checkct/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +members = ["driver"] +resolver = "2" + +[profile.release] +debug = true +panic = "abort" + +[patch.crates-io] +module-lattice = { path = "../../module-lattice" } diff --git a/ml-kem/checkct/checkct_macros/Cargo.toml b/ml-kem/checkct/checkct_macros/Cargo.toml new file mode 100644 index 0000000..559aaf4 --- /dev/null +++ b/ml-kem/checkct/checkct_macros/Cargo.toml @@ -0,0 +1,14 @@ +#----- AUTOGENERATED BY CARGO-CHECKCT: DO NOT MODIFY ----- +[package] +name = "checkct_macros" +version = "0.1.0" +publish = false +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.86" +quote = "1.0.37" +syn = { version = "2.0.77", features = ["full"] } diff --git a/ml-kem/checkct/checkct_macros/src/lib.rs b/ml-kem/checkct/checkct_macros/src/lib.rs new file mode 100644 index 0000000..2051302 --- /dev/null +++ b/ml-kem/checkct/checkct_macros/src/lib.rs @@ -0,0 +1,91 @@ +use proc_macro::TokenStream; +use proc_macro2::{Literal, Span}; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, Error, Ident, ItemFn, LitStr, Result, Token, +}; + +struct AttributeArgs { + descriptor_link_section: Option, +} + +impl Parse for AttributeArgs { + fn parse(input: ParseStream) -> Result { + if input.is_empty() { + return Ok(Self { + descriptor_link_section: None, + }); + } + + let arg_name: Ident = input.parse()?; + if arg_name != "descriptor_link_section" { + return Err(Error::new( + arg_name.span(), + "expected `descriptor_link_section`", + )); + } + input.parse::()?; + let descriptor_link_section: LitStr = input.parse()?; + + Ok(Self { + descriptor_link_section: Some(descriptor_link_section.value()), + }) + } +} + +#[proc_macro_attribute] +pub fn checkct(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as AttributeArgs); + // By default the descriptor will be stored in the .note.checkct section + let descriptor_link_section = Literal::string( + args.descriptor_link_section + .as_deref() + .unwrap_or(".note.checkct"), + ); + + let item_fn = parse_macro_input!(input as ItemFn); + let name = &item_fn.sig.ident; + + let entrypoint_descriptor_name = Ident::new( + &format!("__checkct_entrypoint_descriptor__{name}"), + Span::call_site(), + ); + let entrypoint_descriptor_type = Ident::new( + &format!("__CheckctEntrypointDescriptor__{name}"), + Span::call_site(), + ); + + quote! { + // The descriptor is laid out as an ELF note (which binsec parses), since it is stored + // in a note section by default. It ends with the address of the entrypoint. + #[doc(hidden)] + #[allow(non_camel_case_types, dead_code)] + #[repr(C, packed(4))] + pub struct #entrypoint_descriptor_type { + namesz: u32, + descsz: u32, + kind: u32, + name: [u8; 8], + entrypoint: fn(), + } + + // This variable is used to prevent the compiler from removing the code of the entrypoint + // function if it is unused. Moreover this variable name has a special format which allows + // cargo-checkct to find it. + // Make sure that the descriptor link section is kept by the linker. + #[unsafe(link_section = #descriptor_link_section)] + #[used] + pub static #entrypoint_descriptor_name: #entrypoint_descriptor_type = + #entrypoint_descriptor_type { + namesz: 8, + descsz: ::core::mem::size_of::() as u32, + kind: 1, + name: *b"checkct\0", + entrypoint: #name, + }; + + #item_fn + } + .into() +} diff --git a/ml-kem/checkct/driver/Cargo.toml b/ml-kem/checkct/driver/Cargo.toml new file mode 100644 index 0000000..90155e9 --- /dev/null +++ b/ml-kem/checkct/driver/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "driver" +version = "0.0.0" +publish = false +edition = "2024" + +[dependencies] +checkct_macros = { path = "../checkct_macros" } +ml-kem = { path = "../..", default-features = false } +# PrivateRng and PublicRng implement the traits of all these rand_core versions, +# so that they can be passed to libraries depending on any of them. +rand_core = "0.10" +rand_core_09 = { package = "rand_core", version = "0.9" } +rand_core_06 = { package = "rand_core", version = "0.6" } diff --git a/ml-kem/checkct/driver/src/driver.rs b/ml-kem/checkct/driver/src/driver.rs new file mode 100644 index 0000000..88fec94 --- /dev/null +++ b/ml-kem/checkct/driver/src/driver.rs @@ -0,0 +1,14 @@ +use crate::rng::{PrivateRng, PublicRng}; +use checkct_macros::checkct; +use core::hint::black_box; +use ml_kem::{B32, EncapsulationKey, MlKem512}; + +#[checkct] +pub fn encapsulate_deterministic() { + // The generated BINSEC script references both RNG hooks unconditionally. + black_box(PublicRng.next_u32()); + let ek = EncapsulationKey::::new(&[0u8; 800].into()).unwrap(); + let mut m = B32::default(); + PrivateRng.fill_bytes(&mut m); + black_box(ek.encapsulate_deterministic(&m)); +} diff --git a/ml-kem/checkct/driver/src/main.rs b/ml-kem/checkct/driver/src/main.rs new file mode 100644 index 0000000..b1b07dd --- /dev/null +++ b/ml-kem/checkct/driver/src/main.rs @@ -0,0 +1,24 @@ +//----- AUTOGENERATED BY CARGO-CHECKCT: DO NOT MODIFY ----- +// +#![no_std] +#![no_main] + +mod driver; +mod rng; + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + panic!() +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + __checkct_panic() +} + +/// The analysis of a path stops when it reaches this function. +#[unsafe(no_mangle)] +#[inline(never)] +pub extern "C" fn __checkct_panic() -> ! { + loop {} +} diff --git a/ml-kem/checkct/driver/src/rng.rs b/ml-kem/checkct/driver/src/rng.rs new file mode 100644 index 0000000..2af279c --- /dev/null +++ b/ml-kem/checkct/driver/src/rng.rs @@ -0,0 +1,110 @@ +//----- AUTOGENERATED BY CARGO-CHECKCT: DO NOT MODIFY ----- +// +//! `PrivateRng` produces secret values, and `PublicRng` produces public values. +//! Both provide `next_u32`, `next_u64` and `fill_bytes` methods, and implement +//! the traits of rand_core 0.10, 0.9 and 0.6. +#![allow(dead_code)] + +#[unsafe(no_mangle)] +#[inline(never)] +pub fn __checkct_private_rand() -> u8 { + unsafe { core::ptr::read_volatile(0xcafe as *const u8) } +} + +#[unsafe(no_mangle)] +#[inline(never)] +pub fn __checkct_public_rand() -> u8 { + unsafe { core::ptr::read_volatile(0xf00d as *const u8) } +} + +#[allow(unused_imports)] +pub use rand_core::{CryptoRng, Rng}; +// Drivers generated by older versions of cargo-checkct import `RngCore` +#[allow(unused_imports)] +pub use rand_core_06::RngCore; + +pub struct PrivateRng; +pub struct PublicRng; + +macro_rules! impl_rng { + ($rng:ident, $rand:ident) => { + impl $rng { + pub const fn new() -> Self { + Self + } + + pub fn next_u32(&mut self) -> u32 { + u32::from_be_bytes([$rand(), $rand(), $rand(), $rand()]) + } + + pub fn next_u64(&mut self) -> u64 { + (self.next_u32() as u64) << 32 | (self.next_u32() as u64) + } + + pub fn fill_bytes(&mut self, dest: &mut [u8]) { + for d in dest { + *d = $rand(); + } + } + } + + impl rand_core::TryRng for $rng { + type Error = core::convert::Infallible; + + fn try_next_u32(&mut self) -> Result { + Ok($rng::next_u32(self)) + } + + fn try_next_u64(&mut self) -> Result { + Ok($rng::next_u64(self)) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { + $rng::fill_bytes(self, dest); + Ok(()) + } + } + + impl rand_core::TryCryptoRng for $rng {} + + impl rand_core_09::RngCore for $rng { + fn next_u32(&mut self) -> u32 { + $rng::next_u32(self) + } + + fn next_u64(&mut self) -> u64 { + $rng::next_u64(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + $rng::fill_bytes(self, dest) + } + } + + impl rand_core_09::CryptoRng for $rng {} + + impl rand_core_06::RngCore for $rng { + fn next_u32(&mut self) -> u32 { + $rng::next_u32(self) + } + + fn next_u64(&mut self) -> u64 { + $rng::next_u64(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + $rng::fill_bytes(self, dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + $rng::fill_bytes(self, dest); + Ok(()) + } + } + + impl rand_core_06::CryptoRng for $rng {} + }; +} + +impl_rng!(PrivateRng, __checkct_private_rand); +impl_rng!(PublicRng, __checkct_public_rand); diff --git a/ml-kem/checkct/rust-toolchain.toml b/ml-kem/checkct/rust-toolchain.toml new file mode 100644 index 0000000..3e71075 --- /dev/null +++ b/ml-kem/checkct/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +# Pinned so that driver builds are reproducible, since they rely on the unstable `-Zbuild-std` +channel = "nightly-2026-10-06" +profile = "minimal" +components = ["rust-src"]