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

randombit / botan / 25650639339

10 May 2026 07:03PM UTC coverage: 89.326% (-0.002%) from 89.328%
25650639339

push

github

web-flow
Merge pull request #5592 from randombit/jack/bn-hardening

Various BigInt/mp related hardenings and bug fixes

107853 of 120741 relevant lines covered (89.33%)

11294230.95 hits per line

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

94.05
/src/tests/test_ec_group.cpp
1
/*
2
* (C) 2007 Falko Strenzke
3
*     2007 Manuel Hartl
4
*     2009,2015,2018,2021 Jack Lloyd
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include "tests.h"
10

11
#if defined(BOTAN_HAS_ECC_GROUP)
12
   #include <botan/bigint.h>
13
   #include <botan/data_src.h>
14
   #include <botan/ec_group.h>
15
   #include <botan/hex.h>
16
   #include <botan/numthry.h>
17
   #include <botan/pk_keys.h>
18
   #include <botan/rng.h>
19
   #include <botan/x509_key.h>
20
   #include <botan/internal/barrett.h>
21
   #include <botan/internal/ec_inner_data.h>
22
   #include <array>
23
   #if defined(BOTAN_HAS_ECDSA)
24
      #include <botan/pk_algs.h>
25
   #endif
26
#endif
27

28
namespace Botan_Tests {
29

30
namespace {
31

32
#if defined(BOTAN_HAS_ECC_GROUP)
33

34
   #if defined(BOTAN_HAS_LEGACY_EC_POINT)
35

36
Botan::BigInt test_integer(Botan::RandomNumberGenerator& rng, size_t bits, const BigInt& max) {
560✔
37
   /*
38
   Produces integers with long runs of ones and zeros, for testing for
39
   carry handling problems.
40
   */
41
   Botan::BigInt x = 0;
560✔
42

43
   auto flip_prob = [](size_t i) -> double {
154,040✔
44
      if(i % 64 == 0) {
153,480✔
45
         return .5;
46
      }
47
      if(i % 32 == 0) {
150,980✔
48
         return .4;
49
      }
50
      if(i % 8 == 0) {
148,560✔
51
         return .05;
14,360✔
52
      }
53
      return .01;
54
   };
55

56
   bool active = (rng.next_byte() > 128) ? true : false;
560✔
57
   for(size_t i = 0; i != bits; ++i) {
154,040✔
58
      x <<= 1;
153,480✔
59
      x += static_cast<int>(active);
153,480✔
60

61
      const double prob = flip_prob(i);
153,480✔
62
      const double sample = double(rng.next_byte() % 100) / 100.0;  // biased
153,480✔
63

64
      if(sample < prob) {
153,480✔
65
         active = !active;
4,981✔
66
      }
67
   }
68

69
   if(x == 0) {
560✔
70
      // EC_Scalar rejects zero as an input, if we hit this case instead
71
      // test with a completely randomized scalar
72
      return BigInt::random_integer(rng, 1, max);
×
73
   }
74

75
   if(max > 0) {
560✔
76
      while(x >= max) {
627✔
77
         const size_t b = x.bits() - 1;
67✔
78
         BOTAN_ASSERT(x.get_bit(b) == true, "Set");
134✔
79
         x.clear_bit(b);
67✔
80
      }
81
   }
82

83
   return x;
560✔
84
}
560✔
85

86
Botan::EC_Point create_random_point(Botan::RandomNumberGenerator& rng, const Botan::EC_Group& group) {
112✔
87
   const Botan::BigInt& p = group.get_p();
112✔
88
   auto mod_p = Botan::Barrett_Reduction::for_public_modulus(p);
112✔
89

90
   for(;;) {
116✔
91
      const Botan::BigInt x = Botan::BigInt::random_integer(rng, 1, p);
228✔
92
      const Botan::BigInt x3 = mod_p.multiply(x, mod_p.square(x));
228✔
93
      const Botan::BigInt ax = mod_p.multiply(group.get_a(), x);
228✔
94
      const Botan::BigInt y = mod_p.reduce(x3 + ax + group.get_b());
228✔
95
      const Botan::BigInt sqrt_y = Botan::sqrt_modulo_prime(y, p);
228✔
96

97
      if(sqrt_y > 1) {
228✔
98
         BOTAN_ASSERT_EQUAL(mod_p.square(sqrt_y), y, "Square root is correct");
224✔
99
         return group.point(x, sqrt_y);
112✔
100
      }
101
   }
228✔
102
}
112✔
103

104
class ECC_Randomized_Tests final : public Test {
1✔
105
   public:
106
      std::vector<Test::Result> run() override;
107
};
108

109
std::vector<Test::Result> ECC_Randomized_Tests::run() {
1✔
110
   std::vector<Test::Result> results;
1✔
111
   for(const std::string& group_name : Botan::EC_Group::known_named_groups()) {
29✔
112
      Test::Result result("ECC randomized " + group_name);
28✔
113

114
      result.start_timer();
28✔
115

116
      auto group = Botan::EC_Group::from_name(group_name);
28✔
117

118
      const Botan::EC_Point pt = create_random_point(this->rng(), group);
28✔
119

120
      try {
28✔
121
         std::vector<Botan::BigInt> blind_ws;
28✔
122
         const size_t trials = (Test::run_long_tests() ? 10 : 3);
28✔
123
         for(size_t i = 0; i < trials; ++i) {
308✔
124
            const Botan::BigInt a = test_integer(rng(), group.get_order_bits(), group.get_order());
280✔
125
            const Botan::BigInt b = test_integer(rng(), group.get_order_bits(), group.get_order());
280✔
126
            const Botan::BigInt c = group.mod_order(a + b);
280✔
127

128
            const Botan::EC_Point P = pt * a;
280✔
129
            const Botan::EC_Point Q = pt * b;
280✔
130
            const Botan::EC_Point R = pt * c;
280✔
131

132
            Botan::EC_Point P1 = group.blinded_var_point_multiply(pt, a, this->rng(), blind_ws);
280✔
133
            Botan::EC_Point Q1 = group.blinded_var_point_multiply(pt, b, this->rng(), blind_ws);
280✔
134
            Botan::EC_Point R1 = group.blinded_var_point_multiply(pt, c, this->rng(), blind_ws);
280✔
135

136
            Botan::EC_Point A1 = P + Q;
280✔
137
            Botan::EC_Point A2 = Q + P;
280✔
138

139
            result.test_bin_eq("p + q", A1.xy_bytes(), R.xy_bytes());
560✔
140
            result.test_bin_eq("q + p", A2.xy_bytes(), R.xy_bytes());
560✔
141

142
            A1.force_affine();
280✔
143
            A2.force_affine();
280✔
144
            result.test_bin_eq("p + q", A1.xy_bytes(), R.xy_bytes());
560✔
145
            result.test_bin_eq("q + p", A2.xy_bytes(), R.xy_bytes());
560✔
146

147
            result.test_is_true("p on the curve", P.on_the_curve());
280✔
148
            result.test_is_true("q on the curve", Q.on_the_curve());
280✔
149
            result.test_is_true("r on the curve", R.on_the_curve());
280✔
150

151
            result.test_bin_eq("P1", P1.xy_bytes(), P.xy_bytes());
560✔
152
            result.test_bin_eq("Q1", Q1.xy_bytes(), Q.xy_bytes());
560✔
153
            result.test_bin_eq("R1", R1.xy_bytes(), R.xy_bytes());
560✔
154

155
            P1.force_affine();
280✔
156
            Q1.force_affine();
280✔
157
            R1.force_affine();
280✔
158
            result.test_bin_eq("P1", P1.xy_bytes(), P.xy_bytes());
560✔
159
            result.test_bin_eq("Q1", Q1.xy_bytes(), Q.xy_bytes());
560✔
160
            result.test_bin_eq("R1", R1.xy_bytes(), R.xy_bytes());
560✔
161
         }
280✔
162
      } catch(std::exception& e) {
28✔
163
         result.test_failure(group_name, e.what());
×
164
      }
×
165
      result.end_timer();
28✔
166
      results.push_back(result);
28✔
167
   }
28✔
168

169
   return results;
1✔
170
}
×
171

172
BOTAN_REGISTER_TEST("pubkey", "ecc_randomized", ECC_Randomized_Tests);
173

174
   #endif
175

176
class EC_Group_Tests : public Test {
1✔
177
   public:
178
      std::vector<Test::Result> run() override {
1✔
179
         std::vector<Test::Result> results;
1✔
180

181
         for(const std::string& group_name : Botan::EC_Group::known_named_groups()) {
29✔
182
            Test::Result result("EC_Group " + group_name);
28✔
183

184
            result.start_timer();
28✔
185

186
            const auto group = Botan::EC_Group::from_name(group_name);
28✔
187

188
            result.test_is_true("EC_Group is known", group.get_curve_oid().has_value());
28✔
189
            result.test_is_true("EC_Group is considered valid", group.verify_group(this->rng(), true));
28✔
190
            result.test_is_true("EC_Group is not considered explicit encoding", !group.used_explicit_encoding());
28✔
191

192
            result.test_sz_eq("EC_Group has correct bit size", group.get_p().bits(), group.get_p_bits());
28✔
193
            result.test_sz_eq("EC_Group has byte size", group.get_p().bytes(), group.get_p_bytes());
28✔
194

195
            result.test_bn_eq("EC_Group has cofactor == 1", group.get_cofactor(), 1);
28✔
196

197
            const Botan::OID from_order = Botan::EC_Group::EC_group_identity_from_order(group.get_order());
28✔
198

199
            result.test_str_eq(
28✔
200
               "EC_group_identity_from_order works", from_order.to_string(), group.get_curve_oid().to_string());
56✔
201

202
            result.test_is_true("Same group is same", group == Botan::EC_Group::from_name(group_name));
28✔
203

204
            try {
28✔
205
               const Botan::EC_Group copy(group.get_curve_oid(),
28✔
206
                                          group.get_p(),
207
                                          group.get_a(),
208
                                          group.get_b(),
209
                                          group.get_g_x(),
210
                                          group.get_g_y(),
211
                                          group.get_order());
28✔
212

213
               result.test_is_true("Same group is same even with copy", group == copy);
20✔
214
            } catch(Botan::Invalid_Argument&) {}
28✔
215

216
            const auto group_der_oid = group.DER_encode();
28✔
217
            const Botan::EC_Group group_via_oid(group_der_oid);
28✔
218
            result.test_is_true("EC_Group via OID is not considered explicit encoding",
28✔
219
                                !group_via_oid.used_explicit_encoding());
28✔
220

221
            const auto group_der_explicit = group.DER_encode(Botan::EC_Group_Encoding::Explicit);
28✔
222
            const Botan::EC_Group group_via_explicit(group_der_explicit);
28✔
223
            result.test_is_true("EC_Group via explicit DER is considered explicit encoding",
28✔
224
                                group_via_explicit.used_explicit_encoding());
28✔
225

226
            if(group.a_is_minus_3()) {
28✔
227
               result.test_bn_eq("Group A equals -3", group.get_a(), group.get_p() - 3);
17✔
228
            } else {
229
               result.test_bn_ne("Group " + group_name + " A does not equal -3", group.get_a(), group.get_p() - 3);
33✔
230
            }
231

232
            if(group.a_is_zero()) {
28✔
233
               result.test_bn_eq("Group A is zero", group.get_a(), BigInt(0));
4✔
234
            } else {
235
               result.test_bn_ne("Group " + group_name + " A does not equal zero", group.get_a(), BigInt(0));
72✔
236
            }
237

238
   #if defined(BOTAN_HAS_LEGACY_EC_POINT)
239
            const auto pt_mult_by_order = group.get_base_point() * group.get_order();
28✔
240
            result.test_is_true("Multiplying point by the order results in zero point", pt_mult_by_order.is_zero());
28✔
241

242
            // get a valid point
243
            Botan::EC_Point p = group.get_base_point() * this->rng().next_nonzero_byte();
28✔
244

245
            // get a copy
246
            Botan::EC_Point q = p;
28✔
247

248
            p.randomize_repr(this->rng());
28✔
249
            q.randomize_repr(this->rng());
28✔
250

251
            result.test_bn_eq("affine x after copy", p.get_affine_x(), q.get_affine_x());
28✔
252
            result.test_bn_eq("affine y after copy", p.get_affine_y(), q.get_affine_y());
28✔
253

254
            q.force_affine();
28✔
255

256
            result.test_bn_eq("affine x after copy", p.get_affine_x(), q.get_affine_x());
28✔
257
            result.test_bn_eq("affine y after copy", p.get_affine_y(), q.get_affine_y());
28✔
258

259
            test_ser_der(result, group);
28✔
260
            test_basic_math(result, group);
28✔
261
            test_point_swap(result, group);
28✔
262
            test_zeropoint(result, group);
28✔
263
   #endif
264

265
            result.end_timer();
28✔
266

267
            results.push_back(result);
28✔
268
         }
84✔
269

270
         return results;
1✔
271
      }
×
272

273
   private:
274
   #if defined(BOTAN_HAS_LEGACY_EC_POINT)
275

276
      void test_ser_der(Test::Result& result, const Botan::EC_Group& group) {
28✔
277
         // generate point
278
         const Botan::EC_Point pt = create_random_point(this->rng(), group);
28✔
279
         const Botan::EC_Point zero = group.zero_point();
28✔
280

281
         for(auto scheme : {Botan::EC_Point_Format::Uncompressed,
84✔
282
                            Botan::EC_Point_Format::Compressed,
283
                            Botan::EC_Point_Format::Hybrid}) {
112✔
284
            try {
84✔
285
               result.test_bin_eq("encoded/decode rt works", group.OS2ECP(pt.encode(scheme)).xy_bytes(), pt.xy_bytes());
336✔
286
            } catch(Botan::Exception& e) {
×
287
               result.test_failure("Failed to round trip encode a random point", e.what());
×
288
            }
×
289

290
            try {
84✔
291
               result.test_is_true("encoded/decode rt works", group.OS2ECP(zero.encode(scheme)).is_zero());
252✔
292
            } catch(Botan::Exception& e) {
×
293
               result.test_failure("Failed to round trip encode the identity element", e.what());
×
294
            }
×
295
         }
296
      }
28✔
297

298
      static void test_basic_math(Test::Result& result, const Botan::EC_Group& group) {
28✔
299
         const Botan::EC_Point& G = group.get_base_point();
28✔
300

301
         const auto G2 = G * 2;
56✔
302
         const auto G3 = G * 3;
56✔
303

304
         Botan::EC_Point p1 = G2;
28✔
305
         p1 += G;
28✔
306

307
         result.test_bin_eq("point addition", p1.xy_bytes(), G3.xy_bytes());
56✔
308

309
         p1 -= G2;
28✔
310

311
         result.test_bin_eq("point subtraction", p1.xy_bytes(), G.xy_bytes());
56✔
312

313
         // The scalar multiplication algorithm relies on this being true:
314
         try {
28✔
315
            const Botan::EC_Point zero_coords = group.point(0, 0);
56✔
316
            result.test_is_true("point (0,0) is not on the curve", !zero_coords.on_the_curve());
×
317
         } catch(Botan::Exception&) {
28✔
318
            result.test_success("point (0,0) is rejected");
28✔
319
         }
28✔
320
      }
28✔
321

322
      void test_point_swap(Test::Result& result, const Botan::EC_Group& group) {
28✔
323
         const Botan::EC_Point a(create_random_point(this->rng(), group));
28✔
324
         Botan::EC_Point b(create_random_point(this->rng(), group));
28✔
325
         b *= Botan::BigInt(this->rng(), 20);
28✔
326

327
         Botan::EC_Point c(a);
28✔
328
         Botan::EC_Point d(b);
28✔
329

330
         d.swap(c);
28✔
331
         result.test_bin_eq("swap correct", a.xy_bytes(), d.xy_bytes());
56✔
332
         result.test_bin_eq("swap correct", b.xy_bytes(), c.xy_bytes());
56✔
333
      }
28✔
334

335
      static void test_zeropoint(Test::Result& result, const Botan::EC_Group& group) {
28✔
336
         Botan::EC_Point zero = group.zero_point();
28✔
337

338
         result.test_throws("Zero point throws", "Cannot convert zero point to affine", [&]() { zero.get_affine_x(); });
56✔
339
         result.test_throws("Zero point throws", "Cannot convert zero point to affine", [&]() { zero.get_affine_y(); });
56✔
340

341
         const Botan::EC_Point p1 = group.get_base_point() * 2;
28✔
342

343
         result.test_is_true("point is on the curve", p1.on_the_curve());
28✔
344
         result.test_is_true("point is not zero", !p1.is_zero());
28✔
345

346
         Botan::EC_Point p2 = p1;
28✔
347
         p2 -= p1;
28✔
348

349
         result.test_is_true("p - q with q = p results in zero", p2.is_zero());
28✔
350

351
         const Botan::EC_Point minus_p1 = -p1;
28✔
352
         result.test_is_true("point is on the curve", minus_p1.on_the_curve());
28✔
353
         const Botan::EC_Point shouldBeZero = p1 + minus_p1;
28✔
354
         result.test_is_true("point is on the curve", shouldBeZero.on_the_curve());
28✔
355
         result.test_is_true("point is zero", shouldBeZero.is_zero());
28✔
356

357
         result.test_bn_eq("minus point x", minus_p1.get_affine_x(), p1.get_affine_x());
28✔
358
         result.test_bn_eq("minus point y", minus_p1.get_affine_y(), group.get_p() - p1.get_affine_y());
28✔
359

360
         result.test_is_true("zero point is zero", zero.is_zero());
28✔
361
         result.test_is_true("zero point is on the curve", zero.on_the_curve());
28✔
362
         result.test_bin_eq("addition of zero does nothing", p1.xy_bytes(), (p1 + zero).xy_bytes());
84✔
363
         result.test_bin_eq("addition of zero does nothing", p1.xy_bytes(), (zero + p1).xy_bytes());
84✔
364
         result.test_bin_eq("addition of zero does nothing", p1.xy_bytes(), (p1 - zero).xy_bytes());
84✔
365
         result.test_is_true("zero times anything is the zero point", (zero * 39193).is_zero());
56✔
366

367
         for(auto scheme : {Botan::EC_Point_Format::Uncompressed,
196✔
368
                            Botan::EC_Point_Format::Compressed,
369
                            Botan::EC_Point_Format::Hybrid}) {
112✔
370
            const std::vector<uint8_t> v = zero.encode(scheme);
84✔
371
            result.test_is_true("encoded/decode rt works", group.OS2ECP(v).is_zero());
168✔
372
         }
84✔
373
      }
28✔
374
   #endif
375
};
376

