Coverage Report

Created: 2025-11-16 06:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fips203/src/ml_kem.rs
Line
Count
Source
1
use crate::byte_fns::{byte_decode, byte_encode};
2
use crate::helpers::{g, h, j};
3
use crate::k_pke::{k_pke_decrypt, k_pke_encrypt, k_pke_key_gen};
4
use crate::SharedSecretKey;
5
use rand_core::CryptoRngCore;
6
use subtle::{ConditionallySelectable, ConstantTimeEq};
7
8
9
/// Algorithm 16 `ML-KEM.KeyGen_internal(d,z)` on page 32.
10
/// Uses randomness to generate an encapsulation key and a corresponding decapsulation key.
11
///
12
/// # Parameters
13
/// * `d` - 32-byte random seed for key generation
14
/// * `z` - 32-byte random seed for implicit rejection
15
/// * `ek` - Output buffer for encapsulation key (size: `384·K+32` bytes)
16
/// * `dk` - Output buffer for decapsulation key (size: `768·K+96` bytes)
17
2.14k
pub(crate) fn ml_kem_key_gen_internal<const K: usize, const ETA1_64: usize>(
18
2.14k
    d: [u8; 32], z: [u8; 32], ek: &mut [u8], dk: &mut [u8],
19
2.14k
) {
20
2.14k
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 16: ek len not 384 * K + 32");
21
2.14k
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 16: dk len not 768 * K + 96");
22
23
    // 1: (ek_PKE , dk_PKE) ← K-PKE.KeyGen(𝑑)    ▷ run key generation for K-PKE
24
    // 2: ek ← ek_PKE    ▷ KEM encaps key is just the PKE encryption key
25
2.14k
    let p1 = 384 * K;
26
2.14k
    k_pke_key_gen::<K, ETA1_64>(d, ek, &mut dk[..p1]); // writes ek and first part of dk
27
28
    // 3: dk ← (dk_PKE ‖ ek ‖ H(ek) ‖ 𝑧)    ▷ KEM decaps key includes PKE decryption key
29
2.14k
    let h_ek = h(ek);
30
2.14k
    let p2 = p1 + ek.len();
31
2.14k
    let p3 = p2 + h_ek.len();
32
2.14k
    dk[p1..p2].copy_from_slice(ek);
33
2.14k
    dk[p2..p3].copy_from_slice(&h_ek);
34
2.14k
    dk[p3..].copy_from_slice(&z);
35
36
    // 4: return (ek, dk)
37
2.14k
}
fips203::ml_kem::ml_kem_key_gen_internal::<2, 192>
Line
Count
Source
17
715
pub(crate) fn ml_kem_key_gen_internal<const K: usize, const ETA1_64: usize>(
18
715
    d: [u8; 32], z: [u8; 32], ek: &mut [u8], dk: &mut [u8],
19
715
) {
20
715
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 16: ek len not 384 * K + 32");
21
715
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 16: dk len not 768 * K + 96");
22
23
    // 1: (ek_PKE , dk_PKE) ← K-PKE.KeyGen(𝑑)    ▷ run key generation for K-PKE
24
    // 2: ek ← ek_PKE    ▷ KEM encaps key is just the PKE encryption key
25
715
    let p1 = 384 * K;
26
715
    k_pke_key_gen::<K, ETA1_64>(d, ek, &mut dk[..p1]); // writes ek and first part of dk
27
28
    // 3: dk ← (dk_PKE ‖ ek ‖ H(ek) ‖ 𝑧)    ▷ KEM decaps key includes PKE decryption key
29
715
    let h_ek = h(ek);
30
715
    let p2 = p1 + ek.len();
31
715
    let p3 = p2 + h_ek.len();
32
715
    dk[p1..p2].copy_from_slice(ek);
33
715
    dk[p2..p3].copy_from_slice(&h_ek);
34
715
    dk[p3..].copy_from_slice(&z);
35
36
    // 4: return (ek, dk)
37
715
}
fips203::ml_kem::ml_kem_key_gen_internal::<3, 128>
Line
Count
Source
17
715
pub(crate) fn ml_kem_key_gen_internal<const K: usize, const ETA1_64: usize>(
18
715
    d: [u8; 32], z: [u8; 32], ek: &mut [u8], dk: &mut [u8],
19
715
) {
20
715
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 16: ek len not 384 * K + 32");
21
715
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 16: dk len not 768 * K + 96");
22
23
    // 1: (ek_PKE , dk_PKE) ← K-PKE.KeyGen(𝑑)    ▷ run key generation for K-PKE
24
    // 2: ek ← ek_PKE    ▷ KEM encaps key is just the PKE encryption key
25
715
    let p1 = 384 * K;
26
715
    k_pke_key_gen::<K, ETA1_64>(d, ek, &mut dk[..p1]); // writes ek and first part of dk
27
28
    // 3: dk ← (dk_PKE ‖ ek ‖ H(ek) ‖ 𝑧)    ▷ KEM decaps key includes PKE decryption key
29
715
    let h_ek = h(ek);
30
715
    let p2 = p1 + ek.len();
31
715
    let p3 = p2 + h_ek.len();
32
715
    dk[p1..p2].copy_from_slice(ek);
33
715
    dk[p2..p3].copy_from_slice(&h_ek);
34
715
    dk[p3..].copy_from_slice(&z);
35
36
    // 4: return (ek, dk)
37
715
}
fips203::ml_kem::ml_kem_key_gen_internal::<4, 128>
Line
Count
Source
17
715
pub(crate) fn ml_kem_key_gen_internal<const K: usize, const ETA1_64: usize>(
18
715
    d: [u8; 32], z: [u8; 32], ek: &mut [u8], dk: &mut [u8],
19
715
) {
20
715
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 16: ek len not 384 * K + 32");
21
715
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 16: dk len not 768 * K + 96");
22
23
    // 1: (ek_PKE , dk_PKE) ← K-PKE.KeyGen(𝑑)    ▷ run key generation for K-PKE
24
    // 2: ek ← ek_PKE    ▷ KEM encaps key is just the PKE encryption key
25
715
    let p1 = 384 * K;
26
715
    k_pke_key_gen::<K, ETA1_64>(d, ek, &mut dk[..p1]); // writes ek and first part of dk
27
28
    // 3: dk ← (dk_PKE ‖ ek ‖ H(ek) ‖ 𝑧)    ▷ KEM decaps key includes PKE decryption key
29
715
    let h_ek = h(ek);
30
715
    let p2 = p1 + ek.len();
31
715
    let p3 = p2 + h_ek.len();
32
715
    dk[p1..p2].copy_from_slice(ek);
33
715
    dk[p2..p3].copy_from_slice(&h_ek);
34
715
    dk[p3..].copy_from_slice(&z);
35
36
    // 4: return (ek, dk)
37
715
}
38
39
40
/// Algorithm 17 `ML-KEM.Encaps_internal(ek, m)` on page 33.
41
/// Generates a shared secret key and ciphertext using the encapsulation key and randomness.
42
///
43
/// # Parameters
44
/// * `du`, `dv` - Parameters affecting ciphertext size
45
/// * `m` - 32-byte random input
46
/// * `ek` - Encapsulation key (`384·K+32` bytes)
47
/// * `ct` - Output buffer for ciphertext (`32(du·K+dv)` bytes)
48
///
49
/// # Returns
50
/// * `Ok(SharedSecretKey)` - 32-byte shared secret key
51
/// * `Err(&str)` - Error message if encryption fails
52
1.78k
fn ml_kem_encaps_internal<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
53
1.78k
    du: u32, dv: u32, m: &[u8; 32], ek: &[u8], ct: &mut [u8],
