1use crate::{
4 consts::{DOMSEP_FO, DOMSEP_NOREJECT},
5 pke::{self, PkePublicKey, PkeSecretKey, ciphertext_len, expand_decap_key, max_ciphertext_len},
6 turboshake256_hash,
7};
8
9use rand_core::CryptoRng;
10use subtle::{ConditionallySelectable, ConstantTimeEq};
11use turboshake::CTurboShake256;
12use turboshake::digest::{ExtendableOutput, Update, XofReader};
13use zeroize::{Zeroize, ZeroizeOnDrop};
14
15pub struct KemPublicKey<const L: usize> {
17 pke_pk: PkePublicKey<L>,
19 hash_pke_pk: [u8; 32],
21}
22
23impl<const L: usize> KemPublicKey<L> {
24 pub(crate) const SERIALIZED_LEN: usize = PkePublicKey::<L>::SERIALIZED_LEN;
25
26 pub(crate) fn serialize(&self, out_buf: &mut [u8]) {
28 self.pke_pk.serialize(out_buf);
29 }
30
31 pub(crate) fn from_bytes(bytes: &[u8]) -> Self {
33 let pke_pk = PkePublicKey::from_bytes(bytes);
34 let hash_pke_pk = pke_pk.hash();
35
36 KemPublicKey {
37 pke_pk,
38 hash_pke_pk,
39 }
40 }
41}
42
43pub type SharedSecret = [u8; 32];
45
46#[derive(ZeroizeOnDrop)]
55pub struct KemSecretKey<const L: usize> {
56 seed: [u8; 32],
58 z: [u8; 32],
60 pke_sk: PkeSecretKey<L>,
62 #[zeroize(skip)]
64 pke_pk: PkePublicKey<L>,
65 #[zeroize(skip)]
67 hash_pke_pk: [u8; 32],
68}
69
70impl<const L: usize> KemSecretKey<L> {
71 pub fn expand_from_seed<const MU: usize>(seed: &[u8; 32]) -> KemSecretKey<L> {
73 let (pke_sk, z, pke_pk, hash_pke_pk) = expand_decap_key::<L, MU>(seed);
74
75 KemSecretKey {
76 seed: *seed,
77 z,
78 pke_sk,
79 pke_pk,
80 hash_pke_pk,
81 }
82 }
83
84 pub fn generate<const MU: usize>(rng: &mut impl CryptoRng) -> KemSecretKey<L> {
86 let mut seed = [0u8; 32];
87 rng.fill_bytes(&mut seed);
88 let out = Self::expand_from_seed::<MU>(&seed);
89
90 seed.zeroize();
91 out
92 }
93
94 pub fn seed(&self) -> &[u8; 32] {
96 &self.seed
97 }
98
99 pub(crate) fn public_key(&self) -> KemPublicKey<L> {
100 KemPublicKey {
101 pke_pk: self.pke_pk.clone(),
102 hash_pke_pk: self.hash_pke_pk,
103 }
104 }
105}
106
107pub(crate) fn encap_deterministic<const L: usize, const MU: usize, const T: usize>(
110 randomness: &[u8; 32],
111 kem_pk: &KemPublicKey<L>,
112 out_buf: &mut [u8],
113) -> SharedSecret {
114 let KemPublicKey {
115 pke_pk,
116 hash_pke_pk,
117 } = kem_pk;
118
119 let mut k = [0u8; 32];
121 let mut r = [0u8; 32];
122
123 let mut xof = {
124 let mut hasher = CTurboShake256::<DOMSEP_FO>::default();
125 hasher.update(randomness);
126 hasher.update(hash_pke_pk);
127 hasher.finalize_xof()
128 };
129 xof.read(&mut k);
130 xof.read(&mut r);
131
132 pke::encrypt_deterministic::<L, MU, T>(pke_pk, randomness, &r, out_buf);
134
135 k
136}
137
138pub fn decap<const L: usize, const MU: usize, const T: usize>(
142 sk: &KemSecretKey<L>,
143 ciphertext: &[u8],
144) -> SharedSecret {
145 assert_eq!(ciphertext.len(), ciphertext_len::<L, T>());
146
147 let randomness = pke::decrypt::<L, T>(&sk.pke_sk, ciphertext);
149
150 let mut k = [0u8; 32];
152 let mut rprime = [0u8; 32];
153
154 let mut xof = {
155 let mut hasher = CTurboShake256::<DOMSEP_FO>::default();
156 hasher.update(&randomness);
157 hasher.update(&sk.hash_pke_pk);
158 hasher.finalize_xof()
159 };
160 xof.read(&mut k);
161 xof.read(&mut rprime);
162
163 let mut buf = [0u8; max_ciphertext_len()];
165 let reconstructed_ct = &mut buf[..ciphertext_len::<L, T>()];
166 pke::encrypt_deterministic::<L, MU, T>(&sk.pke_pk, &randomness, &rprime, reconstructed_ct);
167
168 let reject_val = turboshake256_hash::<DOMSEP_NOREJECT>(&sk.z, ciphertext);
170
171 let reconstruction_matched = reconstructed_ct.ct_eq(ciphertext);
173 <[u8; 32]>::conditional_select(&reject_val, &k, reconstruction_matched)
174}
175
176#[cfg(test)]
177mod test {
178 use super::*;
179 use crate::consts::*;
180
181 use rand::Rng;
182
183 #[test]
184 fn kopis512_cca_kem() {
185 test_encap_decap::<KOPIS512_L, KOPIS512_MU, KOPIS512_T>();
186 }
187
188 #[test]
189 fn kopis768_cca_kem() {
190 test_encap_decap::<KOPIS768_L, KOPIS768_MU, KOPIS768_T>();
191 }
192
193 #[test]
194 fn kopis1024_cca_kem() {
195 test_encap_decap::<KOPIS1024_L, KOPIS1024_MU, KOPIS1024_T>();
196 }
197
198 fn test_encap_decap<const L: usize, const MU: usize, const T: usize>() {
199 let mut rng = rand::rng();
200 let mut backing_buf = [0u8; max_ciphertext_len()];
201
202 for _ in 0..100 {
203 let sk = KemSecretKey::<L>::generate::<MU>(&mut rng);
204 let pk = sk.public_key();
205 let ct_buf = &mut backing_buf[..ciphertext_len::<L, T>()];
206
207 let randomness: [u8; 32] = rng.random();
208 let ss1 = encap_deterministic::<L, MU, T>(&randomness, &pk, ct_buf);
209 let ss2 = decap::<L, MU, T>(&sk, ct_buf);
210 assert_eq!(ss1, ss2);
211
212 let perturbed_ct = ct_buf;
216 let idx = (rng.random::<u32>() as usize) % perturbed_ct.len();
218 let byte = loop {
219 let b = rng.random::<u8>();
220 if b != 0 {
221 break b;
222 }
223 };
224 perturbed_ct[idx] ^= byte;
225 let ss1 = decap::<L, MU, T>(&sk, perturbed_ct);
227 let ss2 =
231 recompute_shared_secret_for_pertrubed_ciphertext::<L, MU, T>(&randomness, &pk);
232 assert_ne!(ss1, ss2);
233 }
234 }
235
236 pub(crate) fn recompute_shared_secret_for_pertrubed_ciphertext<
240 const L: usize,
241 const MU: usize,
242 const T: usize,
243 >(
244 randomness: &[u8; 32],
245 kem_pk: &KemPublicKey<L>,
246 ) -> SharedSecret {
247 let hash_pke_pk = kem_pk.pke_pk.hash();
249
250 let mut k = [0u8; 32];
252
253 let mut xof = {
254 let mut hasher = CTurboShake256::<DOMSEP_FO>::default();
255 hasher.update(randomness);
256 hasher.update(&hash_pke_pk);
257 hasher.finalize_xof()
258 };
259 xof.read(&mut k);
260
261 k
262 }
263}