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

randombit / botan / 22224530733

20 Feb 2026 12:42PM UTC coverage: 90.33% (-0.005%) from 90.335%
22224530733

push

github

web-flow
Merge pull request #5361 from randombit/jack/fix-frodokem-concurrency

Fix FrodoKEM concurrency issue

102999 of 114025 relevant lines covered (90.33%)

11685979.94 hits per line

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

86.71
/src/tests/test_concurrent_pk.cpp
1
/*
2
* (C) 2026 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) && defined(BOTAN_TARGET_OS_HAS_THREADS)
10
   #include <botan/pk_algs.h>
11
   #include <botan/pubkey.h>
12
   #include <botan/rng.h>
13
   #include <botan/internal/fmt.h>
14
   #include <future>
15
   #include <sstream>
16
#endif
17

18
namespace Botan_Tests {
19

20
#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) && defined(BOTAN_TARGET_OS_HAS_THREADS)
21

22
/*
23
* Test that public key operations (signing, verification, encryption, decryption, KEM, key
24
* agreement) with a shared key from multiple threads produce correct results without racing.
25
*
26
* TODO: Add concurrent test for ECIES handling
27
*/
28

29
namespace {
30

31
constexpr size_t ConcurrentThreads = 10;  // arbitrary
32

33
class ConcurrentPkTestCase {
34
   public:
35
      ConcurrentPkTestCase(std::string_view pk_algo, std::string_view keygen_params, std::string_view op_params = "") :
36✔
36
            m_pk_algo(pk_algo), m_keygen_params(keygen_params), m_op_params(op_params) {}
108✔
37

38
      const std::string& algo_name() const { return m_pk_algo; }
27✔
39

40
      const std::string& op_params() const { return m_op_params; }
418✔
41

42
      Test::Result result(std::string_view operation) const {
53✔
43
         std::ostringstream name;
53✔
44
         name << "Concurrent " << m_pk_algo;
53✔
45
         if(!m_keygen_params.empty()) {
53✔
46
            name << " " << m_keygen_params;
43✔
47
         }
48
         if(!m_op_params.empty()) {
53✔
49
            name << " " << m_op_params;
34✔
50
         }
51
         name << " " << operation;
53✔
52

53
         return Test::Result(name.str());
106✔
54
      }
53✔
55

56
      Test::Result skip_missing(std::string_view operation) const {
×
57
         auto result = this->result(operation);
×
58
         result.test_note("Skipping due to missing algorithm", this->algo_name());
×
59
         return result;
×
60
      }
×
61

62
      std::unique_ptr<Botan::Private_Key> try_create_key(Botan::RandomNumberGenerator& rng) const {
190✔
63
         try {
190✔
64
            return Botan::create_private_key(m_pk_algo, rng, m_keygen_params);
190✔
65
         } catch(Botan::Lookup_Error&) {
×
66
            return nullptr;
×
67
         } catch(Botan::Not_Implemented&) {
×
68
            return nullptr;
×
69
         }
×
70
      }
71

72
   private:
73
      std::string m_pk_algo;
74
      std::string m_keygen_params;
75
      std::string m_op_params;
76
};
77

78
Test::Result test_concurrent_signing(const ConcurrentPkTestCase& tc,
10✔
79
                                     const Botan::Private_Key& privkey,
80
                                     const Botan::Public_Key& pubkey) {
81
   auto result = tc.result("signing");
10✔
82
   auto rng = Test::new_rng(result.who());
10✔
83
   const auto test_message = rng->random_vec(32);
10✔
84

85
   std::vector<std::future<std::vector<uint8_t>>> futures;
10✔
86
   futures.reserve(ConcurrentThreads);
10✔
87

88
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
110✔
89
      futures.push_back(std::async(std::launch::async, [&, i]() -> std::vector<uint8_t> {
200✔
90
         auto thread_rng = Test::new_rng(Botan::fmt("{} thread {}", result.who(), i));
100✔
91
         Botan::PK_Signer signer(privkey, *thread_rng, tc.op_params());
100✔
92
         return signer.sign_message(test_message, *thread_rng);
100✔
93
      }));
200✔
94
   }
95

96
   Botan::PK_Verifier verifier(pubkey, tc.op_params());
10✔
97

98
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
110✔
99
      try {
100✔
100
         const auto signature = futures[i].get();
100✔
101

102
         if(signature.empty()) {
100✔
103
            result.test_failure(Botan::fmt("Thread {} produced empty signature", i));
×
104
         } else {
105
            const bool valid = verifier.verify_message(test_message, signature);
100✔
106
            result.test_is_true(Botan::fmt("Thread {} signature is valid", i), valid);
200✔
107
         }
108
      } catch(std::exception& e) {
100✔
109
         result.test_failure(Botan::fmt("Thread {} failed: {}", i, e.what()));
×
110
      }
×
111
   }