54
1.78k
) -> Result<SharedSecretKey, &'static str> {
55
    // Note: this is only called via ml_kem_encaps() which validates slice sizes and correct decode
56
57
    // 1: (K, r) ← G(m ∥ H(ek))    ▷ derive shared secret key K and randomness r
58
1.78k
    let h_ek = h(ek);
59
1.78k
    let (k, r) = g(&[m, &h_ek]);
60
61
    // 2: c ← K-PKE.Encrypt(ek, m, r)    ▷ encrypt m using K-PKE with randomness r
62
1.78k
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(du, dv, ek, m, &r, ct)?;
63
64
    // 3: return (K, c)  (note: ct is mutable input)
65
1.78k
    Ok(SharedSecretKey(k))
66
1.78k
}
fips203::ml_kem::ml_kem_encaps_internal::<2, 192, 128>
Line
Count
Source
52
514
fn ml_kem_encaps_internal<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
53
514
    du: u32, dv: u32, m: &[u8; 32], ek: &[u8], ct: &mut [u8],
54
514
) -> Result<SharedSecretKey, &'static str> {
55
    // Note: this is only called via ml_kem_encaps() which validates slice sizes and correct decode
56
57
    // 1: (K, r) ← G(m ∥ H(ek))    ▷ derive shared secret key K and randomness r
58
514
    let h_ek = h(ek);
59
514
    let (k, r) = g(&[m, &h_ek]);
60
61
    // 2: c ← K-PKE.Encrypt(ek, m, r)    ▷ encrypt m using K-PKE with randomness r
62
514
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(du, dv, ek, m, &r, ct)?;
63
64
    // 3: return (K, c)  (note: ct is mutable input)
65
514
    Ok(SharedSecretKey(k))
66
514
}
fips203::ml_kem::ml_kem_encaps_internal::<3, 128, 128>
Line
Count
Source
52
514
fn ml_kem_encaps_internal<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
53
514
    du: u32, dv: u32, m: &[u8; 32], ek: &[u8], ct: &mut [u8],
54
514
) -> Result<SharedSecretKey, &'static str> {
55
    // Note: this is only called via ml_kem_encaps() which validates slice sizes and correct decode
56
57
    // 1: (K, r) ← G(m ∥ H(ek))    ▷ derive shared secret key K and randomness r
58
514
    let h_ek = h(ek);
59
514
    let (k, r) = g(&[m, &h_ek]);
60
61
    // 2: c ← K-PKE.Encrypt(ek, m, r)    ▷ encrypt m using K-PKE with randomness r
62
514
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(du, dv, ek, m, &r, ct)?;
63
64
    // 3: return (K, c)  (note: ct is mutable input)
65
514
    Ok(SharedSecretKey(k))
66
514
}
fips203::ml_kem::ml_kem_encaps_internal::<4, 128, 128>
Line
Count
Source
52
754
fn ml_kem_encaps_internal<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
53
754
    du: u32, dv: u32, m: &[u8; 32], ek: &[u8], ct: &mut [u8],
54
754
) -> Result<SharedSecretKey, &'static str> {
55
    // Note: this is only called via ml_kem_encaps() which validates slice sizes and correct decode
56
57
    // 1: (K, r) ← G(m ∥ H(ek))    ▷ derive shared secret key K and randomness r
58
754
    let h_ek = h(ek);
59
754
    let (k, r) = g(&[m, &h_ek]);
60
61
    // 2: c ← K-PKE.Encrypt(ek, m, r)    ▷ encrypt m using K-PKE with randomness r
62
754
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(du, dv, ek, m, &r, ct)?;
63
64
    // 3: return (K, c)  (note: ct is mutable input)
65
754
    Ok(SharedSecretKey(k))
66
754
}
Unexecuted instantiation: fips203::ml_kem::ml_kem_encaps_internal::<_, _, _>
67
68
69
/// Algorithm 18 `ML-KEM.Decaps_internal(dk, c)` on page 34.
70
/// Recovers the shared secret key from a ciphertext using the decapsulation key.
71
/// Includes implicit rejection if the re-encryption check fails.
72
///
73
/// # Parameters
74
/// * `du`, `dv` - Parameters affecting ciphertext size
75
/// * `dk` - Decapsulation key (`768·K+96` bytes)
76
/// * `ct` - Input ciphertext (`32(du·K+dv)` bytes)
77
///
78
/// # Returns
79
/// * `Ok(SharedSecretKey)` - 32-byte shared secret key
80
/// * `Err(&str)` - Error message if decryption fails
81
#[allow(clippy::similar_names)]
82
1.50k
fn ml_kem_decaps_internal<
83
1.50k
    const K: usize,
84
1.50k
    const ETA1_64: usize,
85
1.50k
    const ETA2_64: usize,
86
1.50k
    const J_LEN: usize,
87
1.50k
    const CT_LEN: usize,
88
1.50k
>(
89
1.50k
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
90
1.50k
) -> Result<SharedSecretKey, &'static str> {
91
    // Decapsulation key type check
92
1.50k
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 18: dk len not 768 ...");
93
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
94
    // such, the two above checks are redundant but will be removed in release builds. The are left
95
    // here for A) caution, B) give guardrails for future changes
96
97
    // 1: dk_PKE ← dk[0 : 384·k]    ▷ extract (from KEM decaps key) the PKE decryption key
98
1.50k
    let dk_pke = &dk[0..384 * K];
99
100
    // 2: ek_PKE ← dk[384·k : 768·k + 32]    ▷ extract PKE encryption key
101
1.50k
    let ek_pke = &dk[384 * K..768 * K + 32];
102
103
    // 3: h ← dk[768·k + 32 : 768·k + 64]    ▷ extract hash of PKE encryption key
104
1.50k
    let h = &dk[768 * K + 32..768 * K + 64];
105
106
    // 4: z ← dk[768·k + 64 : 768·k + 96]    ▷ extract implicit rejection value
107
1.50k
    let z = &dk[768 * K + 64..768 * K + 96];
108
109
    // 5: m′ ← K-PKE.Decrypt(dk_PKE,c)
110
1.50k
    let m_prime = k_pke_decrypt::<K>(du, dv, dk_pke, ct)?;
111
112
    // 6: (K′, r′) ← G(m′ ∥ h)
113
1.47k
    let (mut k_prime, r_prime) = g(&[&m_prime, h]);
114
115
    // 7: K̄ ← J(z ∥ c, 32)
116
1.47k
    let k_bar = j(z.try_into().unwrap(), ct);
117
118
    // 8: c′ ← K-PKE.Encrypt(ek_PKE , m′ , r′ )    ▷ re-encrypt using the derived randomness r′
119
1.47k
    let mut c_prime = [0u8; CT_LEN];
120
1.47k
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(
121
1.47k
        du,
122
1.47k
        dv,
123
1.47k
        ek_pke,
124
1.47k
        &m_prime,
125
1.47k
        &r_prime,
126
1.47k
        &mut c_prime[0..ct.len()],
127
0
    )?;
128
129
    // 9:  if 𝑐 ≠ 𝑐 ′ then
130
    // 10:   𝐾 ′ ← 𝐾̄    ▷ if ciphertexts do not match, “implicitly reject”
131
    // 11: end if
132
1.47k
    k_prime.conditional_assign(&k_bar, ct.ct_ne(&c_prime));
133
134
    // 12: return 𝐾 ′
135
1.47k
    Ok(SharedSecretKey(k_prime))
136
1.50k
}
fips203::ml_kem::ml_kem_decaps_internal::<2, 192, 128, 800, 768>
Line
Count
Source
82
514
fn ml_kem_decaps_internal<
83
514
    const K: usize,
84
514
    const ETA1_64: usize,
85
514
    const ETA2_64: usize,
86
514
    const J_LEN: usize,
87
514
    const CT_LEN: usize,
88
514
>(
89
514
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
90
514
) -> Result<SharedSecretKey, &'static str> {
91
    // Decapsulation key type check
92
514
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 18: dk len not 768 ...");
93
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
94
    // such, the two above checks are redundant but will be removed in release builds. The are left
95
    // here for A) caution, B) give guardrails for future changes
96
97
    // 1: dk_PKE ← dk[0 : 384·k]    ▷ extract (from KEM decaps key) the PKE decryption key
98
514
    let dk_pke = &dk[0..384 * K];
99
100
    // 2: ek_PKE ← dk[384·k : 768·k + 32]    ▷ extract PKE encryption key
101
514
    let ek_pke = &dk[384 * K..768 * K + 32];
102
103
    // 3: h ← dk[768·k + 32 : 768·k + 64]    ▷ extract hash of PKE encryption key
104
514
    let h = &dk[768 * K + 32..768 * K + 64];
105
106
    // 4: z ← dk[768·k + 64 : 768·k + 96]    ▷ extract implicit rejection value
107
514
    let z = &dk[768 * K + 64..768 * K + 96];
108
109
    // 5: m′ ← K-PKE.Decrypt(dk_PKE,c)
110
514
    let m_prime = k_pke_decrypt::<K>(du, dv, dk_pke, ct)?;
111
112
    // 6: (K′, r′) ← G(m′ ∥ h)
