cypheron_core/kem/
ml_kem_768.rs

1// Copyright 2025 Cypheron Labs, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::kem::sizes;
16use crate::kem::{Kem, KemVariant};
17
18use secrecy::{ExposeSecret, SecretBox};
19use thiserror::Error;
20use zeroize::Zeroize;
21
22#[cfg(not(rust_analyzer))]
23#[allow(non_camel_case_types)]
24#[allow(non_snake_case)]
25#[allow(non_upper_case_globals)]
26mod bindings {
27    include!(concat!(env!("OUT_DIR"), "/ml_kem_768_bindings.rs"));
28}
29use bindings::*;
30
31pub struct MlKemSecretKey(pub SecretBox<[u8; sizes::ML_KEM_768_SECRET]>);
32
33#[deprecated(
34    since = "0.2.0",
35    note = "Use MlKemSecretKey instead for NIST FIPS 203 compliance"
36)]
37pub type KyberSecretKey = MlKemSecretKey;
38
39#[derive(Clone)]
40pub struct MlKemPublicKey(pub [u8; sizes::ML_KEM_768_PUBLIC]);
41
42#[deprecated(
43    since = "0.2.0",
44    note = "Use MlKemPublicKey instead for NIST FIPS 203 compliance"
45)]
46pub type KyberPublicKey = MlKemPublicKey;
47
48#[derive(Error, Debug)]
49pub enum MlKemError {
50    #[error("Key generation failed - entropy failure (ERROR-KEM-001)\nSee: https://docs.rs/cypheron-core/troubleshooting/errors.html#error-kem-001")]
51    KeyGenerationEntropyFailure,
52    #[error("Key generation failed - internal error (ERROR-KEM-004)\nSee: https://docs.rs/cypheron-core/troubleshooting/errors.html#error-kem-004")]
53    KeyGenerationInternalError,
54    #[error("Encapsulation failed - invalid public key (ERROR-KEM-003)\nSee: https://docs.rs/cypheron-core/troubleshooting/errors.html#error-kem-003")]
55    EncapsulationInvalidKey,
56    #[error("Encapsulation failed - internal error (ERROR-KEM-005)\nSee: https://docs.rs/cypheron-core/troubleshooting/errors.html#error-kem-005")]
57    EncapsulationInternalError,
58    #[error("Decapsulation failed - invalid ciphertext (ERROR-KEM-002)\nSee: https://docs.rs/cypheron-core/troubleshooting/errors.html#error-kem-002")]
59    DecapsulationInvalidCiphertext,
60    #[error("Decapsulation failed - internal error (ERROR-KEM-006)\nSee: https://docs.rs/cypheron-core/troubleshooting/errors.html#error-kem-006")]
61    DecapsulationInternalError,
62    #[error("Invalid ciphertext length: expected {expected}, got {actual}")]
63    InvalidCiphertextLength { expected: usize, actual: usize },
64    #[error("Invalid public key length: expected {expected}, got {actual}")]
65    InvalidPublicKeyLength { expected: usize, actual: usize },
66    #[error("Invalid secret key length: expected {expected}, got {actual}")]
67    InvalidSecretKeyLength { expected: usize, actual: usize },
68    #[error("ML-KEM C library returned error code: {code}")]
69    CLibraryError { code: i32 },
70}
71
72#[deprecated(
73    since = "0.2.0",
74    note = "Use MlKemError instead for NIST FIPS 203 compliance"
75)]
76pub type KyberError = MlKemError;
77
78impl MlKemError {
79    pub fn from_c_code(code: i32, operation: &str) -> Self {
80        match code {
81            0 => panic!("Should not map success code 0 to error"),
82            -1 => match operation {
83                "keypair" => MlKemError::KeyGenerationInternalError,
84                "encapsulate" => MlKemError::EncapsulationInternalError,
85                "decapsulate" => MlKemError::DecapsulationInternalError,
86                _ => MlKemError::CLibraryError { code },
87            },
88            -2 => match operation {
89                "keypair" => MlKemError::KeyGenerationEntropyFailure,
90                "encapsulate" => MlKemError::EncapsulationInvalidKey,
91                "decapsulate" => MlKemError::DecapsulationInvalidCiphertext,
92                _ => MlKemError::CLibraryError { code },
93            },
94            _ => MlKemError::CLibraryError { code },
95        }
96    }
97}
98
99pub struct MlKem768;
100
101#[deprecated(
102    since = "0.2.0",
103    note = "Use MlKem768 instead for NIST FIPS 203 compliance"
104)]
105pub type Kyber768 = MlKem768;
106
107impl MlKem768 {
108    pub fn variant() -> KemVariant {
109        KemVariant::MlKem768
110    }
111
112    #[deprecated(
113        since = "0.2.0",
114        note = "Use variant() instead for NIST FIPS 203 compliance"
115    )]
116    pub fn legacy_variant() -> KemVariant {
117        #[allow(deprecated)]
118        KemVariant::Kyber768
119    }
120
121    pub fn expose_shared(secret: &SecretBox<[u8; sizes::ML_KEM_768_SHARED]>) -> &[u8] {
122        secret.expose_secret()
123    }
124}
125
126impl Kem for MlKem768 {
127    type PublicKey = MlKemPublicKey;
128    type SecretKey = MlKemSecretKey;
129    type Ciphertext = Vec<u8>;
130    type SharedSecret = SecretBox<[u8; sizes::ML_KEM_768_SHARED]>;
131    type Error = MlKemError;
132
133    fn keypair() -> Result<(Self::PublicKey, Self::SecretKey), Self::Error> {
134        let mut pk = [0u8; sizes::ML_KEM_768_PUBLIC];
135        let mut sk = [0u8; sizes::ML_KEM_768_SECRET];
136
137        let result = unsafe { pqcrystals_kyber768_ref_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()) };
138
139        if result != 0 {
140            pk.zeroize();
141            sk.zeroize();
142            return Err(MlKemError::from_c_code(result, "keypair"));
143        }
144
145        Ok((
146            MlKemPublicKey(pk),
147            MlKemSecretKey(SecretBox::new(Box::new(sk))),
148        ))
149    }
150
151    fn encapsulate(
152        pk: &Self::PublicKey,
153    ) -> Result<(Self::Ciphertext, Self::SharedSecret), Self::Error> {
154        if pk.0.len() != sizes::ML_KEM_768_PUBLIC {
155            return Err(MlKemError::InvalidPublicKeyLength {
156                expected: sizes::ML_KEM_768_PUBLIC,
157                actual: pk.0.len(),
158            });
159        }
160
161        let mut ct = vec![0u8; sizes::ML_KEM_768_CIPHERTEXT];
162        let mut ss = [0u8; sizes::ML_KEM_768_SHARED];
163
164        let result =
165            unsafe { pqcrystals_kyber768_ref_enc(ct.as_mut_ptr(), ss.as_mut_ptr(), pk.0.as_ptr()) };
166
167        if result != 0 {
168            ss.zeroize();
169            return Err(MlKemError::from_c_code(result, "encapsulate"));
170        }
171
172        Ok((ct, SecretBox::new(ss.into())))
173    }
174
175    fn decapsulate(
176        ct: &Self::Ciphertext,
177        sk: &Self::SecretKey,
178    ) -> Result<Self::SharedSecret, Self::Error> {
179        if ct.len() != sizes::ML_KEM_768_CIPHERTEXT {
180            return Err(MlKemError::InvalidCiphertextLength {
181                expected: sizes::ML_KEM_768_CIPHERTEXT,
182                actual: ct.len(),
183            });
184        }
185        if sk.0.expose_secret().len() != sizes::ML_KEM_768_SECRET {
186            return Err(MlKemError::InvalidSecretKeyLength {
187                expected: sizes::ML_KEM_768_SECRET,
188                actual: sk.0.expose_secret().len(),
189            });
190        }
191
192        let mut ss = [0u8; sizes::ML_KEM_768_SHARED];
193
194        let result = unsafe {
195            pqcrystals_kyber768_ref_dec(ss.as_mut_ptr(), ct.as_ptr(), sk.0.expose_secret().as_ptr())
196        };
197
198        if result != 0 {
199            ss.zeroize();
200            return Err(MlKemError::from_c_code(result, "decapsulate"));
201        }
202
203        Ok(SecretBox::new(ss.into()))
204    }
205}