• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 11844561993

14 Nov 2024 07:58PM UTC coverage: 91.178% (+0.1%) from 91.072%
11844561993

Pull #4435

github

web-flow
Merge 81dcb29da into e430f157a
Pull Request #4435: Test duration values ​​are now presented in seconds with six digits of precision. Tests without time measurements have been edited.

91856 of 100744 relevant lines covered (91.18%)

9311006.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.09
/src/tests/test_kyber.cpp
1
/*
2
 * Tests for Crystals Kyber
3
 * - simple roundtrip test
4
 * - KAT tests using the KAT vectors from
5
 *   https://csrc.nist.gov/CSRC/media/Projects/post-quantum-cryptography/documents/round-3/submissions/Kyber-Round3.zip
6
 *
7
 * (C) 2021-2024 Jack Lloyd
8
 * (C) 2021-2022 Manuel Glaser and Michael Boric, Rohde & Schwarz Cybersecurity
9
 * (C) 2021-2022 René Meusel and Hannes Rantzsch, neXenio GmbH
10
 * (C) 2023-2024 René Meusel, Rohde & Schwarz Cybersecurity
11
 *
12
 * Botan is released under the Simplified BSD License (see license.txt)
13
 */
14

15
#include "test_pubkey_pqc.h"
16
#include "test_rng.h"
17
#include "tests.h"
18

19
#include <cmath>
20
#include <iterator>
21
#include <memory>
22

23
#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S) || defined(BOTAN_HAS_ML_KEM)
24
   #include "test_pubkey.h"
25
   #include <botan/hex.h>
26
   #include <botan/kyber.h>
27
   #include <botan/pubkey.h>
28
   #include <botan/rng.h>
29
   #include <botan/internal/fmt.h>
30
   #include <botan/internal/kyber_constants.h>
31
   #include <botan/internal/kyber_helpers.h>
32
   #include <botan/internal/stl_util.h>
33
#endif
34