113
504
    let (mut k_prime, r_prime) = g(&[&m_prime, h]);
114
115
    // 7: K̄ ← J(z ∥ c, 32)
116
504
    let k_bar = j(z.try_into().unwrap(), ct);
117
118
    // 8: c′ ← K-PKE.Encrypt(ek_PKE , m′ , r′ )    ▷ re-encrypt using the derived randomness r′
119
504
    let mut c_prime = [0u8; CT_LEN];
120
504
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(
121
504
        du,
122
504
        dv,
123
504
        ek_pke,
124
504
        &m_prime,
125
504
        &r_prime,
126
504
        &mut c_prime[0..ct.len()],
127
0
    )?;
128
129
    // 9:  if 𝑐 ≠ 𝑐 ′ then
130
    // 10:   𝐾 ′ ← 𝐾̄    ▷ if ciphertexts do not match, “implicitly reject”
131
    // 11: end if
132
504
    k_prime.conditional_assign(&k_bar, ct.ct_ne(&c_prime));
133
134
    // 12: return 𝐾 ′
135
504
    Ok(SharedSecretKey(k_prime))
136
514
}
fips203::ml_kem::ml_kem_decaps_internal::<3, 128, 128, 1120, 1088>
Line
Count
Source
82
504
fn ml_kem_decaps_internal<
83
504
    const K: usize,
84
504
    const ETA1_64: usize,
85
504
    const ETA2_64: usize,
86
504
    const J_LEN: usize,
87
504
    const CT_LEN: usize,
88
504
>(
89
504
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
90
504
) -> Result<SharedSecretKey, &'static str> {
91
    // Decapsulation key type check
92
504
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 18: dk len not 768 ...");
93
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
94
    // such, the two above checks are redundant but will be removed in release builds. The are left
95
    // here for A) caution, B) give guardrails for future changes
96
97
    // 1: dk_PKE ← dk[0 : 384·k]    ▷ extract (from KEM decaps key) the PKE decryption key
98
504
    let dk_pke = &dk[0..384 * K];
99
100
    // 2: ek_PKE ← dk[384·k : 768·k + 32]    ▷ extract PKE encryption key
101
504
    let ek_pke = &dk[384 * K..768 * K + 32];
102
103
    // 3: h ← dk[768·k + 32 : 768·k + 64]    ▷ extract hash of PKE encryption key
104
504
    let h = &dk[768 * K + 32..768 * K + 64];
105
106
    // 4: z ← dk[768·k + 64 : 768·k + 96]    ▷ extract implicit rejection value
107
504
    let z = &dk[768 * K + 64..768 * K + 96];
108
109
    // 5: m′ ← K-PKE.Decrypt(dk_PKE,c)
110
504
    let m_prime = k_pke_decrypt::<K>(du, dv, dk_pke, ct)?;
111
112
    // 6: (K′, r′) ← G(m′ ∥ h)
113
491
    let (mut k_prime, r_prime) = g(&[&m_prime, h]);
114
115
    // 7: K̄ ← J(z ∥ c, 32)
116
491
    let k_bar = j(z.try_into().unwrap(), ct);
117
118
    // 8: c′ ← K-PKE.Encrypt(ek_PKE , m′ , r′ )    ▷ re-encrypt using the derived randomness r′
119
491
    let mut c_prime = [0u8; CT_LEN];
120
491
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(
121
491
        du,
122
491
        dv,
123
491
        ek_pke,
124
491
        &m_prime,
125
491
        &r_prime,
126
491
        &mut c_prime[0..ct.len()],
127
0
    )?;
128
129
    // 9:  if 𝑐 ≠ 𝑐 ′ then
130
    // 10:   𝐾 ′ ← 𝐾̄    ▷ if ciphertexts do not match, “implicitly reject”
131
    // 11: end if
132
491
    k_prime.conditional_assign(&k_bar, ct.ct_ne(&c_prime));
133
134
    // 12: return 𝐾 ′
135
491
    Ok(SharedSecretKey(k_prime))
136
504
}
fips203::ml_kem::ml_kem_decaps_internal::<4, 128, 128, 1600, 1568>
Line
Count
Source
82
491
fn ml_kem_decaps_internal<
83
491
    const K: usize,
84
491
    const ETA1_64: usize,
85
491
    const ETA2_64: usize,
86
491
    const J_LEN: usize,
87
491
    const CT_LEN: usize,
88
491
>(
89
491
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
90
491
) -> Result<SharedSecretKey, &'static str> {
91
    // Decapsulation key type check
92
491
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 18: dk len not 768 ...");
93
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
94
    // such, the two above checks are redundant but will be removed in release builds. The are left
95
    // here for A) caution, B) give guardrails for future changes
96
97
    // 1: dk_PKE ← dk[0 : 384·k]    ▷ extract (from KEM decaps key) the PKE decryption key
98
491
    let dk_pke = &dk[0..384 * K];
99
100
    // 2: ek_PKE ← dk[384·k : 768·k + 32]    ▷ extract PKE encryption key
101
491
    let ek_pke = &dk[384 * K..768 * K + 32];
102
103
    // 3: h ← dk[768·k + 32 : 768·k + 64]    ▷ extract hash of PKE encryption key
104
491
    let h = &dk[768 * K + 32..768 * K + 64];
105
106
    // 4: z ← dk[768·k + 64 : 768·k + 96]    ▷ extract implicit rejection value
107
491
    let z = &dk[768 * K + 64..768 * K + 96];
108
109
    // 5: m′ ← K-PKE.Decrypt(dk_PKE,c)
110
491
    let m_prime = k_pke_decrypt::<K>(du, dv, dk_pke, ct)?;
