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

randombit / botan / 11331525401

14 Oct 2024 04:29PM UTC coverage: 91.093% (-0.03%) from 91.12%
11331525401

Pull #4291

github

web-flow
Merge f5ffe99f5 into ed74c9542
Pull Request #4291: PQC: SLH-DSA

90346 of 99180 relevant lines covered (91.09%)

9678761.99 hits per line

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

90.4
/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp
1
/*
2
* SLH-DSA - Stateless Hash-Based Digital Signature Standard - FIPS 205
3
* (C) 2023 Jack Lloyd
4
*     2023 Fabian Albert, René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
**/
8

9
#include <botan/sphincsplus.h>
10

11
#include <botan/rng.h>
12
#include <botan/internal/int_utils.h>
13
#include <botan/internal/pk_ops_impl.h>
14
#include <botan/internal/sp_fors.h>
15
#include <botan/internal/sp_hash.h>
16
#include <botan/internal/sp_hypertree.h>
17
#include <botan/internal/sp_treehash.h>
18
#include <botan/internal/sp_types.h>
19
#include <botan/internal/sp_wots.h>
20
#include <botan/internal/sp_xmss.h>
21
#include <botan/internal/stl_util.h>
22

23
#include <utility>
24

25
#if !defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) and !defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE) and \
26
   !defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) and !defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
27
static_assert(
28
   false,
29
   "botan module 'sphincsplus_common' is useful only when enabling at least 'sphincsplus_sha2', 'sphincsplus_shake', 'slh_dsa_sha2', or 'slh_dsa_shake'");
30
#endif
31

32
namespace Botan {
33

34
namespace {
35
// FIPS 205, Algorithm 22, line 8
36
SphincsMessageInternal prepare_message(SphincsInputMessage msg,
250✔
37
                                       const Sphincs_Parameters& params,
38
                                       StrongSpan<const SphincsContext> context) {
39
   BOTAN_ARG_CHECK(params.is_slh_dsa() || context.empty(), "Context is not supported for SPHINCS+");
250✔
40
#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
41
   if(params.is_slh_dsa()) {
250✔
42
      // prefix (no pre-hash mode): input mode byte + |ctx| + ctx
43
      const uint8_t input_mode_byte = 0x00;  // Pure (TODO: pre-hash mode: 0x01)
218✔
44
      return {
218✔
45
         .prefix = concat<SphincsMessagePrefix>(
46
            store_be(input_mode_byte), store_be(checked_cast_to<uint8_t>(context.size())), context),
436✔
47
         .message = std::move(msg),
48
      };
49
   }
50
#endif
51
#if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE)
52
   if(!params.is_slh_dsa()) {
32✔
53
      // SPHINCS+ Round 3.1 uses the message without any prefix
54
      return {
32✔
55
         .prefix = {},  // SPHINCS+ has no prefix
56
         .message = std::move(msg),
57
      };
32✔
58
   }
59
#endif
60
   throw Internal_Error("Missing message preparation logic for SLH-DSA or SPHINCS+");
×
61
}
250✔
62
}  // namespace
63

64
class SphincsPlus_PublicKeyInternal final {
65
   public:
66
      SphincsPlus_PublicKeyInternal(Sphincs_Parameters params,
100✔
67
                                    SphincsPublicSeed public_seed,
68
                                    SphincsTreeNode sphincs_root) :
100✔
69
            m_params(params), m_public_seed(std::move(public_seed)), m_sphincs_root(std::move(sphincs_root)) {}
100✔
70

71
      SphincsPlus_PublicKeyInternal(Sphincs_Parameters params, std::span<const uint8_t> key_bits) : m_params(params) {
270✔
72
         if(key_bits.size() != m_params.public_key_bytes()) {
270✔
73
            throw Decoding_Error("SLH-DSA (or SPHINCS+) Public Key doesn't have the expected length");
×
74
         }
75

76
         BufferSlicer s(key_bits);
270✔
77
         m_public_seed = s.copy<SphincsPublicSeed>(params.n());
270✔
78
         m_sphincs_root = s.copy<SphincsTreeNode>(params.n());
270✔
79

80
         BOTAN_ASSERT_NOMSG(s.empty());
270✔
81
      }
270✔
82

83
      std::vector<uint8_t> key_bits() const { return concat<std::vector<uint8_t>>(m_public_seed, m_sphincs_root); }
669✔
84

85
      const SphincsPublicSeed& seed() const { return m_public_seed; }
72✔
86

87
      const SphincsTreeNode& root() const { return m_sphincs_root; }
400✔
88

89
      const Sphincs_Parameters& parameters() const { return m_params; }
1,877✔
90

91
   private:
92
      Sphincs_Parameters m_params;
93
      SphincsPublicSeed m_public_seed;
94
      SphincsTreeNode m_sphincs_root;
95
};
96

97
class SphincsPlus_PrivateKeyInternal final {
98
   public:
99
      SphincsPlus_PrivateKeyInternal(SphincsSecretSeed secret_seed, SphincsSecretPRF prf) :
100✔
100
            m_secret_seed(std::move(secret_seed)), m_prf(std::move(prf)) {}
100✔
101

102
      SphincsPlus_PrivateKeyInternal(const Sphincs_Parameters& params, std::span<const uint8_t> key_bits) {
162✔
103
         if(key_bits.size() != params.private_key_bytes() - params.public_key_bytes()) {
162✔
104
            throw Decoding_Error("SLH-DSA (or SPHINCS+) Private Key doesn't have the expected length");
×
105
         }
106

107
         BufferSlicer s(key_bits);
162✔
108
         m_secret_seed = s.copy<SphincsSecretSeed>(params.n());
162✔
109
         m_prf = s.copy<SphincsSecretPRF>(params.n());
162✔
110

111
         BOTAN_ASSERT_NOMSG(s.empty());
162✔
112
      }
162✔
113

114
      const SphincsSecretSeed& seed() const { return m_secret_seed; }
300✔
115

116
      const SphincsSecretPRF& prf() const { return m_prf; }
100✔
117

118
      secure_vector<uint8_t> key_bits() const { return concat<secure_vector<uint8_t>>(m_secret_seed, m_prf); }
410✔
119

120
   private:
121
      SphincsSecretSeed m_secret_seed;
122
      SphincsSecretPRF m_prf;
123
};
124

125
SphincsPlus_PublicKey::SphincsPlus_PublicKey(std::span<const uint8_t> pub_key,
×
126
                                             Sphincs_Parameter_Set type,
127
                                             Sphincs_Hash_Type hash) :
×
128
      SphincsPlus_PublicKey(pub_key, Sphincs_Parameters::create(type, hash)) {}
×
129

130
SphincsPlus_PublicKey::SphincsPlus_PublicKey(std::span<const uint8_t> pub_key, Sphincs_Parameters params) :
270✔
131
      m_public(std::make_shared<SphincsPlus_PublicKeyInternal>(params, pub_key)) {
270✔
132
   BOTAN_ARG_CHECK(params.is_available(),
270✔
133
                   "The selected parameter-set-hash combination is not activated in this build.");
134
}
270✔
135

136
SphincsPlus_PublicKey::SphincsPlus_PublicKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits) :
66✔
137
      SphincsPlus_PublicKey(key_bits, Sphincs_Parameters::create(alg_id.oid())) {}
66✔
138

139
SphincsPlus_PublicKey::~SphincsPlus_PublicKey() = default;
644✔
140

141
size_t SphincsPlus_PublicKey::key_length() const {
1✔
142
   return m_public->parameters().n() * 8;
1✔
143
}
144

145
std::string SphincsPlus_PublicKey::algo_name() const {
725✔
146
   return m_public->parameters().is_slh_dsa() ? "SLH-DSA" : "SPHINCS+";
1,073✔
147
}
148

149
size_t SphincsPlus_PublicKey::estimated_strength() const {
144✔
150
   return m_public->parameters().bitsec();
144✔
151
}
152

153
AlgorithmIdentifier SphincsPlus_PublicKey::algorithm_identifier() const {
484✔
154
   return m_public->parameters().algorithm_identifier();
484✔
155
}
156

157
OID SphincsPlus_PublicKey::object_identifier() const {
×
158
   return m_public->parameters().object_identifier();
×
159
}
160

161
bool SphincsPlus_PublicKey::check_key(RandomNumberGenerator&, bool) const {
48✔
162
   // Nothing to check. It's literally just hashes. :-)
163
   return true;
48✔
164
}
165

166
std::vector<uint8_t> SphincsPlus_PublicKey::raw_public_key_bits() const {
259✔
167
   return m_public->key_bits();
259✔
168
}
169

170
std::vector<uint8_t> SphincsPlus_PublicKey::public_key_bits() const {
209✔
171
   // Currently, there isn't a finalized definition of an ASN.1 structure for
172
   // SLH-DSA or SPHINCS+ public keys. Therefore, we return the raw public key bits.
173
   return raw_public_key_bits();
209✔
174
}
175

176
std::unique_ptr<Private_Key> SphincsPlus_PublicKey::generate_another(RandomNumberGenerator& rng) const {
24✔
177
   return std::make_unique<SphincsPlus_PrivateKey>(rng, m_public->parameters());
24✔
178
}
179

180
class SphincsPlus_Verification_Operation final : public PK_Ops::Verification {
×
181
   public:
182
      SphincsPlus_Verification_Operation(std::shared_ptr<SphincsPlus_PublicKeyInternal> pub_key) :
75✔
183
            m_public(std::move(pub_key)),
75✔
184
            m_hashes(Botan::Sphincs_Hash_Functions::create(m_public->parameters(), m_public->seed())),
75✔
185
            m_context(/* TODO: Add API */ {}) {
75✔
186
         BOTAN_ARG_CHECK(m_context.size() <= 255, "Context must not exceed 255 bytes");
75✔
187
         BOTAN_ARG_CHECK(m_public->parameters().is_available(),
75✔
188
                         "The selected SLH-DSA (or SPHINCS+) instance is not available in this build.");
189
      }
75✔
190

191
      /**
192
       * Add more data to the message currently being signed
193
       * @param msg the message
194
       */
195
      void update(std::span<const uint8_t> msg) override {
162✔
196
         // TODO(For Pre-Hash Mode): We need to stream the message into a hash function.
197
         m_msg_buffer.get().insert(m_msg_buffer.end(), msg.begin(), msg.end());
162✔
198
      }
162✔
199

200
      /**
201
       * Perform a verification operation
202
       */
203
      bool is_valid_signature(std::span<const uint8_t> sig) override {
150✔
204
         const auto internal_msg = prepare_message(std::exchange(m_msg_buffer, {}), m_public->parameters(), m_context);
300✔
205
         return slh_verify_internal(internal_msg, sig);
300✔
206
      }
150✔
207

208
      std::string hash_function() const override { return m_hashes->msg_hash_function_name(); }
10✔
209

210
   private:
211
      /// FIPS 205, Algorithm 20
212
      bool slh_verify_internal(const SphincsMessageInternal& msg, std::span<const uint8_t> sig) {
150✔
213
         const auto& p = m_public->parameters();
150✔
214
         if(sig.size() != p.sphincs_signature_bytes()) {
150✔
215
            return false;
216
         }
217

218
         BufferSlicer s(sig);
150✔
219
         // Compute leaf and tree index from R
220
         const auto msg_random_s = s.take<SphincsMessageRandomness>(p.n());
150✔
221
         auto [mhash, tree_idx, leaf_idx] = m_hashes->H_msg(msg_random_s, m_public->root(), msg);
150✔
222

223
         // Reconstruct the FORS tree
224
         Sphincs_Address fors_addr(Sphincs_Address_Type::ForsTree);
150✔
225
         fors_addr.set_tree_address(tree_idx).set_keypair_address(leaf_idx);
150✔
226
         const auto fors_sig_s = s.take<ForsSignature>(p.fors_signature_bytes());
150✔
227
         auto fors_root = fors_public_key_from_signature(mhash, fors_sig_s, fors_addr, p, *m_hashes);
150✔
228

229
         // Verify the hypertree signature
230
         const auto ht_sig_s = s.take<SphincsHypertreeSignature>(p.ht_signature_bytes());
150✔
231
         BOTAN_ASSERT_NOMSG(s.empty());
150✔
232
         return ht_verify(fors_root, ht_sig_s, m_public->root(), tree_idx, leaf_idx, p, *m_hashes);
150✔
233
      }
300✔
234

235
      std::shared_ptr<SphincsPlus_PublicKeyInternal> m_public;
236
      std::unique_ptr<Sphincs_Hash_Functions> m_hashes;
237
      SphincsInputMessage m_msg_buffer;
238
      SphincsContext m_context;
239
};
240