35
namespace Botan_Tests {
36

37
#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S) || defined(BOTAN_HAS_ML_KEM)
38

39
class KYBER_Tests final : public Test {
×
40
   public:
41
      static Test::Result run_kyber_test(const char* test_name, Botan::KyberMode mode, size_t strength, size_t psid) {
9✔
42
         Test::Result result(test_name);
9✔
43
         result.start_timer();
9✔
44

45
         if(!mode.is_available()) {
9✔
46
            result.note_missing(mode.to_string());
×
47
            return result;
×
48
         }
49

50
         auto rng = Test::new_rng(test_name);
9✔
51

52
         const std::vector<uint8_t> empty_salt;
9✔
53

54
         // Alice
55
         const Botan::Kyber_PrivateKey priv_key(*rng, mode);
9✔
56
         const auto pub_key = priv_key.public_key();
9✔
57

58
         result.test_eq("estimated strength private", priv_key.estimated_strength(), strength);
9✔
59
         result.test_eq("estimated strength public", pub_key->estimated_strength(), strength);
9✔
60
         result.test_eq("canonical parameter set identifier", priv_key.key_length(), psid);
9✔
61
         result.test_eq("canonical parameter set identifier", pub_key->key_length(), psid);
9✔
62

63
         // Serialize
64
         const auto priv_key_bits = priv_key.private_key_bits();
9✔
65
         const auto pub_key_bits = pub_key->public_key_bits();
9✔
66

67
         // Bob (reading from serialized public key)
68
         Botan::Kyber_PublicKey alice_pub_key(pub_key_bits, mode);
9✔
69
         auto enc = Botan::PK_KEM_Encryptor(alice_pub_key, "Raw", "base");
9✔
70
         const auto kem_result = enc.encrypt(*rng);
9✔
71

72
         // Alice (reading from serialized private key)
73
         Botan::Kyber_PrivateKey alice_priv_key(priv_key_bits, mode);
9✔
74
         auto dec = Botan::PK_KEM_Decryptor(alice_priv_key, *rng, "Raw", "base");
9✔
75
         const auto key_alice = dec.decrypt(kem_result.encapsulated_shared_key(), 0 /* no KDF */, empty_salt);
9✔
76
         result.test_eq("shared secrets are equal", key_alice, kem_result.shared_key());
9✔
77

78
         //
79
         // negative tests
80
         //
81

82
         // Broken cipher_text from Alice (wrong length)
83
         result.test_throws("fail to read cipher_text", "Kyber: unexpected ciphertext length", [&] {
18✔
84
            auto short_cipher_text = kem_result.encapsulated_shared_key();
9✔
85
            short_cipher_text.pop_back();
9✔
86
            dec.decrypt(short_cipher_text, 0, empty_salt);
9✔
87
         });
×
88

89
         // Invalid cipher_text from Alice
90
         Botan::secure_vector<uint8_t> reverse_cipher_text;
9✔
91
         std::copy(kem_result.encapsulated_shared_key().crbegin(),
9✔
92
                   kem_result.encapsulated_shared_key().crend(),
9✔
93
                   std::back_inserter(reverse_cipher_text));
94
         const auto key_alice_rev = dec.decrypt(reverse_cipher_text, 0, empty_salt);
9✔
95
         result.confirm("shared secrets are not equal", key_alice != key_alice_rev);
18✔
96

97
         // Try to decrypt the valid ciphertext again
98
         const auto key_alice_try2 = dec.decrypt(kem_result.encapsulated_shared_key(), 0 /* no KDF */, empty_salt);
9✔
99
         result.test_eq("shared secrets are equal", key_alice_try2, kem_result.shared_key());
9✔
100

101
         result.end_timer();
9✔
102
         return result;
9✔
103
      }
72✔
104

105
      std::vector<Test::Result> run() override {
1✔
106
         return {
1✔
107
            run_kyber_test("Kyber512_90s API", Botan::KyberMode::Kyber512_90s, 128, 512),
108
            run_kyber_test("Kyber768_90s API", Botan::KyberMode::Kyber768_90s, 192, 768),
109
            run_kyber_test("Kyber1024_90s API", Botan::KyberMode::Kyber1024_90s, 256, 1024),
110
            run_kyber_test("Kyber512 API", Botan::KyberMode::Kyber512_R3, 128, 512),
111
            run_kyber_test("Kyber768 API", Botan::KyberMode::Kyber768_R3, 192, 768),
112
            run_kyber_test("Kyber1024 API", Botan::KyberMode::Kyber1024_R3, 256, 1024),
113
            run_kyber_test("ML-KEM-512 API", Botan::KyberMode::ML_KEM_512, 128, 512),
114
            run_kyber_test("ML-KEM-768 API", Botan::KyberMode::ML_KEM_768, 192, 768),
115
            run_kyber_test("ML-KEM-1024 API", Botan::KyberMode::ML_KEM_1024, 256, 1024),
116
         };
10✔
117
      }
2✔
118
};
119

120
BOTAN_REGISTER_TEST("pubkey", "kyber_pairwise", KYBER_Tests);
121

122
namespace {
123

124
class Kyber_KAT_Tests : public PK_PQC_KEM_KAT_Test {
125
   protected:
126
      Kyber_KAT_Tests(const std::string& algo_name,
2✔
127
                      const std::string& kat_file,
128
                      const std::string& further_optional_keys = "") :
2✔
129
            PK_PQC_KEM_KAT_Test(algo_name, kat_file, further_optional_keys) {}
2✔
130

131
   private:
132
      Botan::KyberMode get_mode(const std::string& mode) const { return Botan::KyberMode(mode); }
2,400✔
133

134
      bool is_available(const std::string& mode) const final { return get_mode(mode).is_available(); }
225✔
135

136
      std::vector<uint8_t> map_value(const std::string& mode,
900✔
137
                                     std::span<const uint8_t> value,
138
                                     VarType var_type) const final {
139
         if(var_type == VarType::SharedSecret) {
900✔
140
            return {value.begin(), value.end()};
225✔
141
         }
142

143
         // We use different hash functions for Kyber 90s, as those are
144
         // consistent with the algorithm requirements of the implementations.
145
         std::string_view hash_name = get_mode(mode).is_90s() ? "SHA-256" : "SHAKE-256(128)";
675✔
146

147
         auto hash = Botan::HashFunction::create_or_throw(hash_name);
675✔
148
         const auto digest = hash->process(value);
675✔
149
         return {digest.begin(), digest.begin() + 16};
675✔
150
      }
1,350✔
151

152
      Fixed_Output_RNG rng_for_keygen(const std::string& mode, Botan::RandomNumberGenerator& rng) const final {
225✔
153
         if(get_mode(mode).is_kyber_round3()) {
225✔
154
            const auto seed = rng.random_vec(32);
150✔
155
            const auto z = rng.random_vec(32);
150✔
156
            return Fixed_Output_RNG(Botan::concat(seed, z));
300✔
157
         } else if(get_mode(mode).is_ml_kem()) {
375✔
158
            const auto z = rng.random_vec(32);
75✔
159
            const auto d = rng.random_vec(32);
75✔
160
            return Fixed_Output_RNG(Botan::concat(d, z));
150✔
161
         } else {
150✔
162
            return Fixed_Output_RNG(rng.random_vec(64));
×
163
         }
164
      }
165

166
      Fixed_Output_RNG rng_for_encapsulation(const std::string&, Botan::RandomNumberGenerator& rng) const final {
225✔
167
         return Fixed_Output_RNG(rng.random_vec(32));
450✔
168
      }
169
};
170

171
class KyberR3_KAT_Tests : public Kyber_KAT_Tests {
172
   public:
173
      KyberR3_KAT_Tests() : Kyber_KAT_Tests("Kyber", "pubkey/kyber_kat.vec") {}
2✔
174
};
175

176
class ML_KEM_KAT_Tests : public Kyber_KAT_Tests {
177
   public:
178
      ML_KEM_KAT_Tests() : Kyber_KAT_Tests("ML-KEM", "pubkey/ml_kem.vec", "CT_N,SS_N") {}
2✔
179
};
180

181
class ML_KEM_ACVP_KAT_KeyGen_Tests : public PK_PQC_KEM_ACVP_KAT_KeyGen_Test {
182
   public:
183
      ML_KEM_ACVP_KAT_KeyGen_Tests() :
1✔
184
            PK_PQC_KEM_ACVP_KAT_KeyGen_Test("ML-KEM", "pubkey/ml_kem_acvp_keygen.vec", "Z,D") {}
1✔
185

186
   private:
187
      Botan::KyberMode get_mode(const std::string& mode) const { return Botan::KyberMode(mode); }
150✔
188

189
      bool is_available(const std::string& mode) const final { return get_mode(mode).is_available(); }
75✔
190

191
      Fixed_Output_RNG rng_for_keygen(const VarMap& vars) const override {
75✔
192
         const auto d = vars.get_req_bin("D");
75✔
193
         const auto z = vars.get_req_bin("Z");
75✔
194
         return Fixed_Output_RNG(Botan::concat(d, z));
150✔
195
      }
150✔
196
};
197

198
class ML_KEM_PQC_KEM_ACVP_KAT_Encap_Test : public PK_PQC_KEM_ACVP_KAT_Encap_Test {
199
   public:
200
      ML_KEM_PQC_KEM_ACVP_KAT_Encap_Test() : PK_PQC_KEM_ACVP_KAT_Encap_Test("ML-KEM", "pubkey/ml_kem_acvp_encap.vec") {}
2✔
201

202
   private:
203
      Botan::KyberMode get_mode(const std::string& mode) const { return Botan::KyberMode(mode); }
300✔
204

205
      bool is_available(const std::string& mode) const final { return get_mode(mode).is_available(); }
75✔
206

207
      std::unique_ptr<Botan::Public_Key> load_public_key(const VarMap& vars, const std::string& mode) const final {
75✔
208
         return std::make_unique<Botan::Kyber_PublicKey>(vars.get_req_bin("EK"), get_mode(mode));
225✔
209
      }
210
};
211

212
}  // namespace
213

214
BOTAN_REGISTER_TEST("pubkey", "kyber_kat", KyberR3_KAT_Tests);
215
BOTAN_REGISTER_TEST("pubkey", "ml_kem_kat", ML_KEM_KAT_Tests);
216
BOTAN_REGISTER_TEST("pubkey", "ml_kem_acvp_kat_keygen", ML_KEM_ACVP_KAT_KeyGen_Tests);
217
BOTAN_REGISTER_TEST("pubkey", "ml_kem_acvp_kat_encap", ML_KEM_PQC_KEM_ACVP_KAT_Encap_Test);
218

219
// Currently we cannot use the ACVP decapsulation tests because they do not
220
// provide the private key's seed values.
221
//BOTAN_REGISTER_TEST("pubkey", "ml_kem_acvp_kat_decap", ML_KEM_PQC_KEM_ACVP_KAT_Decap_Test);
222

223
class Kyber_Encoding_Test : public Text_Based_Test {
224
   public:
225
      Kyber_Encoding_Test() : Text_Based_Test("pubkey/kyber_encodings.vec", "PrivateRaw,PublicRaw", "Error") {}
2✔
226

227
   public:
228
      bool skip_this_test(const std::string& algo_name, const VarMap& /*vars*/) override {
13✔
229
         return !Botan::KyberMode(algo_name).is_available();
13✔
230
      }
231

232
      Test::Result run_one_test(const std::string& algo_name, const VarMap& vars) override {
13✔
233
         Test::Result result("kyber_encodings");
13✔
234
         result.start_timer();
13✔
235

236
         const auto mode = Botan::KyberMode(algo_name);
13✔
237
         const auto pk_raw = Botan::hex_decode(vars.get_req_str("PublicRaw"));
26✔
238
         const auto sk_raw = Botan::hex_decode_locked(vars.get_req_str("PrivateRaw"));
26✔
239
         const auto error = vars.get_opt_str("Error", "");
26✔
240

241
         if(!error.empty()) {
13✔
242
            // negative tests
243

244
            result.test_throws("failing decoding", error, [&] {
10✔
245
               if(!sk_raw.empty()) {
5✔
246
                  Botan::Kyber_PrivateKey(sk_raw, mode);
4✔
247
               }
248
               if(!pk_raw.empty()) {
3✔
249
                  Botan::Kyber_PublicKey(pk_raw, mode);
3✔
250
               }
251
            });
×
252

253
            return result;
5✔
254
         } else {
255
            const auto skr = std::make_unique<Botan::Kyber_PrivateKey>(sk_raw, mode);
8✔
256
            const auto pkr = std::make_unique<Botan::Kyber_PublicKey>(pk_raw, mode);
8✔
257

258
            result.test_eq("sk's encoding of pk", skr->public_key_bits(), pk_raw);
16✔
259
            result.test_eq("sk's encoding of sk", skr->private_key_bits(), sk_raw);
16✔
260
            result.test_eq("pk's encoding of pk", skr->public_key_bits(), pk_raw);
24✔
261
         }
8✔
262

263
         result.end_timer();
8✔
264
         return result;
265
      }
39✔
266
};
267

268
BOTAN_REGISTER_TEST("pubkey", "kyber_encodings", Kyber_Encoding_Test);
269

270
class Kyber_Keygen_Tests final : public PK_Key_Generation_Test {
×
271
   public:
272
      std::vector<std::string> keygen_params() const override {
1✔
273
         return {
1✔
274
   #if defined(BOTAN_HAS_KYBER_90S)
275
            "Kyber-512-90s-r3", "Kyber-768-90s-r3", "Kyber-1024-90s-r3",
276
   #endif
277
   #if defined(BOTAN_HAS_KYBER)
278
               "Kyber-512-r3", "Kyber-768-r3", "Kyber-1024-r3",
279
   #endif
280
   #if defined(BOTAN_HAS_ML_KEM)
281
               "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024",
282
   #endif
283
         };
1✔
284
      }
285

286
      std::string algo_name(std::string_view param) const override {
9✔
287
         if(param.starts_with("Kyber-")) {
9✔
288
            return "Kyber";
6✔
289
         } else {
290
            return "ML-KEM";
3✔
291
         }
292
      }
293

294
      std::string algo_name() const override { throw Test_Error("No default algo name set for Kyber"); }
×
295

296
      std::unique_ptr<Botan::Public_Key> public_key_from_raw(std::string_view keygen_params,
9✔
297
                                                             std::string_view /* provider */,
298
                                                             std::span<const uint8_t> raw_pk) const override {
299
         return std::make_unique<Botan::Kyber_PublicKey>(raw_pk, Botan::KyberMode(keygen_params));
9✔
300
      }
301
};
302

303
BOTAN_REGISTER_TEST("pubkey", "kyber_keygen", Kyber_Keygen_Tests);
304

305
namespace {
306

307
template <size_t d>
308
void test_compress(Test::Result& res) {
5✔
309
   using namespace Botan;
310
   constexpr auto q = KyberConstants::Q;
5✔
311

312
   res.start_timer();
5✔
313

314
   for(uint16_t x = 0; x < q; ++x) {
16,650✔
315
      const uint32_t c = Kyber_Algos::compress<d>(x);
16,645✔
316
      const auto twotothed = (uint32_t(1) << d);
16,645✔
317
      const auto expected = (static_cast<double>(twotothed) / q) * x;
16,645✔
318
      const auto e = static_cast<uint32_t>(std::round(expected)) % twotothed;
16,645✔
319

320
      if(c != e) {
16,645✔
321
         res.test_failure(fmt("compress<{}>({}) = {}; expected {}", d, x, c, e));
×
322
         return;
×
323
      }
324
   }
325

326
   res.end_timer();
5✔
327
   res.test_success();
10✔
328
}
329

330
template <size_t d>
331
void test_decompress(Test::Result& result) {
5✔
332
   using namespace Botan;
333
   constexpr auto q = KyberConstants::Q;
5✔
334

335
   result.start_timer();
5✔
336

337
   const auto twotothed = (uint32_t(1) << d);
5✔
338
   using from_t = std::conditional_t<d <= 8, uint8_t, uint16_t>;
339

340
   for(from_t y = 0; y < twotothed; ++y) {
3,127✔
341
      const uint32_t c = Kyber_Algos::decompress<d>(y);
3,122✔
342
      const auto expected = (static_cast<double>(q) / twotothed) * y;
3,122✔
343
      const auto e = static_cast<uint32_t>(std::round(expected)) % q;
3,122✔
344

345
      if(c != e) {
3,122✔
346
         result.test_failure(fmt("decompress<{}>({}) = {}; expected {}", d, static_cast<uint16_t>(y), c, e));
×
347
         return;
×
348
      }
349
   }
350

351
   result.end_timer();
5✔
352
   result.test_success();
10✔
353
}
354

355
template <size_t d>
356
void test_compress_roundtrip(Test::Result& result) {
5✔
357
   using namespace Botan;
358
   constexpr auto q = KyberConstants::Q;
5✔
359

360
   result.start_timer();
5✔
361

362
   for(uint16_t x = 0; x < q && x < (1 << d); ++x) {
3,127✔
363
      const uint16_t c = Kyber_Algos::compress<d>(Kyber_Algos::decompress<d>(x));
3,122✔
364
      if(x != c) {
3,122✔
365
         result.test_failure(fmt("compress<{}>(decompress<{}>({})) != {}", d, d, x, c));
×
366
         return;
×
367
      }
368
   }
369

370
   result.end_timer();
5✔
371
   result.test_success();
10✔
372
}
373

374
std::vector<Test::Result> test_kyber_helpers() {
1✔
375
   return {
1✔
376
      Botan_Tests::CHECK("compress<1>", [](Test::Result& res) { test_compress<1>(res); }),
1✔
377
      Botan_Tests::CHECK("compress<4>", [](Test::Result& res) { test_compress<4>(res); }),
1✔
378
      Botan_Tests::CHECK("compress<5>", [](Test::Result& res) { test_compress<5>(res); }),
1✔
379
      Botan_Tests::CHECK("compress<10>", [](Test::Result& res) { test_compress<10>(res); }),
1✔
380
      Botan_Tests::CHECK("compress<11>", [](Test::Result& res) { test_compress<11>(res); }),
1✔
381

382
      Botan_Tests::CHECK("decompress<1>", [](Test::Result& res) { test_decompress<1>(res); }),
1✔
383
      Botan_Tests::CHECK("decompress<4>", [](Test::Result& res) { test_decompress<4>(res); }),
1✔
384
      Botan_Tests::CHECK("decompress<5>", [](Test::Result& res) { test_decompress<5>(res); }),
1✔
385
      Botan_Tests::CHECK("decompress<10>", [](Test::Result& res) { test_decompress<10>(res); }),
1✔
386
      Botan_Tests::CHECK("decompress<11>", [](Test::Result& res) { test_decompress<11>(res); }),
1✔
387

388
      Botan_Tests::CHECK("compress<1>(decompress())", [](Test::Result& res) { test_compress_roundtrip<1>(res); }),
1✔
389
      Botan_Tests::CHECK("compress<4>(decompress())", [](Test::Result& res) { test_compress_roundtrip<4>(res); }),
1✔
390
      Botan_Tests::CHECK("compress<5>(decompress())", [](Test::Result& res) { test_compress_roundtrip<5>(res); }),
1✔
391
      Botan_Tests::CHECK("compress<10>(decompress())>", [](Test::Result& res) { test_compress_roundtrip<10>(res); }),
1✔
392
      Botan_Tests::CHECK("compress<11>(decompress())>", [](Test::Result& res) { test_compress_roundtrip<11>(res); }),
1✔
393
   };
16✔
394
}
1✔
395

396
}  // namespace
397

398
BOTAN_REGISTER_TEST_FN("pubkey", "kyber_helpers", test_kyber_helpers);
399

400
#endif
401

402
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc