/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 | | } |