fips203-ffi-0.4.3/.cargo_vcs_info.json0000644000000001410000000000100127640ustar { "git": { "sha1": "51a74b57e92f4444790ba72f305eae7b7038d959" }, "path_in_vcs": "ffi" }fips203-ffi-0.4.3/Cargo.lock0000644000000120150000000000100107420ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "fips203" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8bdb6454f692ca2a2b45cd554c6828c639d7f9c968cf83a678899ec4443a280" dependencies = [ "rand_core", "sha3", "subtle", "zeroize", ] [[package]] name = "fips203-ffi" version = "0.4.3" dependencies = [ "fips203", "rand_core", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest", "keccak", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", "syn", ] fips203-ffi-0.4.3/Cargo.toml0000644000000023730000000000100107730ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "fips203-ffi" version = "0.4.3" authors = ["Daniel Kahn Gillmor "] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "C shared library exposing FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism" documentation = "https://docs.rs/fips203" readme = "README.md" keywords = [ "FIPS", "FIPS203", "lattice", "kem", "ml", ] categories = ["cryptography"] license = "MIT OR Apache-2.0" repository = "https://github.com/integritychain/fips203" [lib] name = "fips203" crate-type = [ "staticlib", "cdylib", ] path = "src/lib.rs" bench = false [dependencies.fips203] version = "0.4.2" [dependencies.rand_core] version = "0.6.4" fips203-ffi-0.4.3/Cargo.toml.orig000064400000000000000000000012011046102023000144410ustar 00000000000000[package] name = "fips203-ffi" version = "0.4.3" edition = "2021" license = "MIT OR Apache-2.0" description = "C shared library exposing FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism" authors = ["Daniel Kahn Gillmor "] documentation = "https://docs.rs/fips203" categories = ["cryptography"] repository = "https://github.com/integritychain/fips203" keywords = ["FIPS", "FIPS203", "lattice", "kem", "ml"] rust-version = "1.70" [lib] crate-type = ["staticlib", "cdylib"] bench = false name = "fips203" [dependencies] rand_core = { version = "0.6.4"} [dependencies.fips203] path = ".." version = "0.4.2" fips203-ffi-0.4.3/README.md000064400000000000000000000024441046102023000130430ustar 00000000000000# C Shared Object for ML-KEM This crate provides a shared object (dynamically-linked library) using standard C FFI ABI that provides a functional implementation of ML-KEM. The goals of this implementation are: - simplicity - correctness - caller deals only with serialized objects - no library-specific memory management (caller manages all objects) - no internal state held by the library between calls - minimal symbol visibility - stable API/ABI security-related goals: - constant-time operations - clean library RAM (objects should be zeroed out of any library-allocated memory before function exit) non-goals are: - speed - efficiency - size # Outstanding work - better internal error handling - testing! - reduce symbol visibility in shared object # Paths considered but discarded - Autogenerate stable C headers (e.g. with cbindgen); manually-crafted headers are probably fine, given the simplicity of the API/ABI # Quick start ~~~ $ cd ffi # this directory $ cargo build $ (cd tests && make) $ cd python $ python3 >>> from fips203 import ML_KEM_512 >>> >>> (encapsulation_key, decapsulation_key) = ML_KEM_512.keygen() >>> (ciphertext, shared_secret_1) = encapsulation_key.encaps() >>> shared_secret_2 = decapsulation_key.decaps(ciphertext) >>> assert(shared_secret_1 == shared_secret_2) ~~~ fips203-ffi-0.4.3/build.rs000064400000000000000000000024541046102023000132320ustar 00000000000000use std::env; use std::fs; use std::path::Path; fn main() { if cfg!(target_os = "macos") { println!("cargo:rustc-cdylib-link-arg=-shared"); println!("cargo:rustc-cdylib-link-arg=-dynamiclib"); println!("cargo:rustc-cdylib-link-arg=-install_name"); println!( "cargo:rustc-cdylib-link-arg=@rpath/libfips203.dylib" //, env::var("CARGO_PKG_VERSION_MAJOR").unwrap() ); } else { println!( "cargo:rustc-cdylib-link-arg=-Wl,-soname,libfips203.so.{}", env::var("CARGO_PKG_VERSION_MAJOR").unwrap() ); } // Write minimal pkg-config file: let out_dir = env::var("OUT_DIR").unwrap(); let libname = "fips203"; let mut desc = env::var("CARGO_PKG_DESCRIPTION").unwrap(); // strip beginning text: let begin_text = "C shared library exposing "; assert!(desc.starts_with(begin_text)); desc.replace_range(..begin_text.len(), ""); let version = env::var("CARGO_PKG_VERSION").unwrap(); let url = env::var("CARGO_PKG_REPOSITORY").unwrap(); let pc_dest_path = Path::new(&out_dir).join(format!("{libname}.pc")); fs::write( pc_dest_path, format!( "Name: {libname} Description: {desc} Version: {version} URL: {url} Libs: -l{libname} " ), ) .unwrap() } fips203-ffi-0.4.3/fips203.h000064400000000000000000000101271046102023000131200ustar 00000000000000#ifndef __FIPS203_H__ #define __FIPS203_H__ /* Minimalist ML-KEM C interface Author: Daniel Kahn Gillmor Memory allocation and tracking are entirely the job of the caller. The shared object backing this interface has no internal state between calls, and should be completely reentrant. These functions return 0 (ML_KEM_OK) on success, or a more specific non-zero octet on error. */ #include typedef uint8_t ml_kem_err; const ml_kem_err ML_KEM_OK = 0; const ml_kem_err ML_KEM_NULL_PTR_ERROR = 1; const ml_kem_err ML_KEM_SERIALIZATION_ERROR = 2; const ml_kem_err ML_KEM_DESERIALIZATION_ERROR = 3; const ml_kem_err ML_KEM_KEYGEN_ERROR = 4; const ml_kem_err ML_KEM_ENCAPSULATION_ERROR = 5; const ml_kem_err ML_KEM_DECAPSULATION_ERROR = 6; typedef struct ml_kem_shared_secret { uint8_t data[32]; } ml_kem_shared_secret; typedef struct ml_kem_seed { uint8_t data[64]; } ml_kem_seed; typedef struct ml_kem_512_encaps_key { uint8_t data[800]; } ml_kem_512_encaps_key; typedef struct ml_kem_512_decaps_key { uint8_t data[1632]; } ml_kem_512_decaps_key; typedef struct ml_kem_512_ciphertext { uint8_t data[768]; } ml_kem_512_ciphertext; typedef struct ml_kem_768_encaps_key { uint8_t data[1184]; } ml_kem_768_encaps_key; typedef struct ml_kem_768_decaps_key { uint8_t data[2400]; } ml_kem_768_decaps_key; typedef struct ml_kem_768_ciphertext { uint8_t data[1088]; } ml_kem_768_ciphertext; typedef struct ml_kem_1024_encaps_key { uint8_t data[1568]; } ml_kem_1024_encaps_key; typedef struct ml_kem_1024_decaps_key { uint8_t data[3168]; } ml_kem_1024_decaps_key; typedef struct ml_kem_1024_ciphertext { uint8_t data[1568]; } ml_kem_1024_ciphertext; #ifdef __cplusplus extern "C" { #endif ml_kem_err ml_kem_populate_seed(ml_kem_seed *seed_out); ml_kem_err ml_kem_512_keygen(ml_kem_512_encaps_key *encaps_out, ml_kem_512_decaps_key *decaps_out); ml_kem_err ml_kem_512_keygen_from_seed(const ml_kem_seed *d_z, ml_kem_512_encaps_key *encaps_out, ml_kem_512_decaps_key *decaps_out); ml_kem_err ml_kem_512_encaps(const ml_kem_512_encaps_key *encaps, ml_kem_512_ciphertext *ciphertext_out, ml_kem_shared_secret *shared_secret_out); ml_kem_err ml_kem_512_decaps(const ml_kem_512_decaps_key *decaps, const ml_kem_512_ciphertext *ciphertext, ml_kem_shared_secret *shared_secret_out); ml_kem_err ml_kem_768_keygen(ml_kem_768_encaps_key *encaps_out, ml_kem_768_decaps_key *decaps_out); ml_kem_err ml_kem_768_keygen_from_seed(const ml_kem_seed *d_z, ml_kem_768_encaps_key *encaps_out, ml_kem_768_decaps_key *decaps_out); ml_kem_err ml_kem_768_encaps(const ml_kem_768_encaps_key *encaps, ml_kem_768_ciphertext *ciphertext_out, ml_kem_shared_secret *shared_secret_out); ml_kem_err ml_kem_768_decaps(const ml_kem_768_decaps_key *decaps, const ml_kem_768_ciphertext *ciphertext, ml_kem_shared_secret *shared_secret_out); ml_kem_err ml_kem_1024_keygen(ml_kem_1024_encaps_key *encaps_out, ml_kem_1024_decaps_key *decaps_out); ml_kem_err ml_kem_1024_keygen_from_seed(const ml_kem_seed *d_z, ml_kem_1024_encaps_key *encaps_out, ml_kem_1024_decaps_key *decaps_out); ml_kem_err ml_kem_1024_encaps(const ml_kem_1024_encaps_key *encaps, ml_kem_1024_ciphertext *ciphertext_out, ml_kem_shared_secret *shared_secret_out); ml_kem_err ml_kem_1024_decaps(const ml_kem_1024_decaps_key *decaps, const ml_kem_1024_ciphertext *ciphertext, ml_kem_shared_secret *shared_secret_out); #ifdef __cplusplus } // extern "C" #endif #endif // __FIPS203_H__ fips203-ffi-0.4.3/python/README.md000064400000000000000000000050671046102023000143700ustar 00000000000000# `fips203` Python module This Python module provides an implementation of FIPS 203, the Module-Lattice-based Key Encapsulation Mechanism Standard. The underlying mechanism is intended to offer "post-quantum" asymmetric encryption and decryption. ## Example The following example shows using the standard ML-KEM algorithm to produce identical 32-byte shared secrets: ``` from fips203 import ML_KEM_512 (encapsulation_key, decapsulation_key) = ML_KEM_512.keygen() (ciphertext, shared_secret_1) = encapsulation_key.encaps() shared_secret_2 = decapsulation_key.decaps(ciphertext) assert(shared_secret_1 == shared_secret_2) ``` Key generation can also be done deterministically, by passing a `SEED_SIZE`-byte seed (the concatenation of d and z) to `keygen`: ``` from fips203 import ML_KEM_512, Seed seed1 = Seed() # Generate a random seed (ek1, dk1) = ML_KEM_512.keygen(seed1) seed2 = Seed(b'\x00'*ML_KEM_512.SEED_SIZE) # This seed is clearly not a secret! (ek2, dk2) = ML_KEM_512.keygen(seed2) ``` Encapsulation keys, decapsulation keys, seeds, and ciphertexts can all be serialized by accessing them as `bytes`, and deserialized by initializing them with the appropriate size bytes object. A serialization example: ``` from fips203 import ML_KEM_768 seed = Seed() (ek,dk) = ML_KEM_768.keygen(seed) with open('encapskey.bin', 'wb') as f: f.write(bytes(ek)) with open('decapskey.bin', 'wb') as f: f.write(bytes(dk)) with open('seed.bin', 'wb') as f: f.write(bytes(seed)) ``` A deserialization example, followed by use: ``` import fips203 with open('encapskey.bin', 'b') as f: ekdata = f.read() ek = fips203.EncapsulationKey(ekdata) (ct, ss) = ek.Encaps() ``` The expected sizes (in bytes) of the different objects in each parameter set can be accessed with `EK_SIZE`, `DK_SIZE`, `CT_SIZE`, `SEED_SIZE`, and `SS_SIZE`: ``` from fips203 import ML_KEM_768 print(f"ML-KEM-768 Ciphertext size (in bytes) is {ML_KEM_768.CT_SIZE}") ``` ## Implementation Notes This is a wrapper around libfips203, built from the Rust fips203-ffi crate. If that library is not installed in the expected path for libraries on your system, any attempt to use this module will fail. This module should have reasonable type annotations and docstrings for the public interface. If you discover a problem with type annotations, or see a way that this kind of documentation could be improved, please report it! ## See Also - https://doi.org/10.6028/NIST.FIPS.203.ipd - https://github.com/integritychain/fips203 ## Bug Reporting Please report issues at https://github.com/integritychain/fips203/issues fips203-ffi-0.4.3/python/fips203.py000064400000000000000000000357211046102023000146510ustar 00000000000000'''FIPS 203 (ML-KEM) Asymmetric Post-Quantum Cryptography This Python module provides an implementation of FIPS 203, the Module-Lattice-based Key Encapsulation Mechanism Standard. The underlying mechanism is intended to offer "post-quantum" asymmetric encryption and decryption. ## Example The following example shows using the standard ML-KEM algorithm to produce identical 32-byte shared secrets: ``` from fips203 import ML_KEM_512 (encapsulation_key, decapsulation_key) = ML_KEM_512.keygen() (ciphertext, shared_secret_1) = encapsulation_key.encaps() shared_secret_2 = decapsulation_key.decaps(ciphertext) assert(shared_secret_1 == shared_secret_2) ``` Key generation can also be done deterministically, by passing a `SEED_SIZE`-byte seed (the concatenation of d and z) to `keygen`: ``` from fips203 import ML_KEM_512, Seed seed1 = Seed() # Generate a random seed (ek1, dk1) = ML_KEM_512.keygen(seed1) seed2 = Seed(b'\x00'*ML_KEM_512.SEED_SIZE) # This seed is clearly not a secret! (ek2, dk2) = ML_KEM_512.keygen(seed2) ``` Encapsulation keys, decapsulation keys, seeds, and ciphertexts can all be serialized by accessing them as `bytes`, and deserialized by initializing them with the appropriate size bytes object. A serialization example: ``` from fips203 import ML_KEM_768 seed = Seed() (ek,dk) = ML_KEM_768.keygen(seed) with open('encapskey.bin', 'wb') as f: f.write(bytes(ek)) with open('decapskey.bin', 'wb') as f: f.write(bytes(dk)) with open('seed.bin', 'wb') as f: f.write(bytes(seed) ``` A deserialization example, followed by use: ``` import fips203 with open('encapskey.bin', 'b') as f: ekdata = f.read() ek = fips203.EncapsulationKey(ekdata) (ct, ss) = ek.Encaps() ``` The expected sizes (in bytes) of the different objects in each parameter set can be accessed with `EK_SIZE`, `DK_SIZE`, `CT_SIZE`, `SEED_SIZE`, and `SS_SIZE`: ``` from fips203 import ML_KEM_768 print(f"ML-KEM-768 Ciphertext size (in bytes) is {ML_KEM_768.CT_SIZE}") ``` ## Implementation Notes This is a wrapper around libfips203, built from the Rust fips203-ffi crate. If that library is not installed in the expected path for libraries on your system, any attempt to use this module will fail. This module should have reasonable type annotations and docstrings for the public interface. If you discover a problem with type annotations, or see a way that this kind of documentation could be improved, please report it! ## See Also - https://doi.org/10.6028/NIST.FIPS.203.ipd - https://github.com/integritychain/fips203 ## Bug Reporting Please report issues at https://github.com/integritychain/fips203/issues ''' from __future__ import annotations '''__version__ should track package.version from ../Cargo.toml''' __version__ = '0.4.1' __author__ = 'Daniel Kahn Gillmor ' __all__ = [ 'ML_KEM_512', 'ML_KEM_768', 'ML_KEM_1024', 'Ciphertext', 'EncapsulationKey', 'DecapsulationKey', 'Seed', ] import ctypes import ctypes.util import enum import secrets from typing import Tuple, Dict, Any, Union, Optional from abc import ABC import sys class _SharedSecret(ctypes.Structure): _fields_ = [('data', ctypes.c_uint8 * 32)] class _Seed(ctypes.Structure): _fields_ = [('data', ctypes.c_uint8 * 64)] class Err(enum.IntEnum): OK = 0 NULL_PTR_ERROR = 1 SERIALIZATION_ERROR = 2 DESERIALIZATION_ERROR = 3 KEYGEN_ERROR = 4 ENCAPSULATION_ERROR = 5 DECAPSULATION_ERROR = 6 class Seed(): '''ML-KEM Seed This seed can be used to generate an ML-KEM keypair ''' def __init__(self, data: Optional[bytes] = None) -> None: '''If initialized with None, the seed will be randomly populated.''' self._seed = _Seed() if data is None: # FIXME: perhaps use ml_kem_populate_seed instead? data = secrets.token_bytes(len(self._seed.data)) if len(data) != len(self._seed.data): raise ValueError(f"Expected {len(self._seed.data)} bytes, " f"got {len(data)}.") for i in range(len(data)): self._seed.data[i] = data[i] def __repr__(self) -> str: return '' def __bytes__(self) -> bytes: return bytes(self._seed.data) def keygen(self, strength: int) -> Tuple[EncapsulationKey, DecapsulationKey]: for kt in ML_KEM_512, ML_KEM_768, ML_KEM_1024: if kt._strength == strength: return kt.keygen(self) raise Exception(f"Unknown strength: {strength}, must be 512, 768, or 1024.") class Ciphertext(): '''ML-KEM Ciphertext Serialize this object by asking for it as `bytes`. You can convert it to a 32-byte shared secret by passing it to the Decaps() function of the appropriate Decapsulation Key. ''' def __init__(self, data: Union[bytes, int]) -> None: '''Create ML-KEM Ciphertext from bytes (or strength level).''' if isinstance(data, bytes): self._strength = _ML_KEM.strength_from_length('CT_SIZE', len(data)) elif isinstance(data, int): self._strength = data else: raise Exception("Initialize ML-KEM Ciphertext object with " f"bytes or a strength level, not {type(data)}") self._ffi = _ML_KEM.strength(self._strength) self._ct = self._ffi['Ciphertext']() if isinstance(data, bytes): self._set(data) def __repr__(self) -> str: return f'' def __bytes__(self) -> bytes: return bytes(self._ct.data) def _set(self, data: bytes) -> None: if len(data) != len(self._ct.data): raise ValueError(f"Expected {len(self._ct.data)} bytes, " f"got {len(data)}") for i in range(len(data)): self._ct.data[i] = data[i] class EncapsulationKey(): '''ML-KEM Encapsulation Key Serialize this object by asking for it as `bytes`. Produce a Ciphertext and a 32-byte shared secret by invoking Encaps() on it. ''' def __init__(self, data: Union[bytes, int]) -> None: '''Create ML-KEM Encapsulation Key from bytes (or strength level).''' if isinstance(data, bytes): self._strength = _ML_KEM.strength_from_length('EK_SIZE', len(data)) elif isinstance(data, int): self._strength = data else: raise Exception("Initialize ML-KEM Encapsulation Key with " f"bytes or a strength level, not {type(data)}") self._ffi = _ML_KEM.strength(self._strength) self._ek = self._ffi['EncapsKey']() if isinstance(data, bytes): self._set(data) def __repr__(self) -> str: return f'' def __bytes__(self) -> bytes: return bytes(self._ek.data) def _set(self, data: bytes) -> None: if len(data) != len(self._ek.data): raise ValueError(f"Expected {len(self._ek.data)} bytes, " f"got {len(data)}") for i in range(len(data)): self._ek.data[i] = data[i] def encaps(self) -> Tuple[Ciphertext, bytes]: '''Produce a new Ciphertext and corresponding 32-byte shared secret.''' ct = Ciphertext(self._strength) ss = _SharedSecret() ret = Err(self._ffi['encaps'](ctypes.byref(self._ek), ctypes.byref(ct._ct), ctypes.byref(ss))) if ret is not Err.OK: raise Exception(f"ml_kem_{self._strength}_encaps() " f"returned {ret} ({ret.name})") return (ct, bytes(ss.data)) class DecapsulationKey(): '''ML-KEM Decapsulation Key Serialize this object by asking for it as `bytes`. Produce a 32-byte shared secret from a Ciphertext by invoking Decaps() on it. ''' def __init__(self, data: Union[bytes, int]) -> None: '''Create ML-KEM Decapsulation Key from bytes (or strength level).''' if isinstance(data, bytes): self._strength = _ML_KEM.strength_from_length('DK_SIZE', len(data)) elif isinstance(data, int): self._strength = data else: raise Exception("Initialize ML-KEM Encapsulation Key with bytes " f"or a strength level, not {type(data)}") self._ffi = _ML_KEM.strength(self._strength) self._dk = self._ffi['DecapsKey']() if isinstance(data, bytes): self._set(data) def __repr__(self) -> str: return f'' def __bytes__(self) -> bytes: return bytes(self._dk.data) def _set(self, data: bytes) -> None: if len(data) != len(self._dk.data): raise ValueError(f"Expected {len(self._dk.data)} bytes, " f"got {len(data)}") for i in range(len(data)): self._dk.data[i] = data[i] def decaps(self, ct: Ciphertext) -> bytes: '''Get 32-byte shared secret corresponding to the given Ciphertext.''' if self._strength != ct._strength: raise Exception(f"Cannot decapsulate {ct} with {self}") ss = _SharedSecret() ret = Err(self._ffi['decaps'](ctypes.byref(self._dk), ctypes.byref(ct._ct), ctypes.byref(ss))) if ret is not Err.OK: raise Exception(f"ml_kem_{self._strength}_decaps() " f"returned {ret} ({ret.name})") return bytes(ss.data) class _ML_KEM(): params: Dict[int, Dict[str, int]] = { 512: { 'EK_SIZE': 800, 'DK_SIZE': 1632, 'CT_SIZE': 768, }, 768: { 'EK_SIZE': 1184, 'DK_SIZE': 2400, 'CT_SIZE': 1088, }, 1024: { 'EK_SIZE': 1568, 'DK_SIZE': 3168, 'CT_SIZE': 1568, }, } lib = ctypes.CDLL(ctypes.util.find_library('fips203')) if not hasattr(lib, 'ml_kem_512_keygen'): if sys.platform == 'darwin': lib = ctypes.CDLL('../../target/debug/libfips203.dylib') else: lib = ctypes.CDLL("../../target/debug/libfips203.so") # use Any below because i don't know how to specify the type of the FuncPtr ffi: Dict[int, Dict[str, Any]] = {} @classmethod def strength(cls, level: int) -> Dict[str, Any]: if level not in cls.ffi: class _EncapsKey(ctypes.Structure): _fields_ = [('data', ctypes.c_uint8 * cls.params[level]['EK_SIZE'])] class _DecapsKey(ctypes.Structure): _fields_ = [('data', ctypes.c_uint8 * cls.params[level]['DK_SIZE'])] class _Ciphertext(ctypes.Structure): _fields_ = [('data', ctypes.c_uint8 * cls.params[level]['CT_SIZE'])] ffi: Dict[str, Any] = {} ffi['keygen'] = cls.lib[f'ml_kem_{level}_keygen'] ffi['keygen'].argtypes = [ctypes.POINTER(_EncapsKey), ctypes.POINTER(_DecapsKey)] ffi['keygen'].restype = ctypes.c_uint8 ffi['keygen_from_seed'] = cls.lib[f'ml_kem_{level}_keygen_from_seed'] ffi['keygen_from_seed'].argtypes = [ctypes.POINTER(_Seed), ctypes.POINTER(_EncapsKey), ctypes.POINTER(_DecapsKey)] ffi['keygen_from_seed'].restype = ctypes.c_uint8 ffi['encaps'] = cls.lib[f'ml_kem_{level}_encaps'] ffi['encaps'].argtypes = [ctypes.POINTER(_EncapsKey), ctypes.POINTER(_Ciphertext), ctypes.POINTER(_SharedSecret)] ffi['encaps'].restype = ctypes.c_uint8 ffi['decaps'] = cls.lib[f'ml_kem_{level}_decaps'] ffi['decaps'].argtypes = [ctypes.POINTER(_DecapsKey), ctypes.POINTER(_Ciphertext), ctypes.POINTER(_SharedSecret)] ffi['decaps'].restype = ctypes.c_uint8 ffi['EncapsKey'] = _EncapsKey ffi['DecapsKey'] = _DecapsKey ffi['Ciphertext'] = _Ciphertext cls.ffi[level] = ffi return cls.ffi[level] @classmethod def strength_from_length(cls, object_type: str, object_len: int) -> int: for strength in cls.params: if cls.params[strength][object_type] == object_len: return strength raise Exception(f"No ML-KEM parameter set has {object_type} " f"of {object_len} bytes") @classmethod def _keygen(cls, strength: int) -> Tuple[EncapsulationKey, DecapsulationKey]: ek = EncapsulationKey(strength) dk = DecapsulationKey(strength) ret = Err(cls.strength(strength)['keygen'](ctypes.byref(ek._ek), ctypes.byref(dk._dk))) if ret is not Err.OK: raise Exception(f"ml_kem_{strength}_keygen() returned " f"{ret} ({ret.name})") return (ek, dk) @classmethod def _keygen_from_seed(cls, strength: int, seed: Seed) -> Tuple[EncapsulationKey, DecapsulationKey]: ek = EncapsulationKey(strength) dk = DecapsulationKey(strength) ret = Err(cls.strength(strength)['keygen_from_seed']( ctypes.byref(seed._seed), ctypes.byref(ek._ek), ctypes.byref(dk._dk) )) if ret is not Err.OK: raise Exception(f"ml_kem_{strength}_keygen() returned " f"{ret} ({ret.name})") return (ek, dk) class ML_KEM(ABC): '''Abstract base class for all ML-KEM (FIPS 203) parameter sets.''' _strength: int EK_SIZE: int DK_SIZE: int CT_SIZE: int SS_SIZE: int = 32 SEED_SIZE: int = 64 @classmethod def keygen(cls, seed: Optional[Seed] = None) -> Tuple[EncapsulationKey, DecapsulationKey]: '''Generate a pair of Encapsulation and Decapsulation Keys. If a Seed is supplied, do a deterministic generation from the seed. Otherwise, randomly generate the key.''' if seed is None: return _ML_KEM._keygen(cls._strength) else: return _ML_KEM._keygen_from_seed(cls._strength, seed) class ML_KEM_512(ML_KEM): '''ML-KEM-512 (FIPS 203) Implementation.''' _strength: int = 512 EK_SIZE: int = 800 DK_SIZE: int = 1632 CT_SIZE: int = 768 class ML_KEM_768(ML_KEM): '''ML-KEM-768 (FIPS 203) Implementation.''' _strength: int = 768 EK_SIZE: int = 1184 DK_SIZE: int = 2400 CT_SIZE: int = 1088 class ML_KEM_1024(ML_KEM): '''ML-KEM-1024 (FIPS 203) Implementation.''' _strength: int = 1024 EK_SIZE: int = 1568 DK_SIZE: int = 3168 CT_SIZE: int = 1568 fips203-ffi-0.4.3/python/pyproject.toml000064400000000000000000000032371046102023000160220ustar 00000000000000[build-system] requires = ["setuptools >= 61.0"] build-backend = "setuptools.build_meta" [project] name = "fips203" dynamic = ["version"] description = "ML-KEM (FIPS203) -- asymmetric, quantum-secure encryption" authors = [{name = "Daniel Kahn Gillmor", email = "dkg@fifthhorseman.net"}] keywords = [ "cryptography", "encryption", "FIPS", "FIPS203", "KEM", "lattice", "module-lattice", "post-quantum", ] # README.md duplicates the module docstring. # I do not know how to keep them automatically in sync readme = "README.md" # only dependencies are having the libfips203 shared object available # everything else is from the stdlib dependencies = [] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Telecommunications Industry", "Intended Audience :: Information Technology", "Topic :: Security :: Cryptography", "License :: OSI Approved :: MIT License", # Apache 2.0 license is not in the list of known classifiers # So the dual licensing of this module is not adequately represented # "License :: OSI Approved :: Apache 2.0 License", "License :: DFSG approved", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] [project.urls] Homepage = "https://github.com/integritychain/fips203/tree/main/ffi/python" Repository = "https://github.com/integritychain/fips203.git" Issues = "https://github.com/integritychain/fips203/issues" [tool.setuptools.dynamic] version = {attr = "fips203.__version__"} fips203-ffi-0.4.3/python/test/nist/keygen.py000075500000000000000000000047201046102023000166770ustar 00000000000000#!/usr/bin/python3 """Tests for fips203 python module From the ffi/python/ directory, do: PYTHONPATH=. test/nist/keygen.py """ from __future__ import annotations import fips203 import json import re from binascii import a2b_hex, b2a_hex from typing import Dict, Union, List, TypedDict with open( "../../tests/nist_vectors/ML-KEM-keyGen-FIPS203/internalProjection.json" ) as f: t = json.load(f) assert t["vsId"] == 42 assert t["algorithm"] == "ML-KEM" assert t["mode"] == "keyGen" assert t["revision"] == "FIPS203" assert t["isSample"] == False class KeyGenTestData(TypedDict): tcId: int deferred: bool z: str d: str ek: str dk: str class KeyGenTest: def __init__(self, data: KeyGenTestData): self.tcId = data["tcId"] self.deferred = data["deferred"] self.d = a2b_hex(data["d"]) self.z = a2b_hex(data["z"]) self.ek = a2b_hex(data["ek"]) self.dk = a2b_hex(data["dk"]) def run(self, group: TestGroup) -> None: seed = fips203.Seed(self.d + self.z) (ek, dk) = seed.keygen(group.strength) if bytes(ek) != self.ek: raise Exception( f"""test {self.tcId} (group {group.tgId}, str: {group.strength}) ek failed: got: {b2a_hex(bytes(ek))} wanted: {b2a_hex(self.ek)}""" ) if bytes(dk) != self.dk: raise Exception( f"""test {self.tcId} (group {group.tgId}, str: {group.strength}) dk failed: got: {b2a_hex(bytes(dk))} wanted: {b2a_hex(self.dk)}""" ) class TestGroupData(TypedDict): tgId: int testType: str parameterSet: str tests: List[KeyGenTestData] class TestGroup: param_matcher = re.compile("^ML-KEM-(?P512|768|1024)$") def __init__(self, d: TestGroupData) -> None: self.tgId: int = d["tgId"] self.testType: str = d["testType"] assert self.testType == "AFT" # i don't know what AFT means self.parameterSet: str = d["parameterSet"] m = self.param_matcher.match(self.parameterSet) assert m self.strength: int = int(m["strength"]) self.tests: List[KeyGenTest] = [] for t in d["tests"]: self.tests.append(KeyGenTest(t)) def run(self) -> None: for t in self.tests: t.run(self) groups: List[TestGroup] = [] for g in t["testGroups"]: groups.append(TestGroup(g)) for g in groups: g.run() fips203-ffi-0.4.3/src/.rustfmt.toml000064400000000000000000000000361046102023000150250ustar 00000000000000fn_params_layout = "Vertical" fips203-ffi-0.4.3/src/lib.rs000064400000000000000000000236121046102023000134670ustar 00000000000000use rand_core::{OsRng, RngCore}; #[repr(C)] pub struct ml_kem_shared_secret { data: [u8; fips203::SSK_LEN], } #[repr(C)] pub struct ml_kem_seed { data: [u8; 64], } pub const ML_KEM_OK: u8 = 0; pub const ML_KEM_NULL_PTR_ERROR: u8 = 1; pub const ML_KEM_SERIALIZATION_ERROR: u8 = 2; pub const ML_KEM_DESERIALIZATION_ERROR: u8 = 3; pub const ML_KEM_KEYGEN_ERROR: u8 = 4; pub const ML_KEM_ENCAPSULATION_ERROR: u8 = 5; pub const ML_KEM_DECAPSULATION_ERROR: u8 = 6; #[no_mangle] pub extern "C" fn ml_kem_populate_seed(seed_out: Option<&mut ml_kem_seed>) -> u8 { let Some(seed_out) = seed_out else { return ML_KEM_NULL_PTR_ERROR; }; OsRng.fill_bytes(&mut seed_out.data); ML_KEM_OK } // ML-KEM-512 #[repr(C)] pub struct ml_kem_512_encaps_key { data: [u8; fips203::ml_kem_512::EK_LEN], } #[repr(C)] pub struct ml_kem_512_decaps_key { data: [u8; fips203::ml_kem_512::DK_LEN], } #[repr(C)] pub struct ml_kem_512_ciphertext { data: [u8; fips203::ml_kem_512::CT_LEN], } #[no_mangle] pub extern "C" fn ml_kem_512_keygen( encaps_out: Option<&mut ml_kem_512_encaps_key>, decaps_out: Option<&mut ml_kem_512_decaps_key>, ) -> u8 { use fips203::traits::{KeyGen, SerDes}; let (Some(encaps_out), Some(decaps_out)) = (encaps_out, decaps_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok((ek, dk)) = fips203::ml_kem_512::KG::try_keygen() else { return ML_KEM_KEYGEN_ERROR; }; encaps_out.data = ek.into_bytes(); decaps_out.data = dk.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_512_keygen_from_seed( seed: Option<&ml_kem_seed>, encaps_out: Option<&mut ml_kem_512_encaps_key>, decaps_out: Option<&mut ml_kem_512_decaps_key>, ) -> u8 { use fips203::traits::{KeyGen, SerDes}; let (Some(encaps_out), Some(decaps_out), Some(seed)) = (encaps_out, decaps_out, seed) else { return ML_KEM_NULL_PTR_ERROR; }; let (ek, dk) = fips203::ml_kem_512::KG::keygen_from_seed( seed.data[0..32].try_into().unwrap(), seed.data[32..64].try_into().unwrap(), ); encaps_out.data = ek.into_bytes(); decaps_out.data = dk.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_512_encaps( encaps: Option<&ml_kem_512_encaps_key>, ciphertext_out: Option<&mut ml_kem_512_ciphertext>, shared_secret_out: Option<&mut ml_kem_shared_secret>, ) -> u8 { use fips203::traits::{Encaps, SerDes}; let (Some(encaps), Some(ciphertext_out), Some(shared_secret_out)) = (encaps, ciphertext_out, shared_secret_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok(ek) = fips203::ml_kem_512::EncapsKey::try_from_bytes(encaps.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok((ssk, ct)) = ek.try_encaps() else { return ML_KEM_ENCAPSULATION_ERROR; }; shared_secret_out.data = ssk.into_bytes(); ciphertext_out.data = ct.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_512_decaps( decaps: Option<&ml_kem_512_decaps_key>, ciphertext: Option<&ml_kem_512_ciphertext>, shared_secret_out: Option<&mut ml_kem_shared_secret>, ) -> u8 { use fips203::traits::{Decaps, SerDes}; let (Some(decaps), Some(ciphertext), Some(shared_secret_out)) = (decaps, ciphertext, shared_secret_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok(dk) = fips203::ml_kem_512::DecapsKey::try_from_bytes(decaps.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok(ct) = fips203::ml_kem_512::CipherText::try_from_bytes(ciphertext.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok(ssk) = dk.try_decaps(&ct) else { return ML_KEM_DECAPSULATION_ERROR; }; shared_secret_out.data = ssk.into_bytes(); ML_KEM_OK } // ML-KEM-768 #[repr(C)] pub struct ml_kem_768_encaps_key { data: [u8; fips203::ml_kem_768::EK_LEN], } #[repr(C)] pub struct ml_kem_768_decaps_key { data: [u8; fips203::ml_kem_768::DK_LEN], } #[repr(C)] pub struct ml_kem_768_ciphertext { data: [u8; fips203::ml_kem_768::CT_LEN], } #[no_mangle] pub extern "C" fn ml_kem_768_keygen( encaps_out: Option<&mut ml_kem_768_encaps_key>, decaps_out: Option<&mut ml_kem_768_decaps_key>, ) -> u8 { use fips203::traits::{KeyGen, SerDes}; let (Some(encaps_out), Some(decaps_out)) = (encaps_out, decaps_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok((ek, dk)) = fips203::ml_kem_768::KG::try_keygen() else { return ML_KEM_KEYGEN_ERROR; }; encaps_out.data = ek.into_bytes(); decaps_out.data = dk.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_768_keygen_from_seed( seed: Option<&ml_kem_seed>, encaps_out: Option<&mut ml_kem_768_encaps_key>, decaps_out: Option<&mut ml_kem_768_decaps_key>, ) -> u8 { use fips203::traits::{KeyGen, SerDes}; let (Some(encaps_out), Some(decaps_out), Some(seed)) = (encaps_out, decaps_out, seed) else { return ML_KEM_NULL_PTR_ERROR; }; let (ek, dk) = fips203::ml_kem_768::KG::keygen_from_seed( seed.data[0..32].try_into().unwrap(), seed.data[32..64].try_into().unwrap(), ); encaps_out.data = ek.into_bytes(); decaps_out.data = dk.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_768_encaps( encaps: Option<&ml_kem_768_encaps_key>, ciphertext_out: Option<&mut ml_kem_768_ciphertext>, shared_secret_out: Option<&mut ml_kem_shared_secret>, ) -> u8 { use fips203::traits::{Encaps, SerDes}; let (Some(encaps), Some(ciphertext_out), Some(shared_secret_out)) = (encaps, ciphertext_out, shared_secret_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok(ek) = fips203::ml_kem_768::EncapsKey::try_from_bytes(encaps.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok((ssk, ct)) = ek.try_encaps() else { return ML_KEM_ENCAPSULATION_ERROR; }; shared_secret_out.data = ssk.into_bytes(); ciphertext_out.data = ct.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_768_decaps( decaps: Option<&ml_kem_768_decaps_key>, ciphertext: Option<&ml_kem_768_ciphertext>, shared_secret_out: Option<&mut ml_kem_shared_secret>, ) -> u8 { use fips203::traits::{Decaps, SerDes}; let (Some(decaps), Some(ciphertext), Some(shared_secret_out)) = (decaps, ciphertext, shared_secret_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok(dk) = fips203::ml_kem_768::DecapsKey::try_from_bytes(decaps.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok(ct) = fips203::ml_kem_768::CipherText::try_from_bytes(ciphertext.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok(ssk) = dk.try_decaps(&ct) else { return ML_KEM_DECAPSULATION_ERROR; }; shared_secret_out.data = ssk.into_bytes(); ML_KEM_OK } // ML-KEM-1024 #[repr(C)] pub struct ml_kem_1024_encaps_key { data: [u8; fips203::ml_kem_1024::EK_LEN], } #[repr(C)] pub struct ml_kem_1024_decaps_key { data: [u8; fips203::ml_kem_1024::DK_LEN], } #[repr(C)] pub struct ml_kem_1024_ciphertext { data: [u8; fips203::ml_kem_1024::CT_LEN], } #[no_mangle] pub extern "C" fn ml_kem_1024_keygen( encaps_out: Option<&mut ml_kem_1024_encaps_key>, decaps_out: Option<&mut ml_kem_1024_decaps_key>, ) -> u8 { use fips203::traits::{KeyGen, SerDes}; let (Some(encaps_out), Some(decaps_out)) = (encaps_out, decaps_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok((ek, dk)) = fips203::ml_kem_1024::KG::try_keygen() else { return ML_KEM_KEYGEN_ERROR; }; encaps_out.data = ek.into_bytes(); decaps_out.data = dk.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_1024_keygen_from_seed( seed: Option<&ml_kem_seed>, encaps_out: Option<&mut ml_kem_1024_encaps_key>, decaps_out: Option<&mut ml_kem_1024_decaps_key>, ) -> u8 { use fips203::traits::{KeyGen, SerDes}; let (Some(encaps_out), Some(decaps_out), Some(seed)) = (encaps_out, decaps_out, seed) else { return ML_KEM_NULL_PTR_ERROR; }; let (ek, dk) = fips203::ml_kem_1024::KG::keygen_from_seed( seed.data[0..32].try_into().unwrap(), seed.data[32..64].try_into().unwrap(), ); encaps_out.data = ek.into_bytes(); decaps_out.data = dk.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_1024_encaps( encaps: Option<&ml_kem_1024_encaps_key>, ciphertext_out: Option<&mut ml_kem_1024_ciphertext>, shared_secret_out: Option<&mut ml_kem_shared_secret>, ) -> u8 { use fips203::traits::{Encaps, SerDes}; let (Some(encaps), Some(ciphertext_out), Some(shared_secret_out)) = (encaps, ciphertext_out, shared_secret_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok(ek) = fips203::ml_kem_1024::EncapsKey::try_from_bytes(encaps.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok((ssk, ct)) = ek.try_encaps() else { return ML_KEM_ENCAPSULATION_ERROR; }; shared_secret_out.data = ssk.into_bytes(); ciphertext_out.data = ct.into_bytes(); ML_KEM_OK } #[no_mangle] pub extern "C" fn ml_kem_1024_decaps( decaps: Option<&ml_kem_1024_decaps_key>, ciphertext: Option<&ml_kem_1024_ciphertext>, shared_secret_out: Option<&mut ml_kem_shared_secret>, ) -> u8 { use fips203::traits::{Decaps, SerDes}; let (Some(decaps), Some(ciphertext), Some(shared_secret_out)) = (decaps, ciphertext, shared_secret_out) else { return ML_KEM_NULL_PTR_ERROR; }; let Ok(dk) = fips203::ml_kem_1024::DecapsKey::try_from_bytes(decaps.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok(ct) = fips203::ml_kem_1024::CipherText::try_from_bytes(ciphertext.data) else { return ML_KEM_DESERIALIZATION_ERROR; }; let Ok(ssk) = dk.try_decaps(&ct) else { return ML_KEM_DECAPSULATION_ERROR; }; shared_secret_out.data = ssk.into_bytes(); ML_KEM_OK } fips203-ffi-0.4.3/tests/Makefile000064400000000000000000000027051046102023000143660ustar 00000000000000#!/usr/bin/make -f # Usage: (cargo build && cd tests && make) # If the library and its development headers are installed system-wide, # run the tests with: # # (cd tests && make AS_INSTALLED=true) SIZES = 512 768 1024 FRAMES = encaps_key decaps_key ciphertext encaps decaps keygen keygen_from_seed # should derive SONAME somehow, e.g. from CARGO_PKG_VERSION_MAJOR SONAME = libfips203.$(SO_EXT) OS := $(shell uname) CXXFLAGS += -DLINUX ifeq ($(OS), Darwin) SO_EXT = dylib else SO_EXT = so endif all: check # adjustments for testing the local debug or release build: ifneq ($(AS_INSTALLED),true) SO_LOCATIONS = $(foreach w,.. ../..,$(foreach x,release debug,$w/target/$x $w/target/*/$x)) SO_LOCATION = $(dir $(firstword $(foreach d,$(SO_LOCATIONS),$(wildcard $d/libfips203.$(SO_EXT))))) COMPILE_FLAGS = -Wl,-rpath,$(SO_LOCATION) -L $(SO_LOCATION) -I.. RUN_PREFIX = LD_LIBRARY_PATH=$(SO_LOCATION) #ADDITIONAL_RUN_DEPENDS = $(SO_LOCATION)libfips203.so.$(SONAME) ADDITIONAL_RUN_DEPENDS = $(SO_LOCATION)$(SONAME) endif BASELINES=$(foreach sz, $(SIZES), baseline-$(sz)) CHECKS=$(foreach sz, $(SIZES), runtest-$(sz)) check: $(CHECKS) runtest-%: baseline-% $(ADDITIONAL_RUN_DEPENDS) $(RUN_PREFIX) ./$< baseline-%: baseline.c ../fips203.h $(CC) -o $@ -g -D MLKEM_size=$* $(foreach v, $(FRAMES),-D MLKEM_$(v)=ml_kem_$*_$(v)) -Werror -Wall -pedantic $< -Wall $(COMPILE_FLAGS) -lfips203 clean: rm -f $(BASELINES) $(ADDITIONAL_RUN_DEPENDS) .PHONY: clean check all fips203-ffi-0.4.3/tests/baseline.c000064400000000000000000000076021046102023000146550ustar 00000000000000#include #include #include int main(int argc, const char **argv) { MLKEM_encaps_key encaps; MLKEM_decaps_key decaps; MLKEM_encaps_key encaps_2; MLKEM_decaps_key decaps_2; MLKEM_ciphertext ct; ml_kem_shared_secret ssk_a; ml_kem_shared_secret ssk_b; ml_kem_seed seed; ml_kem_err err; MLKEM_encaps_key encaps_weird; MLKEM_decaps_key decaps_weird; memset (&seed, 0, sizeof(seed)); /* ensure that seed-based generation is deterministic */ if (MLKEM_keygen_from_seed (&seed, &encaps, &decaps)) return 1; if (MLKEM_keygen_from_seed (&seed, &encaps_2, &decaps_2)) return 1; if (memcmp(&encaps, &encaps_2, sizeof(encaps))) { fprintf (stderr, "encaps keys generated by seed did not match\n"); return 5; } if (memcmp(&decaps, &decaps_2, sizeof(decaps))) { fprintf (stderr, "decaps keys generated by seed did not match\n"); return 6; } if (MLKEM_keygen (&encaps, &decaps)) return 1; printf("Encaps (%d): ", MLKEM_size); for (int n = 0; n < sizeof(encaps.data); n++) printf ("%02x ", encaps.data[n]); printf("\n"); printf("Decaps (%d): ", MLKEM_size); for (int n = 0; n < sizeof(decaps.data); n++) printf ("%02x ", decaps.data[n]); printf("\n"); if (MLKEM_encaps (&encaps, &ct, &ssk_a)) return 2; printf("Ciphertext (%d): ", MLKEM_size); for (int n = 0; n < sizeof(ct.data); n++) printf ("%02x ", ct.data[n]); printf("\n"); printf("Shared Secret A: "); for (int n = 0; n < sizeof(ssk_a.data); n++) printf ("%02x ", ssk_a.data[n]); printf("\n"); if (MLKEM_decaps (&decaps, &ct, &ssk_b)) return 3; printf("Shared Secret B: "); for (int n = 0; n < sizeof(ssk_b.data); n++) printf ("%02x ", ssk_b.data[n]); printf("\n"); if (! MLKEM_keygen (&encaps, NULL)) { fprintf (stderr, "keygen should have failed with NULL decaps\n"); return 1; } if (! MLKEM_keygen (NULL, &decaps)) { fprintf (stderr, "keygen should have failed with NULL encaps\n"); return 1; } if (! MLKEM_keygen (NULL, NULL)) { fprintf (stderr, "keygen should have failed with NULL encaps and decaps\n"); return 1; } if (! MLKEM_encaps (&encaps, &ct, NULL)) { fprintf (stderr, "encaps should have failed with NULL shared_secret_out\n"); return 1; } if (! MLKEM_encaps (&encaps, NULL, &ssk_a)) { fprintf (stderr, "encaps should have failed with NULL ciphertext_out\n"); return 1; } if (! MLKEM_encaps (NULL, &ct, &ssk_a)) { fprintf (stderr, "encaps should have failed with NULL encaps_key\n"); return 1; } if (! MLKEM_encaps (NULL, NULL, NULL)) { fprintf (stderr, "encaps should have failed with NULL arguments\n"); return 1; } if (! MLKEM_decaps (&decaps, &ct, NULL)) { fprintf (stderr, "decaps should have failed with NULL shared_secret_out\n"); return 1; } if (! MLKEM_decaps (&decaps, NULL, &ssk_b)) { fprintf (stderr, "decaps should have failed with NULL ciphertext\n"); return 1; } if (! MLKEM_decaps (NULL, &ct, &ssk_b)) { fprintf (stderr, "decaps should have failed with NULL decaps_key\n"); return 1; } if (! MLKEM_decaps (NULL, NULL, NULL)) { fprintf (stderr, "decaps should have failed with NULL arguments\n"); return 1; } for (int i = 0; i < sizeof(encaps_weird.data); i++) encaps_weird.data[i] = 0xff; err = MLKEM_encaps (&encaps_weird, &ct, &ssk_a); if (err != ML_KEM_DESERIALIZATION_ERROR) { fprintf (stderr, "encaps against an encaps_key of all 0xff octets should have failed with deserialization error, got %d\n", err); return 1; } for (int i = 0; i < sizeof(decaps_weird.data); i++) decaps_weird.data[i] = 0xff; err = MLKEM_decaps (&decaps_weird, &ct, &ssk_a); if (err != ML_KEM_DESERIALIZATION_ERROR) { fprintf (stderr, "decaps against a tampered decaps_key should have failed with deserialization error, got %d\n", err); return 1; } return 0; }