241
std::unique_ptr<PK_Ops::Verification> SphincsPlus_PublicKey::create_verification_op(std::string_view /*params*/,
45✔
242
                                                                                    std::string_view provider) const {
243
   if(provider.empty() || provider == "base") {
48✔
244
      return std::make_unique<SphincsPlus_Verification_Operation>(m_public);
39✔
245
   }
246
   throw Provider_Not_Found(algo_name(), provider);
12✔
247
}
248

249
std::unique_ptr<PK_Ops::Verification> SphincsPlus_PublicKey::create_x509_verification_op(
36✔
250
   const AlgorithmIdentifier& signature_algorithm, std::string_view provider) const {
251
   if(provider.empty() || provider == "base") {
36✔
252
      if(signature_algorithm != this->algorithm_identifier()) {
36✔
253
         throw Decoding_Error("Unexpected AlgorithmIdentifier for SLH-DSA (or SPHINCS+) signature");
×
254
      }
255
      return std::make_unique<SphincsPlus_Verification_Operation>(m_public);
36✔
256
   }
257
   throw Provider_Not_Found(algo_name(), provider);
×
258
}
259

260
bool SphincsPlus_PublicKey::supports_operation(PublicKeyOperation op) const {
165✔
261
   return op == PublicKeyOperation::Signature;
165✔
262
}
263

264
namespace {
265

266
std::span<const uint8_t> slice_off_public_key(const OID& oid, std::span<const uint8_t> key_bits) {
162✔
267
   const auto params = Sphincs_Parameters::create(oid);
162✔
268
   // Note: We need to transiently instantiate the `Sphincs_Parameters` object
269
   //       to know the size of the public/private key. That's slightly
270
   //       inefficient but was the best we could do. Once we get rid of the
271
   //       PublicKey-PrivateKey inheritance, we might want to reconsider this
272
   //       control flow.
273
   if(key_bits.size() != params.private_key_bytes()) {
162✔
274
      throw Decoding_Error("Sphincs Private Key doesn't have the expected length");
×
275
   }
276

277
   return key_bits.subspan(params.private_key_bytes() - params.public_key_bytes());
162✔
278
}
279

280
}  // namespace
281

282
SphincsPlus_PrivateKey::SphincsPlus_PrivateKey(std::span<const uint8_t> private_key,
×
283
                                               Sphincs_Parameter_Set type,
284
                                               Sphincs_Hash_Type hash) :
×
285
      SphincsPlus_PrivateKey(private_key, Sphincs_Parameters::create(type, hash)) {}
×
286

287
SphincsPlus_PrivateKey::SphincsPlus_PrivateKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits) :
144✔
288
      SphincsPlus_PrivateKey(key_bits, Sphincs_Parameters::create(alg_id.oid())) {}
144✔
289

290
SphincsPlus_PrivateKey::SphincsPlus_PrivateKey(std::span<const uint8_t> private_key, Sphincs_Parameters params) :
162✔
291
      SphincsPlus_PublicKey(slice_off_public_key(params.object_identifier(), private_key), params) {
162✔
292
   BOTAN_ARG_CHECK(params.is_available(),
162✔
293
                   "The selected parameter-set-hash combination is not activated in this build.");
294
   const auto private_portion_bytes = params.private_key_bytes() - params.public_key_bytes();
162✔
295
   BOTAN_ASSERT_NOMSG(private_key.size() >= private_portion_bytes);
162✔
296

297
   m_private = std::make_shared<SphincsPlus_PrivateKeyInternal>(params, private_key.first(private_portion_bytes));
162✔
298
}
162✔
299

300
SphincsPlus_PrivateKey::SphincsPlus_PrivateKey(RandomNumberGenerator& rng,
×
301
                                               Sphincs_Parameter_Set type,
302
                                               Sphincs_Hash_Type hash) :
×
303
      SphincsPlus_PrivateKey(rng, Sphincs_Parameters::create(type, hash)) {}
×
304

305
// FIPS 205, Algorithm 21
306
SphincsPlus_PrivateKey::SphincsPlus_PrivateKey(RandomNumberGenerator& rng, Sphincs_Parameters params) {
100✔
307
   BOTAN_ARG_CHECK(params.is_available(),
100✔
308
                   "The selected parameter-set-hash combination is not activated in this build.");
309
   auto sk_seed = rng.random_vec<SphincsSecretSeed>(params.n());
100✔
310
   auto sk_prf = rng.random_vec<SphincsSecretPRF>(params.n());
100✔
311

312
   m_private = std::make_shared<SphincsPlus_PrivateKeyInternal>(std::move(sk_seed), std::move(sk_prf));
100✔
313

314
   auto pub_seed = rng.random_vec<SphincsPublicSeed>(params.n());
100✔
315
   auto hashes = Sphincs_Hash_Functions::create(params, pub_seed);
100✔
316
   auto root = xmss_gen_root(params, m_private->seed(), *hashes);
100✔
317

318
   m_public = std::make_shared<SphincsPlus_PublicKeyInternal>(params, std::move(pub_seed), std::move(root));
100✔
319
}
200✔
320

321
SphincsPlus_PrivateKey::~SphincsPlus_PrivateKey() = default;
758✔
322

323
secure_vector<uint8_t> SphincsPlus_PrivateKey::private_key_bits() const {
410✔
324
   return concat(m_private->key_bits(), m_public->key_bits());
1,640✔
325
}
326

327
secure_vector<uint8_t> SphincsPlus_PrivateKey::raw_private_key_bits() const {
26✔
328
   return this->private_key_bits();
26✔
329
}
330

331
std::unique_ptr<Public_Key> SphincsPlus_PrivateKey::public_key() const {
85✔
332
   return std::make_unique<SphincsPlus_PublicKey>(*this);
85✔
333
}
334

335
class SphincsPlus_Signature_Operation final : public PK_Ops::Signature {
×
336
   public:
337
      SphincsPlus_Signature_Operation(std::shared_ptr<SphincsPlus_PrivateKeyInternal> private_key,
98✔
338
                                      std::shared_ptr<SphincsPlus_PublicKeyInternal> public_key,
339
                                      bool randomized) :
98✔
340
            m_private(std::move(private_key)),
98✔
341
            m_public(std::move(public_key)),
98✔
342
            m_hashes(Botan::Sphincs_Hash_Functions::create(m_public->parameters(), m_public->seed())),
98✔
343
            m_randomized(randomized),
98✔
344
            m_context(/* TODO: add API for context */ {}) {
98✔
345
         BOTAN_ARG_CHECK(m_context.size() <= 255, "Context must not exceed 255 bytes");
98✔
346
         BOTAN_ARG_CHECK(m_public->parameters().is_available(),
98✔
347
                         "The selected SLH-DSA (or SPHINCS+) instance is not available in this build.");
348
      }
98✔
349

350
      void update(std::span<const uint8_t> msg) override {
124✔
351
         // TODO(For Pre-Hash Mode): We need to stream the message into a hash function.
352
         m_msg_buffer.get().insert(m_msg_buffer.end(), msg.begin(), msg.end());
124✔
353
      }
124✔
354

355
      std::vector<uint8_t> sign(RandomNumberGenerator& rng) override {
100✔
356
         std::optional<SphincsOptionalRandomness> addrnd = std::nullopt;
100✔
357
         if(m_randomized) {
100✔
358
            addrnd = rng.random_vec<SphincsOptionalRandomness>(m_public->parameters().n());
28✔
359
         }
360
         auto internal_msg = prepare_message(std::exchange(m_msg_buffer, {}), m_public->parameters(), m_context);
200✔
361

362
         return slh_sign_internal(internal_msg, addrnd);
128✔
363
      }
100✔
364

365
      size_t signature_length() const override { return m_public->parameters().sphincs_signature_bytes(); }
13✔
366

367
      AlgorithmIdentifier algorithm_identifier() const override {
20✔
368
         return m_public->parameters().algorithm_identifier();
20✔
369
      }
370

371
      std::string hash_function() const override { return m_hashes->msg_hash_function_name(); }
34✔
372

373
   private:
374
      // FIPS 205, Algorithm 19
375
      std::vector<uint8_t> slh_sign_internal(const SphincsMessageInternal& message,
100✔
376
                                             std::optional<StrongSpan<const SphincsOptionalRandomness>> addrnd) {
377
         const auto& p = m_public->parameters();
100✔
378

379
         std::vector<uint8_t> sphincs_sig_buffer(p.sphincs_signature_bytes());
100✔
380
         BufferStuffer sphincs_sig(sphincs_sig_buffer);
100✔
381

382
         // Compute and append the digest randomization value (R of spec).
383
         // Use addrng for the randomized variant. Use the public seed for the deterministic one.
384
         const auto opt_rand =
100✔
385
            (addrnd.has_value()) ? addrnd.value() : StrongSpan<const SphincsOptionalRandomness>(m_public->seed());
100✔
386

387
         auto msg_random_s = sphincs_sig.next<SphincsMessageRandomness>(p.n());
100✔
388
         m_hashes->PRF_msg(msg_random_s, m_private->prf(), opt_rand, message);
100✔
389

390
         // Derive the message digest and leaf index from R, PK and M.
391
         auto [mhash, tree_idx, leaf_idx] = m_hashes->H_msg(msg_random_s, m_public->root(), message);
100✔
392

393
         // Compute and append the FORS signature
394
         Sphincs_Address fors_addr(Sphincs_Address_Type::ForsTree);
100✔
395
         fors_addr.set_tree_address(tree_idx).set_keypair_address(leaf_idx);
100✔
396
         auto fors_root = fors_sign_and_pkgen(sphincs_sig.next<ForsSignature>(p.fors_signature_bytes()),
100✔
397
                                              mhash,
398
                                              m_private->seed(),
100✔
399
                                              fors_addr,
400
                                              p,
401
                                              *m_hashes);
100✔
402

403
         // Compute and append the XMSS hypertree signature
404
         ht_sign(sphincs_sig.next<SphincsHypertreeSignature>(p.ht_signature_bytes()),
100✔
405
                 fors_root,
406
                 m_private->seed(),
100✔
407
                 tree_idx,
408
                 leaf_idx,
409
                 p,
410
                 *m_hashes);
100✔
411

412
         BOTAN_ASSERT_NOMSG(sphincs_sig.full());
100✔
413
         return sphincs_sig_buffer;
100✔
414
      }
200✔
415

416
      std::shared_ptr<SphincsPlus_PrivateKeyInternal> m_private;
417
      std::shared_ptr<SphincsPlus_PublicKeyInternal> m_public;
418
      std::unique_ptr<Sphincs_Hash_Functions> m_hashes;
419
      SphincsInputMessage m_msg_buffer;
420
      bool m_randomized;
421
      SphincsContext m_context;
422
};
423

424
std::unique_ptr<PK_Ops::Signature> SphincsPlus_PrivateKey::create_signature_op(RandomNumberGenerator& rng,
101✔
425
                                                                               std::string_view params,
426
                                                                               std::string_view provider) const {
427
   BOTAN_UNUSED(rng);
101✔
428
   BOTAN_ARG_CHECK(params.empty() || params == "Deterministic" || params == "Randomized",
141✔
429
                   "Unexpected parameters for signing with SLH-DSA (or SPHINCS+)");
430

431
   const bool randomized = (params == "Randomized");
101✔
432
   if(provider.empty() || provider == "base") {
103✔
433
      return std::make_unique<SphincsPlus_Signature_Operation>(m_private, m_public, randomized);
98✔
434
   }
435
   throw Provider_Not_Found(algo_name(), provider);
6✔
436
}
437

438
}  // namespace Botan
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