Botan 3.12.0
Crypto and TLS for C&
pcurves_wrap.h
Go to the documentation of this file.
1/*
2* (C) 2024 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#ifndef BOTAN_PCURVES_WRAP_H_
8#define BOTAN_PCURVES_WRAP_H_
9
10#include <botan/exceptn.h>
11#include <botan/internal/pcurves.h>
12#include <botan/internal/pcurves_impl.h>
13
14namespace Botan::PCurve {
15
16template <typename C>
17concept curve_supports_scalar_invert = requires(const typename C::Scalar& s) {
18 { C::scalar_invert(s) } -> std::same_as<typename C::Scalar>;
19};
20
21/**
22* This class provides a bridge between the "public" (actually still
23* internal) PrimeOrderCurve type, and the inner templates which are
24* subclasses of EllipticCurve from pcurves_impl.h
25*/
26template <typename C>
28 public:
29 static_assert(C::OrderBits <= PrimeOrderCurve::MaximumBitLength);
30 static_assert(C::PrimeFieldBits <= PrimeOrderCurve::MaximumBitLength);
31
32 size_t order_bits() const override { return C::OrderBits; }
33
34 size_t scalar_bytes() const override { return C::Scalar::BYTES; }
35
36 size_t field_element_bytes() const override { return C::FieldElement::BYTES; }
37
38 ProjectivePoint mul_by_g(const Scalar& scalar, RandomNumberGenerator& rng) const override {
39 return stash(m_mul_by_g.mul(from_stash(scalar), rng));
40 }
41
42 ProjectivePoint mul(const AffinePoint& pt, const Scalar& scalar, RandomNumberGenerator& rng) const override {
43 auto tbl = WindowedBoothMulTable<C, VarPointWindowBits>(from_stash(pt));
44 return stash(tbl.mul(from_stash(scalar), rng));
45 }
46
48 const Scalar& scalar,
49 RandomNumberGenerator& rng) const override {
50 auto tbl = WindowedBoothMulTable<C, VarPointWindowBits>(from_stash(pt));
51 auto result = tbl.mul(from_stash(scalar), rng);
52 BOTAN_STATE_CHECK(!result.is_identity().as_bool());
53 auto pt_x = to_affine_x<C>(result);
54 secure_vector<uint8_t> x_bytes(C::FieldElement::BYTES);
55 pt_x.serialize_to(std::span<uint8_t, C::FieldElement::BYTES>{x_bytes});
56 return x_bytes;
57 }
58
60 public:
61 const auto& table() const { return m_table; }
62
63 explicit PrecomputedMul2TableC(const typename C::AffinePoint& x, const typename C::AffinePoint& y) :
64 m_table(x, y) {}
65
66 private:
68 };
69
70 std::unique_ptr<const PrecomputedMul2Table> mul2_setup_g(const AffinePoint& q) const override {
71 return std::make_unique<PrecomputedMul2TableC>(C::G, from_stash(q));
72 }
73
74 std::optional<ProjectivePoint> mul2_vartime(const PrecomputedMul2Table& tableb,
75 const Scalar& x,
76 const Scalar& y) const override {
77 try {
78 const auto& table = dynamic_cast<const PrecomputedMul2TableC&>(tableb);
79 auto pt = table.table().mul2_vartime(from_stash(x), from_stash(y));
80 if(pt.is_identity().as_bool()) {
81 return {};
82 } else {
83 return stash(pt);
84 }
85 } catch(std::bad_cast&) {
86 throw Invalid_Argument("Curve mismatch");
87 }
88 }
89
90 std::optional<ProjectivePoint> mul_px_qy(const AffinePoint& p,
91 const Scalar& x,
92 const AffinePoint& q,
93 const Scalar& y,
94 RandomNumberGenerator& rng) const override {
95 const WindowedMul2Table<C, Mul2WindowBits> tbl(from_stash(p), from_stash(q));
96 auto pt = tbl.mul2(from_stash(x), from_stash(y), rng);
97 if(pt.is_identity().as_bool()) {
98 return {};
99 } else {
100 return stash(pt);
101 }
102 }
103
105 const Scalar& v,
106 const Scalar& x,
107 const Scalar& y) const override {
108 try {
109 const auto& table = dynamic_cast<const PrecomputedMul2TableC&>(tableb);
110 const auto pt = table.table().mul2_vartime(from_stash(x), from_stash(y));
111 // Variable time here, so the early return is fine
112 if(pt.is_identity().as_bool()) {
113 return false;
114 }
115
116 /*
117 * Avoid the inversion by instead projecting v.
118 *
119 * Given (x*z2) and v we want to know if x % n == v
120 *
121 * Inverting z2 to extract x is expensive. Instead compute (v*z2) and
122 * compare it with (x*z2).
123 *
124 * With overwhelming probability, this conversion is correct. The
125 * only time it is not is in the extremely unlikely case where the
126 * signer actually reduced the x coordinate modulo the group order.
127 * That is handled separately in a second step.
128 */
129 const auto z2 = pt.z().square();
130
131 std::array<uint8_t, C::Scalar::BYTES> v_bytes{};
132 from_stash(v).serialize_to(v_bytes);
133
134 if(const auto fe_v = C::FieldElement::deserialize(v_bytes)) {
135 if((*fe_v * z2 == pt.x()).as_bool()) {
136 return true;
137 }
138
139 /*
140 * Possibly (if cryptographically unlikely) the signer
141 * reduced the value modulo the group order.
142 *
143 * If so we must check v + n similarly as before. However here
144 * we must be careful to not overflow since otherwise that
145 * would lead to us accepting an incorrect signature.
146 *
147 * If the order is > p then the reduction modulo p would not have
148 * had any effect and we don't need to consider the possibility
149 */
150 if constexpr(C::OrderIsLessThanField) {
151 /*
152 * We have to be careful to avoid overflow since this would
153 * lead to a forgery
154 *
155 * v < (p)-n => v + n < p
156 *
157 * The values n and neg_n could be precomputed but they are
158 * fast to compute and this codepath will ~never be taken
159 * unless when verifying an invalid signature. In any case
160 * it is many times cheaper than performing the modular inversion
161 * which this approach avoids.
162 */
163
164 // Create the group order as a field element, safe because n < p
165 const auto n = C::FieldElement::from_words(C::NW);
166 const auto neg_n = n.negate().to_words();
167
168 const auto vw = fe_v->to_words();
169 if(bigint_ct_is_lt(vw.data(), vw.size(), neg_n.data(), neg_n.size()).as_bool()) {
170 return (((*fe_v + n) * z2) == pt.x()).as_bool();
171 }
172 }
173 }
174
175 return false;
176 } catch(std::bad_cast&) {
177 throw Invalid_Argument("Curve mismatch");
178 }
179 }
180
181 Scalar base_point_mul_x_mod_order(const Scalar& scalar, RandomNumberGenerator& rng) const override {
182 auto pt = m_mul_by_g.mul(from_stash(scalar), rng);
183 BOTAN_STATE_CHECK(!pt.is_identity().as_bool());
184 std::array<uint8_t, C::FieldElement::BYTES> x_bytes{};
185 to_affine_x<C>(pt).serialize_to(std::span{x_bytes});
186 // Reduction might be required (if unlikely)
187 return stash(C::Scalar::from_wide_bytes(std::span<const uint8_t, C::FieldElement::BYTES>{x_bytes}));
188 }
189
190 AffinePoint generator() const override { return stash(C::G); }
191
192 AffinePoint point_to_affine(const ProjectivePoint& pt) const override {
193 auto affine = to_affine<C>(from_stash(pt));
194
195 const auto y2 = affine.y().square();
196 const auto x3_ax_b = C::x3_ax_b(affine.x());
197 const auto valid_point = affine.is_identity() || (y2 == x3_ax_b);
198
199 BOTAN_ASSERT(valid_point.as_bool(), "Computed point is on the curve");
200
201 return stash(affine);
202 }
203
204 ProjectivePoint point_add(const AffinePoint& a, const AffinePoint& b) const override {
205 return stash(C::ProjectivePoint::from_affine(from_stash(a)) + from_stash(b));
206 }
207
208 AffinePoint point_negate(const AffinePoint& pt) const override { return stash(from_stash(pt).negate()); }
209
210 bool affine_point_is_identity(const AffinePoint& pt) const override {
211 return from_stash(pt).is_identity().as_bool();
212 }
213
214 void serialize_point(std::span<uint8_t> bytes, const AffinePoint& pt) const override {
215 BOTAN_ARG_CHECK(bytes.size() == C::AffinePoint::BYTES, "Invalid length for serialize_point");
216 from_stash(pt).serialize_to(bytes.subspan<0, C::AffinePoint::BYTES>());
217 }
218
219 void serialize_scalar(std::span<uint8_t> bytes, const Scalar& scalar) const override {
220 BOTAN_ARG_CHECK(bytes.size() == C::Scalar::BYTES, "Invalid length to serialize_scalar");
221 return from_stash(scalar).serialize_to(bytes.subspan<0, C::Scalar::BYTES>());
222 }
223
224 std::optional<Scalar> deserialize_scalar(std::span<const uint8_t> bytes) const override {
225 if(auto scalar = C::Scalar::deserialize(bytes)) {
226 if(!scalar->is_zero().as_bool()) {
227 return stash(*scalar);
228 }
229 }
230
231 return {};
232 }
233
234 std::optional<Scalar> scalar_from_wide_bytes(std::span<const uint8_t> bytes) const override {
235 if(auto s = C::Scalar::from_wide_bytes_varlen(bytes)) {
236 return stash(*s);
237 } else {
238 return {};
239 }
240 }
241
242 std::optional<AffinePoint> deserialize_point(std::span<const uint8_t> bytes) const override {
243 // The identity element (see SEC1 section 2.3.4)
244 // TODO(Botan4) remove this - we should reject the identity encoding
245 if(bytes.size() == 1 && bytes[0] == 0x00) {
246 return stash(C::AffinePoint::identity());
247 }
248
249 constexpr size_t FieldElementBytes = C::FieldElement::BYTES;
250 constexpr size_t CompressedBytes = C::FieldElement::BYTES + 1;
251 constexpr size_t UncompressedBytes = 2 * C::FieldElement::BYTES + 1;
252
253 if(bytes.size() == UncompressedBytes && bytes[0] == 0x04) {
254 const auto encoded_point = bytes.subspan(1);
255 auto x = C::FieldElement::deserialize(encoded_point.first(FieldElementBytes));
256 auto y = C::FieldElement::deserialize(encoded_point.last(FieldElementBytes));
257
258 if(x && y) {
259 // Check that y^2 = x^3 + ax + b
260 const auto lhs = (*y).square();
261 const auto rhs = C::x3_ax_b(*x);
262 const auto valid = (lhs == rhs);
263 if(valid.as_bool()) {
264 return stash(typename C::AffinePoint(*x, *y));
265 }
266 }
267 } else if(bytes.size() == CompressedBytes && (bytes[0] == 0x02 || bytes[0] == 0x03)) {
268 const CT::Choice y_is_even = CT::Mask<uint8_t>::is_equal(bytes[0], 0x02).as_choice();
269
270 if(auto x = C::FieldElement::deserialize(bytes.subspan(1, FieldElementBytes))) {
271 if(auto y = sqrt_field_element<C>(C::x3_ax_b(*x)).as_optional_vartime()) {
272 return stash(typename C::AffinePoint(*x, y->correct_sign(y_is_even)));
273 }
274 }
275 }
276
277 return {};
278 }
279
280 AffinePoint hash_to_curve_nu(std::function<void(std::span<uint8_t>)> expand_message) const override {
281 if constexpr(C::ValidForSswuHash) {
282 return stash(hash_to_curve_sswu<C, false>(expand_message));
283 } else {
284 throw Not_Implemented("Hash to curve is not implemented for this curve");
285 }
286 }
287
288 ProjectivePoint hash_to_curve_ro(std::function<void(std::span<uint8_t>)> expand_message) const override {
289 if constexpr(C::ValidForSswuHash) {
290 return stash(hash_to_curve_sswu<C, true>(expand_message));
291 } else {
292 throw Not_Implemented("Hash to curve is not implemented for this curve");
293 }
294 }
295
296 Scalar scalar_add(const Scalar& a, const Scalar& b) const override {
297 return stash(from_stash(a) + from_stash(b));
298 }
299
300 Scalar scalar_sub(const Scalar& a, const Scalar& b) const override {
301 return stash(from_stash(a) - from_stash(b));
302 }
303
304 Scalar scalar_mul(const Scalar& a, const Scalar& b) const override {
305 return stash(from_stash(a) * from_stash(b));
306 }
307
308 Scalar scalar_square(const Scalar& s) const override { return stash(from_stash(s).square()); }
309
310 Scalar scalar_invert(const Scalar& ss) const override {
311 auto s = from_stash(ss);
312 if constexpr(curve_supports_scalar_invert<C>) {
313 return stash(C::scalar_invert(s));
314 } else {
315 return stash(s.invert());
316 }
317 }
318
319 Scalar scalar_invert_vartime(const Scalar& ss) const override {
320 auto s = from_stash(ss);
321 return stash(s.invert_vartime());
322 }
323
324 Scalar scalar_negate(const Scalar& s) const override { return stash(from_stash(s).negate()); }
325
326 bool scalar_is_zero(const Scalar& s) const override { return from_stash(s).is_zero().as_bool(); }
327
328 bool scalar_equal(const Scalar& a, const Scalar& b) const override {
329 return (from_stash(a) == from_stash(b)).as_bool();
330 }
331
332 Scalar scalar_one() const override { return stash(C::Scalar::one()); }
333
334 Scalar random_scalar(RandomNumberGenerator& rng) const override { return stash(C::Scalar::random(rng)); }
335
336 PrimeOrderCurveImpl() : m_mul_by_g(C::G) {}
337
338 static std::shared_ptr<const PrimeOrderCurve> instance() {
339 static auto g_curve = std::make_shared<const PrimeOrderCurveImpl<C>>();
340 return g_curve;
341 }
342
343 private:
344 static Scalar stash(const typename C::Scalar& s) {
345 return Scalar::_create(instance(), s.template stash_value<StorageWords>());
346 }
347
348 static typename C::Scalar from_stash(const Scalar& s) {
349 if(s._curve() != instance()) {
350 throw Invalid_Argument("Curve mismatch");
351 }
352 return C::Scalar::from_stash(s._value());
353 }
354
355 static AffinePoint stash(const typename C::AffinePoint& pt) {
356 auto x_w = pt.x().template stash_value<StorageWords>();
357 auto y_w = pt.y().template stash_value<StorageWords>();
358 return AffinePoint::_create(instance(), x_w, y_w);
359 }
360
361 static typename C::AffinePoint from_stash(const AffinePoint& pt) {
362 if(pt._curve() != instance()) {
363 throw Invalid_Argument("Curve mismatch");
364 }
365 auto x = C::FieldElement::from_stash(pt._x());
366 auto y = C::FieldElement::from_stash(pt._y());
367 return typename C::AffinePoint(x, y);
368 }
369
370 static ProjectivePoint stash(const typename C::ProjectivePoint& pt) {
371 auto x_w = pt.x().template stash_value<StorageWords>();
372 auto y_w = pt.y().template stash_value<StorageWords>();
373 auto z_w = pt.z().template stash_value<StorageWords>();
374 return ProjectivePoint::_create(instance(), x_w, y_w, z_w);
375 }
376
377 static typename C::ProjectivePoint from_stash(const ProjectivePoint& pt) {
378 if(pt._curve() != instance()) {
379 throw Invalid_Argument("Curve mismatch");
380 }
381 auto x = C::FieldElement::from_stash(pt._x());
382 auto y = C::FieldElement::from_stash(pt._y());
383 auto z = C::FieldElement::from_stash(pt._z());
384 return typename C::ProjectivePoint(x, y, z);
385 }
386
387 private:
388 const PrecomputedBaseMulTable<C, BasePointWindowBits> m_mul_by_g;
389};
390
391} // namespace Botan::PCurve
392
393#endif
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:49
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
#define BOTAN_ASSERT(expr, assertion_made)
Definition assert.h:62
static constexpr Mask< T > is_equal(T x, T y)
Definition ct_utils.h:442
PrecomputedMul2TableC(const typename C::AffinePoint &x, const typename C::AffinePoint &y)
AffinePoint point_negate(const AffinePoint &pt) const override
Scalar scalar_invert(const Scalar &ss) const override
Scalar inversion.
size_t field_element_bytes() const override
std::unique_ptr< const PrecomputedMul2Table > mul2_setup_g(const AffinePoint &q) const override
Setup a table for 2-ary multiplication where the first point is the generator.
bool scalar_equal(const Scalar &a, const Scalar &b) const override
Test if two scalars are equal.
void serialize_scalar(std::span< uint8_t > bytes, const Scalar &scalar) const override
bool affine_point_is_identity(const AffinePoint &pt) const override
Scalar scalar_sub(const Scalar &a, const Scalar &b) const override
Scalar subtraction.
size_t order_bits() const override
Return the bit length of the group order.
AffinePoint generator() const override
Return the standard generator.
ProjectivePoint mul_by_g(const Scalar &scalar, RandomNumberGenerator &rng) const override
size_t scalar_bytes() const override
Return the byte length of the scalar element.
AffinePoint hash_to_curve_nu(std::function< void(std::span< uint8_t >)> expand_message) const override
bool scalar_is_zero(const Scalar &s) const override
Test if scalar is zero.
Scalar scalar_one() const override
static std::shared_ptr< const PrimeOrderCurve > instance()
std::optional< Scalar > scalar_from_wide_bytes(std::span< const uint8_t > bytes) const override
ProjectivePoint hash_to_curve_ro(std::function< void(std::span< uint8_t >)> expand_message) const override
Scalar base_point_mul_x_mod_order(const Scalar &scalar, RandomNumberGenerator &rng) const override
std::optional< Scalar > deserialize_scalar(std::span< const uint8_t > bytes) const override
Scalar scalar_add(const Scalar &a, const Scalar &b) const override
Scalar addition.
Scalar scalar_negate(const Scalar &s) const override
Scalar negation.
Scalar scalar_invert_vartime(const Scalar &ss) const override
Scalar inversion (variable time).
bool mul2_vartime_x_mod_order_eq(const PrecomputedMul2Table &tableb, const Scalar &v, const Scalar &x, const Scalar &y) const override
ProjectivePoint point_add(const AffinePoint &a, const AffinePoint &b) const override
std::optional< AffinePoint > deserialize_point(std::span< const uint8_t > bytes) const override
Scalar random_scalar(RandomNumberGenerator &rng) const override
void serialize_point(std::span< uint8_t > bytes, const AffinePoint &pt) const override
Scalar scalar_square(const Scalar &s) const override
Scalar squaring.
secure_vector< uint8_t > mul_x_only(const AffinePoint &pt, const Scalar &scalar, RandomNumberGenerator &rng) const override
Scalar scalar_mul(const Scalar &a, const Scalar &b) const override
Scalar multiplication.
AffinePoint point_to_affine(const ProjectivePoint &pt) const override
std::optional< ProjectivePoint > mul2_vartime(const PrecomputedMul2Table &tableb, const Scalar &x, const Scalar &y) const override
ProjectivePoint mul(const AffinePoint &pt, const Scalar &scalar, RandomNumberGenerator &rng) const override
std::optional< ProjectivePoint > mul_px_qy(const AffinePoint &p, const Scalar &x, const AffinePoint &q, const Scalar &y, RandomNumberGenerator &rng) const override
static constexpr size_t MaximumBitLength
Definition pcurves.h:37
ProjectivePoint mul2(const Scalar &s1, const Scalar &s2, RandomNumberGenerator &rng) const
auto to_affine_x(const typename C::ProjectivePoint &pt)
constexpr CT::Option< typename C::FieldElement > sqrt_field_element(const typename C::FieldElement &fe)
BigInt square(const BigInt &x)
Definition numthry.cpp:184
constexpr auto to_affine(const typename C::ProjectivePoint &pt)
auto hash_to_curve_sswu(const ExpandMsg &expand_message) -> std::conditional_t< RO, typename C::ProjectivePoint, typename C::AffinePoint >
constexpr auto bigint_ct_is_lt(const W x[], size_t x_size, const W y[], size_t y_size, bool lt_or_equal=false) -> CT::Mask< W >
Definition mp_core.h:473
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:68