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

randombit / botan / 13455899634

21 Feb 2025 11:31AM UTC coverage: 91.663% (-0.008%) from 91.671%
13455899634

push

github

web-flow
Merge pull request #4687 from randombit/jack/ec-lookup-or-create

Refactor EC_Group_Data_Map::lookup_or_create

95012 of 103654 relevant lines covered (91.66%)

11389334.62 hits per line

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

92.41
/src/lib/pubkey/ec_group/ec_inner_data.cpp
1
/*
2
* (C) 2024 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include <botan/internal/ec_inner_data.h>
8

9
#include <botan/der_enc.h>
10
#include <botan/internal/ec_inner_pc.h>
11
#include <botan/internal/fmt.h>
12
#include <botan/internal/pcurves.h>
13

14
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
15
   #include <botan/internal/ec_inner_bn.h>
16
   #include <botan/internal/point_mul.h>
17
#endif
18

19
#if defined(BOTAN_HAS_XMD)
20
   #include <botan/internal/xmd.h>
21
#endif
22

23
namespace Botan {
24

25
EC_Group_Data::~EC_Group_Data() = default;
5,772✔
26

27
// Note this constructor *does not* initialize m_curve, m_base_point or m_base_mult
28
EC_Group_Data::EC_Group_Data(const BigInt& p,
1,155✔
29
                             const BigInt& a,
30
                             const BigInt& b,
31
                             const BigInt& g_x,
32
                             const BigInt& g_y,
33
                             const BigInt& order,
34
                             const BigInt& cofactor,
35
                             const OID& oid,
36
                             EC_Group_Source source) :
1,155✔
37
      m_p(p),
1,155✔
38
      m_a(a),
1,155✔
39
      m_b(b),
1,155✔
40
      m_g_x(g_x),
1,155✔
41
      m_g_y(g_y),
1,155✔
42
      m_order(order),
1,155✔
43
      m_cofactor(cofactor),
1,155✔
44
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
45
      m_mod_field(Modular_Reducer::for_public_modulus(p)),
1,155✔
46
      m_mod_order(Modular_Reducer::for_public_modulus(order)),
1,155✔
47
      m_monty(m_p, m_mod_field),
1,155✔
48
#endif
49
      m_oid(oid),
1,155✔
50
      m_p_words(p.sig_words()),
1,155✔
51
      m_p_bits(p.bits()),
1,155✔
52
      m_order_bits(order.bits()),
1,155✔
53
      m_order_bytes((m_order_bits + 7) / 8),
1,155✔
54
      m_a_is_minus_3(a == p - 3),
1,155✔
55
      m_a_is_zero(a.is_zero()),
1,155✔
56
      m_has_cofactor(m_cofactor != 1),
1,155✔
57
      m_order_is_less_than_p(m_order < p),
1,155✔
58
      m_source(source) {
3,465✔
59
   if(!m_oid.empty()) {
1,155✔
60
      DER_Encoder der(m_der_named_curve);
1,152✔
61
      der.encode(m_oid);
1,152✔
62

63
      const std::string name = m_oid.human_name_or_empty();
1,152✔
64
      if(!name.empty()) {
1,152✔
65
         // returns nullptr if unknown or not supported
66
         m_pcurve = PCurve::PrimeOrderCurve::for_named_curve(name);
1,147✔
67
      }
68
      if(m_pcurve) {
1,152✔
69
         m_engine = EC_Group_Engine::Optimized;
912✔
70
      }
71
   }
1,152✔
72

73
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
74
   secure_vector<word> ws;
1,155✔
75
   m_a_r = m_monty.mul(a, m_monty.R2(), ws);
2,310✔
76
   m_b_r = m_monty.mul(b, m_monty.R2(), ws);
2,310✔
77
   if(!m_pcurve) {
1,155✔
78
      m_engine = EC_Group_Engine::Legacy;
243✔
79
   }
80
#else
81
   if(!m_pcurve) {
82
      if(m_oid.empty()) {
83
         throw Not_Implemented("EC_Group this group is not supported in this build configuration");
84
      } else {
85
         throw Not_Implemented(
86
            fmt("EC_Group the group {} is not supported in this build configuration", oid.to_string()));
87
      }
88
   }
89
#endif
90
}
1,155✔
91

92
std::shared_ptr<EC_Group_Data> EC_Group_Data::create(const BigInt& p,
1,155✔
93
                                                     const BigInt& a,
94
                                                     const BigInt& b,
95
                                                     const BigInt& g_x,
96
                                                     const BigInt& g_y,
97
                                                     const BigInt& order,
98
                                                     const BigInt& cofactor,
99
                                                     const OID& oid,
100
                                                     EC_Group_Source source) {
101
   auto group = std::make_shared<EC_Group_Data>(p, a, b, g_x, g_y, order, cofactor, oid, source);
1,155✔
102

103
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
104
   group->m_curve = CurveGFp(group.get());
1,155✔
105
   group->m_base_point = EC_Point(group->m_curve, g_x, g_y);
2,310✔
106
   if(!group->m_pcurve) {
1,155✔
107
      group->m_base_mult = std::make_unique<EC_Point_Base_Point_Precompute>(group->m_base_point, group->m_mod_order);
243✔
108
   }
109
#endif
110

111
   return group;
1,155✔
112
}
×
113

114
bool EC_Group_Data::params_match(const BigInt& p,
771✔
115
                                 const BigInt& a,
116
                                 const BigInt& b,
117
                                 const BigInt& g_x,
118
                                 const BigInt& g_y,
119
                                 const BigInt& order,
120
                                 const BigInt& cofactor) const {
121
   return (this->p() == p && this->a() == a && this->b() == b && this->order() == order &&
872✔
122
           this->cofactor() == cofactor && this->g_x() == g_x && this->g_y() == g_y);
863✔
123
}
124

125
bool EC_Group_Data::params_match(const EC_Group_Data& other) const {
×
126
   return params_match(other.p(), other.a(), other.b(), other.g_x(), other.g_y(), other.order(), other.cofactor());
×
127
}
128

129
void EC_Group_Data::set_oid(const OID& oid) {
×
130
   BOTAN_ARG_CHECK(!oid.empty(), "OID should be set");
×
131
   BOTAN_STATE_CHECK(m_oid.empty() && m_der_named_curve.empty());
×
132
   m_oid = oid;
×
133

134
   DER_Encoder der(m_der_named_curve);
×
135
   der.encode(m_oid);
×
136
}
×
137

138
std::unique_ptr<EC_Scalar_Data> EC_Group_Data::scalar_from_bytes_with_trunc(std::span<const uint8_t> bytes) const {
26,075✔
139
   const size_t bit_length = 8 * bytes.size();
26,075✔
140

141
   if(bit_length < order_bits()) {
26,075✔
142
      // No shifting required, but might still need to reduce by modulus
143
      return this->scalar_from_bytes_mod_order(bytes);
5,251✔
144
   } else {
145
      const size_t shift = bit_length - order_bits();
20,824✔
146

147
      const size_t new_length = bytes.size() - (shift / 8);
20,824✔
148
      const size_t bit_shift = shift % 8;
20,824✔
149

150
      if(bit_shift == 0) {
20,824✔
151
         // Easy case just read different bytes
152
         return this->scalar_from_bytes_mod_order(bytes.first(new_length));
18,184✔
153
      } else {
154
         std::vector<uint8_t> sbytes(new_length);
2,640✔
155

156
         uint8_t carry = 0;
2,640✔
157
         for(size_t i = 0; i != new_length; ++i) {
72,141✔
158
            const uint8_t w = bytes[i];
69,501✔
159
            sbytes[i] = (w >> bit_shift) | carry;
69,501✔
160
            carry = w << (8 - bit_shift);
69,501✔
161
         }
162

163
         return this->scalar_from_bytes_mod_order(sbytes);
2,640✔
164
      }
2,640✔
165
   }
166
}
167

168
std::unique_ptr<EC_Scalar_Data> EC_Group_Data::scalar_from_bytes_mod_order(std::span<const uint8_t> bytes) const {
34,790✔
169
   if(bytes.size() > 2 * order_bytes()) {
34,790✔
170
      return {};
×
171
   }
172

173
   if(m_pcurve) {
34,790✔
174
      if(auto s = m_pcurve->scalar_from_wide_bytes(bytes)) {
23,553✔
175
         return std::make_unique<EC_Scalar_Data_PC>(shared_from_this(), std::move(*s));
23,553✔
176
      } else {
177
         return {};
×
178
      }
23,553✔
179
   } else {
180
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
181
      return std::make_unique<EC_Scalar_Data_BN>(shared_from_this(), m_mod_order.reduce(BigInt(bytes)));
11,237✔
182
#else
183
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
184
#endif
185
   }
186
}
187

188
std::unique_ptr<EC_Scalar_Data> EC_Group_Data::scalar_random(RandomNumberGenerator& rng) const {
44,415✔
189
   if(m_pcurve) {
44,415✔
190
      return std::make_unique<EC_Scalar_Data_PC>(shared_from_this(), m_pcurve->random_scalar(rng));
19,610✔
191
   } else {
192
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
193
      return std::make_unique<EC_Scalar_Data_BN>(shared_from_this(),
49,610✔
194
                                                 BigInt::random_integer(rng, BigInt::one(), m_order));
74,415✔
195
#else
196
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
197
#endif
198
   }
199
}
200

201
std::unique_ptr<EC_Scalar_Data> EC_Group_Data::scalar_one() const {
101✔
202
   if(m_pcurve) {
101✔
203
      return std::make_unique<EC_Scalar_Data_PC>(shared_from_this(), m_pcurve->scalar_one());
61✔
204
   } else {
205
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
206
      return std::make_unique<EC_Scalar_Data_BN>(shared_from_this(), BigInt::one());
40✔
207
#else
208
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
209
#endif
210
   }
211
}
212

213
std::unique_ptr<EC_Scalar_Data> EC_Group_Data::scalar_from_bigint(const BigInt& bn) const {
4,482✔
214
   if(bn <= 0 || bn >= m_order) {
4,482✔
215
      return {};
×
216
   }
217

218
   if(m_pcurve) {
4,482✔
219
      return this->scalar_deserialize(bn.serialize(m_order_bytes));
6,754✔
220
   } else {
221
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
222
      return std::make_unique<EC_Scalar_Data_BN>(shared_from_this(), bn);
1,105✔
223
#else
224
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
225
#endif
226
   }
227
}
228

229
std::unique_ptr<EC_Scalar_Data> EC_Group_Data::gk_x_mod_order(const EC_Scalar_Data& scalar,
3,313✔
230
                                                              RandomNumberGenerator& rng,
231
                                                              std::vector<BigInt>& ws) const {
232
   if(m_pcurve) {
3,313✔
233
      const auto& k = EC_Scalar_Data_PC::checked_ref(scalar);
1,693✔
234
      auto gk_x_mod_order = m_pcurve->base_point_mul_x_mod_order(k.value(), rng);
1,693✔
235
      return std::make_unique<EC_Scalar_Data_PC>(shared_from_this(), gk_x_mod_order);
1,693✔
236
   } else {
1,693✔
237
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
238
      const auto& k = EC_Scalar_Data_BN::checked_ref(scalar);
1,620✔
239
      BOTAN_STATE_CHECK(m_base_mult != nullptr);
1,620✔
240
      const auto pt = m_base_mult->mul(k.value(), rng, m_order, ws);
1,620✔
241

242
      if(pt.is_zero()) {
3,240✔
243
         return std::make_unique<EC_Scalar_Data_BN>(shared_from_this(), BigInt::zero());
×
244
      } else {
245
         return std::make_unique<EC_Scalar_Data_BN>(shared_from_this(), m_mod_order.reduce(pt.get_affine_x()));
1,620✔
246
      }
247
#else
248
      BOTAN_UNUSED(ws);
249
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
250
#endif
251
   }
1,620✔
252
}
253

254
std::unique_ptr<EC_Scalar_Data> EC_Group_Data::scalar_deserialize(std::span<const uint8_t> bytes) const {
68,708✔
255
   if(bytes.size() != m_order_bytes) {
68,708✔
256
      return nullptr;
5,848✔
257
   }
258

259
   if(m_pcurve) {
62,860✔
260
      if(auto s = m_pcurve->deserialize_scalar(bytes)) {
46,965✔
261
         return std::make_unique<EC_Scalar_Data_PC>(shared_from_this(), *s);
44,761✔
262
      } else {
263
         return nullptr;
2,204✔
264
      }
46,965✔
265
   } else {
266
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
267
      BigInt r(bytes);
15,895✔
268

269
      if(r.is_zero() || r >= m_order) {
31,790✔
270
         return nullptr;
719✔
271
      }
272

273
      return std::make_unique<EC_Scalar_Data_BN>(shared_from_this(), std::move(r));
15,176✔
274
#else
275
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
276
#endif
277
   }
15,895✔
278
}
279

280
std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::point_deserialize(std::span<const uint8_t> bytes) const {
44,560✔
281
   // The deprecated "hybrid" point format
282
   // TODO(Botan4) remove this
283
   if(bytes.size() >= 1 + 2 * 4 && (bytes[0] == 0x06 || bytes[0] == 0x07)) {
44,560✔
284
      bool hdr_y_is_even = bytes[0] == 0x06;
207✔
285
      bool y_is_even = (bytes.back() & 0x01) == 0;
207✔
286

287
      if(hdr_y_is_even == y_is_even) {
207✔
288
         std::vector<uint8_t> sec1(bytes.begin(), bytes.end());
147✔
289
         sec1[0] = 0x04;
147✔
290
         return this->point_deserialize(sec1);
147✔
291
      }
147✔
292
   }
293

294
   try {
44,413✔
295
      if(m_pcurve) {
44,413✔
296
         if(auto pt = m_pcurve->deserialize_point(bytes)) {
33,034✔
297
            return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), std::move(*pt));
26,917✔
298
         } else {
299
            return {};
6,117✔
300
         }
33,034✔
301
      } else {
302
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
303
         auto pt = Botan::OS2ECP(bytes, m_curve);
11,379✔
304
         return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(pt));
11,123✔
305
#else
306
         throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
307
#endif
308
      }
11,123✔
309
   } catch(...) {
256✔
310
      return {};
256✔
311
   }
256✔
312
}
313

314
namespace {
315

316
std::function<void(std::span<uint8_t>)> h2c_expand_message(std::string_view hash_fn,
59✔
317
                                                           std::span<const uint8_t> input,
318
                                                           std::span<const uint8_t> domain_sep) {
319
   /*
320
   * This could be extended to support expand_message_xof or a MHF like Argon2
321
   */