112

113
   return result;
20✔
114
}
30✔
115

116
Test::Result test_concurrent_verification(const ConcurrentPkTestCase& tc,
10✔
117
                                          const Botan::Private_Key& privkey,
118
                                          const Botan::Public_Key& pubkey) {
119
   auto result = tc.result("verification");
10✔
120
   auto rng = Test::new_rng(result.who());
10✔
121
   const auto test_message = rng->random_vec(32);
10✔
122

123
   Botan::PK_Signer signer(privkey, *rng, tc.op_params());
10✔
124
   const auto signature = signer.sign_message(test_message, *rng);
10✔
125

126
   std::vector<std::future<bool>> futures;
10✔
127
   futures.reserve(ConcurrentThreads);
10✔
128

129
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
110✔
130
      futures.push_back(std::async(std::launch::async, [&]() -> bool {
200✔
131
         Botan::PK_Verifier verifier(pubkey, tc.op_params());
100✔
132
         return verifier.verify_message(test_message, signature);
200✔
133
      }));
100✔
134
   }
135

136
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
110✔
137
      try {
100✔
138
         const bool valid = futures[i].get();
100✔
139
         result.test_is_true(Botan::fmt("Thread {} verification succeeded", i), valid);
200✔
140
      } catch(std::exception& e) {
×
141
         result.test_failure(Botan::fmt("Thread {} threw: {}", i, e.what()));
×
142
      }
×
143
   }
144

145
   return result;
20✔
146
}
40✔
147

148
Test::Result test_concurrent_encryption(const ConcurrentPkTestCase& tc,
2✔
149
                                        const Botan::Private_Key& privkey,
150
                                        const Botan::Public_Key& pubkey) {
151
   auto result = tc.result("encryption");
2✔
152
   auto rng = Test::new_rng(result.who());
2✔
153
   const auto test_message = rng->random_vec(32);
2✔
154

155
   std::vector<std::future<std::vector<uint8_t>>> futures;
2✔
156
   futures.reserve(ConcurrentThreads);
2✔
157

158
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
22✔
159
      futures.push_back(std::async(std::launch::async, [&, i]() -> std::vector<uint8_t> {
40✔
160
         auto thread_rng = Test::new_rng(Botan::fmt("{} thread {}", result.who(), i));
20✔
161
         const Botan::PK_Encryptor_EME encryptor(pubkey, *thread_rng, tc.op_params());
20✔
162
         return encryptor.encrypt(test_message, *thread_rng);
20✔
163
      }));
40✔
164
   }
165

166
   const Botan::PK_Decryptor_EME decryptor(privkey, *rng, tc.op_params());
2✔
167

168
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
22✔
169
      try {
20✔
170
         const auto ciphertext = futures[i].get();
20✔
171
         const auto plaintext = decryptor.decrypt(ciphertext);
20✔
172
         result.test_bin_eq(Botan::fmt("Thread {} decrypts correctly", i), plaintext, test_message);
40✔
173
      } catch(std::exception& e) {
40✔
174
         result.test_failure(Botan::fmt("Thread {} encrypt threw: {}", i, e.what()));
×
175
      }
×
176
   }
177

178
   return result;
4✔
179
}
6✔
180

181
Test::Result test_concurrent_decryption(const ConcurrentPkTestCase& tc,
2✔
182
                                        const Botan::Private_Key& privkey,
183
                                        const Botan::Public_Key& pubkey) {
184
   auto result = tc.result("decryption");
2✔
185
   auto rng = Test::new_rng(result.who());
2✔
186
   const auto test_message = rng->random_vec(32);
2✔
187

188
   const Botan::PK_Encryptor_EME encryptor(pubkey, *rng, tc.op_params());
2✔
189
   const auto ciphertext = encryptor.encrypt(test_message, *rng);
2✔
190

191
   std::vector<std::future<Botan::secure_vector<uint8_t>>> futures;
2✔
192
   futures.reserve(ConcurrentThreads);
2✔
193

194
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
22✔
195
      futures.push_back(std::async(std::launch::async, [&, i]() -> Botan::secure_vector<uint8_t> {
40✔
196
         auto thread_rng = Test::new_rng(Botan::fmt("{} thread {}", result.who(), i));
20✔
197
         const Botan::PK_Decryptor_EME decryptor(privkey, *thread_rng, tc.op_params());
20✔
198
         return decryptor.decrypt(ciphertext);
20✔
199
      }));
40✔
200
   }
201

202
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
22✔
203
      try {
20✔
204
         const auto plaintext = futures[i].get();
20✔
205
         result.test_bin_eq(Botan::fmt("Thread {} decrypts correctly", i), plaintext, test_message);
40✔
206
      } catch(std::exception& e) {
20✔
207
         result.test_failure(Botan::fmt("Thread {} decrypt threw: {}", i, e.what()));
×
208
      }
×
209
   }
210

211
   return result;
4✔
212
}
8✔
213

214
Test::Result test_concurrent_kem_encap(const ConcurrentPkTestCase& tc,
5✔
215
                                       const Botan::Private_Key& privkey,
216
                                       const Botan::Public_Key& pubkey) {
217
   auto result = tc.result("KEM encapsulate");
5✔
218
   auto rng = Test::new_rng(result.who());
5✔
219

220
   std::vector<std::future<Botan::KEM_Encapsulation>> futures;
5✔
221
   futures.reserve(ConcurrentThreads);
5✔
222

223
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
55✔
224
      futures.push_back(std::async(std::launch::async, [&, i]() -> Botan::KEM_Encapsulation {
100✔
225
         auto thread_rng = Test::new_rng(Botan::fmt("{} thread {}", result.who(), i));
50✔
226
         Botan::PK_KEM_Encryptor encryptor(pubkey, tc.op_params());
50✔
227
         return encryptor.encrypt(*thread_rng);
50✔
228
      }));
100✔
229
   }
230

231
   Botan::PK_KEM_Decryptor decryptor(privkey, *rng, tc.op_params());
5✔
232

233
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
55✔
234
      try {
50✔
235
         const auto kr = futures[i].get();
50✔
236
         const auto shared_key = decryptor.decrypt(kr.encapsulated_shared_key(), 32);
50✔
237
         result.test_bin_eq(Botan::fmt("Thread {} shared key matches", i), shared_key, kr.shared_key());
100✔
238
      } catch(std::exception& e) {
50✔
239
         result.test_failure(Botan::fmt("Thread {} encapsulate threw: {}", i, e.what()));
×
240
      }
×
241
   }
242

243
   return result;
10✔
244
}
10✔
245

246
Test::Result test_concurrent_kem_decap(const ConcurrentPkTestCase& tc,
5✔
247
                                       const Botan::Private_Key& privkey,
248
                                       const Botan::Public_Key& pubkey) {
249
   auto result = tc.result("KEM decapsulate");
5✔
250
   auto rng = Test::new_rng(result.who());
5✔
251

252
   Botan::PK_KEM_Encryptor encryptor(pubkey, tc.op_params());
5✔
253
   auto kem_enc = encryptor.encrypt(*rng);
5✔
254

255
   std::vector<std::future<Botan::secure_vector<uint8_t>>> futures;
5✔
256
   futures.reserve(ConcurrentThreads);
5✔
257

258
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
55✔
259
      futures.push_back(std::async(std::launch::async, [&, i]() -> Botan::secure_vector<uint8_t> {
100✔
260
         auto thread_rng = Test::new_rng(Botan::fmt("{} thread {}", result.who(), i));
50✔
261
         Botan::PK_KEM_Decryptor decryptor(privkey, *thread_rng, tc.op_params());
50✔
262
         return decryptor.decrypt(kem_enc.encapsulated_shared_key(), 0);
50✔
263
      }));
100✔
264
   }
265

266
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
55✔
267
      try {
50✔
268
         const auto shared_key = futures[i].get();
50✔
269
         result.test_bin_eq(Botan::fmt("Thread {} shared key matches", i), shared_key, kem_enc.shared_key());
100✔
270
      } catch(std::exception& e) {
50✔
271
         result.test_failure(Botan::fmt("Thread {} decapsulate threw: {}", i, e.what()));
×
272
      }
×
273
   }
274

275
   return result;
10✔
276
}
10✔
277

278
Test::Result test_concurrent_key_agreement(const ConcurrentPkTestCase& tc) {
4✔
279
   auto result = tc.result("key agreement");
4✔
280

281
   auto rng = Test::new_rng(result.who());
4✔
282
   auto our_key = tc.try_create_key(*rng);
4✔
283
   if(!our_key) {
4✔
284
      result.test_note("Skipping due to missing algorithm");
×
285
      return result;
×
286
   }
287

288
   auto peer_key = tc.try_create_key(*rng);
4✔
289

290
   const auto* our_ka_key = dynamic_cast<Botan::PK_Key_Agreement_Key*>(our_key.get());
4✔
291
   const auto* peer_ka_key = dynamic_cast<Botan::PK_Key_Agreement_Key*>(peer_key.get());
4✔
292
   if(our_ka_key == nullptr || peer_ka_key == nullptr) {
4✔
293
      result.test_failure("Key does not support key agreement");
×
294
      return result;
295
   }
296

297
   const auto peer_public = peer_ka_key->public_value();
4✔
298

299
   // Compute reference shared secret single-threaded
300
   const Botan::PK_Key_Agreement ref_ka(*our_key, *rng, tc.op_params());
4✔
301
   const auto reference_secret = ref_ka.derive_key(32, peer_public).bits_of();
8✔
302

303
   std::vector<std::future<std::vector<uint8_t>>> futures;
4✔
304
   futures.reserve(ConcurrentThreads);
4✔
305

306
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
44✔
307
      futures.push_back(std::async(std::launch::async, [&, i]() -> std::vector<uint8_t> {
80✔
308
         auto thread_rng = Test::new_rng(Botan::fmt("{} thread {}", result.who(), i));
40✔
309
         const Botan::PK_Key_Agreement ka(*our_key, *thread_rng, tc.op_params());
40✔
310
         return Botan::unlock(ka.derive_key(32, peer_public).bits_of());
120✔
311
      }));
80✔
312
   }
313

314
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
44✔
315
      try {
40✔
316
         const auto shared_secret = futures[i].get();
40✔
317
         result.test_bin_eq(Botan::fmt("Thread {} shared secret matches", i), shared_secret, reference_secret);
80✔
318
      } catch(std::exception& e) {
40✔
319
         result.test_failure(Botan::fmt("Thread {} threw: {}", i, e.what()));
×
320
      }
×
321
   }
322

323
   return result;
4✔
324
}
24✔
325

326
Test::Result test_concurrent_key_generation(const ConcurrentPkTestCase& tc) {
15✔
327
   auto result = tc.result("key generation");
15✔
328

329
   auto rng = Test::new_rng(result.who());
15✔
330

331
   if(tc.try_create_key(*rng) == nullptr) {
30✔
332
      result.test_note("Keygen not available");
×
333
      return result;
×
334
   }
335

336
   std::vector<std::future<std::unique_ptr<Botan::Private_Key>>> futures;
15✔
337
   futures.reserve(ConcurrentThreads);
15✔
338

339
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
165✔
340
      futures.push_back(std::async(std::launch::async, [&, i]() -> std::unique_ptr<Botan::Private_Key> {
300✔
341
         auto thread_rng = Test::new_rng(Botan::fmt("{} thread {}", result.who(), i));
150✔
342
         return tc.try_create_key(*thread_rng);
150✔
343
      }));
150✔
344
   }