377
BOTAN_REGISTER_TEST("pubkey", "ec_group", EC_Group_Tests);
378

379
Test::Result test_decoding_with_seed() {
1✔
380
   Test::Result result("Decode EC_Group with seed");
1✔
381

382
   try {
1✔
383
      if(Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
384
         const auto secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
385
         const auto secp384r1_with_seed =
1✔
386
            Botan::EC_Group::from_PEM(Test::read_data_file("x509/ecc/secp384r1_seed.pem"));
2✔
387
         result.test_is_true("decoding worked", secp384r1_with_seed.initialized());
1✔
388
         result.test_bn_eq("P-384 prime", secp384r1_with_seed.get_p(), secp384r1.get_p());
1✔
389
      }
1✔
390
   } catch(Botan::Exception& e) {
×
391
      result.test_failure(e.what());
×
392
   }
×
393

394
   return result;
1✔
395
}
×
396

397
Test::Result test_mixed_points() {
1✔
398
   Test::Result result("Mixed Point Arithmetic");
1✔
399

400
   if(Botan::EC_Group::supports_named_group("secp256r1") && Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
401
      const auto secp256r1 = Botan::EC_Group::from_name("secp256r1");
1✔
402
      const auto secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
403

404
   #if defined(BOTAN_HAS_LEGACY_EC_POINT)
405
      const Botan::EC_Point& G256 = secp256r1.get_base_point();
1✔
406
      const Botan::EC_Point& G384 = secp384r1.get_base_point();
1✔
407

408
      result.test_throws("Mixing points from different groups", [&] { const Botan::EC_Point p = G256 + G384; });
2✔
409
   #endif
410

411
      const auto p1 = Botan::EC_AffinePoint::generator(secp256r1);
1✔
412
      const auto p2 = Botan::EC_AffinePoint::generator(secp384r1);
1✔
413
      result.test_throws("Mixing points from different groups", [&] { auto p3 = p1.add(p2); });
2✔
414
   }
1✔
415

416
   return result;
1✔
417
}
×
418