322

323
   if(hash_fn.starts_with("SHAKE")) {
59✔
324
      throw Not_Implemented("Hash to curve currently does not support expand_message_xof");
×
325
   }
326

327
   return [=](std::span<uint8_t> uniform_bytes) {
174✔
328
#if defined(BOTAN_HAS_XMD)
329
      expand_message_xmd(hash_fn, uniform_bytes, input, domain_sep);
56✔
330
#else
331
      BOTAN_UNUSED(hash_fn, uniform_bytes, input, domain_sep);
332
      throw Not_Implemented("Hash to curve is not implemented due to XMD being disabled");
333
#endif
334
   };
59✔
335
}
336

337
}  // namespace
338

339
std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::point_hash_to_curve_ro(std::string_view hash_fn,
25✔
340
                                                                           std::span<const uint8_t> input,
341
                                                                           std::span<const uint8_t> domain_sep) const {
342
   if(m_pcurve) {
25✔
343
      auto pt = m_pcurve->hash_to_curve_ro(h2c_expand_message(hash_fn, input, domain_sep));
25✔
344
      return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), m_pcurve->point_to_affine(pt));
25✔
345
   } else {
25✔
346
      throw Not_Implemented("Hash to curve is not implemented for this curve");
×
347
   }
348
}
349

350
std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::point_hash_to_curve_nu(std::string_view hash_fn,
34✔
351
                                                                           std::span<const uint8_t> input,
352
                                                                           std::span<const uint8_t> domain_sep) const {
353
   if(m_pcurve) {
34✔
354
      auto pt = m_pcurve->hash_to_curve_nu(h2c_expand_message(hash_fn, input, domain_sep));
37✔
355
      return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), std::move(pt));
31✔
356
   } else {
31✔
357
      throw Not_Implemented("Hash to curve is not implemented for this curve");
×
358
   }
359
}
360

361
std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::point_g_mul(const EC_Scalar_Data& scalar,
15,372✔
362
                                                                RandomNumberGenerator& rng,
363
                                                                std::vector<BigInt>& ws) const {
364
   if(m_pcurve) {
15,372✔
365
      const auto& k = EC_Scalar_Data_PC::checked_ref(scalar);
7,982✔
366
      auto pt = m_pcurve->point_to_affine(m_pcurve->mul_by_g(k.value(), rng));
7,982✔
367
      return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), std::move(pt));
7,982✔
368
   } else {
7,982✔
369
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
370
      const auto& group = scalar.group();
7,390✔
371
      const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar);
7,390✔
372

373
      BOTAN_STATE_CHECK(group->m_base_mult != nullptr);
7,390✔
374
      auto pt = group->m_base_mult->mul(bn.value(), rng, m_order, ws);
7,390✔
375
      return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(pt));
7,390✔
376
#else
377
      BOTAN_UNUSED(ws);
378
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
379
#endif
380
   }
7,390✔
381
}
382

383
std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::mul_px_qy(const EC_AffinePoint_Data& p,
3,472✔
384
                                                              const EC_Scalar_Data& x,
385
                                                              const EC_AffinePoint_Data& q,
386
                                                              const EC_Scalar_Data& y,
387
                                                              RandomNumberGenerator& rng) const {
388
   if(m_pcurve) {
3,472✔
389
      auto pt = m_pcurve->mul_px_qy(EC_AffinePoint_Data_PC::checked_ref(p).value(),
5,208✔
390
                                    EC_Scalar_Data_PC::checked_ref(x).value(),
2,604✔
391
                                    EC_AffinePoint_Data_PC::checked_ref(q).value(),
2,604✔
392
                                    EC_Scalar_Data_PC::checked_ref(y).value(),
2,604✔
393
                                    rng);
2,604✔
394

395
      if(pt) {
2,604✔
396
         return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), m_pcurve->point_to_affine(*pt));
2,520✔
397
      } else {
398
         return nullptr;
84✔
399
      }
400
   } else {
2,604✔
401
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
402
      std::vector<BigInt> ws;
868✔
403
      const auto& group = p.group();
868✔
404

405
      // TODO this could be better!
406
      EC_Point_Var_Point_Precompute p_mul(p.to_legacy_point(), rng, ws);
868✔
407
      EC_Point_Var_Point_Precompute q_mul(q.to_legacy_point(), rng, ws);
868✔
408

409
      const auto order = group->order() * group->cofactor();  // See #3800
868✔
410

411
      auto px = p_mul.mul(EC_Scalar_Data_BN::checked_ref(x).value(), rng, order, ws);
868✔
412
      auto qy = q_mul.mul(EC_Scalar_Data_BN::checked_ref(y).value(), rng, order, ws);
868✔
413

414
      auto px_qy = px + qy;
868✔
415

416
      if(!px_qy.is_zero()) {
1,624✔
417
         px_qy.force_affine();
756✔
418
         return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(px_qy));
756✔
419
      } else {
420
         return nullptr;
112✔
421
      }
422
#else
423
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
424
#endif
425
   }
2,604✔
426
}
427

428
std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::affine_add(const EC_AffinePoint_Data& p,
7,754✔
429
                                                               const EC_AffinePoint_Data& q) const {
430
   if(m_pcurve) {
7,754✔
431
      auto pt = m_pcurve->point_add(EC_AffinePoint_Data_PC::checked_ref(p).value(),
5,046✔
432
                                    EC_AffinePoint_Data_PC::checked_ref(q).value());
5,046✔
433

434
      return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), m_pcurve->point_to_affine(pt));
5,045✔
435
   } else {
5,045✔
436
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
437
      auto pt = p.to_legacy_point() + q.to_legacy_point();
2,708✔
438
      return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(pt));
2,708✔
439
#else
440
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
441
#endif
442
   }
2,708✔
443
}
444

445
std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::affine_neg(const EC_AffinePoint_Data& p) const {
9,512✔
446
   if(m_pcurve) {
9,512✔
447
      auto pt = m_pcurve->point_negate(EC_AffinePoint_Data_PC::checked_ref(p).value());
6,312✔
448
      return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), pt);
6,312✔
449
   } else {
6,312✔
450
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
451
      auto pt = p.to_legacy_point();
3,200✔
452
      pt.negate();  // negates in place
3,200✔
453
      return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(pt));
3,200✔
454
#else
455
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
456
#endif
457
   }
3,200✔
458
}
459

460
std::unique_ptr<EC_Mul2Table_Data> EC_Group_Data::make_mul2_table(const EC_AffinePoint_Data& h) const {
13,257✔
461
   if(m_pcurve) {
13,257✔
462
      return std::make_unique<EC_Mul2Table_Data_PC>(h);
11,177✔
463
   } else {
464
#if defined(BOTAN_HAS_LEGACY_EC_POINT)
465
      EC_AffinePoint_Data_BN g(shared_from_this(), this->base_point());
4,160✔
466
      return std::make_unique<EC_Mul2Table_Data_BN>(g, h);
2,080✔
467
#else
468
      throw Not_Implemented("Legacy EC interfaces disabled in this build configuration");
469
#endif
470
   }
2,080✔
471
}
472

473
}  // namespace Botan
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc