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

randombit / botan / 22045109103

15 Feb 2026 11:27PM UTC coverage: 90.043% (-0.01%) from 90.054%
22045109103

push

github

web-flow
Merge pull request #5342 from randombit/jack/test-h-arb-eq

Rename test_is_eq to test_arb_eq and require the type be printable

102325 of 113640 relevant lines covered (90.04%)

11463137.71 hits per line

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

94.48
/src/tests/test_cmce.cpp
1
/*
2
* Tests for Classic McEliece
3
*
4
* (C) 2023 Jack Lloyd
5
*     2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9

10
#include "tests.h"
11

12
#if defined(BOTAN_HAS_CLASSICMCELIECE)
13

14
   #include "test_arb_eq.h"
15
   #include "test_pubkey.h"
16
   #include "test_pubkey_pqc.h"
17
   #include "test_rng.h"
18
   #include <botan/cmce.h>
19
   #include <botan/hash.h>
20
   #include <botan/pk_algs.h>
21
   #include <botan/pubkey.h>
22
   #include <botan/internal/cmce_gf.h>
23
   #include <botan/internal/cmce_parameters.h>
24
   #include <botan/internal/cmce_poly.h>
25
   #include <algorithm>
26

27
namespace Botan_Tests {
28

29
namespace {
30

31
Botan::Classic_McEliece_Polynomial create_element_from_bytes(std::span<const uint8_t> bytes,
3✔
32
                                                             const Botan::Classic_McEliece_Polynomial_Ring& ring) {
33
   BOTAN_ARG_CHECK(bytes.size() == ring.degree() * 2, "Correct input size");
3✔
34
   std::vector<uint16_t> coef(ring.degree());
3✔
35
   Botan::load_le<uint16_t>(coef.data(), bytes.data(), ring.degree());
3✔
36

37
   std::vector<Botan::Classic_McEliece_GF> coeff_vec_gf;
3✔
38
   coeff_vec_gf.reserve(coef.size());
3✔
39
   for(const auto& coeff : coef) {
195✔
40
      coeff_vec_gf.push_back(Botan::Classic_McEliece_GF(Botan::CmceGfElem(coeff), ring.poly_f()));
192✔
41
   }
42
   return Botan::Classic_McEliece_Polynomial(coeff_vec_gf);
3✔
43
}
6✔
44

45
std::vector<Botan::Classic_McEliece_Parameter_Set> get_test_instances_all() {
16✔
46
   return {// All instances
16✔
47
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_348864,
48
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_348864f,
49

50
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_460896,
51
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_460896f,
52

53
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6688128,
54
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6688128f,
55
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6688128pc,
56
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6688128pcf,
57

58
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6960119,
59
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6960119f,
60
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6960119pc,
61
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6960119pcf,
62

63
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_8192128,
64
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_8192128f,
65
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_8192128pc,
66
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_8192128pcf};
16✔
67
}
68

69
std::vector<Botan::Classic_McEliece_Parameter_Set> get_test_instances_min() {
1✔
70
   return {// Testing with and without pc and f. Also testing 6960119 with m*t mod 8 != 0.
1✔
71
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_348864,
72
           Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_6960119pcf};
×
73
}
74

75
std::vector<Botan::Classic_McEliece_Parameter_Set> instances_to_test() {
16✔
76
   if(Test::run_long_tests()) {
16✔
77
      return get_test_instances_all();
16✔
78
   } else {
79
      return get_test_instances_min();
×
80
   }
81
}
82

83
bool skip_cmce_test(const std::string& params_str) {
16✔
84
   auto params = Botan::Classic_McEliece_Parameters::create(params_str);
16✔
85
   auto to_test = instances_to_test();
16✔
86
   return std::find(to_test.begin(), to_test.end(), params.parameter_set()) == to_test.end();
16✔
87
}
16✔
88
}  // namespace
89

90
class CMCE_Utility_Tests final : public Test {
1✔
91
   public:
92
      Test::Result expand_seed_test() {
1✔
93
         Test::Result result("Seed expansion");
1✔
94

95
         auto params =
1✔
96
            Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_348864);
1✔
97

98
         // Created using the reference implementation
99
         auto seed = Botan::hex_decode_locked("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
1✔
100

101
         auto exp_first_and_last_bytes = Botan::hex_decode(
1✔
102
            "543e2791fd98dbc1"    // first 8 bytes
103
            "d332a7c40776ca01");  // last 8 bytes
1✔
104

105
         const size_t byte_length =
1✔
106
            (params.n() + params.sigma2() * params.q() + params.sigma1() * params.t() + params.ell()) / 8;
1✔
107

108
         auto rand = params.prg(seed)->output_stdvec(byte_length);
1✔
109
         rand.erase(rand.begin() + 8, rand.end() - 8);
1✔
110

111
         result.test_bin_eq("Seed expansion", rand, exp_first_and_last_bytes);
1✔
112

113
         return result;
1✔
114
      }
3✔
115

116
      Test::Result irreducible_poly_gen_test() {
1✔
117
         Test::Result result("Irreducible Polynomial Generation");
1✔
118

119
         auto params =
1✔
120
            Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_348864);
1✔
121

122
         // Created using the reference implementation
123
         auto random_bits = Botan::CmceIrreducibleBits(Botan::hex_decode(
1✔
124
            "d9b8bb962a3f9dac0f832d243def581e7d26f4028de1ff9cd168460e5050ab095a32a372b40d720bd5d75389a6b3f08fa1d13cec60a4b716d4d6c240f2f80cd3"
125
            "cbc76ae0dddca164c1130da185bd04e890f2256fb9f4754864811e14ea5a43b8b3612d59cecde1b2fdb6362659a0193d2b7d4b9d79aa1801dde3ca90dc300773"));
1✔
126

127
         auto exp_g = Botan::Classic_McEliece_Minimal_Polynomial::from_bytes(
1✔
128
            Botan::hex_decode(
1✔
129
               "8d00a50f520a0307b8007c06cb04b9073b0f4a0f800fb706a60f2a05910a670b460375091209fc060a09ab036c09e5085a0df90d3506b404a30fda041d09970f"
130
               "1206d000e00aac01c00dc80f490cd80b4108330c0208cf00d602450ec00a21079806eb093f00de015f052905560917081b09270c820af002000c34094504cd03"),
131
            params.poly_f());
1✔
132

133
         auto g = params.poly_ring().compute_minimal_polynomial(random_bits);
1✔
134
         result.test_is_true("Minimize polynomial successful", g.has_value());
1✔
135
         result.test_is_true("Minimize polynomial", g.value().coef() == exp_g.coef());
2✔
136

137
         return result;
1✔
138
      }
2✔
139

140
      Test::Result gf_inv_test() {
1✔
141
         Test::Result result("GF inv test");
1✔
142

143
         auto params =
1✔
144
            Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_348864);
1✔
145

146
         auto v = params.gf(Botan::CmceGfElem(42));
1✔
147
         auto v_inv = v.inv();
1✔
148
         test_arb_eq(result, "Control bits creation", (v * v_inv).elem(), Botan::CmceGfElem(1));
1✔
149

150
         return result;
1✔
151
      }
1✔
152

153
      Test::Result gf_poly_mul_test() {
1✔
154
         Test::Result result("GF Poly Mul");
1✔
155

156
         auto params =
1✔
157
            Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::ClassicMcEliece_348864);
1✔
158

159
         const auto& field = params.poly_ring();
1✔
160

161
         auto val1 = create_element_from_bytes(
1✔
162
            Botan::hex_decode(
1✔
163
               "bb02d40437094c0ae4034c00b10fed090a04850f660c3b0e110eb409810a86015b0f5804ca0e78089806e20b5b03aa0bc2020b05ea03710da902340c390f630b"
164
               "bc07a70db20b9e0ee4038905a00a09090a0521045e0a0706370b5a00050a4100480c4d0e8f00730692093701fe04650dbe0fd00702011a04910360023f04fb0a"),
165
            field);
1✔
166

167
         auto val2 = create_element_from_bytes(
1✔
168
            Botan::hex_decode(
1✔
169
               "060c630b170abb00020fef03e501020e89098108bf01f30dd30900000e0d3d0ca404ec01190760021f088c09b90b0a06a702d104500f0f02f00a580287010a09"
170
               "4e01490d270c73051800bc0af303b901b202b50321002802b903ce0ab40806083f0a2d06d002df0f260811005c02a10b300e5c0ba20d14045003c50f2f02de02"),
171
            field);
1✔
172

173
         auto exp_mul = create_element_from_bytes(
1✔
174
            Botan::hex_decode(
1✔
175
               "370d090b19008f0efb01f5011b04f9054b0d1f071d0457011e09cd0dfa093c004f08500e670abb0567090000f603770a3905bf044408b8025805930b25012201"
176
               "8d0a560e840d960d9d0a280d1d06fc08d5078c06fe0cb406d0061e02c6090507d20eb10cb90146085c042e030c0e1a07910fcd0c5f0fda066c0cee061d01f40f"),
177
            field);
1✔
178

179
         auto mul = field.multiply(val1, val2);  // val1 * val2;
1✔
180
         result.test_is_true("GF multiplication", mul.coef() == exp_mul.coef());
2✔
181

182
         return result;
1✔
183
      }
1✔
184

185
      Test::Result rigged_rng_encryption_test() {
1✔
186
         // RNG that always returns zero bytes
187
         class All_Zero_RNG : public Botan::RandomNumberGenerator {
2✔
188
            public:
189
               void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> /* ignored */) override {
648✔
190
                  std::fill(output.begin(), output.end(), static_cast<uint8_t>(0));
648✔
191
               }
648✔
192

193
               std::string name() const override { return "All_Zero_RNG"; }
×
194

195
               bool accepts_input() const override { return false; }
×
196

197
               void clear() override {}
×
198

199
               bool is_seeded() const override { return true; }
×
200
         } rigged_rng;
1✔
201

202
         Test::Result result("No endless loop with rigged RNG");
1✔
203
         // Key creation should work even with a rigged RNG (PRNG is not used for key creation)
204
         auto private_key = Botan::create_private_key("ClassicMcEliece", rigged_rng, "348864f");
1✔
205
         if(!private_key) {
1✔
206
            result.test_failure("Key generation failed");
×
207
            return result;
208
         }
209
         auto enc = Botan::PK_KEM_Encryptor(*private_key, "Raw");
1✔
210
         result.test_throws("Many failed encryption attempts throws exception", [&] { enc.encrypt(rigged_rng); });
2✔
211

212
         return result;
1✔
213
      }
2✔
214

215
      std::vector<Test::Result> run() override {
1✔
216
         return {expand_seed_test(),
1✔
217
                 irreducible_poly_gen_test(),
218
                 gf_inv_test(),
219
                 gf_poly_mul_test(),
220
                 rigged_rng_encryption_test()};
6✔
221
      }
1✔
222
};
223

224
   #if defined(BOTAN_HAS_AES)
225
class CMCE_Invalid_Test : public Text_Based_Test {
226
   public:
227
      CMCE_Invalid_Test() :
1✔
228
            Text_Based_Test("pubkey/cmce_negative.vec", "seed,ct_invalid,ss_invalid", "ct_invalid_c1,ss_invalid_c1") {}
2✔
229

230
      Test::Result run_one_test(const std::string& params_str, const VarMap& vars) override {
2✔
231
         Test::Result result("CMCE Invalid Ciphertext Test");
2✔
232

233
         auto params = Botan::Classic_McEliece_Parameters::create(params_str);
2✔
234

235
         const auto kat_seed = vars.get_req_bin("seed");
2✔
236
         const auto ct_invalid = vars.get_req_bin("ct_invalid");
2✔
237
         const auto ref_ss_invalid = vars.get_req_bin("ss_invalid");
2✔
238

239
         const auto test_rng = std::make_unique<CTR_DRBG_AES256>(kat_seed);
2✔
240

241
         auto private_key = Botan::create_private_key("ClassicMcEliece", *test_rng, params_str);
2✔
242

243
         // Decaps an invalid ciphertext
244
         auto dec = Botan::PK_KEM_Decryptor(*private_key, *test_rng, "Raw");
2✔
245
         auto decaps_ct_invalid = dec.decrypt(ct_invalid);
2✔
246

247
         result.test_bin_eq("Decaps an invalid encapsulated key", decaps_ct_invalid, ref_ss_invalid);
2✔
248

249
         if(params.is_pc()) {
2✔
250
            // For pc variants, additionally check the plaintext confirmation (pc) logic by
251
            // flipping a bit in the second part of the ciphertext (C_1 in pc). In this case
252
            // C_0 is decoded correctly, but pc will change the shared secret, since C_1' != C_1.
253
            const auto ct_invalid_c1 = vars.get_opt_bin("ct_invalid_c1");
1✔
254
            const auto ref_ss_invalid_c1 = vars.get_opt_bin("ss_invalid_c1");
1✔
255
            auto decaps_ct_invalid_c1 = dec.decrypt(ct_invalid_c1);
1✔
256

257
            result.test_bin_eq("Decaps with invalid C_1 in pc", decaps_ct_invalid_c1, ref_ss_invalid_c1);
1✔
258
         }
3✔
259

260
         return result;
2✔
261
      }
12✔
262
};
263
   #endif  // BOTAN_HAS_AES
264

265
class CMCE_Generic_Keygen_Tests final : public PK_Key_Generation_Test {
1✔
266
   public:
267
      std::vector<std::string> keygen_params() const override {
1✔
268
         std::vector<std::string> res;
1✔
269
         for(const auto& param_set : get_test_instances_min()) {
3✔
270
            res.push_back(param_set.to_string());
4✔
271
         }
×
272
         return res;
1✔
273
      }
×
274

275
      std::unique_ptr<Botan::Public_Key> public_key_from_raw(std::string_view keygen_params,
2✔
276
                                                             std::string_view /*provider*/,
277
                                                             std::span<const uint8_t> raw_key_bits) const override {
278
         return std::make_unique<Botan::Classic_McEliece_PublicKey>(
2✔
279
            raw_key_bits, Botan::Classic_McEliece_Parameter_Set::from_string(keygen_params));
2✔
280
      }
281

282
      std::string algo_name() const override { return "ClassicMcEliece"; }
2✔
283
};
284

285
class Classic_McEliece_KAT_Tests final : public Botan_Tests::PK_PQC_KEM_KAT_Test {
286
   public:
287
      Classic_McEliece_KAT_Tests() : PK_PQC_KEM_KAT_Test("ClassicMcEliece", "pubkey/cmce_kat_hashed.vec") {}
2✔
288

289
   private:
290
      Botan::Classic_McEliece_Parameters get_params(const std::string& header) const {
16✔
291
         return Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::from_string(header));
16✔
292
      }
293

294
      bool is_available(const std::string& alg_name) const final { return !skip_cmce_test(alg_name); }
16✔
295

296
      std::vector<uint8_t> map_value(const std::string& /*params*/,
64✔
297
                                     std::span<const uint8_t> value,
298
                                     VarType var_type) const final {
299
         if(var_type == VarType::Ciphertext || var_type == VarType::SharedSecret) {
64✔
300
            return {value.begin(), value.end()};
32✔
301
         }
302
         auto hash = Botan::HashFunction::create_or_throw("SHAKE-256(512)");
32✔
303
         return hash->process<std::vector<uint8_t>>(value);
32✔
304
      }
32✔
305

306
      Fixed_Output_RNG rng_for_keygen(const std::string& /*params*/, Botan::RandomNumberGenerator& rng) const final {
16✔
307
         const auto seed = rng.random_vec(Botan::Classic_McEliece_Parameters::seed_len());
16✔
308
         return Fixed_Output_RNG(seed);
16✔
309
      }
16✔
310

311
      Fixed_Output_RNG rng_for_encapsulation(const std::string& alg_name,
16✔
312
                                             Botan::RandomNumberGenerator& rng) const final {
313
         // There is no way to tell exactly how much randomness is
314
         // needed for encapsulation (rejection sampling)
315
         // For testing we use a number that fits for all test cases
316
         auto params = get_params(alg_name);
16✔
317
         const size_t max_attempts = 100;
16✔
318
         const size_t bits_per_attempt = (params.sigma1() / 8) * params.tau();
16✔
319

320
         std::vector<uint8_t> rand_buffer;
16✔
321
         for(size_t attempt = 0; attempt < max_attempts; ++attempt) {
1,616✔
322
            auto random_bytes = rng.random_vec(bits_per_attempt);
1,600✔
323
            rand_buffer.insert(rand_buffer.end(), random_bytes.begin(), random_bytes.end());
1,600✔
324
         }
1,600✔
325

326
         return Fixed_Output_RNG(rand_buffer);
16✔
327
      }
16✔
328

329
      void inspect_rng_after_encaps(const std::string& /*params*/,
16✔
330
                                    const Fixed_Output_RNG& /*rng*/,
331
                                    Test::Result& /*result*/) const final {
332
         // Encaps uses any number of random bytes, so we cannot check the RNG
333
      }
16✔
334
};
335

336
BOTAN_REGISTER_TEST("cmce", "cmce_utility", CMCE_Utility_Tests);
337
BOTAN_REGISTER_TEST("cmce", "cmce_generic_keygen", CMCE_Generic_Keygen_Tests);
338
BOTAN_REGISTER_TEST("cmce", "cmce_generic_kat", Classic_McEliece_KAT_Tests);
339
   #if defined(BOTAN_HAS_AES)
340
BOTAN_REGISTER_TEST("cmce", "cmce_invalid", CMCE_Invalid_Test);
341
   #endif
342

343
}  // namespace Botan_Tests
344

345
#endif  // BOTAN_HAS_CLASSICMCELIECE
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

© 2026 Coveralls, Inc