111
112
    // 6: (K′, r′) ← G(m′ ∥ h)
113
480
    let (mut k_prime, r_prime) = g(&[&m_prime, h]);
114
115
    // 7: K̄ ← J(z ∥ c, 32)
116
480
    let k_bar = j(z.try_into().unwrap(), ct);
117
118
    // 8: c′ ← K-PKE.Encrypt(ek_PKE , m′ , r′ )    ▷ re-encrypt using the derived randomness r′
119
480
    let mut c_prime = [0u8; CT_LEN];
120
480
    k_pke_encrypt::<K, ETA1_64, ETA2_64>(
121
480
        du,
122
480
        dv,
123
480
        ek_pke,
124
480
        &m_prime,
125
480
        &r_prime,
126
480
        &mut c_prime[0..ct.len()],
127
0
    )?;
128
129
    // 9:  if 𝑐 ≠ 𝑐 ′ then
130
    // 10:   𝐾 ′ ← 𝐾̄    ▷ if ciphertexts do not match, “implicitly reject”
131
    // 11: end if
132
480
    k_prime.conditional_assign(&k_bar, ct.ct_ne(&c_prime));
133
134
    // 12: return 𝐾 ′
135
480
    Ok(SharedSecretKey(k_prime))
136
491
}
137
138
139
/// Algorithm 19 `ML-KEM.KeyGen()` on page 35.
140
/// Entry point for key generation. Generates random seeds and calls internal key generation.
141
///
142
/// # Parameters
143
/// * `rng` - Cryptographically secure random number generator
144
/// * `ek` - Output buffer for encapsulation key (`384·K+32` bytes)
145
/// * `dk` - Output buffer for decapsulation key (`768·K+96` bytes)
146
///
147
/// # Returns
148
/// * `Ok(())` - Success
149
/// * `Err(&str)` - Error message if RNG fails
150
720
pub(crate) fn ml_kem_key_gen<const K: usize, const ETA1_64: usize>(
151
720
    rng: &mut impl CryptoRngCore, ek: &mut [u8], dk: &mut [u8],
152
720
) -> Result<(), &'static str> {
153
720
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 19: ek len not 384 * K + 32");
154
720
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 19: dk len not 768 * K + 96");
155
156
    // 1: d ←− B^{32}    ▷ d is 32 random bytes (see Section 3.3)
157
720
    let mut d = [0u8; 32];
158
720
    rng.try_fill_bytes(&mut d).map_err(|_| "Alg 19: Random number generator failed for d")?;
159
160
    // 2: z ←− B^{32}    ▷ z is 32 random bytes (see Section 3.3)
161
720
    let mut z = [0u8; 32];
162
720
    rng.try_fill_bytes(&mut z).map_err(|_| "Alg 19: Random number generator failed for z")?;
163
164
    // 3: if 𝑑 == NULL or 𝑧 == NULL then
165
    // 4:   return ⊥    ▷ return an error indication if random bit generation failed
166
    // 5: end if
167
    // Note: the above functionality is present in the map_err() in step 1 and 2
168
169
    // 6: (ek, dk) ← ML-KEM.KeyGen_internal(𝑑, 𝑧)    ▷ run internal key generation algorithm
170
720
    ml_kem_key_gen_internal::<K, ETA1_64>(d, z, ek, dk);
171
172
    // 7: return (ek, dk)
173
720
    Ok(())
174
720
}
fips203::ml_kem::ml_kem_key_gen::<2, 192, ml_kem_fuzz::_::__libfuzzer_sys_run::TestRng>
Line
Count
Source
150
240
pub(crate) fn ml_kem_key_gen<const K: usize, const ETA1_64: usize>(
151
240
    rng: &mut impl CryptoRngCore, ek: &mut [u8], dk: &mut [u8],
152
240
) -> Result<(), &'static str> {
153
240
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 19: ek len not 384 * K + 32");
154
240
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 19: dk len not 768 * K + 96");
155
156
    // 1: d ←− B^{32}    ▷ d is 32 random bytes (see Section 3.3)
157
240
    let mut d = [0u8; 32];
158
240
    rng.try_fill_bytes(&mut d).map_err(|_| "Alg 19: Random number generator failed for d")?;
159
160
    // 2: z ←− B^{32}    ▷ z is 32 random bytes (see Section 3.3)
161
240
    let mut z = [0u8; 32];
162
240
    rng.try_fill_bytes(&mut z).map_err(|_| "Alg 19: Random number generator failed for z")?;
163
164
    // 3: if 𝑑 == NULL or 𝑧 == NULL then
165
    // 4:   return ⊥    ▷ return an error indication if random bit generation failed
166
    // 5: end if
167
    // Note: the above functionality is present in the map_err() in step 1 and 2
168
169
    // 6: (ek, dk) ← ML-KEM.KeyGen_internal(𝑑, 𝑧)    ▷ run internal key generation algorithm
170
240
    ml_kem_key_gen_internal::<K, ETA1_64>(d, z, ek, dk);
171
172
    // 7: return (ek, dk)
173
240
    Ok(())
174
240
}
fips203::ml_kem::ml_kem_key_gen::<3, 128, ml_kem_fuzz::_::__libfuzzer_sys_run::TestRng>
Line
Count
Source
150
240
pub(crate) fn ml_kem_key_gen<const K: usize, const ETA1_64: usize>(
151
240
    rng: &mut impl CryptoRngCore, ek: &mut [u8], dk: &mut [u8],
152
240
) -> Result<(), &'static str> {
153
240
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 19: ek len not 384 * K + 32");
154
240
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 19: dk len not 768 * K + 96");
155
156
    // 1: d ←− B^{32}    ▷ d is 32 random bytes (see Section 3.3)
