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

randombit / botan / 10325883160

09 Aug 2024 09:21PM UTC coverage: 91.294% (+0.03%) from 91.268%
10325883160

push

github

web-flow
Merge pull request #4218 from randombit/jack/pcurves-p224

Add pcurves P-224

87709 of 96073 relevant lines covered (91.29%)

9336080.68 hits per line

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

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

7
#include "tests.h"
8

9
#if defined(BOTAN_HAS_PCURVES)
10
   #include "test_rng.h"
11
   #include <botan/hash.h>
12
   #include <botan/mem_ops.h>
13
   #include <botan/internal/pcurves.h>
14
   #include <botan/internal/stl_util.h>
15
#endif
16

17
namespace Botan_Tests {
18

19
#if defined(BOTAN_HAS_PCURVES)
20

21
class Pcurve_Basemul_Tests final : public Text_Based_Test {
×
22
   public:
23
      Pcurve_Basemul_Tests() : Text_Based_Test("pubkey/ecc_base_point_mul.vec", "k,P") {}
2✔
24

25
      Test::Result run_one_test(const std::string& group_id, const VarMap& vars) override {
529✔
26
         Test::Result result("Pcurves base point multiply " + group_id);
529✔
27

28
         const auto k_bytes = vars.get_req_bin("k");
529✔
29
         const auto P_bytes = vars.get_req_bin("P");
529✔
30

31
         auto& rng = Test::rng();
529✔
32
         Botan::Null_RNG null_rng;
529✔
33

34
         if(auto curve = Botan::PCurve::PrimeOrderCurve::from_name(group_id)) {
529✔
35
            if(auto scalar = curve->deserialize_scalar(k_bytes)) {
527✔
36
               const auto k = scalar.value();
527✔
37
               auto pt2 = curve->mul_by_g(k, rng).to_affine().serialize();
1,581✔
38
               result.test_eq("mul_by_g correct", pt2, P_bytes);
527✔
39

40
               auto pt3 = curve->mul_by_g(k, null_rng).to_affine().serialize();
1,581✔
41
               result.test_eq("mul_by_g (Null_RNG) correct", pt3, P_bytes);
527✔
42

43
               auto g = curve->generator();
527✔
44
               auto pt4 = curve->mul(g, k, rng).to_affine().serialize();
1,581✔
45
               result.test_eq("mul correct", pt4, P_bytes);
527✔
46

47
               auto pt5 = curve->mul(g, k, null_rng).to_affine().serialize();
1,581✔
48
               result.test_eq("mul correct (Null_RNG)", pt5, P_bytes);
527✔
49

50
               // Now test the var point mul with a blinded point ((g*b)*k)/b = pt
51
               auto b = curve->random_scalar(rng);
527✔
52
               auto binv = b.invert();
527✔
53
               auto gx = curve->mul_by_g(b, rng).to_affine();
1,054✔
54
               auto gx_k = curve->mul(gx, k, rng).to_affine();
1,054✔
55
               auto g_k = curve->mul(gx_k, binv, rng).to_affine();
1,054✔
56
               result.test_eq("blinded mul correct", g_k.serialize(), P_bytes);
1,581✔
57
            } else {
5,797✔
58
               result.test_failure("Curve rejected scalar input");
×
59
            }
527✔
60
         }
×
61

62
         return result;
529✔
63
      }
1,587✔
64
};
65

66
BOTAN_REGISTER_TEST("pcurves", "pcurves_basemul", Pcurve_Basemul_Tests);
67

68
class Pcurve_Ecdh_Tests final : public Text_Based_Test {
×
69
   public:
70
      Pcurve_Ecdh_Tests() : Text_Based_Test("pubkey/ecdh.vec", "Secret,CounterKey,K") {}
2✔
71

72
      Test::Result run_one_test(const std::string& group_id, const VarMap& vars) override {
156✔
73
         Test::Result result("Pcurves ECDH " + group_id);
156✔
74

75
         const auto sk = vars.get_req_bin("Secret");
156✔
76
         const auto peer_key = vars.get_req_bin("CounterKey");
156✔
77
         const auto shared_secret = vars.get_req_bin("K");
156✔
78

79
         auto curve = Botan::PCurve::PrimeOrderCurve::from_name(group_id);
156✔
80

81
         if(!curve) {
156✔
82
            result.test_note("Skipping test due to missing pcurve " + group_id);
×
83
            return result;
×
84
         }
85

86
         auto x = curve->deserialize_scalar(sk);
156✔
87
         auto pt = curve->deserialize_point(peer_key);
156✔
88

89
         if(x && pt) {
156✔
90
            auto ss = curve->mul(pt.value(), x.value(), rng()).to_affine().x_bytes();
468✔
91
            result.test_eq("shared secret", ss, shared_secret);
312✔
92
         } else {
156✔
93
            result.test_failure("Curve rejected test inputs");
×
94
         }
95

96
         return result;
156✔
97
      }
936✔
98
};
99

100
BOTAN_REGISTER_TEST("pcurves", "pcurves_ecdh", Pcurve_Ecdh_Tests);
101

102
class Pcurve_Ecdsa_Sign_Tests final : public Text_Based_Test {
×
103
   public:
104
      Pcurve_Ecdsa_Sign_Tests() : Text_Based_Test("pubkey/ecdsa_prob.vec", "Group,Hash,Msg,Nonce,X,Signature") {}
2✔
105

106
      bool clear_between_callbacks() const override { return false; }
251✔
107

108
      static auto ecdsa_sign(const Botan::PCurve::PrimeOrderCurve& curve,
251✔
109
                             const Botan::PCurve::PrimeOrderCurve::Scalar& x,
110
                             std::span<const uint8_t> msg,
111
                             Botan::RandomNumberGenerator& nonce_rng,
112
                             Botan::RandomNumberGenerator& rng) {
113
         const auto e = curve.scalar_from_bits_with_trunc(msg);
251✔
114
         const auto k = curve.random_scalar(nonce_rng);
251✔
115
         const auto r = curve.base_point_mul_x_mod_order(k, rng);
251✔
116

117
         /*
118
         * Blind the input message and compute x*r+e as (b*x*r + b*e)/b
119
         */
120
         auto b = curve.random_scalar(rng);
251✔
121
         auto b_inv = b.invert();
251✔
122

123
         const auto k_inv = (b * k).invert() * b;
502✔
124

125
         b = b.square();
251✔
126
         b_inv = b_inv.square();
251✔
127

128
         const auto be = b * e;
251✔
129
         const auto bx = b * x;
251✔
130

131
         const auto bxr_e = (bx * r) + be;
251✔
132

133
         const auto s = (k_inv * bxr_e) * b_inv;
251✔
134

135
         // With overwhelming probability, a bug rather than actual zero r/s
136
         if(r.is_zero() || s.is_zero()) {
502✔
137
            throw Botan::Internal_Error("During ECDSA signature generated zero r/s");
×
138
         }
139

140
         return std::pair{r, s};
251✔
141
      }
2,510✔
142

143
      static bool ecdsa_verify(const Botan::PCurve::PrimeOrderCurve& curve,
251✔
144
                               const Botan::PCurve::PrimeOrderCurve::AffinePoint& pk,
145
                               const std::span<const uint8_t> msg,
146
                               const Botan::PCurve::PrimeOrderCurve::Scalar& r,
147
                               const Botan::PCurve::PrimeOrderCurve::Scalar& s) {
148
         if(r.is_zero() || s.is_zero()) {
502✔
149
            return false;
×
150
         }
151

152
         const auto z = curve.scalar_from_bits_with_trunc(msg);
251✔
153

154
         const auto s_inv = curve.scalar_invert(s);
251✔
155
         auto u1 = z * s_inv;
251✔
156
         auto u2 = r * s_inv;
251✔
157
         const auto table = curve.mul2_setup(curve.generator(), pk);
251✔
158

159
         return curve.mul2_vartime_x_mod_order_eq(*table, r, u1, u2);
251✔
160
      }
1,255✔
161

162
      Test::Result run_one_test(const std::string&, const VarMap& vars) override {
251✔
163
         const std::string group_id = vars.get_req_str("Group");
502✔
164
         const std::string hash_fn = vars.get_req_str("Hash");
251✔
165

166
         const auto sk = vars.get_req_bin("X");
251✔
167
         const auto msg = vars.get_req_bin("Msg");
251✔
168
         const auto nonce = vars.get_req_bin("Nonce");
251✔
169
         const auto expected_sig = vars.get_req_bin("Signature");
251✔
170

171
         Test::Result result("Pcurves ECDSA sign " + group_id);
251✔
172

173
         const auto hash = Botan::HashFunction::create(hash_fn);
251✔
174
         if(!hash && !hash_fn.starts_with("Raw")) {
320✔
175
            result.test_note("Skipping test due to missing hash " + hash_fn);
×
176
            return result;
×
177
         }
178

179
         auto curve = Botan::PCurve::PrimeOrderCurve::from_name(group_id);
251✔
180

181
         if(!curve) {
251✔
182
            result.test_note("Skipping test due to missing pcurve " + group_id);
×
183
            return result;
×
184
         }
185

186
         if(auto x = curve->deserialize_scalar(sk)) {
251✔
187
            Fixed_Output_RNG nonce_rng(nonce);
251✔
188
            const auto e = (hash) ? hash->process<std::vector<uint8_t>>(msg) : msg;
251✔
189
            const auto [r, s] = ecdsa_sign(*curve, x.value(), e, nonce_rng, rng());
251✔
190

191
            const auto sig_bytes = Botan::concat(r.serialize(), s.serialize());
753✔
192
            result.test_eq("correct signature generated", sig_bytes, expected_sig);
251✔
193

194
            const auto pk = curve->mul_by_g(x.value(), rng()).to_affine();
502✔
195

196
            const bool valid = ecdsa_verify(*curve, pk, e, r, s);
251✔
197
            result.confirm("signature is valid", valid);
502✔
198
         } else {
753✔
199
            result.test_failure("Curve rejected secret key");
×
200
         }
×
201

202
         return result;
251✔
203
      }
1,506✔
204
};
205

206
BOTAN_REGISTER_TEST("pcurves", "pcurves_ecdsa_sign", Pcurve_Ecdsa_Sign_Tests);
207

208
class Pcurve_Arithmetic_Tests final : public Test {
×
209
   public:
210
      std::vector<Test::Result> run() override {
1✔
211
         std::vector<Test::Result> results;
1✔
212

213
         auto& rng = Test::rng();
1✔
214

215
         for(auto id : Botan::PCurve::PrimeOrderCurveId::all()) {
13✔
216
            Test::Result result("Pcurves point operations " + id.to_string());
24✔
217

218
            result.start_timer();
12✔
219

220
            auto curve = Botan::PCurve::PrimeOrderCurve::from_id(id);
12✔
221

222
            if(!curve) {
12✔
223
               result.test_note("Skipping test due to missing pcurve " + id.to_string());
×
224
               continue;
×
225
            }
226

227
            const auto zero = curve->scalar_zero();
12✔
228
            const auto one = curve->scalar_one();
12✔
229
            const auto g = curve->generator();
12✔
230
            const auto g_bytes = g.serialize();
12✔
231

232
            const auto inf = curve->mul_by_g(zero, rng);
12✔
233
            result.confirm("g*zero is point at infinity", inf.to_affine().is_identity());
24✔
234

235
            std::vector<uint8_t> inf_bytes(g_bytes.size());
12✔
236
            inf_bytes[0] = 0x04;
12✔
237

238
            result.test_eq("infinity has expected encoding", inf.to_affine().serialize(), inf_bytes);
36✔
239

240
            const auto inf2 = inf.dbl();
12✔
241
            result.test_eq("infinity * 2 is infinity", inf2.to_affine().serialize(), inf_bytes);
36✔
242

243
            const auto inf3 = inf2 + inf;
12✔
244
            result.test_eq("infinity plus itself is infinity", inf3.to_affine().serialize(), inf_bytes);
36✔
245

246
            const auto g_one = curve->mul_by_g(one, rng);
12✔
247
            result.test_eq("g*one == generator", g_one.to_affine().serialize(), g_bytes);
36✔
248

249
            const auto g_plus_inf = g_one + inf;
12✔
250
            result.test_eq("g + inf == g", g_plus_inf.to_affine().serialize(), g_bytes);
36✔
251

252
            const auto g_plus_infa = g_one + inf.to_affine();
12✔
253
            result.test_eq("g + inf (affine) == g", g_plus_infa.to_affine().serialize(), g_bytes);
36✔
254

255
            const auto inf_plus_g = inf + g_one;
12✔
256
            result.test_eq("inf + g == g", inf_plus_g.to_affine().serialize(), g_bytes);
36✔
257

258
            const auto inf_plus_ga = inf + g_one.to_affine();
12✔
259
            result.test_eq("inf + g (affine) == g", inf_plus_ga.to_affine().serialize(), g_bytes);
36✔
260

261
            const auto g_neg_one = curve->mul_by_g(one.negate(), rng).to_affine();
36✔
262

263
            const auto inf_from_g = g_one + g_neg_one;
12✔
264
            result.test_eq("g - g is infinity", inf_from_g.to_affine().serialize(), inf_bytes);
36✔
265

266
            const auto g_two = curve->mul_by_g(one + one, rng);
12✔
267
            const auto g_plus_g = g_one + g_one;
12✔
268
            result.test_eq("2*g == g+g", g_two.to_affine().serialize(), g_plus_g.to_affine().serialize());
72✔
269

270
            result.confirm("Scalar::zero is zero", curve->scalar_zero().is_zero());
36✔
271
            result.confirm("Scalar::one is not zero", !curve->scalar_one().is_zero());
36✔
272

273
            for(size_t i = 0; i != 16; ++i) {
204✔
274
               const auto pt = curve->mul_by_g(curve->random_scalar(rng), rng).to_affine();
576✔
275

276
               const auto a = curve->random_scalar(rng);
192✔
277
               const auto b = curve->random_scalar(rng);
192✔
278
               const auto c = a + b;
192✔
279

280
               const auto Pa = curve->mul(pt, a, rng);
192✔
281
               const auto Pb = curve->mul(pt, b, rng);
192✔
282
               const auto Pc = curve->mul(pt, c, rng);
192✔
283

284
               const auto Pc_bytes = Pc.to_affine().serialize();
192✔
285

286
               const auto Pab = Pa + Pb;
192✔
287
               result.test_eq("Pa + Pb == Pc", Pab.to_affine().serialize(), Pc_bytes);
576✔
288

289
               const auto Pba = Pb + Pa;
192✔
290
               result.test_eq("Pb + Pa == Pc", Pba.to_affine().serialize(), Pc_bytes);
576✔
291

292
               const auto Pabm = Pa + Pb.to_affine();
192✔
293
               result.test_eq("Pa + Pb == Pc (mixed)", Pabm.to_affine().serialize(), Pc_bytes);
576✔
294
               const auto Pbam = Pb + Pa.to_affine();
192✔
295
               result.test_eq("Pb + Pa == Pc (mixed)", Pbam.to_affine().serialize(), Pc_bytes);
768✔
296
            }
2,304✔
297

298
            for(size_t i = 0; i != 16; ++i) {
204✔
299
               const auto pt1 = curve->mul_by_g(curve->random_scalar(rng), rng).to_affine();
576✔
300
               const auto pt2 = curve->mul_by_g(curve->random_scalar(rng), rng).to_affine();
576✔
301

302
               const auto s1 = curve->random_scalar(rng);
192✔
303
               const auto s2 = curve->random_scalar(rng);
192✔
304

305
               const auto mul2_table = curve->mul2_setup(pt1, pt2);
192✔
306

307
               const auto ref = (curve->mul(pt1, s1, rng) + curve->mul(pt2, s2, rng)).to_affine();
768✔
308

309
               if(auto mul2pt = curve->mul2_vartime(*mul2_table, s1, s2)) {
192✔
310
                  result.test_eq("ref == mul2t", ref.serialize(), mul2pt->to_affine().serialize());
960✔
311
               } else {
312
                  result.confirm("ref is identity", ref.is_identity());
×
313
               }
192✔
314
            }
1,152✔
315

316
            // Test cases where the two points have a linear relation
317
            for(size_t i = 0; i != 16; ++i) {
204✔
318
               const auto pt1 = curve->generator();
192✔
319

320
               auto pt2 = [&]() {
×
321
                  const auto lo = curve->scalar_from_u32(static_cast<uint32_t>(i / 2));
192✔
322
                  auto x = curve->mul_by_g(lo, rng);
192✔
323
                  if(i % 2 == 0) {
192✔
324
                     x = x.negate();
96✔
325
                  }
326
                  return x.to_affine();
192✔
327
               }();
576✔
328

329
               const auto s1 = curve->random_scalar(rng);
192✔
330
               const auto s2 = curve->random_scalar(rng);
192✔
331

332
               const auto mul2_table = curve->mul2_setup(pt1, pt2);
192✔
333

334
               const auto ref = (curve->mul(pt1, s1, rng) + curve->mul(pt2, s2, rng)).to_affine();
768✔
335

336
               if(auto mul2pt = curve->mul2_vartime(*mul2_table, s1, s2)) {
192✔
337
                  result.test_eq("ref == mul2t", ref.serialize(), mul2pt->to_affine().serialize());
960✔
338
               } else {
339
                  result.confirm("ref is identity", ref.is_identity());
×
340
               }
192✔
341
            }
1,152✔
342

343
            result.end_timer();
12✔
344

345
            results.push_back(result);
12✔
346
         }
204✔
347

348
         return results;
1✔
349
      }
×
350
};
351

352
BOTAN_REGISTER_TEST("pcurves", "pcurves_arith", Pcurve_Arithmetic_Tests);
353

354
class Pcurve_PointEnc_Tests final : public Test {
×
355
   public:
356
      std::vector<Test::Result> run() override {
1✔
357
         std::vector<Test::Result> results;
1✔
358

359
         auto& rng = Test::rng();
1✔
360

361
         for(auto id : Botan::PCurve::PrimeOrderCurveId::all()) {
13✔
362
            Test::Result result("Pcurves point operations " + id.to_string());
24✔
363

364
            result.start_timer();
12✔
365

366
            auto curve = Botan::PCurve::PrimeOrderCurve::from_id(id);
12✔
367

368
            if(!curve) {
12✔
369
               result.test_note("Skipping test due to missing pcurve " + id.to_string());
×
370
               continue;
×
371
            }
372

373
            for(size_t trial = 0; trial != 100; ++trial) {
1,212✔
374
               const auto scalar = curve->random_scalar(rng);
1,200✔
375
               const auto pt = curve->mul_by_g(scalar, rng).to_affine();
2,400✔
376

377
               const auto pt_u = pt.serialize();
1,200✔
378
               result.test_eq("Expected uncompressed header", static_cast<size_t>(pt_u[0]), 0x04);
1,200✔
379
               const size_t fe_bytes = (pt_u.size() - 1) / 2;
1,200✔
380
               const auto pt_c = pt.serialize_compressed();
1,200✔
381

382
               result.test_eq("Expected compressed size", pt_c.size(), 1 + fe_bytes);
1,200✔
383
               result.confirm("Expected compressed header", pt_c[0] == 0x02 || pt_c[0] == 0x03);
3,600✔
384

385
               if(auto d_pt_u = curve->deserialize_point(pt_u)) {
1,200✔
386
                  result.test_eq("Deserializing uncompressed returned correct point", d_pt_u->serialize(), pt_u);
3,600✔
387
               } else {
388
                  result.test_failure("Failed to deserialize uncompressed point");
×
389
               }
×
390

391
               if(auto d_pt_c = curve->deserialize_point(pt_c)) {
1,200✔
392
                  result.test_eq("Deserializing compressed returned correct point", d_pt_c->serialize(), pt_u);
3,600✔
393
               } else {
394
                  result.test_failure("Failed to deserialize compressed point");
×
395
               }
1,200✔
396
            }
3,600✔
397

398
            result.end_timer();
12✔
399

400
            results.push_back(result);
12✔
401
         }
12✔
402

403
         return results;
1✔
404
      }
×
405
};
406

407
BOTAN_REGISTER_TEST("pcurves", "pcurves_point_enc", Pcurve_PointEnc_Tests);
408

409
#endif
410

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