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

randombit / botan / 10094794035

25 Jul 2024 01:12PM UTC coverage: 91.646% (-0.005%) from 91.651%
10094794035

push

github

web-flow
Merge pull request #4259 from randombit/jack/blind-ecdsa-inv

In ECDSA blind the inversion of k

87249 of 95202 relevant lines covered (91.65%)

9377362.19 hits per line

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

90.5
/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 {
434✔
26
         Test::Result result("Pcurves base point multiply " + group_id);
434✔
27

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

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

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

39
               auto pt3 = curve->mul_by_g(scalar.value(), null_rng).to_affine().serialize();
1,140✔
40
               result.test_eq("mul_by_g (Null_RNG) correct", pt3, P_bytes);
380✔
41

42
               auto g = curve->generator();
380✔
43
               auto pt4 = curve->mul(g, scalar.value(), rng).to_affine().serialize();
1,140✔
44
               result.test_eq("mul correct", pt4, P_bytes);
380✔
45

46
               auto pt5 = curve->mul(g, scalar.value(), null_rng).to_affine().serialize();
1,140✔
47
               result.test_eq("mul correct (Null_RNG)", pt5, P_bytes);
760✔
48
            } else {
1,900✔
49
               result.test_failure("Curve rejected scalar input");
×
50
            }
380✔
51
         }
×
52

53
         return result;
434✔
54
      }
1,302✔
55
};
56

57
BOTAN_REGISTER_TEST("pcurves", "pcurves_basemul", Pcurve_Basemul_Tests);
58

59
class Pcurve_Ecdh_Tests final : public Text_Based_Test {
×
60
   public:
61
      Pcurve_Ecdh_Tests() : Text_Based_Test("pubkey/ecdh.vec", "Secret,CounterKey,K") {}
2✔
62

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

66
         const auto sk = vars.get_req_bin("Secret");
156✔
67
         const auto peer_key = vars.get_req_bin("CounterKey");
156✔
68
         const auto shared_secret = vars.get_req_bin("K");
156✔
69

70
         auto curve = Botan::PCurve::PrimeOrderCurve::from_name(group_id);
156✔
71

72
         if(!curve) {
156✔
73
            result.test_note("Skipping test due to missing pcurve " + group_id);
25✔
74
            return result;
25✔
75
         }
76

77
         auto x = curve->deserialize_scalar(sk);
131✔
78
         auto pt = curve->deserialize_point(peer_key);
131✔
79

80
         if(x && pt) {
131✔
81
            auto ss = curve->mul(pt.value(), x.value(), rng()).to_affine().x_bytes();
393✔
82
            result.test_eq("shared secret", ss, shared_secret);
262✔
83
         } else {
131✔
84
            result.test_failure("Curve rejected test inputs");
×
85
         }
86

87
         return result;
131✔
88
      }
886✔
89
};
90

91
BOTAN_REGISTER_TEST("pcurves", "pcurves_ecdh", Pcurve_Ecdh_Tests);
92

93
class Pcurve_Ecdsa_Sign_Tests final : public Text_Based_Test {
×
94
   public:
95
      Pcurve_Ecdsa_Sign_Tests() : Text_Based_Test("pubkey/ecdsa_prob.vec", "Group,Hash,Msg,Nonce,X,Signature") {}
2✔
96

97
      bool clear_between_callbacks() const override { return false; }
251✔
98

99
      static auto ecdsa_sign(const Botan::PCurve::PrimeOrderCurve& curve,
191✔
100
                             const Botan::PCurve::PrimeOrderCurve::Scalar& x,
101
                             std::span<const uint8_t> msg,
102
                             Botan::RandomNumberGenerator& nonce_rng,
103
                             Botan::RandomNumberGenerator& rng) {
104
         const auto e = curve.scalar_from_bits_with_trunc(msg);
191✔
105
         const auto k = curve.random_scalar(nonce_rng);
191✔
106
         const auto r = curve.base_point_mul_x_mod_order(k, rng);
191✔
107

108
         /*
109
         * Blind the input message and compute x*r+e as (b*x*r + b*e)/b
110
         */
111
         auto b = curve.random_scalar(rng);
191✔
112
         auto b_inv = b.invert();
191✔
113

114
         const auto k_inv = (b * k).invert() * b;
382✔
115

116
         b = b.square();
191✔
117
         b_inv = b_inv.square();
191✔
118

119
         const auto be = b * e;
191✔
120
         const auto bx = b * x;
191✔
121

122
         const auto bxr_e = (bx * r) + be;
191✔
123

124
         const auto s = (k_inv * bxr_e) * b_inv;
191✔
125

126
         // With overwhelming probability, a bug rather than actual zero r/s
127
         if(r.is_zero() || s.is_zero()) {
382✔
128
            throw Botan::Internal_Error("During ECDSA signature generated zero r/s");
×
129
         }
130

131
         return std::pair{r, s};
191✔
132
      }
1,910✔
133

134
      static bool ecdsa_verify(const Botan::PCurve::PrimeOrderCurve& curve,
191✔
135
                               const Botan::PCurve::PrimeOrderCurve::AffinePoint& pk,
136
                               const std::span<const uint8_t> msg,
137
                               const Botan::PCurve::PrimeOrderCurve::Scalar& r,
138
                               const Botan::PCurve::PrimeOrderCurve::Scalar& s) {
139
         if(r.is_zero() || s.is_zero()) {
382✔
140
            return false;
×
141
         }
142

143
         const auto z = curve.scalar_from_bits_with_trunc(msg);
191✔
144

145
         const auto s_inv = curve.scalar_invert(s);
191✔
146
         auto u1 = z * s_inv;
191✔
147
         auto u2 = r * s_inv;
191✔
148
         const auto table = curve.mul2_setup(curve.generator(), pk);
191✔
149

150
         return curve.mul2_vartime_x_mod_order_eq(*table, r, u1, u2);
191✔
151
      }
955✔
152

153
      Test::Result run_one_test(const std::string&, const VarMap& vars) override {
251✔
154
         const std::string group_id = vars.get_req_str("Group");
251✔
155
         const std::string hash_fn = vars.get_req_str("Hash");
251✔
156

157
         const auto sk = vars.get_req_bin("X");
251✔
158
         const auto msg = vars.get_req_bin("Msg");
251✔
159
         const auto nonce = vars.get_req_bin("Nonce");
251✔
160
         const auto expected_sig = vars.get_req_bin("Signature");
251✔
161

162
         Test::Result result("Pcurves ECDSA sign " + group_id);
251✔
163

164
         const auto hash = Botan::HashFunction::create(hash_fn);
251✔
165
         if(!hash && !hash_fn.starts_with("Raw")) {
320✔
166
            result.test_note("Skipping test due to missing hash " + hash_fn);
×
167
            return result;
×
168
         }
169

170
         auto curve = Botan::PCurve::PrimeOrderCurve::from_name(group_id);
251✔
171

172
         if(!curve) {
251✔
173
            result.test_note("Skipping test due to missing pcurve " + group_id);
60✔
174
            return result;
60✔
175
         }
176

177
         if(auto x = curve->deserialize_scalar(sk)) {
191✔
178
            Fixed_Output_RNG nonce_rng(nonce);
191✔
179
            const auto e = (hash) ? hash->process<std::vector<uint8_t>>(msg) : msg;
191✔
180
            const auto [r, s] = ecdsa_sign(*curve, x.value(), e, nonce_rng, rng());
191✔
181

182
            const auto sig_bytes = Botan::concat(r.serialize(), s.serialize());
573✔
183
            result.test_eq("correct signature generated", sig_bytes, expected_sig);
191✔
184

185
            const auto pk = curve->mul_by_g(x.value(), rng()).to_affine();
382✔
186

187
            const bool valid = ecdsa_verify(*curve, pk, e, r, s);
191✔
188
            result.confirm("signature is valid", valid);
382✔
189
         } else {
573✔
190
            result.test_failure("Curve rejected secret key");
×
191
         }
×
192

193
         return result;
191✔
194
      }
1,506✔
195
};
196

197
BOTAN_REGISTER_TEST("pcurves", "pcurves_ecdsa_sign", Pcurve_Ecdsa_Sign_Tests);
198

199
class Pcurve_Point_Tests final : public Test {
×
200
   public:
201
      std::vector<Test::Result> run() override {
1✔
202
         std::vector<Test::Result> results;
1✔
203

204
         auto& rng = Test::rng();
1✔
205

206
         for(auto id : Botan::PCurve::PrimeOrderCurveId::all()) {
11✔
207
            Test::Result result("Pcurves point operations " + id.to_string());
20✔
208

209
            result.start_timer();
10✔
210

211
            auto curve = Botan::PCurve::PrimeOrderCurve::from_id(id);
10✔
212

213
            if(!curve) {
10✔
214
               result.test_note("Skipping test due to missing pcurve " + id.to_string());
×
215
               continue;
×
216
            }
217

218
            const auto zero = curve->scalar_zero();
10✔
219
            const auto one = curve->scalar_one();
10✔
220
            const auto g = curve->generator();
10✔
221
            const auto g_bytes = g.serialize();
10✔
222

223
            const auto inf = curve->mul_by_g(zero, rng);
10✔
224
            result.confirm("g*zero is point at infinity", inf.to_affine().is_identity());
20✔
225

226
            std::vector<uint8_t> inf_bytes(g_bytes.size());
10✔
227
            inf_bytes[0] = 0x04;
10✔
228

229
            result.test_eq("infinity has expected encoding", inf.to_affine().serialize(), inf_bytes);
30✔
230

231
            const auto inf2 = inf.dbl();
10✔
232
            result.test_eq("infinity * 2 is infinity", inf2.to_affine().serialize(), inf_bytes);
30✔
233

234
            const auto inf3 = inf2 + inf;
10✔
235
            result.test_eq("infinity plus itself is infinity", inf3.to_affine().serialize(), inf_bytes);
30✔
236

237
            const auto g_one = curve->mul_by_g(one, rng);
10✔
238
            result.test_eq("g*one == generator", g_one.to_affine().serialize(), g_bytes);
30✔
239

240
            const auto g_plus_inf = g_one + inf;
10✔
241
            result.test_eq("g + inf == g", g_plus_inf.to_affine().serialize(), g_bytes);
30✔
242

243
            const auto g_plus_infa = g_one + inf.to_affine();
10✔
244
            result.test_eq("g + inf (affine) == g", g_plus_infa.to_affine().serialize(), g_bytes);
30✔
245

246
            const auto inf_plus_g = inf + g_one;
10✔
247
            result.test_eq("inf + g == g", inf_plus_g.to_affine().serialize(), g_bytes);
30✔
248

249
            const auto inf_plus_ga = inf + g_one.to_affine();
10✔
250
            result.test_eq("inf + g (affine) == g", inf_plus_ga.to_affine().serialize(), g_bytes);
30✔
251

252
            const auto g_neg_one = curve->mul_by_g(one.negate(), rng);
10✔
253

254
            const auto inf_from_g = g_one + g_neg_one;
10✔
255
            result.test_eq("g - g is infinity", inf_from_g.to_affine().serialize(), inf_bytes);
30✔
256

257
            const auto g_two = curve->mul_by_g(one + one, rng);
10✔
258
            const auto g_plus_g = g_one + g_one;
10✔
259
            result.test_eq("2*g == g+g", g_two.to_affine().serialize(), g_plus_g.to_affine().serialize());
60✔
260

261
            result.confirm("Scalar::zero is zero", curve->scalar_zero().is_zero());
30✔
262
            result.confirm("Scalar::one is not zero", !curve->scalar_one().is_zero());
30✔
263

264
            for(size_t i = 0; i != 16; ++i) {
170✔
265
               const auto pt = curve->mul_by_g(curve->random_scalar(rng), rng).to_affine();
480✔
266

267
               const auto a = curve->random_scalar(rng);
160✔
268
               const auto b = curve->random_scalar(rng);
160✔
269
               const auto c = a + b;
160✔
270

271
               const auto Pa = curve->mul(pt, a, rng);
160✔
272
               const auto Pb = curve->mul(pt, b, rng);
160✔
273
               const auto Pc = curve->mul(pt, c, rng);
160✔
274

275
               const auto Pc_bytes = Pc.to_affine().serialize();
160✔
276

277
               const auto Pab = Pa + Pb;
160✔
278
               result.test_eq("Pa + Pb == Pc", Pab.to_affine().serialize(), Pc_bytes);
480✔
279

280
               const auto Pba = Pb + Pa;
160✔
281
               result.test_eq("Pb + Pa == Pc", Pba.to_affine().serialize(), Pc_bytes);
480✔
282

283
               const auto Pabm = Pa + Pb.to_affine();
160✔
284
               result.test_eq("Pa + Pb == Pc (mixed)", Pabm.to_affine().serialize(), Pc_bytes);
480✔
285
               const auto Pbam = Pb + Pa.to_affine();
160✔
286
               result.test_eq("Pb + Pa == Pc (mixed)", Pbam.to_affine().serialize(), Pc_bytes);
640✔
287
            }
1,920✔
288

289
            for(size_t i = 0; i != 16; ++i) {
170✔
290
               const auto pt1 = curve->mul_by_g(curve->random_scalar(rng), rng).to_affine();
480✔
291
               const auto pt2 = curve->mul_by_g(curve->random_scalar(rng), rng).to_affine();
480✔
292

293
               const auto s1 = curve->random_scalar(rng);
160✔
294
               const auto s2 = curve->random_scalar(rng);
160✔
295

296
               const auto mul2_table = curve->mul2_setup(pt1, pt2);
160✔
297

298
               const auto ref = (curve->mul(pt1, s1, rng) + curve->mul(pt2, s2, rng)).to_affine();
640✔
299

300
               if(auto mul2pt = curve->mul2_vartime(*mul2_table, s1, s2)) {
160✔
301
                  result.test_eq("ref == mul2t", ref.serialize(), mul2pt->to_affine().serialize());
800✔
302
               } else {
303
                  result.confirm("ref is identity", ref.is_identity());
×
304
               }
160✔
305
            }
960✔
306

307
            // Test cases where the two points have a linear relation
308
            for(size_t i = 0; i != 16; ++i) {
170✔
309
               const auto pt1 = curve->generator();
160✔
310

311
               auto pt2 = [&]() {
×
312
                  const auto lo = curve->scalar_from_u32(static_cast<uint32_t>(i / 2));
160✔
313
                  auto x = curve->mul_by_g(lo, rng);
160✔
314
                  if(i % 2 == 0) {
160✔
315
                     x = x.negate();
80✔
316
                  }
317
                  return x.to_affine();
160✔
318
               }();
480✔
319

320
               const auto s1 = curve->random_scalar(rng);
160✔
321
               const auto s2 = curve->random_scalar(rng);
160✔
322

323
               const auto mul2_table = curve->mul2_setup(pt1, pt2);
160✔
324

325
               const auto ref = (curve->mul(pt1, s1, rng) + curve->mul(pt2, s2, rng)).to_affine();
640✔
326

327
               if(auto mul2pt = curve->mul2_vartime(*mul2_table, s1, s2)) {
160✔
328
                  result.test_eq("ref == mul2t", ref.serialize(), mul2pt->to_affine().serialize());
800✔
329
               } else {
330
                  result.confirm("ref is identity", ref.is_identity());
×
331
               }
160✔
332
            }
960✔
333

334
            result.end_timer();
10✔
335

336
            results.push_back(result);
10✔
337
         }
170✔
338

339
         return results;
1✔
340
      }
×
341
};
342

343
BOTAN_REGISTER_TEST("pcurves", "pcurves_points", Pcurve_Point_Tests);
344

345
#endif
346

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