157
240
    let mut d = [0u8; 32];
158
240
    rng.try_fill_bytes(&mut d).map_err(|_| "Alg 19: Random number generator failed for d")?;
159
160
    // 2: z ←− B^{32}    ▷ z is 32 random bytes (see Section 3.3)
161
240
    let mut z = [0u8; 32];
162
240
    rng.try_fill_bytes(&mut z).map_err(|_| "Alg 19: Random number generator failed for z")?;
163
164
    // 3: if 𝑑 == NULL or 𝑧 == NULL then
165
    // 4:   return ⊥    ▷ return an error indication if random bit generation failed
166
    // 5: end if
167
    // Note: the above functionality is present in the map_err() in step 1 and 2
168
169
    // 6: (ek, dk) ← ML-KEM.KeyGen_internal(𝑑, 𝑧)    ▷ run internal key generation algorithm
170
240
    ml_kem_key_gen_internal::<K, ETA1_64>(d, z, ek, dk);
171
172
    // 7: return (ek, dk)
173
240
    Ok(())
174
240
}
fips203::ml_kem::ml_kem_key_gen::<4, 128, rand_core::os::OsRng>
Line
Count
Source
150
240
pub(crate) fn ml_kem_key_gen<const K: usize, const ETA1_64: usize>(
151
240
    rng: &mut impl CryptoRngCore, ek: &mut [u8], dk: &mut [u8],
152
240
) -> Result<(), &'static str> {
153
240
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 19: ek len not 384 * K + 32");
154
240
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 19: dk len not 768 * K + 96");
155
156
    // 1: d ←− B^{32}    ▷ d is 32 random bytes (see Section 3.3)
157
240
    let mut d = [0u8; 32];
158
240
    rng.try_fill_bytes(&mut d).map_err(|_| "Alg 19: Random number generator failed for d")?;
159
160
    // 2: z ←− B^{32}    ▷ z is 32 random bytes (see Section 3.3)
161
240
    let mut z = [0u8; 32];
162
240
    rng.try_fill_bytes(&mut z).map_err(|_| "Alg 19: Random number generator failed for z")?;
163
164
    // 3: if 𝑑 == NULL or 𝑧 == NULL then
165
    // 4:   return ⊥    ▷ return an error indication if random bit generation failed
166
    // 5: end if
167
    // Note: the above functionality is present in the map_err() in step 1 and 2
168
169
    // 6: (ek, dk) ← ML-KEM.KeyGen_internal(𝑑, 𝑧)    ▷ run internal key generation algorithm
170
240
    ml_kem_key_gen_internal::<K, ETA1_64>(d, z, ek, dk);
171
172
    // 7: return (ek, dk)
173
240
    Ok(())
174
240
}
Unexecuted instantiation: fips203::ml_kem::ml_kem_key_gen::<_, _, _>
175
176
177
/// Algorithm 20 `ML-KEM.Encaps(ek)` on page 37.
178
/// Uses the encapsulation key to generate a shared key and an associated ciphertext.
179
///
180
/// # Parameters
181
/// * `rng` - Cryptographically secure random number generator
182
/// * `du`, `dv` - Parameters affecting ciphertext size
183
/// * `ek` - Encapsulation key (`384·K+32` bytes)
184
/// * `ct` - Output buffer for ciphertext (`32(du·K+dv)` bytes)
185
///
186
/// # Returns
187
/// * `Ok(SharedSecretKey)` - 32-byte shared secret key
188
/// * `Err(&str)` - Error message if RNG fails or encryption fails
189
///
190
/// # Input Validation
191
/// The encapsulation key `ek` must pass modulus check: `ek = ByteEncode12(ByteDecode12(ek))`.
192
/// External `ek` values are validated via `try_from_bytes()`.
193
1.78k
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
1.78k
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
1.78k
) -> Result<SharedSecretKey, &'static str> {
196
1.78k
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
1.78k
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
1.78k
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
1.78k
    let mut m = [0u8; 32];
226
1.78k
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
1.78k
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
1.78k
    Ok(k)
230
1.78k
}
fips203::ml_kem::ml_kem_encaps::<2, 192, 128, fips203::traits::DummyRng>
Line
Count
Source
193
274
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
274
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
274
) -> Result<SharedSecretKey, &'static str> {
196
274
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
274
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
274
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
274
    let mut m = [0u8; 32];
226
274
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
274
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
274
    Ok(k)
230
274
}
fips203::ml_kem::ml_kem_encaps::<2, 192, 128, ml_kem_fuzz::_::__libfuzzer_sys_run::TestRng>
Line
Count
Source
193
240
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
240
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
240
) -> Result<SharedSecretKey, &'static str> {
196
240
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
240
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
240
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
240
    let mut m = [0u8; 32];
226
240
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
240
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
240
    Ok(k)
230
240
}
fips203::ml_kem::ml_kem_encaps::<3, 128, 128, fips203::traits::DummyRng>
Line
Count
Source
193
274
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
274
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
274
) -> Result<SharedSecretKey, &'static str> {
196
274
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
274
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
274
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
274
    let mut m = [0u8; 32];
226
274
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
274
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
274
    Ok(k)
230
274
}
fips203::ml_kem::ml_kem_encaps::<3, 128, 128, ml_kem_fuzz::_::__libfuzzer_sys_run::TestRng>
Line
Count
Source
193
240
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
240
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
240
) -> Result<SharedSecretKey, &'static str> {
196
240
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
240
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
240
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
240
    let mut m = [0u8; 32];
226
240
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
240
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
240
    Ok(k)
230
240
}
fips203::ml_kem::ml_kem_encaps::<4, 128, 128, fips203::traits::DummyRng>
Line
Count
Source
193
274
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
274
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
274
) -> Result<SharedSecretKey, &'static str> {
196
274
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
274
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
274
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
274
    let mut m = [0u8; 32];
226
274
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
274
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
274
    Ok(k)
230
274
}
fips203::ml_kem::ml_kem_encaps::<4, 128, 128, rand_core::os::OsRng>
Line
Count
Source
193
240
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
240
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
240
) -> Result<SharedSecretKey, &'static str> {
196
240
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
240
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
240
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
240
    let mut m = [0u8; 32];
226
240
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
240
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
240
    Ok(k)
230
240
}
fips203::ml_kem::ml_kem_encaps::<4, 128, 128, ml_kem_fuzz::_::__libfuzzer_sys_run::TestRng>
Line
Count
Source
193
240
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
194
240
    rng: &mut impl CryptoRngCore, du: u32, dv: u32, ek: &[u8], ct: &mut [u8],
195
240
) -> Result<SharedSecretKey, &'static str> {
196
240
    debug_assert_eq!(ek.len(), 384 * K + 32, "Alg 20: ek len not 384 * K + 32"); // also: size check at top level
197
240
    debug_assert_eq!(
198
0
        ct.len(),
199
0
        32 * (du as usize * K + dv as usize),
200
0
        "Alg 20: ct len not 32*(DU*K+DV)"
201
    ); // also: size check at top level
202
203
    // modulus check: perform/confirm the computation ek ← ByteEncode12(ByteDecode12(ek_tilde).
204
    // Note: An *external* ek can only arrive via try_from_bytes() which does this validation already.
205
    // As such, this check is redundant but is left in for caution and as a fuzz target, as it is
206
    // removed in release builds anyway. It also supports quicker changes if the spec moves...
207
240
    debug_assert!(
208
        {
209
0
            let mut pass = true;
210
0
            for i in 0..K {
211
0
                let mut ek_tilde = [0u8; 384];
212
0
                let ek_hat = byte_decode(12, &ek[384 * i..384 * (i + 1)]).unwrap(); // btw, going to panic
213
0
                byte_encode(12, &ek_hat, &mut ek_tilde);
214
0
                pass &= ek_tilde == ek[384 * i..384 * (i + 1)];
215
0
            }
216
0
            pass
217
        },
218
0
        "Alg 20: ek fails modulus check"
219
    );
220
221
    // 1: m ← B^{32}          ▷ m is 32 random bytes (see Section 3.3)
222
    // 2: if 𝑚 == NULL then
223
    // 3:   return ⊥    ▷ return an error indication if random bit generation failed
224
    // 4: end if
225
240
    let mut m = [0u8; 32];
226
240
    rng.try_fill_bytes(&mut m).map_err(|_| "Alg 20: random number generator failed")?;
227
228
240
    let k = ml_kem_encaps_internal::<K, ETA1_64, ETA2_64>(du, dv, &m, ek, ct)?;
229
240
    Ok(k)
230
240
}
Unexecuted instantiation: fips203::ml_kem::ml_kem_encaps::<_, _, _, _>
231
232
233
/// Algorithm 21 `ML-KEM.Decaps(c, dk)` on page 38.
234
/// Uses the decapsulation key to produce a shared key from a ciphertext.
235
/// Implements implicit rejection for invalid ciphertexts.
236
///
237
/// # Parameters
238
/// * `du`, `dv` - Parameters affecting ciphertext size
239
/// * `dk` - Decapsulation key (`768·K+96` bytes)
240
/// * `ct` - Input ciphertext (`32(du·K+dv)` bytes)
241
///
242
/// # Returns
243
/// * `Ok(SharedSecretKey)` - 32-byte shared secret key
244
/// * `Err(&str)` - Error message if decryption fails
245
///
246
/// # Input Validation
247
/// - Ciphertext size must be exactly `32(du·K+dv)` bytes
248
/// - Decapsulation key size must be exactly `768·K+96` bytes
249
/// - External `dk` values are validated via `try_from_bytes()`
250
#[allow(clippy::similar_names)]
251
1.50k
pub(crate) fn ml_kem_decaps<
252
1.50k
    const K: usize,
253
1.50k
    const ETA1_64: usize,
254
1.50k
    const ETA2_64: usize,
255
1.50k
    const J_LEN: usize,
256
1.50k
    const CT_LEN: usize,
257
1.50k
>(
258
1.50k
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
259
1.50k
) -> Result<SharedSecretKey, &'static str> {
260
    // Ciphertext type check
261
1.50k
    debug_assert_eq!(ct.len(), 32 * (du as usize * K + dv as usize), "Alg 21: ct len not 32 * ...");
262
    // Decapsulation key type check
263
1.50k
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 21: dk len not 768 ...");
264
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
265
    // such, the two above checks are redundant but will be removed in release builds. The are left
266
    // here for A) caution, B) give guardrails for future changes
267
268
    // 1: 𝐾 ′ ← ML-KEM.Decaps_internal(dk, 𝑐)    ▷ run internal decapsulation algorithm
269
    // 2: return 𝐾 ′
270
1.50k
    ml_kem_decaps_internal::<K, ETA1_64, ETA2_64, J_LEN, CT_LEN>(du, dv, dk, ct)
271
1.50k
}
fips203::ml_kem::ml_kem_decaps::<2, 192, 128, 800, 768>
Line
Count
Source
251
514
pub(crate) fn ml_kem_decaps<
252
514
    const K: usize,
253
514
    const ETA1_64: usize,
254
514
    const ETA2_64: usize,
255
514
    const J_LEN: usize,
256
514
    const CT_LEN: usize,
257
514
>(
258
514
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
259
514
) -> Result<SharedSecretKey, &'static str> {
260
    // Ciphertext type check
261
514
    debug_assert_eq!(ct.len(), 32 * (du as usize * K + dv as usize), "Alg 21: ct len not 32 * ...");
262
    // Decapsulation key type check
263
514
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 21: dk len not 768 ...");
264
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
265
    // such, the two above checks are redundant but will be removed in release builds. The are left
266
    // here for A) caution, B) give guardrails for future changes
267
268
    // 1: 𝐾 ′ ← ML-KEM.Decaps_internal(dk, 𝑐)    ▷ run internal decapsulation algorithm
269
    // 2: return 𝐾 ′
270
514
    ml_kem_decaps_internal::<K, ETA1_64, ETA2_64, J_LEN, CT_LEN>(du, dv, dk, ct)
271
514
}
fips203::ml_kem::ml_kem_decaps::<3, 128, 128, 1120, 1088>
Line
Count
Source
251
504
pub(crate) fn ml_kem_decaps<
252
504
    const K: usize,
253
504
    const ETA1_64: usize,
254
504
    const ETA2_64: usize,
255
504
    const J_LEN: usize,
256
504
    const CT_LEN: usize,
257
504
>(
258
504
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
259
504
) -> Result<SharedSecretKey, &'static str> {
260
    // Ciphertext type check
261
504
    debug_assert_eq!(ct.len(), 32 * (du as usize * K + dv as usize), "Alg 21: ct len not 32 * ...");
262
    // Decapsulation key type check
263
504
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 21: dk len not 768 ...");
264
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
265
    // such, the two above checks are redundant but will be removed in release builds. The are left
266
    // here for A) caution, B) give guardrails for future changes
267
268
    // 1: 𝐾 ′ ← ML-KEM.Decaps_internal(dk, 𝑐)    ▷ run internal decapsulation algorithm
269
    // 2: return 𝐾 ′
270
504
    ml_kem_decaps_internal::<K, ETA1_64, ETA2_64, J_LEN, CT_LEN>(du, dv, dk, ct)
271
504
}
fips203::ml_kem::ml_kem_decaps::<4, 128, 128, 1600, 1568>
Line
Count
Source
251
491
pub(crate) fn ml_kem_decaps<
252
491
    const K: usize,
253
491
    const ETA1_64: usize,
254
491
    const ETA2_64: usize,
255
491
    const J_LEN: usize,
256
491
    const CT_LEN: usize,
257
491
>(
258
491
    du: u32, dv: u32, dk: &[u8], ct: &[u8; CT_LEN],
259
491
) -> Result<SharedSecretKey, &'static str> {
260
    // Ciphertext type check
261
491
    debug_assert_eq!(ct.len(), 32 * (du as usize * K + dv as usize), "Alg 21: ct len not 32 * ...");
262
    // Decapsulation key type check
263
491
    debug_assert_eq!(dk.len(), 768 * K + 96, "Alg 21: dk len not 768 ...");
264
    // Note: decaps key is either correctly sourced from KeyGen, or validated by try_from_bytes(). As
265
    // such, the two above checks are redundant but will be removed in release builds. The are left
266
    // here for A) caution, B) give guardrails for future changes
267
268
    // 1: 𝐾 ′ ← ML-KEM.Decaps_internal(dk, 𝑐)    ▷ run internal decapsulation algorithm
269
    // 2: return 𝐾 ′
270
491
    ml_kem_decaps_internal::<K, ETA1_64, ETA2_64, J_LEN, CT_LEN>(du, dv, dk, ct)
271
491
}
272
273
274
#[cfg(test)]
275
mod tests {
276
    use rand_core::SeedableRng;
277
278
    use crate::ml_kem::{ml_kem_decaps, ml_kem_encaps, ml_kem_key_gen};
279
280
    /// Test constants for ML-KEM-512
281
    const ETA1: u32 = 3;
282
    const ETA2: u32 = 2;
283
    const DU: u32 = 10;
284
    const DV: u32 = 4;
285
    const K: usize = 2;
286
    const ETA1_64: usize = ETA1 as usize * 64;
287
    const ETA2_64: usize = ETA2 as usize * 64;
288
    /// Size of encapsulation key in bytes
289
    const EK_LEN: usize = 800;
290
    /// Size of decapsulation key in bytes
291
    const DK_LEN: usize = 1632;
292
    /// Size of ciphertext in bytes
293
    const CT_LEN: usize = 768;
294
    /// Size of input to function J (z || c)
295
    const J_LEN: usize = 32 + 32 * (DU as usize * K + DV as usize);
296
297
    /// Tests that the key generation, encapsulation, and decapsulation functions
298
    /// complete successfully with valid inputs.
299
    #[test]
300
    #[allow(clippy::similar_names)]
301
    fn test_result_errs() {
302
        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
303
        let mut ek = [0u8; EK_LEN];
304
        let mut dk = [0u8; DK_LEN];
305
        let mut ct = [0u8; CT_LEN];
306
307
        let res = ml_kem_key_gen::<K, ETA1_64>(&mut rng, &mut ek, &mut dk);
308
        assert!(res.is_ok());
309
310
        let res = ml_kem_encaps::<K, ETA1_64, ETA2_64>(&mut rng, DU, DV, &ek, &mut ct);
311
        assert!(res.is_ok());
312
313
        let res = ml_kem_decaps::<K, ETA1_64, ETA2_64, J_LEN, CT_LEN>(DU, DV, &dk, &ct);
314
        assert!(res.is_ok());
315
    }
316
}