419
class ECC_Unit_Tests final : public Test {
1✔
420
   public:
421
      std::vector<Test::Result> run() override {
1✔
422
         std::vector<Test::Result> results;
1✔
423

424
         results.push_back(test_decoding_with_seed());
2✔
425
         results.push_back(test_mixed_points());
2✔
426

427
         return results;
1✔
428
      }
×
429
};
430

431
BOTAN_REGISTER_TEST("pubkey", "ecc_unit", ECC_Unit_Tests);
432

433
class EC_Group_Registration_Tests final : public Test {
1✔
434
   public:
435
      std::vector<Test::Result> run() override {
1✔
436
         std::vector<Test::Result> results;
1✔
437

438
         if(Botan::EC_Group::supports_application_specific_group()) {
1✔
439
            results.push_back(test_ecc_registration());
2✔
440
            results.push_back(test_ec_group_from_params());
2✔
441
            results.push_back(test_ec_group_bad_registration());
2✔
442
            results.push_back(test_ec_group_off_curve_generator());
2✔
443
            results.push_back(test_ec_group_duplicate_orders());
2✔
444
            results.push_back(test_ec_group_registration_with_custom_oid());
2✔
445
            results.push_back(test_ec_group_alias_oid_cache());
2✔
446
            results.push_back(test_ec_group_unregistration());
2✔
447
            results.push_back(test_supports_named_group_with_registration());
2✔
448
         }
449

450
         return results;
1✔
451
      }
×
452

453
   private:
454
      Test::Result test_ecc_registration() {
1✔
455
         Test::Result result("ECC registration");
1✔
456

457
         // numsp256d1
458
         const Botan::BigInt p("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43");
1✔
459
         const Botan::BigInt a("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40");
1✔
460
         const Botan::BigInt b("0x25581");
1✔
461
         const Botan::BigInt order("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE43C8275EA265C6020AB20294751A825");
1✔
462

463
         const Botan::BigInt g_x("0x01");
1✔
464
         const Botan::BigInt g_y("0x696F1853C1E466D7FC82C96CCEEEDD6BD02C2F9375894EC10BF46306C2B56C77");
1✔
465

466
         const Botan::OID oid("1.3.6.1.4.1.25258.4.1");
1✔
467

468
         // Creating this object implicitly registers the curve for future use ...
469
         const Botan::EC_Group reg_group(oid, p, a, b, g_x, g_y, order);
1✔
470

471
         auto group = Botan::EC_Group::from_OID(oid);
1✔
472

473
         result.test_bn_eq("Group registration worked", group.get_p(), p);
1✔
474

475
         // TODO(Botan4) this could change to == Generic
476
         result.test_is_true("Group is not pcurve", group.engine() != Botan::EC_Group_Engine::Optimized);
1✔
477

478
         return result;
1✔
479
      }
1✔
480

481
      Test::Result test_ec_group_from_params() {
1✔
482
         Test::Result result("EC_Group from params");
1✔
483

484
         Botan::EC_Group::clear_registered_curve_data();
1✔
485

486
         // secp256r1
487
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
488
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
489
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B");
1✔
490

491
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
492
         const Botan::BigInt g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5");
1✔
493
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
494

495
         const Botan::OID oid("1.2.840.10045.3.1.7");
1✔
496

497
         // This uses the deprecated constructor to verify we dedup even without an OID
498
         // This whole test can be removed once explicit curve support is removed
499
         const Botan::EC_Group reg_group(p, a, b, g_x, g_y, order, 1);
1✔
500
         result.test_is_true("Group has correct OID", reg_group.get_curve_oid() == oid);
1✔
501

502
         return result;
1✔
503
      }
1✔
504

505
      Test::Result test_ec_group_bad_registration() {
1✔
506
         Test::Result result("EC_Group registering non-match");
1✔
507

508
         Botan::EC_Group::clear_registered_curve_data();
1✔
509

510
         // secp256r1 params except with a bad B param
511
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
512
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
513
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604C");
1✔
514

515
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
516
         const Botan::BigInt g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5");
1✔
517
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
518

519
         const Botan::OID oid("1.2.840.10045.3.1.7");
1✔
520

521
         try {
1✔
522
            const Botan::EC_Group reg_group(oid, p, a, b, g_x, g_y, order);
1✔
523
            result.test_failure("Should have failed");
×
524
         } catch(Botan::Invalid_Argument&) {
1✔
525
            result.test_success("Got expected exception");
1✔
526
         }
1✔
527

528
         return result;
2✔
529
      }
1✔
530

531
      Test::Result test_ec_group_off_curve_generator() {
1✔
532
         Test::Result result("EC_Group rejects off-curve generator");
1✔
533

534
         Botan::EC_Group::clear_registered_curve_data();
1✔
535

536
         // secp256r1 params, but g_y has its low bit flipped so (g_x, g_y) is not on the curve
537
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
538
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
539
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B");
1✔
540

541
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
542
         const Botan::BigInt bad_g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F4");
1✔
543
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
544

545
         result.test_throws("Deprecated EC_Group constructor rejects off-curve generator",
1✔
546
                            "EC_Group generator is not on the curve",
547
                            [&]() { const Botan::EC_Group g(p, a, b, g_x, bad_g_y, order, 1); });
3✔
548

549
         const Botan::OID custom_oid("1.3.6.1.4.1.25258.100.42");
1✔
550
         result.test_throws("Deprecated EC_Group constructor with custom OID rejects off-curve generator",
1✔
551
                            "EC_Group generator is not on the curve",
552
                            [&]() { const Botan::EC_Group g(p, a, b, g_x, bad_g_y, order, 1, custom_oid); });
2✔
553

554
         return result;
2✔
555
      }
1✔
556

557
      Test::Result test_ec_group_duplicate_orders() {
1✔
558
         Test::Result result("EC_Group with duplicate group order");
1✔
559

560
         Botan::EC_Group::clear_registered_curve_data();
1✔
561

562
         // secp256r1
563
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
564
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
565
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B");
1✔
566

567
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
568
         const Botan::BigInt g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5");
1✔
569
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
570

571
         const Botan::OID oid("1.3.6.1.4.1.25258.100.0");  // some other random OID
1✔
572

573
         const Botan::EC_Group reg_group(oid, p, a, b, g_x, g_y, order);
1✔
574
         result.test_success("Registration success");
1✔
575
         result.test_is_true("Group has correct OID", reg_group.get_curve_oid() == oid);
1✔
576

577
         // We can now get it by OID:
578
         const auto hc_group = Botan::EC_Group::from_OID(oid);
1✔
579
         result.test_is_true("Group has correct OID", hc_group.get_curve_oid() == oid);
1✔
580

581
         // Existing secp256r1 unmodified:
582
         const Botan::OID secp160r1("1.2.840.10045.3.1.7");
1✔
583
         const auto other_group = Botan::EC_Group::from_OID(secp160r1);
1✔
584
         result.test_is_true("Group has correct OID", other_group.get_curve_oid() == secp160r1);
1✔
585

586
         return result;
1✔
587
      }
1✔
588

589
      Test::Result test_ec_group_registration_with_custom_oid() {
1✔
590
         Test::Result result("EC_Group registration of standard group with custom OID");
1✔
591

592
         Botan::EC_Group::clear_registered_curve_data();
1✔
593

594
         const Botan::OID secp256r1_oid("1.2.840.10045.3.1.7");
1✔
595
         const auto secp256r1 = Botan::EC_Group::from_OID(secp256r1_oid);
1✔
596
         result.test_is_true("Group has correct OID", secp256r1.get_curve_oid() == secp256r1_oid);
1✔
597

598
         const Botan::OID custom_oid("1.3.6.1.4.1.25258.100.99");  // some other random OID
1✔
599

600
         Botan::OID::register_oid(custom_oid, "secp256r1");
1✔
601

602
         const Botan::EC_Group reg_group(custom_oid,
1✔
603
                                         secp256r1.get_p(),
604
                                         secp256r1.get_a(),
605
                                         secp256r1.get_b(),
606
                                         secp256r1.get_g_x(),
607
                                         secp256r1.get_g_y(),
608
                                         secp256r1.get_order());
1✔
609

610
         result.test_success("Registration success");
1✔
611
         result.test_is_true("Group has correct OID", reg_group.get_curve_oid() == custom_oid);
1✔
612

613
         // We can now get it by OID:
614
         result.test_is_true("Group has correct OID",
1✔
615
                             Botan::EC_Group::from_OID(custom_oid).get_curve_oid() == custom_oid);
1✔
616

617
         // In the current data model of EC_Group there is a 1:1 OID:group, so these
618
         // have distinct underlying data
619
         result.test_is_true("Groups have different inner data pointers", reg_group._data() != secp256r1._data());
1✔
620

621
   #if defined(BOTAN_HAS_PCURVES_SECP256R1)
622
         // However we should have gotten a pcurves out of the deal *and* it
623
         // should be the exact same shared_ptr as the official curve
624

625
         result.test_enum_eq("Group is pcurves based", reg_group.engine(), Botan::EC_Group_Engine::Optimized);
1✔
626

627
         try {
1✔
628
            const auto& pcurve = reg_group._data()->pcurve();
1✔
629
            result.test_is_true("Group with custom OID got the same pcurve pointer",
1✔
630
                                &pcurve == &secp256r1._data()->pcurve());
1✔
631
         } catch(...) {
×
632
            result.test_failure("Group with custom OID did not get a pcurve pointer");
×
633
         }
×
634
   #endif
635

636
         return result;
2✔
637
      }
1✔
638

639
      Test::Result test_supports_named_group_with_registration() {
1✔
640
         Test::Result result("EC_Group::supports_named_group with custom registration");
1✔
641

642
         Botan::EC_Group::clear_registered_curve_data();
1✔
643

644
         result.test_is_false("Unknown name is not supported",
1✔
645
                              Botan::EC_Group::supports_named_group("not_a_real_curve_name_xyz"));
1✔
646

647
         const Botan::OID custom_oid("1.3.6.1.4.1.25258.4.9000");
1✔
648
         const std::string custom_name = "goku-curve";  // very strong
1✔
649

650
         Botan::OID::register_oid(custom_oid, custom_name);
1✔
651

652
         result.test_is_false("Mapped OID without registered EC_Group is not supported",
1✔
653
                              Botan::EC_Group::supports_named_group(custom_name));
1✔
654

655
         const Botan::BigInt p("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43");
1✔
656
         const Botan::BigInt a("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40");
1✔
657
         const Botan::BigInt b("0x25581");
1✔
658
         const Botan::BigInt order("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE43C8275EA265C6020AB20294751A825");
1✔
659
         const Botan::BigInt g_x("0x01");
1✔
660
         const Botan::BigInt g_y("0x696F1853C1E466D7FC82C96CCEEEDD6BD02C2F9375894EC10BF46306C2B56C77");
1✔
661

662
         const Botan::EC_Group reg_group(custom_oid, p, a, b, g_x, g_y, order);
1✔
663

664
         result.test_is_true("After registration the custom name is supported",
1✔
665
                             Botan::EC_Group::supports_named_group(custom_name));
1✔
666

667
         const auto resolved = Botan::EC_Group::from_name(custom_name);
1✔
668
         result.test_is_true("from_name resolves to the registered OID", resolved.get_curve_oid() == custom_oid);
1✔
669

670
         result.test_is_false("known_named_groups still does not include custom name",
1✔
671
                              Botan::EC_Group::known_named_groups().contains(custom_name));
1✔
672

673
         result.test_is_true("Unregistering removes support for the custom name",
1✔
674
                             Botan::EC_Group::unregister(custom_oid));
1✔
675
         result.test_is_false("After unregister the custom name is not supported",
1✔
676
                              Botan::EC_Group::supports_named_group(custom_name));
1✔
677

678
         return result;
2✔
679
      }
1✔
680

681
      Test::Result test_ec_group_alias_oid_cache() {
1✔
682
         Test::Result result("EC_Group lookup by alias OID does not duplicate cache entry");
1✔
683

684
         // TODO(Botan4) once groups are required to have a single canonical OID this test can be removed
685

686
         if(!Botan::EC_Group::known_named_groups().contains("gost_256A")) {
1✔
687
            result.test_note("Skipping: gost_256A not enabled in this build");
×
688
            return result;
×
689
         }
690

691
         const Botan::OID gost_canonical("1.2.643.7.1.2.1.1.1");
1✔
692
         const std::array<Botan::OID, 2> aliases = {
1✔
693
            Botan::OID("1.2.643.2.2.35.1"),
694
            Botan::OID("1.2.643.2.2.36.0"),
695
         };
1✔
696

697
         // Exercise every ordering of {canonical, alias_a, alias_b} as the first lookup
698
         // to confirm the cache dedup works regardless of which OID populates it first.
699
         const std::array<std::array<Botan::OID, 3>, 3> orderings = {{
1✔
700
            {gost_canonical, aliases[0], aliases[1]},
2✔
701
            {aliases[0], gost_canonical, aliases[1]},
2✔
702
            {aliases[1], aliases[0], gost_canonical},
2✔
703
         }};
1✔
704

705
         for(const auto& order : orderings) {
4✔
706
            Botan::EC_Group::clear_registered_curve_data();
3✔
707

708
            const auto first = Botan::EC_Group::from_OID(order[0]);
3✔
709
            const auto second = Botan::EC_Group::from_OID(order[1]);
3✔
710
            const auto third = Botan::EC_Group::from_OID(order[2]);
3✔
711

712
            result.test_is_true("First lookup yields canonical OID", first.get_curve_oid() == gost_canonical);
3✔
713
            result.test_is_true("Second lookup yields canonical OID", second.get_curve_oid() == gost_canonical);
3✔
714
            result.test_is_true("Third lookup yields canonical OID", third.get_curve_oid() == gost_canonical);
3✔
715

716
            result.test_is_true("Second lookup shares cached data with first", second._data() == first._data());
3✔
717
            result.test_is_true("Third lookup shares cached data with first", third._data() == first._data());
3✔
718

719
            for(size_t i = 0; i != 16; ++i) {
51✔
720
               const auto repeated = Botan::EC_Group::from_OID(order[i % 3]);
48✔
721
               result.test_is_true("Repeated lookup is stable", repeated._data() == first._data());
48✔
722
            }
48✔
723
         }
3✔
724

725
         return result;
726
      }
2✔
727

728
      Test::Result test_ec_group_unregistration() {
1✔
729
         Test::Result result("EC_Group unregistration of group");
1✔
730

731
         Botan::EC_Group::clear_registered_curve_data();
1✔
732

733
         const Botan::OID secp256r1_oid("1.2.840.10045.3.1.7");
1✔
734
         const auto secp256r1 = Botan::EC_Group::from_OID(secp256r1_oid);
1✔
735
         const Botan::OID custom_oid("1.3.6.1.4.1.25258.100.99");
1✔
736
         Botan::OID::register_oid(custom_oid, "secp256r1");
1✔
737

738
         const Botan::EC_Group reg_group(custom_oid,
1✔
739
                                         secp256r1.get_p(),
740
                                         secp256r1.get_a(),
741
                                         secp256r1.get_b(),
742
                                         secp256r1.get_g_x(),
743
                                         secp256r1.get_g_y(),
744
                                         secp256r1.get_order());
1✔
745

746
         const Botan::EC_Group group_from_oid = Botan::EC_Group::from_OID(custom_oid);
1✔
747

748
         result.test_is_true("Internal group is unregistered", Botan::EC_Group::unregister(secp256r1_oid));
1✔
749
         result.test_is_false("Unregistering internal group again does nothing",
1✔
750
                              Botan::EC_Group::unregister(secp256r1_oid));
1✔
751
         result.test_is_true("User defined group is unregistered", Botan::EC_Group::unregister(custom_oid));
1✔
752
         result.test_is_false("Unregistering user defined group again does nothing",
1✔
753
                              Botan::EC_Group::unregister(custom_oid));
1✔
754

755
         try {
1✔
756
            Botan::EC_Group::from_OID(custom_oid);
1✔
757
            result.test_failure("Should have failed");
×
758
         } catch(Botan::Invalid_Argument&) {
1✔
759
            result.test_success("Got expected exception");
1✔
760
         }
1✔
761

762
         result.test_is_true("Group can still be accessed", reg_group.get_curve_oid() == custom_oid);
1✔
763
         result.test_is_true("Group has correct p parameter", reg_group.get_p() == secp256r1.get_p());
1✔
764
         result.test_is_true("Group has correct a parameter", reg_group.get_a() == secp256r1.get_a());
1✔
765
         result.test_is_true("Group has correct b parameter", reg_group.get_b() == secp256r1.get_b());
1✔
766
         result.test_is_true("Group has correct g_x parameter", reg_group.get_g_x() == secp256r1.get_g_x());
1✔
767
         result.test_is_true("Group has correct g_y parameter", reg_group.get_g_y() == secp256r1.get_g_y());
1✔
768
         result.test_is_true("Group has correct order parameter", reg_group.get_order() == secp256r1.get_order());
1✔
769

770
   #if defined(BOTAN_HAS_ECDSA)
771
         std::unique_ptr<Botan::RandomNumberGenerator> rng = Test::new_rng("test_ec_group_unregistration");
1✔
772
         std::unique_ptr<Botan::Private_Key> key = Botan::create_ec_private_key("ECDSA", group_from_oid, *rng);
1✔
773
         result.test_success("Can still use group to create a key");
1✔
774
         result.test_is_true("Key was created correctly", key->check_key(*rng, true));
1✔
775
   #endif
776

777
         // TODO(Botan4) remove this when OIDs lose internal nullability
778
         try {
1✔
779
            const Botan::OID empty_oid("");
1✔
780
            Botan::EC_Group::unregister(empty_oid);
1✔
781
            result.test_failure("Should have failed");
×
782
         } catch(Botan::Invalid_Argument&) {
1✔
783
            result.test_success("Got expected exception");
1✔
784
         }
1✔
785

786
         return result;
1✔
787
      }
2✔
788
};
789

790
BOTAN_REGISTER_SERIALIZED_TEST("pubkey", "ec_group_registration", EC_Group_Registration_Tests);
791

792
class EC_PointEnc_Tests final : public Test {
1✔
793
   public:
794
      std::vector<Test::Result> run() override {
1✔
795
         std::vector<Test::Result> results;
1✔
796

797
         auto& rng = Test::rng();
1✔
798

799
         for(const auto& group_id : Botan::EC_Group::known_named_groups()) {
29✔
800
            const auto group = Botan::EC_Group::from_name(group_id);
28✔
801

802
            Result result("EC_AffinePoint encoding " + group_id);
28✔
803

804
            result.start_timer();
28✔
805

806
            for(size_t trial = 0; trial != 100; ++trial) {
2,828✔
807
               const auto scalar = Botan::EC_Scalar::random(group, rng);
2,800✔
808
               const auto pt = Botan::EC_AffinePoint::g_mul(scalar, rng);
2,800✔
809

810
               const auto pt_u = pt.serialize_uncompressed();
2,800✔
811
               result.test_u8_eq("Expected uncompressed header", pt_u[0], 0x04);
2,800✔
812
               const size_t fe_bytes = (pt_u.size() - 1) / 2;
2,800✔
813
               const auto pt_c = pt.serialize_compressed();
2,800✔
814

815
               result.test_sz_eq("Expected compressed size", pt_c.size(), 1 + fe_bytes);
2,800✔
816
               const uint8_t expected_c_header = (pt_u[pt_u.size() - 1] % 2 == 0) ? 0x02 : 0x03;
2,800✔
817
               result.test_u8_eq("Expected compressed header", pt_c[0], expected_c_header);
2,800✔
818

819
               result.test_bin_eq(
2,800✔
820
                  "Expected compressed x", std::span{pt_c}.subspan(1), std::span{pt_u}.subspan(1, fe_bytes));
821

822
               if(auto d_pt_u = Botan::EC_AffinePoint::deserialize(group, pt_u)) {
2,800✔
823
                  result.test_bin_eq(
2,800✔
824
                     "Deserializing uncompressed returned correct point", d_pt_u->serialize_uncompressed(), pt_u);
5,600✔
825
               } else {
826
                  result.test_failure("Failed to deserialize uncompressed point");
×
827
               }
×
828

829
               if(auto d_pt_c = Botan::EC_AffinePoint::deserialize(group, pt_c)) {
2,800✔
830
                  result.test_bin_eq(
2,800✔
831
                     "Deserializing compressed returned correct point", d_pt_c->serialize_uncompressed(), pt_u);
5,600✔
832
               } else {
833
                  result.test_failure("Failed to deserialize compressed point");
×
834
               }
×
835

836
               const auto neg_pt_c = [&]() {
2,800✔
837
                  auto x = pt_c;
2,800✔
838
                  x[0] ^= 0x01;
2,800✔
839
                  return x;
2,800✔
840
               }();
2,800✔
841

842
               if(auto d_neg_pt_c = Botan::EC_AffinePoint::deserialize(group, neg_pt_c)) {
2,800✔
843
                  result.test_bin_eq("Deserializing compressed with inverted header returned negated point",
2,800✔
844
                                     d_neg_pt_c->serialize_uncompressed(),
5,600✔
845
                                     pt.negate().serialize_uncompressed());
5,600✔
846
               } else {
847
                  result.test_failure("Failed to deserialize compressed point");
×
848
               }
2,800✔
849
            }
2,800✔
850

851
            result.end_timer();
28✔
852

853
            results.push_back(result);
28✔
854
         }
28✔
855

856
         return results;
1✔
857
      }
2,800✔
858
};
859

860
BOTAN_REGISTER_TEST("pubkey", "ec_point_enc", EC_PointEnc_Tests);
861

862
class EC_Point_Arithmetic_Tests final : public Test {
1✔
863
   public:
864
      std::vector<Test::Result> run() override {
1✔
865
         std::vector<Test::Result> results;
1✔
866

867
         auto& rng = Test::rng();
1✔
868

869
         for(const auto& group_id : Botan::EC_Group::known_named_groups()) {
29✔
870
            const auto group = Botan::EC_Group::from_name(group_id);
28✔
871

872
            Result result("EC_AffinePoint arithmetic " + group_id);
28✔
873

874
            result.start_timer();
28✔
875

876
            const auto one = Botan::EC_Scalar::one(group);
28✔
877
            const auto zero = one - one;  // NOLINT(*-redundant-expression)
28✔
878
            const auto g = Botan::EC_AffinePoint::generator(group);
28✔
879
            const auto g_bytes = g.serialize_uncompressed();
28✔
880

881
            const auto id = Botan::EC_AffinePoint::g_mul(zero, rng);
28✔
882
            result.test_is_true("g*zero is point at identity", id.is_identity());
28✔
883

884
            const auto id2 = id.add(id);
28✔
885
            result.test_is_true("identity plus itself is identity", id2.is_identity());
28✔
886

887
            const auto g_one = Botan::EC_AffinePoint::g_mul(one, rng);
28✔
888
            result.test_bin_eq("g*one == generator", g_one.serialize_uncompressed(), g_bytes);
28✔
889

890
            const auto g_plus_id = g_one.add(id);
28✔
891
            result.test_bin_eq("g + id == g", g_plus_id.serialize_uncompressed(), g_bytes);
28✔
892

893
            const auto id_plus_g = id.add(g_one);
28✔
894
            result.test_bin_eq("id + g == g", id_plus_g.serialize_uncompressed(), g_bytes);
28✔
895

896
            const auto g_neg_one = Botan::EC_AffinePoint::g_mul(one.negate(), rng);
28✔
897

898
            const auto id_from_g = g_one.add(g_neg_one);
28✔
899
            result.test_is_true("g - g is identity", id_from_g.is_identity());
28✔
900

901
            const auto g_two = Botan::EC_AffinePoint::g_mul(one + one, rng);
28✔
902
            const auto g_plus_g = g_one.add(g_one);
28✔
903
            result.test_bin_eq("2*g == g+g", g_two.serialize_uncompressed(), g_plus_g.serialize_uncompressed());
56✔
904

905
            result.test_is_true("Scalar::zero is zero", zero.is_zero());
28✔
906
            result.test_is_true("(zero+zero) is zero", (zero + zero).is_zero());
28✔
907
            result.test_is_true("(zero*zero) is zero", (zero * zero).is_zero());
28✔
908
            result.test_is_true("(zero-zero) is zero", (zero - zero).is_zero());  // NOLINT(*-redundant-expression)
28✔
909

910
            const auto neg_zero = zero.negate();
28✔
911
            result.test_is_true("zero.negate() is zero", neg_zero.is_zero());
28✔
912

913
            result.test_is_true("(zero+nz) is zero", (zero + neg_zero).is_zero());
28✔
914
            result.test_is_true("(nz+nz) is zero", (neg_zero + neg_zero).is_zero());
28✔
915
            result.test_is_true("(nz+zero) is zero", (neg_zero + zero).is_zero());
28✔
916

917
            result.test_is_true("Scalar::one is not zero", !one.is_zero());
28✔
918
            result.test_is_true("(one-one) is zero", (one - one).is_zero());  // NOLINT(*-redundant-expression)
28✔
919
            result.test_is_true("(one+one.negate()) is zero", (one + one.negate()).is_zero());
56✔
920
            result.test_is_true("(one.negate()+one) is zero", (one.negate() + one).is_zero());
56✔
921

922
            for(size_t i = 0; i != 16; ++i) {
476✔
923
               const auto pt = Botan::EC_AffinePoint::g_mul(Botan::EC_Scalar::random(group, rng), rng);
448✔
924

925
               const auto a = Botan::EC_Scalar::random(group, rng);
448✔
926
               const auto b = Botan::EC_Scalar::random(group, rng);
448✔
927
               const auto c = a + b;
448✔
928

929
               const auto Pa = pt.mul(a, rng);
448✔
930
               const auto Pb = pt.mul(b, rng);
448✔
931
               const auto Pc = pt.mul(c, rng);
448✔
932

933
               const auto Pc_bytes = Pc.serialize_uncompressed();
448✔
934

935
               const auto Pab = Pa.add(Pb);
448✔
936
               result.test_bin_eq("Pa + Pb == Pc", Pab.serialize_uncompressed(), Pc_bytes);
448✔
937

938
               const auto Pba = Pb.add(Pa);
448✔
939
               result.test_bin_eq("Pb + Pa == Pc", Pba.serialize_uncompressed(), Pc_bytes);
448✔
940
            }
896✔
941

942
            for(size_t i = 0; i != 64; ++i) {
1,820✔
943
               auto h = [&]() {
5,376✔
944
                  const auto s = [&]() {
5,376✔
945
                     if(i == 0) {
1,792✔
946
                        // Test the identity case
947
                        return Botan::EC_Scalar(zero);
28✔
948
                     } else if(i <= 32) {
1,764✔
949
                        // Test cases where the two points have a linear relation
950
                        std::vector<uint8_t> sbytes(group.get_order_bytes());
896✔
951
                        sbytes[sbytes.size() - 1] = static_cast<uint8_t>((i + 1) / 2);
896✔
952
                        auto si = Botan::EC_Scalar::deserialize(group, sbytes).value();
1,792✔
953
                        if(i % 2 == 0) {
896✔
954
                           return si;
448✔
955
                        } else {
956
                           return si.negate();
448✔
957
                        }
958
                     } else {
1,792✔
959
                        return Botan::EC_Scalar::random(group, rng);
868✔
960
                     }
961
                  }();
1,792✔
962
                  auto x = Botan::EC_AffinePoint::g_mul(s, rng);
1,792✔
963
                  return x;
1,792✔
964
               }();
3,584✔
965

966
               const auto s1 = Botan::EC_Scalar::random(group, rng);
1,792✔
967
               const auto s2 = Botan::EC_Scalar::random(group, rng);
1,792✔
968

969
               const Botan::EC_Group::Mul2Table mul2_table(h);
1,792✔
970

971
               const auto ref = Botan::EC_AffinePoint::g_mul(s1, rng).add(h.mul(s2, rng));
1,792✔
972

973
               if(auto mul2pt = mul2_table.mul2_vartime(s1, s2)) {
1,792✔
974
                  result.test_bin_eq("ref == mul2t", ref.serialize_uncompressed(), mul2pt->serialize_uncompressed());
5,376✔
975
               } else {
976
                  result.test_is_true("ref is identity", ref.is_identity());
×
977
               }
×
978
            }
1,792✔
979

980
            result.end_timer();
28✔
981

982
            results.push_back(result);
28✔
983
         }
56✔
984

985
         return results;
1✔
986
      }
×
987
};
988

989
BOTAN_REGISTER_TEST("pubkey", "ec_point_arith", EC_Point_Arithmetic_Tests);
990

991
   #if defined(BOTAN_HAS_ECDSA)
992

993
class ECC_Invalid_Key_Tests final : public Text_Based_Test {
×
994
   public:
995
      ECC_Invalid_Key_Tests() : Text_Based_Test("pubkey/ecc_invalid.vec", "SubjectPublicKey") {}
2✔
996

997
      bool clear_between_callbacks() const override { return false; }
5✔
998

999
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
5✔
1000
         Test::Result result("ECC invalid keys");
5✔
1001

1002
         const std::string encoded = vars.get_req_str("SubjectPublicKey");
5✔
1003
         Botan::DataSource_Memory key_data(Botan::hex_decode(encoded));
10✔
1004

1005
         try {
5✔
1006
            auto key = Botan::X509::load_key(key_data);
5✔
1007
            result.test_is_false("public key fails check", key->check_key(this->rng(), false));
×
1008
         } catch(Botan::Decoding_Error&) {
5✔
1009
            result.test_success("Decoding invalid ECC key results in decoding error exception");
5✔
1010
         }
5✔
1011

1012
         return result;
5✔
1013
      }
5✔
1014
};
1015

1016
BOTAN_REGISTER_TEST("pubkey", "ecc_invalid", ECC_Invalid_Key_Tests);
1017

1018
   #endif
1019

1020
#endif
1021

1022
}  // namespace
1023

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