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

randombit / botan / 27083073843

06 Jun 2026 10:39PM UTC coverage: 89.361% (-0.003%) from 89.364%
27083073843

push

github

randombit
Use a mirror for Intel SDE [ci skip]

Downloads fine with a browser but has recently started failing with wget or a script.
Seems like Intel has started playing some silly game with JavaScript. The license
allows anyone to copy and redistribute SDE so I'm not sure why they are doing this.

110631 of 123803 relevant lines covered (89.36%)

11056986.77 hits per line

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

94.24
/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,926✔
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) {
645✔
77
         const size_t b = x.bits() - 1;
85✔
78
         BOTAN_ASSERT(x.get_bit(b) == true, "Set");
170✔
79
         x.clear_bit(b);
85✔
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(;;) {
105✔
91
      const Botan::BigInt x = Botan::BigInt::random_integer(rng, 1, p);
217✔
92
      const Botan::BigInt x3 = mod_p.multiply(x, mod_p.square(x));
217✔
93
      const Botan::BigInt ax = mod_p.multiply(group.get_a(), x);
217✔
94
      const Botan::BigInt y = mod_p.reduce(x3 + ax + group.get_b());
217✔
95
      const Botan::BigInt sqrt_y = Botan::sqrt_modulo_prime(y, p);
217✔
96

97
      if(sqrt_y > 1) {
217✔
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
   }
217✔
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
Test::Result test_mixed_scalars() {
1✔
420
   Test::Result result("Mixed EC_Scalar Arithmetic");
1✔
421

422
   if(Botan::EC_Group::supports_named_group("secp256r1") && Botan::EC_Group::supports_named_group("secp384r1")) {
1✔
423
      const auto secp256r1 = Botan::EC_Group::from_name("secp256r1");
1✔
424
      const auto secp384r1 = Botan::EC_Group::from_name("secp384r1");
1✔
425

426
      const auto five_256 = Botan::EC_Scalar::from_bigint(secp256r1, 5);
1✔
427
      const auto five_384 = Botan::EC_Scalar::from_bigint(secp384r1, 5);
1✔
428

429
      result.test_is_false("same integer in different scalar groups is different", five_256 == five_384);
1✔
430
      result.test_throws<Botan::Invalid_Argument>("Adding scalars from different groups",
1✔
431
                                                  [&] { static_cast<void>(five_256 + five_384); });
2✔
432
      result.test_throws<Botan::Invalid_Argument>("Subtracting scalars from different groups",
1✔
433
                                                  [&] { static_cast<void>(five_256 - five_384); });
2✔
434
      result.test_throws<Botan::Invalid_Argument>("Multiplying scalars from different groups",
1✔
435
                                                  [&] { static_cast<void>(five_256 * five_384); });
2✔
436

437
      auto scalar_copy = Botan::EC_Scalar::from_bigint(secp256r1, 1);
1✔
438
      scalar_copy = five_384;
1✔
439
      result.test_is_true("copy assignment replaces scalar group", scalar_copy == five_384);
1✔
440
      result.test_sz_eq("copy assignment replaces scalar size", scalar_copy.bytes(), five_384.bytes());
1✔
441

442
      auto scalar_assign = Botan::EC_Scalar::from_bigint(secp256r1, 1);
1✔
443
      scalar_assign.assign(five_384);
1✔
444
      result.test_is_true("assign replaces scalar group", scalar_assign == five_384);
1✔
445
      result.test_sz_eq("assign replaces scalar size", scalar_assign.bytes(), five_384.bytes());
1✔
446
   }
1✔
447

448
   return result;
1✔
449
}
×
450

451
class ECC_Unit_Tests final : public Test {
1✔
452
   public:
453
      std::vector<Test::Result> run() override {
1✔
454
         std::vector<Test::Result> results;
1✔
455

456
         results.push_back(test_decoding_with_seed());
2✔
457
         results.push_back(test_mixed_points());
2✔
458
         results.push_back(test_mixed_scalars());
2✔
459

460
         return results;
1✔
461
      }
×
462
};
463

464
BOTAN_REGISTER_TEST("pubkey", "ecc_unit", ECC_Unit_Tests);
465

466
class EC_Group_Registration_Tests final : public Test {
1✔
467
   public:
468
      std::vector<Test::Result> run() override {
1✔
469
         std::vector<Test::Result> results;
1✔
470

471
         if(Botan::EC_Group::supports_application_specific_group()) {
1✔
472
            results.push_back(test_ecc_registration());
2✔
473
            results.push_back(test_ec_group_from_params());
2✔
474
            results.push_back(test_ec_group_bad_registration());
2✔
475
            results.push_back(test_ec_group_off_curve_generator());
2✔
476
            results.push_back(test_ec_group_duplicate_orders());
2✔
477
            results.push_back(test_ec_group_registration_with_custom_oid());
2✔
478
            results.push_back(test_ec_group_alias_oid_cache());
2✔
479
            results.push_back(test_ec_group_unregistration());
2✔
480
            results.push_back(test_supports_named_group_with_registration());
2✔
481
         }
482

483
         return results;
1✔
484
      }
×
485

486
   private:
487
      Test::Result test_ecc_registration() {
1✔
488
         Test::Result result("ECC registration");
1✔
489

490
         // numsp256d1
491
         const Botan::BigInt p("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43");
1✔
492
         const Botan::BigInt a("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40");
1✔
493
         const Botan::BigInt b("0x25581");
1✔
494
         const Botan::BigInt order("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE43C8275EA265C6020AB20294751A825");
1✔
495

496
         const Botan::BigInt g_x("0x01");
1✔
497
         const Botan::BigInt g_y("0x696F1853C1E466D7FC82C96CCEEEDD6BD02C2F9375894EC10BF46306C2B56C77");
1✔
498

499
         const Botan::OID oid("1.3.6.1.4.1.25258.4.1");
1✔
500

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

504
         auto group = Botan::EC_Group::from_OID(oid);
1✔
505

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

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

511
         return result;
1✔
512
      }
1✔
513

514
      Test::Result test_ec_group_from_params() {
1✔
515
         Test::Result result("EC_Group from params");
1✔
516

517
         Botan::EC_Group::clear_registered_curve_data();
1✔
518

519
         // secp256r1
520
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
521
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
522
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B");
1✔
523

524
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
525
         const Botan::BigInt g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5");
1✔
526
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
527

528
         const Botan::OID oid("1.2.840.10045.3.1.7");
1✔
529

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

535
         return result;
1✔
536
      }
1✔
537

538
      Test::Result test_ec_group_bad_registration() {
1✔
539
         Test::Result result("EC_Group registering non-match");
1✔
540

541
         Botan::EC_Group::clear_registered_curve_data();
1✔
542

543
         // secp256r1 params except with a bad B param
544
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
545
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
546
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604C");
1✔
547

548
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
549
         const Botan::BigInt g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5");
1✔
550
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
551

552
         const Botan::OID oid("1.2.840.10045.3.1.7");
1✔
553

554
         try {
1✔
555
            const Botan::EC_Group reg_group(oid, p, a, b, g_x, g_y, order);
1✔
556
            result.test_failure("Should have failed");
×
557
         } catch(Botan::Invalid_Argument&) {
1✔
558
            result.test_success("Got expected exception");
1✔
559
         }
1✔
560

561
         return result;
2✔
562
      }
1✔
563

564
      Test::Result test_ec_group_off_curve_generator() {
1✔
565
         Test::Result result("EC_Group rejects off-curve generator");
1✔
566

567
         Botan::EC_Group::clear_registered_curve_data();
1✔
568

569
         // secp256r1 params, but g_y has its low bit flipped so (g_x, g_y) is not on the curve
570
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
571
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
572
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B");
1✔
573

574
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
575
         const Botan::BigInt bad_g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F4");
1✔
576
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
577

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

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

587
         return result;
2✔
588
      }
1✔
589

590
      Test::Result test_ec_group_duplicate_orders() {
1✔
591
         Test::Result result("EC_Group with duplicate group order");
1✔
592

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

595
         // secp256r1
596
         const Botan::BigInt p("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
1✔
597
         const Botan::BigInt a("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
1✔
598
         const Botan::BigInt b("0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B");
1✔
599

600
         const Botan::BigInt g_x("0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
1✔
601
         const Botan::BigInt g_y("0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5");
1✔
602
         const Botan::BigInt order("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
1✔
603

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

606
         const Botan::EC_Group reg_group(oid, p, a, b, g_x, g_y, order);
1✔
607
         result.test_success("Registration success");
1✔
608
         result.test_is_true("Group has correct OID", reg_group.get_curve_oid() == oid);
1✔
609

610
         // We can now get it by OID:
611
         const auto hc_group = Botan::EC_Group::from_OID(oid);
1✔
612
         result.test_is_true("Group has correct OID", hc_group.get_curve_oid() == oid);
1✔
613

614
         // Existing secp256r1 unmodified:
615
         const Botan::OID secp160r1("1.2.840.10045.3.1.7");
1✔
616
         const auto other_group = Botan::EC_Group::from_OID(secp160r1);
1✔
617
         result.test_is_true("Group has correct OID", other_group.get_curve_oid() == secp160r1);
1✔
618

619
         return result;
1✔
620
      }
1✔
621

622
      Test::Result test_ec_group_registration_with_custom_oid() {
1✔
623
         Test::Result result("EC_Group registration of standard group with custom OID");
1✔
624

625
         Botan::EC_Group::clear_registered_curve_data();
1✔
626

627
         const Botan::OID secp256r1_oid("1.2.840.10045.3.1.7");
1✔
628
         const auto secp256r1 = Botan::EC_Group::from_OID(secp256r1_oid);
1✔
629
         result.test_is_true("Group has correct OID", secp256r1.get_curve_oid() == secp256r1_oid);
1✔
630

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

633
         Botan::OID::register_oid(custom_oid, "secp256r1");
1✔
634

635
         const Botan::EC_Group reg_group(custom_oid,
1✔
636
                                         secp256r1.get_p(),
637
                                         secp256r1.get_a(),
638
                                         secp256r1.get_b(),
639
                                         secp256r1.get_g_x(),
640
                                         secp256r1.get_g_y(),
641
                                         secp256r1.get_order());
1✔
642

643
         result.test_success("Registration success");
1✔
644
         result.test_is_true("Group has correct OID", reg_group.get_curve_oid() == custom_oid);
1✔
645

646
         // We can now get it by OID:
647
         result.test_is_true("Group has correct OID",
1✔
648
                             Botan::EC_Group::from_OID(custom_oid).get_curve_oid() == custom_oid);
1✔
649

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

654
   #if defined(BOTAN_HAS_PCURVES_SECP256R1)
655
         // However we should have gotten a pcurves out of the deal *and* it
656
         // should be the exact same shared_ptr as the official curve
657

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

660
         try {
1✔
661
            const auto& pcurve = reg_group._data()->pcurve();
1✔
662
            result.test_is_true("Group with custom OID got the same pcurve pointer",
1✔
663
                                &pcurve == &secp256r1._data()->pcurve());
1✔
664
         } catch(...) {
×
665
            result.test_failure("Group with custom OID did not get a pcurve pointer");
×
666
         }
×
667
   #endif
668

669
         return result;
2✔
670
      }
1✔
671

672
      Test::Result test_supports_named_group_with_registration() {
1✔
673
         Test::Result result("EC_Group::supports_named_group with custom registration");
1✔
674

675
         Botan::EC_Group::clear_registered_curve_data();
1✔
676

677
         result.test_is_false("Unknown name is not supported",
1✔
678
                              Botan::EC_Group::supports_named_group("not_a_real_curve_name_xyz"));
1✔
679

680
         const Botan::OID custom_oid("1.3.6.1.4.1.25258.4.9000");
1✔
681
         const std::string custom_name = "goku-curve";  // very strong
1✔
682

683
         Botan::OID::register_oid(custom_oid, custom_name);
1✔
684

685
         result.test_is_false("Mapped OID without registered EC_Group is not supported",
1✔
686
                              Botan::EC_Group::supports_named_group(custom_name));
1✔
687

688
         const Botan::BigInt p("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43");
1✔
689
         const Botan::BigInt a("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40");
1✔
690
         const Botan::BigInt b("0x25581");
1✔
691
         const Botan::BigInt order("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE43C8275EA265C6020AB20294751A825");
1✔
692
         const Botan::BigInt g_x("0x01");
1✔
693
         const Botan::BigInt g_y("0x696F1853C1E466D7FC82C96CCEEEDD6BD02C2F9375894EC10BF46306C2B56C77");
1✔
694

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

697
         result.test_is_true("After registration the custom name is supported",
1✔
698
                             Botan::EC_Group::supports_named_group(custom_name));
1✔
699

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

703
         result.test_is_false("known_named_groups still does not include custom name",
1✔
704
                              Botan::EC_Group::known_named_groups().contains(custom_name));
1✔
705

706
         result.test_is_true("Unregistering removes support for the custom name",
1✔
707
                             Botan::EC_Group::unregister(custom_oid));
1✔
708
         result.test_is_false("After unregister the custom name is not supported",
1✔
709
                              Botan::EC_Group::supports_named_group(custom_name));
1✔
710

711
         return result;
2✔
712
      }
1✔
713

714
      Test::Result test_ec_group_alias_oid_cache() {
1✔
715
         Test::Result result("EC_Group lookup by alias OID does not duplicate cache entry");
1✔
716

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

719
         if(!Botan::EC_Group::known_named_groups().contains("gost_256A")) {
1✔
720
            result.test_note("Skipping: gost_256A not enabled in this build");
×
721
            return result;
×
722
         }
723

724
         const Botan::OID gost_canonical("1.2.643.7.1.2.1.1.1");
1✔
725
         const std::array<Botan::OID, 2> aliases = {
1✔
726
            Botan::OID("1.2.643.2.2.35.1"),
727
            Botan::OID("1.2.643.2.2.36.0"),
728
         };
1✔
729

730
         // Exercise every ordering of {canonical, alias_a, alias_b} as the first lookup
731
         // to confirm the cache dedup works regardless of which OID populates it first.
732
         const std::array<std::array<Botan::OID, 3>, 3> orderings = {{
1✔
733
            {gost_canonical, aliases[0], aliases[1]},
2✔
734
            {aliases[0], gost_canonical, aliases[1]},
2✔
735
            {aliases[1], aliases[0], gost_canonical},
2✔
736
         }};
1✔
737

738
         for(const auto& order : orderings) {
4✔
739
            Botan::EC_Group::clear_registered_curve_data();
3✔
740

741
            const auto first = Botan::EC_Group::from_OID(order[0]);
3✔
742
            const auto second = Botan::EC_Group::from_OID(order[1]);
3✔
743
            const auto third = Botan::EC_Group::from_OID(order[2]);
3✔
744

745
            result.test_is_true("First lookup yields canonical OID", first.get_curve_oid() == gost_canonical);
3✔
746
            result.test_is_true("Second lookup yields canonical OID", second.get_curve_oid() == gost_canonical);
3✔
747
            result.test_is_true("Third lookup yields canonical OID", third.get_curve_oid() == gost_canonical);
3✔
748

749
            result.test_is_true("Second lookup shares cached data with first", second._data() == first._data());
3✔
750
            result.test_is_true("Third lookup shares cached data with first", third._data() == first._data());
3✔
751

752
            for(size_t i = 0; i != 16; ++i) {
51✔
753
               const auto repeated = Botan::EC_Group::from_OID(order[i % 3]);
48✔
754
               result.test_is_true("Repeated lookup is stable", repeated._data() == first._data());
48✔
755
            }
48✔
756
         }
3✔
757

758
         return result;
759
      }
2✔
760

761
      Test::Result test_ec_group_unregistration() {
1✔
762
         Test::Result result("EC_Group unregistration of group");
1✔
763

764
         Botan::EC_Group::clear_registered_curve_data();
1✔
765

766
         const Botan::OID secp256r1_oid("1.2.840.10045.3.1.7");
1✔
767
         const auto secp256r1 = Botan::EC_Group::from_OID(secp256r1_oid);
1✔
768
         const Botan::OID custom_oid("1.3.6.1.4.1.25258.100.99");
1✔
769
         Botan::OID::register_oid(custom_oid, "secp256r1");
1✔
770

771
         const Botan::EC_Group reg_group(custom_oid,
1✔
772
                                         secp256r1.get_p(),
773
                                         secp256r1.get_a(),
774
                                         secp256r1.get_b(),
775
                                         secp256r1.get_g_x(),
776
                                         secp256r1.get_g_y(),
777
                                         secp256r1.get_order());
1✔
778

779
         const Botan::EC_Group group_from_oid = Botan::EC_Group::from_OID(custom_oid);
1✔
780

781
         result.test_is_true("Internal group is unregistered", Botan::EC_Group::unregister(secp256r1_oid));
1✔
782
         result.test_is_false("Unregistering internal group again does nothing",
1✔
783
                              Botan::EC_Group::unregister(secp256r1_oid));
1✔
784
         result.test_is_true("User defined group is unregistered", Botan::EC_Group::unregister(custom_oid));
1✔
785
         result.test_is_false("Unregistering user defined group again does nothing",
1✔
786
                              Botan::EC_Group::unregister(custom_oid));
1✔
787

788
         try {
1✔
789
            Botan::EC_Group::from_OID(custom_oid);
1✔
790
            result.test_failure("Should have failed");
×
791
         } catch(Botan::Invalid_Argument&) {
1✔
792
            result.test_success("Got expected exception");
1✔
793
         }
1✔
794

795
         result.test_is_true("Group can still be accessed", reg_group.get_curve_oid() == custom_oid);
1✔
796
         result.test_is_true("Group has correct p parameter", reg_group.get_p() == secp256r1.get_p());
1✔
797
         result.test_is_true("Group has correct a parameter", reg_group.get_a() == secp256r1.get_a());
1✔
798
         result.test_is_true("Group has correct b parameter", reg_group.get_b() == secp256r1.get_b());
1✔
799
         result.test_is_true("Group has correct g_x parameter", reg_group.get_g_x() == secp256r1.get_g_x());
1✔
800
         result.test_is_true("Group has correct g_y parameter", reg_group.get_g_y() == secp256r1.get_g_y());
1✔
801
         result.test_is_true("Group has correct order parameter", reg_group.get_order() == secp256r1.get_order());
1✔
802

803
   #if defined(BOTAN_HAS_ECDSA)
804
         std::unique_ptr<Botan::RandomNumberGenerator> rng = Test::new_rng("test_ec_group_unregistration");
1✔
805
         std::unique_ptr<Botan::Private_Key> key = Botan::create_ec_private_key("ECDSA", group_from_oid, *rng);
1✔
806
         result.test_success("Can still use group to create a key");
1✔
807
         result.test_is_true("Key was created correctly", key->check_key(*rng, true));
1✔
808
   #endif
809

810
         // TODO(Botan4) remove this when OIDs lose internal nullability
811
         try {
1✔
812
            const Botan::OID empty_oid("");
1✔
813
            Botan::EC_Group::unregister(empty_oid);
1✔
814
            result.test_failure("Should have failed");
×
815
         } catch(Botan::Invalid_Argument&) {
1✔
816
            result.test_success("Got expected exception");
1✔
817
         }
1✔
818

819
         return result;
1✔
820
      }
2✔
821
};
822

823
BOTAN_REGISTER_SERIALIZED_TEST("pubkey", "ec_group_registration", EC_Group_Registration_Tests);
824

825
class EC_PointEnc_Tests final : public Test {
1✔
826
   public:
827
      std::vector<Test::Result> run() override {
1✔
828
         std::vector<Test::Result> results;
1✔
829

830
         auto& rng = Test::rng();
1✔
831

832
         for(const auto& group_id : Botan::EC_Group::known_named_groups()) {
29✔
833
            const auto group = Botan::EC_Group::from_name(group_id);
28✔
834

835
            Result result("EC_AffinePoint encoding " + group_id);
28✔
836

837
            result.start_timer();
28✔
838

839
            for(size_t trial = 0; trial != 100; ++trial) {
2,828✔
840
               const auto scalar = Botan::EC_Scalar::random(group, rng);
2,800✔
841
               const auto pt = Botan::EC_AffinePoint::g_mul(scalar, rng);
2,800✔
842

843
               const auto pt_u = pt.serialize_uncompressed();
2,800✔
844
               result.test_u8_eq("Expected uncompressed header", pt_u[0], 0x04);
2,800✔
845
               const size_t fe_bytes = (pt_u.size() - 1) / 2;
2,800✔
846
               const auto pt_c = pt.serialize_compressed();
2,800✔
847

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

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

855
               if(auto d_pt_u = Botan::EC_AffinePoint::deserialize(group, pt_u)) {
2,800✔
856
                  result.test_bin_eq(
2,800✔
857
                     "Deserializing uncompressed returned correct point", d_pt_u->serialize_uncompressed(), pt_u);
5,600✔
858
               } else {
859
                  result.test_failure("Failed to deserialize uncompressed point");
×
860
               }
×
861

862
               if(auto d_pt_c = Botan::EC_AffinePoint::deserialize(group, pt_c)) {
2,800✔
863
                  result.test_bin_eq(
2,800✔
864
                     "Deserializing compressed returned correct point", d_pt_c->serialize_uncompressed(), pt_u);
5,600✔
865
               } else {
866
                  result.test_failure("Failed to deserialize compressed point");
×
867
               }
×
868

869
               const auto neg_pt_c = [&]() {
2,800✔
870
                  auto x = pt_c;
2,800✔
871
                  x[0] ^= 0x01;
2,800✔
872
                  return x;
2,800✔
873
               }();
2,800✔
874

875
               if(auto d_neg_pt_c = Botan::EC_AffinePoint::deserialize(group, neg_pt_c)) {
2,800✔
876
                  result.test_bin_eq("Deserializing compressed with inverted header returned negated point",
2,800✔
877
                                     d_neg_pt_c->serialize_uncompressed(),
5,600✔
878
                                     pt.negate().serialize_uncompressed());
5,600✔
879
               } else {
880
                  result.test_failure("Failed to deserialize compressed point");
×
881
               }
2,800✔
882
            }
2,800✔
883

884
            result.end_timer();
28✔
885

886
            results.push_back(result);
28✔
887
         }
28✔
888

889
         return results;
1✔
890
      }
2,800✔
891
};
892

893
BOTAN_REGISTER_TEST("pubkey", "ec_point_enc", EC_PointEnc_Tests);
894

895
class EC_Point_Arithmetic_Tests final : public Test {
1✔
896
   public:
897
      std::vector<Test::Result> run() override {
1✔
898
         std::vector<Test::Result> results;
1✔
899

900
         auto& rng = Test::rng();
1✔
901

902
         for(const auto& group_id : Botan::EC_Group::known_named_groups()) {
29✔
903
            const auto group = Botan::EC_Group::from_name(group_id);
28✔
904

905
            Result result("EC_AffinePoint arithmetic " + group_id);
28✔
906

907
            result.start_timer();
28✔
908

909
            const auto one = Botan::EC_Scalar::one(group);
28✔
910
            const auto zero = one - one;  // NOLINT(*-redundant-expression)
28✔
911
            const auto g = Botan::EC_AffinePoint::generator(group);
28✔
912
            const auto g_bytes = g.serialize_uncompressed();
28✔
913

914
            auto moved_from = Botan::EC_Scalar::from_bigint(group, 1);
28✔
915
            const auto replacement = Botan::EC_Scalar::from_bigint(group, 2);
28✔
916
            auto moved_to = std::move(moved_from);
28✔
917
            auto as_rvalue = [](auto& x) -> decltype(auto) { return std::move(x); };
28✔
918
            moved_from = as_rvalue(moved_from);
28✔
919
            moved_from = replacement;
28✔
920
            result.test_is_true("copy assignment into moved-from EC_Scalar", moved_from == replacement);
28✔
921
            moved_from = std::move(moved_to);
28✔
922
            result.test_is_true("move assignment into EC_Scalar", moved_from == one);
28✔
923
            const auto before_self_move = moved_from.to_bigint();
28✔
924
            moved_from = as_rvalue(moved_from);
28✔
925
            result.test_bn_eq("self-move keeps EC_Scalar value", moved_from.to_bigint(), before_self_move);
28✔
926

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

930
            const auto id2 = id.add(id);
28✔
931
            result.test_is_true("identity plus itself is identity", id2.is_identity());
28✔
932

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

936
            const auto g_plus_id = g_one.add(id);
28✔
937
            result.test_bin_eq("g + id == g", g_plus_id.serialize_uncompressed(), g_bytes);
28✔
938

939
            const auto id_plus_g = id.add(g_one);
28✔
940
            result.test_bin_eq("id + g == g", id_plus_g.serialize_uncompressed(), g_bytes);
28✔
941

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

944
            const auto id_from_g = g_one.add(g_neg_one);
28✔
945
            result.test_is_true("g - g is identity", id_from_g.is_identity());
28✔
946

947
            const auto g_two = Botan::EC_AffinePoint::g_mul(one + one, rng);
28✔
948
            const auto g_plus_g = g_one.add(g_one);
28✔
949
            result.test_bin_eq("2*g == g+g", g_two.serialize_uncompressed(), g_plus_g.serialize_uncompressed());
56✔
950

951
            result.test_is_true("Scalar::zero is zero", zero.is_zero());
28✔
952
            result.test_is_true("(zero+zero) is zero", (zero + zero).is_zero());
28✔
953
            result.test_is_true("(zero*zero) is zero", (zero * zero).is_zero());
28✔
954
            result.test_is_true("(zero-zero) is zero", (zero - zero).is_zero());  // NOLINT(*-redundant-expression)
28✔
955

956
            const auto neg_zero = zero.negate();
28✔
957
            result.test_is_true("zero.negate() is zero", neg_zero.is_zero());
28✔
958

959
            result.test_is_true("(zero+nz) is zero", (zero + neg_zero).is_zero());
28✔
960
            result.test_is_true("(nz+nz) is zero", (neg_zero + neg_zero).is_zero());
28✔
961
            result.test_is_true("(nz+zero) is zero", (neg_zero + zero).is_zero());
28✔
962

963
            result.test_is_true("Scalar::one is not zero", !one.is_zero());
28✔
964
            result.test_is_true("(one-one) is zero", (one - one).is_zero());  // NOLINT(*-redundant-expression)
28✔
965
            result.test_is_true("(one+one.negate()) is zero", (one + one.negate()).is_zero());
56✔
966
            result.test_is_true("(one.negate()+one) is zero", (one.negate() + one).is_zero());
56✔
967

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

971
               const auto a = Botan::EC_Scalar::random(group, rng);
448✔
972
               const auto b = Botan::EC_Scalar::random(group, rng);
448✔
973
               const auto c = a + b;
448✔
974

975
               const auto Pa = pt.mul(a, rng);
448✔
976
               const auto Pb = pt.mul(b, rng);
448✔
977
               const auto Pc = pt.mul(c, rng);
448✔
978

979
               const auto Pc_bytes = Pc.serialize_uncompressed();
448✔
980

981
               const auto Pab = Pa.add(Pb);
448✔
982
               result.test_bin_eq("Pa + Pb == Pc", Pab.serialize_uncompressed(), Pc_bytes);
448✔
983

984
               const auto Pba = Pb.add(Pa);
448✔
985
               result.test_bin_eq("Pb + Pa == Pc", Pba.serialize_uncompressed(), Pc_bytes);
448✔
986
            }
896✔
987

988
            for(size_t i = 0; i != 64; ++i) {
1,820✔
989
               auto h = [&]() {
5,376✔
990
                  const auto s = [&]() {
5,376✔
991
                     if(i == 0) {
1,792✔
992
                        // Test the identity case
993
                        return Botan::EC_Scalar(zero);
28✔
994
                     } else if(i <= 32) {
1,764✔
995
                        // Test cases where the two points have a linear relation
996
                        std::vector<uint8_t> sbytes(group.get_order_bytes());
896✔
997
                        sbytes[sbytes.size() - 1] = static_cast<uint8_t>((i + 1) / 2);
896✔
998
                        auto si = Botan::EC_Scalar::deserialize(group, sbytes).value();
1,792✔
999
                        if(i % 2 == 0) {
896✔
1000
                           return si;
448✔
1001
                        } else {
1002
                           return si.negate();
448✔
1003
                        }
1004
                     } else {
1,792✔
1005
                        return Botan::EC_Scalar::random(group, rng);
868✔
1006
                     }
1007
                  }();
1,792✔
1008
                  auto x = Botan::EC_AffinePoint::g_mul(s, rng);
1,792✔
1009
                  return x;
1,792✔
1010
               }();
3,584✔
1011

1012
               const auto s1 = Botan::EC_Scalar::random(group, rng);
1,792✔
1013
               const auto s2 = Botan::EC_Scalar::random(group, rng);
1,792✔
1014

1015
               const Botan::EC_Group::Mul2Table mul2_table(h);
1,792✔
1016

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

1019
               if(auto mul2pt = mul2_table.mul2_vartime(s1, s2)) {
1,792✔
1020
                  result.test_bin_eq("ref == mul2t", ref.serialize_uncompressed(), mul2pt->serialize_uncompressed());
5,376✔
1021
               } else {
1022
                  result.test_is_true("ref is identity", ref.is_identity());
×
1023
               }
×
1024
            }
1,792✔
1025

1026
            result.end_timer();
28✔
1027

1028
            results.push_back(result);
28✔
1029
         }
56✔
1030

1031
         return results;
1✔
1032
      }
×
1033
};
1034

1035
BOTAN_REGISTER_TEST("pubkey", "ec_point_arith", EC_Point_Arithmetic_Tests);
1036

1037
   #if defined(BOTAN_HAS_ECDSA)
1038

1039
class ECC_Invalid_Key_Tests final : public Text_Based_Test {
×
1040
   public:
1041
      ECC_Invalid_Key_Tests() : Text_Based_Test("pubkey/ecc_invalid.vec", "SubjectPublicKey") {}
2✔
1042

1043
      bool clear_between_callbacks() const override { return false; }
5✔
1044

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

1048
         const std::string encoded = vars.get_req_str("SubjectPublicKey");
5✔
1049
         Botan::DataSource_Memory key_data(Botan::hex_decode(encoded));
10✔
1050

1051
         try {
5✔
1052
            auto key = Botan::X509::load_key(key_data);
5✔
1053
            result.test_is_false("public key fails check", key->check_key(this->rng(), false));
×
1054
         } catch(Botan::Decoding_Error&) {
5✔
1055
            result.test_success("Decoding invalid ECC key results in decoding error exception");
5✔
1056
         }
5✔
1057

1058
         return result;
5✔
1059
      }
5✔
1060
};
1061

1062
BOTAN_REGISTER_TEST("pubkey", "ecc_invalid", ECC_Invalid_Key_Tests);
1063

1064
   #endif
1065

1066
#endif
1067

1068
}  // namespace
1069

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