345

346
   for(size_t i = 0; i != ConcurrentThreads; ++i) {
165✔
347
      try {
150✔
348
         const auto sk = futures[i].get();
150✔
349
         result.test_not_null(Botan::fmt("Thread {} generated a key", i), sk.get());
150✔
350

351
         if(sk) {
150✔
352
            result.test_is_true(Botan::fmt("Thread {} generated key seems valid", i), sk->check_key(*rng, true));
300✔
353
         }
354
      } catch(std::exception& e) {
150✔
355
         result.test_failure(Botan::fmt("Thread {} threw: {}", i, e.what()));
×
356
      }
×
357
   }
358

359
   return result;
15✔
360
}
30✔
361

362
class Concurrent_Public_Key_Operations_Test : public Test {
1✔
363
   public:
364
      std::vector<Test::Result> run() override {
1✔
365
         std::vector<Test::Result> results;
1✔
366

367
         concurrent_signing_and_verification_tests(results);
1✔
368
         concurrent_encryption_tests(results);
1✔
369
         concurrent_kem_tests(results);
1✔
370
         concurrent_key_agreement_tests(results);
1✔
371
         concurrent_key_generation_tests(results);
1✔
372

373
         return results;
1✔
374
      }
×
375

376
   private:
377
      void concurrent_signing_and_verification_tests(std::vector<Test::Result>& results) {
1✔
378
         const std::vector<ConcurrentPkTestCase> test_cases = {
1✔
379
            ConcurrentPkTestCase("RSA", "1536", "PKCS1v15(SHA-256)"),
380
            ConcurrentPkTestCase("ECDSA", "secp256r1", "SHA-256"),
381
            ConcurrentPkTestCase("ECKCDSA", "secp256r1", "SHA-256"),
382
            ConcurrentPkTestCase("ECGDSA", "secp256r1", "SHA-256"),
383
            ConcurrentPkTestCase("DSA", "dsa/jce/1024", "SHA-256"),
384
            ConcurrentPkTestCase("SM2", "sm2p256v1", "SM3"),
385
            ConcurrentPkTestCase("Ed25519", "", "Pure"),
386
            ConcurrentPkTestCase("Ed448", "", "Pure"),
387
            ConcurrentPkTestCase("SLH-DSA", "SLH-DSA-SHA2-128f"),
388
            ConcurrentPkTestCase("HSS-LMS", "SHA-256,HW(5,8)"),
389
         };
11✔
390

391
         for(const auto& tc : test_cases) {
11✔
392
            if(tc.algo_name() == "XMSS" && !Test::run_long_tests()) {
10✔
393
               continue;
×
394
            }
395

396
            auto rng = Test::new_rng(tc.algo_name());
10✔
397

398
            if(auto privkey = tc.try_create_key(*rng)) {
10✔
399
               auto pubkey = privkey->public_key();
10✔
400
               results.push_back(test_concurrent_signing(tc, *privkey, *pubkey));
20✔
401
               results.push_back(test_concurrent_verification(tc, *privkey, *pubkey));
20✔
402
            } else {
10✔
403
               results.push_back(tc.skip_missing("signing"));
×
404
            }
10✔
405
         }
10✔
406
      }
3✔
407

408
      void concurrent_encryption_tests(std::vector<Test::Result>& results) {
1✔
409
         const std::vector<ConcurrentPkTestCase> test_cases = {
1✔
410
            ConcurrentPkTestCase("RSA", "1536", "OAEP(SHA-256)"),
411
            ConcurrentPkTestCase("ElGamal", "modp/ietf/1536", "PKCS1v15"),
412
         };
3✔
413

414
         for(const auto& tc : test_cases) {
3✔
415
            auto rng = Test::new_rng(tc.algo_name());
2✔
416

417
            if(auto privkey = tc.try_create_key(*rng)) {
2✔
418
               auto pubkey = privkey->public_key();
2✔
419
               results.push_back(test_concurrent_encryption(tc, *privkey, *pubkey));
4✔
420
               results.push_back(test_concurrent_decryption(tc, *privkey, *pubkey));
4✔
421
            } else {
2✔
422
               results.push_back(tc.skip_missing("encryption"));
×
423
            }
2✔
424
         }
2✔
425
      }
3✔
426

427
      void concurrent_kem_tests(std::vector<Test::Result>& results) {
1✔
428
         const std::vector<ConcurrentPkTestCase> test_cases = {
1✔
429
            ConcurrentPkTestCase("RSA", "1536", "Raw"),
430
            ConcurrentPkTestCase("ClassicMcEliece", "348864f", "Raw"),
431
            ConcurrentPkTestCase("McEliece", "1632,33", "Raw"),
432
            ConcurrentPkTestCase("FrodoKEM", "FrodoKEM-640-SHAKE", "Raw"),
433
            ConcurrentPkTestCase("FrodoKEM", "FrodoKEM-640-AES", "Raw"),
434
         };
6✔
435

436
         for(const auto& tc : test_cases) {
6✔
437
            auto rng = Test::new_rng(tc.algo_name());
5✔
438
            if(auto privkey = tc.try_create_key(*rng)) {
5✔
439
               auto pubkey = privkey->public_key();
5✔
440
               results.push_back(test_concurrent_kem_encap(tc, *privkey, *pubkey));
10✔
441
               results.push_back(test_concurrent_kem_decap(tc, *privkey, *pubkey));
10✔
442
            } else {
5✔
443
               results.push_back(tc.skip_missing("KEM encapsulate"));
×
444
            }
5✔
445
         }
5✔
446
      }
3✔
447

448
      void concurrent_key_agreement_tests(std::vector<Test::Result>& results) {
1✔
449
         const std::vector<ConcurrentPkTestCase> test_cases = {
1✔
450
            ConcurrentPkTestCase("DH", "modp/ietf/1536", "Raw"),
451
            ConcurrentPkTestCase("ECDH", "secp256r1", "Raw"),
452
            ConcurrentPkTestCase("X25519", "", "Raw"),
453
            ConcurrentPkTestCase("X448", "", "Raw"),
454
         };
5✔
455

456
         for(const auto& tc : test_cases) {
5✔
457
            results.push_back(test_concurrent_key_agreement(tc));
8✔
458
         }
459
      }
3✔
460

461
      void concurrent_key_generation_tests(std::vector<Test::Result>& results) {
1✔
462
         const std::vector<ConcurrentPkTestCase> test_cases = {
1✔
463
            ConcurrentPkTestCase("ClassicMcEliece", "348864f"),
464
            ConcurrentPkTestCase("DH", "modp/ietf/1536"),
465
            ConcurrentPkTestCase("DSA", "dsa/jce/1024"),
466
            ConcurrentPkTestCase("ECDH", "secp256r1"),
467
            ConcurrentPkTestCase("ECDSA", "secp256r1"),
468
            ConcurrentPkTestCase("ECGDSA", "secp256r1"),
469
            ConcurrentPkTestCase("ECKCDSA", "secp256r1"),
470
            ConcurrentPkTestCase("Ed25519", ""),
471
            ConcurrentPkTestCase("Ed448", ""),
472
            ConcurrentPkTestCase("HSS-LMS", "SHA-256,HW(5,8)"),
473
            ConcurrentPkTestCase("RSA", "1536"),
474
            ConcurrentPkTestCase("SLH-DSA", "SLH-DSA-SHA2-128f"),
475
            ConcurrentPkTestCase("SM2", "sm2p256v1"),
476
            ConcurrentPkTestCase("X25519", ""),
477
            ConcurrentPkTestCase("X448", ""),
478
         };
16✔
479

480
         for(const auto& tc : test_cases) {
16✔
481
            results.push_back(test_concurrent_key_generation(tc));
30✔
482
         }
483
      }
3✔
484
};
485

486
BOTAN_REGISTER_SERIALIZED_TEST("pubkey", "pk_concurrent_ops", Concurrent_Public_Key_Operations_Test);
487

488
}  // namespace
489

490
#endif
491

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

© 2026 Coveralls, Inc