Botan 3.11.0
Crypto and TLS for C&
pcurves_algos.h
Go to the documentation of this file.
1/*
2* (C) 2024,2025 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#ifndef BOTAN_PCURVES_ALGOS_H_
8#define BOTAN_PCURVES_ALGOS_H_
9
10#include <botan/types.h>
11#include <botan/internal/ct_utils.h>
12#include <algorithm>
13#include <span>
14#include <vector>
15
16namespace Botan {
17
18/**
19* Field inversion concept
20*
21* This concept checks if the curve class supports fe_invert2
22*/
23template <typename C>
24concept curve_supports_fe_invert2 = requires(const typename C::FieldElement& fe) {
25 { C::fe_invert2(fe) } -> std::same_as<typename C::FieldElement>;
26};
27
28/**
29* Field inversion
30*
31* Uses the specialized fe_invert2 if available, or otherwise the standard
32* (FLT-based) field inversion.
33*/
34template <typename C>
35inline constexpr auto invert_field_element(const typename C::FieldElement& fe) {
36 if constexpr(curve_supports_fe_invert2<C>) {
37 return C::fe_invert2(fe) * fe;
38 } else {
39 return fe.invert();
40 }
41}
42
43/**
44* Field square root
45*
46* This concept checks if the curve class supports fe_sqrt
47*/
48template <typename C>
49concept curve_supports_fe_sqrt = requires(const typename C::FieldElement& fe) {
50 { C::fe_sqrt(fe) } -> std::same_as<typename C::FieldElement>;
51};
52
53/**
54* Field square root
55*
56* Uses the specialized fe_sqrt if available, or otherwise the standard
57* square root
58*/
59template <typename C>
60inline constexpr CT::Option<typename C::FieldElement> sqrt_field_element(const typename C::FieldElement& fe) {
61 if constexpr(curve_supports_fe_sqrt<C>) {
62 auto z = C::fe_sqrt(fe);
63 // Zero out the return value if it would otherwise be incorrect
64 const CT::Choice correct = (z.square() == fe);
65 z.conditional_assign(!correct, C::FieldElement::zero());
66 return CT::Option(z, correct);
67 } else {
68 return fe.sqrt();
69 }
70}
71
72/**
73* Convert a projective point into affine
74*/
75template <typename C>
76inline constexpr auto to_affine(const typename C::ProjectivePoint& pt) {
77 // Not strictly required right? - default should work as long
78 // as (0,0) is identity and invert returns 0 on 0
79
80 if constexpr(curve_supports_fe_invert2<C>) {
81 const auto z2_inv = C::fe_invert2(pt.z());
82 const auto z3_inv = z2_inv.square() * pt.z();
83 return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv);
84 } else {
85 const auto z_inv = invert_field_element<C>(pt.z());
86 const auto z2_inv = z_inv.square();
87 const auto z3_inv = z_inv * z2_inv;
88 return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv);
89 }
90}
91
92/**
93* Convert a projective point into affine and return x coordinate only
94*/
95template <typename C>
96auto to_affine_x(const typename C::ProjectivePoint& pt) {
97 if constexpr(curve_supports_fe_invert2<C>) {
98 return pt.x() * C::fe_invert2(pt.z());
99 } else {
100 const auto z_inv = invert_field_element<C>(pt.z());
101 const auto z2_inv = z_inv.square();
102 return pt.x() * z2_inv;
103 }
104}
105
106template <typename C, bool VariableTime = false>
107auto to_affine_batch(std::span<const typename C::ProjectivePoint> projective) {
108 using AffinePoint = typename C::AffinePoint;
109
110 const size_t N = projective.size();
111 std::vector<AffinePoint> affine;
112 affine.reserve(N);
113
114 CT::Choice any_identity = CT::Choice::no();
115
116 for(const auto& pt : projective) {
117 any_identity = any_identity || pt.is_identity();
118 }
119
120 // Conditional acceptable: N is public. State of points is not necessarily
121 // public, but we don't leak which point was the identity. In practice with
122 // the algorithms currently in use, the only time an identity can occur is
123 // during mul2 where the two points g/h have a small relation (ie h = g*k for
124 // some k < 16)
125
126 if(N <= 2 || any_identity.as_bool()) {
127 // If there are identity elements, using the batch inversion gets
128 // tricky. It can be done, but this should be a rare situation so
129 // just punt to the serial conversion if it occurs
130 for(size_t i = 0; i != N; ++i) {
131 affine.push_back(to_affine<C>(projective[i]));
132 }
133 } else {
134 std::vector<typename C::FieldElement> c;
135 c.reserve(N);
136
137 /*
138 Batch projective->affine using Montgomery's trick
139
140 See Algorithm 2.26 in "Guide to Elliptic Curve Cryptography"
141 (Hankerson, Menezes, Vanstone)
142 */
143
144 c.push_back(projective[0].z());
145 for(size_t i = 1; i != N; ++i) {
146 c.push_back(c[i - 1] * projective[i].z());
147 }
148
149 auto s_inv = [&]() {
150 if constexpr(VariableTime) {
151 return c[N - 1].invert_vartime();
152 } else {
153 return invert_field_element<C>(c[N - 1]);
154 }
155 }();
156
157 for(size_t i = N - 1; i > 0; --i) {
158 const auto& p = projective[i];
159
160 const auto z_inv = s_inv * c[i - 1];
161 const auto z2_inv = z_inv.square();
162 const auto z3_inv = z_inv * z2_inv;
163
164 s_inv = s_inv * p.z();
165
166 affine.push_back(AffinePoint(p.x() * z2_inv, p.y() * z3_inv));
167 }
168
169 const auto z2_inv = s_inv.square();
170 const auto z3_inv = s_inv * z2_inv;
171 affine.push_back(AffinePoint(projective[0].x() * z2_inv, projective[0].y() * z3_inv));
172 std::reverse(affine.begin(), affine.end());
173 return affine;
174 }
175
176 return affine;
177}
178
179/*
180Projective point addition
181
182https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2
183
184Cost: 12M + 4S + 6add + 1*2
185*/
186template <typename ProjectivePoint, typename FieldElement>
187inline constexpr ProjectivePoint point_add(const ProjectivePoint& a, const ProjectivePoint& b) {
188 const auto a_is_identity = a.is_identity();
189 const auto b_is_identity = b.is_identity();
190
191 const auto Z1Z1 = a.z().square();
192 const auto Z2Z2 = b.z().square();
193 const auto U1 = a.x() * Z2Z2;
194 const auto U2 = b.x() * Z1Z1;
195 const auto S1 = a.y() * b.z() * Z2Z2;
196 const auto S2 = b.y() * a.z() * Z1Z1;
197 const auto H = U2 - U1;
198 const auto r = S2 - S1;
199
200 /* Risky conditional
201 *
202 * This implementation uses projective coordinates, which do not have an efficient complete
203 * addition formula. We rely on the design of the multiplication algorithms to avoid doublings.
204 *
205 * This conditional only comes into play for the actual doubling case, not x + (-x) which
206 * is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0,
207 * in which case at the end we'll set z to a.z * b.z * H = 0, resulting in the correct
208 * output (the identity element)
209 */
210 if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) {
211 return a.dbl();
212 }
213
214 const auto HH = H.square();
215 const auto HHH = H * HH;
216 const auto V = U1 * HH;
217 const auto t2 = r.square();
218 const auto t3 = V + V;
219 const auto t4 = t2 - HHH;
220 auto X3 = t4 - t3;
221 const auto t5 = V - X3;
222 const auto t6 = S1 * HHH;
223 const auto t7 = r * t5;
224 auto Y3 = t7 - t6;
225 const auto t8 = b.z() * H;
226 auto Z3 = a.z() * t8;
227
228 // if a is identity then return b
229 FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), b.y(), b.z());
230
231 // if b is identity then return a
232 FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z());
233
234 return ProjectivePoint(X3, Y3, Z3);
235}
236
237template <typename ProjectivePoint, typename AffinePoint, typename FieldElement>
238inline constexpr ProjectivePoint point_add_mixed(const ProjectivePoint& a,
239 const AffinePoint& b,
240 const FieldElement& one) {
241 const auto a_is_identity = a.is_identity();
242 const auto b_is_identity = b.is_identity();
243
244 /*
245 https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2
246
247 Cost: 8M + 3S + 6add + 1*2
248 */
249
250 const auto Z1Z1 = a.z().square();
251 const auto U2 = b.x() * Z1Z1;
252 const auto S2 = b.y() * a.z() * Z1Z1;
253 const auto H = U2 - a.x();
254 const auto r = S2 - a.y();
255
256 /* Risky conditional
257 *
258 * This implementation uses projective coordinates, which do not have an efficient complete
259 * addition formula. We rely on the design of the multiplication algorithms to avoid doublings.
260 *
261 * This conditional only comes into play for the actual doubling case, not x + (-x) which
262 * is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0,
263 * in which case at the end we'll set z to a.z * H = 0, resulting in the correct output
264 * (the identity element)
265 */
266 if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) {
267 return a.dbl();
268 }
269
270 const auto HH = H.square();
271 const auto HHH = H * HH;
272 const auto V = a.x() * HH;
273 const auto t2 = r.square();
274 const auto t3 = V + V;
275 const auto t4 = t2 - HHH;
276 auto X3 = t4 - t3;
277 const auto t5 = V - X3;
278 const auto t6 = a.y() * HHH;
279 const auto t7 = r * t5;
280 auto Y3 = t7 - t6;
281 auto Z3 = a.z() * H;
282
283 // if a is identity then return b
284 FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), b.y(), one);
285
286 // if b is identity then return a
287 FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z());
288
289 return ProjectivePoint(X3, Y3, Z3);
290}
291
292template <typename ProjectivePoint, typename AffinePoint, typename FieldElement>
293inline constexpr ProjectivePoint point_add_or_sub_mixed(const ProjectivePoint& a,
294 const AffinePoint& b,
295 CT::Choice sub,
296 const FieldElement& one) {
297 const auto a_is_identity = a.is_identity();
298 const auto b_is_identity = b.is_identity();
299
300 /*
301 https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2
302
303 Cost: 8M + 3S + 6add + 1*2
304 */
305
306 auto by = b.y();
307 by.conditional_assign(sub, by.negate());
308
309 const auto Z1Z1 = a.z().square();
310 const auto U2 = b.x() * Z1Z1;
311 const auto S2 = by * a.z() * Z1Z1;
312 const auto H = U2 - a.x();
313 const auto r = S2 - a.y();
314
315 /* Risky conditional
316 *
317 * This implementation uses projective coordinates, which do not have an efficient complete
318 * addition formula. We rely on the design of the multiplication algorithms to avoid doublings.
319 *
320 * This conditional only comes into play for the actual doubling case, not x + (-x) which
321 * is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0,
322 * in which case at the end we'll set z to a.z * H = 0, resulting in the correct output
323 * (the identity element)
324 */
325 if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) {
326 return a.dbl();
327 }
328
329 const auto HH = H.square();
330 const auto HHH = H * HH;
331 const auto V = a.x() * HH;
332 const auto t2 = r.square();
333 const auto t3 = V + V;
334 const auto t4 = t2 - HHH;
335 auto X3 = t4 - t3;
336 const auto t5 = V - X3;
337 const auto t6 = a.y() * HHH;
338 const auto t7 = r * t5;
339 auto Y3 = t7 - t6;
340 auto Z3 = a.z() * H;
341
342 // if a is identity then return b
343 FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), by, one);
344
345 // if b is identity then return a
346 FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z());
347
348 return ProjectivePoint(X3, Y3, Z3);
349}
350
351/*
352Point doubling
353
354Using https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2
355
356Cost (generic A): 4M + 6S + 4A + 2*2 + 1*3 + 1*4 + 1*8
357Cost (A == -3): 4M + 4S + 5A + 2*2 + 1*3 + 1*4 + 1*8
358Cost (A == 0): 3M + 4S + 3A + 2*2 + 1*3 + 1*4 + 1*8
359*/
360
361template <typename ProjectivePoint>
362inline constexpr ProjectivePoint dbl_a_minus_3(const ProjectivePoint& pt) {
363 /*
364 if a == -3 then
365 3*x^2 + a*z^4 == 3*x^2 - 3*z^4 == 3*(x^2-z^4) == 3*(x-z^2)*(x+z^2)
366 */
367 const auto z2 = pt.z().square();
368 const auto m = (pt.x() - z2).mul3() * (pt.x() + z2);
369
370 // Remaining cost: 3M + 3S + 3A + 2*2 + 1*4 + 1*8
371 const auto y2 = pt.y().square();
372 const auto s = pt.x().mul4() * y2;
373 const auto nx = m.square() - s.mul2();
374 const auto ny = m * (s - nx) - y2.square().mul8();
375 const auto nz = pt.y().mul2() * pt.z();
376
377 return ProjectivePoint(nx, ny, nz);
378}
379
380template <typename ProjectivePoint>
381inline constexpr ProjectivePoint dbl_a_zero(const ProjectivePoint& pt) {
382 // If a == 0 then 3*x^2 + a*z^4 == 3*x^2
383 // Cost: 1S + 1*3
384 const auto m = pt.x().square().mul3();
385
386 // Remaining cost: 3M + 3S + 3A + 2*2 + 1*4 + 1*8
387 const auto y2 = pt.y().square();
388 const auto s = pt.x().mul4() * y2;
389 const auto nx = m.square() - s.mul2();
390 const auto ny = m * (s - nx) - y2.square().mul8();
391 const auto nz = pt.y().mul2() * pt.z();
392
393 return ProjectivePoint(nx, ny, nz);
394}
395
396template <typename ProjectivePoint, typename FieldElement>
397inline constexpr ProjectivePoint dbl_generic(const ProjectivePoint& pt, const FieldElement& A) {
398 // Cost: 1M + 3S + 1A + 1*3
399 const auto z2 = pt.z().square();
400 const auto m = pt.x().square().mul3() + A * z2.square();
401
402 // Remaining cost: 3M + 3S + 3A + 2*2 + 1*4 + 1*8
403 const auto y2 = pt.y().square();
404 const auto s = pt.x().mul4() * y2;
405 const auto nx = m.square() - s.mul2();
406 const auto ny = m * (s - nx) - y2.square().mul8();
407 const auto nz = pt.y().mul2() * pt.z();
408
409 return ProjectivePoint(nx, ny, nz);
410}
411
412/*
413Repeated doubling using an adaptation of Algorithm 3.23 in
414"Guide To Elliptic Curve Cryptography" (Hankerson, Menezes, Vanstone)
415
416Curiously the book gives the algorithm only for A == -3, but
417the largest gains come from applying it to the generic A case,
418where it saves 2 squarings per iteration.
419
420For A == 0
421Pay 1*2 + 1half to save n*(1*4 + 1*8)
422
423For A == -3:
424Pay 2S + 1*2 + 1half to save n*(1A + 1*4 + 1*8) + 1M
425
426For generic A:
427Pay 2S + 1*2 + 1half to save n*(2S + 1*4 + 1*8)
428
429The value of n is assumed to be public and should be a constant
430*/
431template <typename ProjectivePoint>
432inline constexpr ProjectivePoint dbl_n_a_minus_3(const ProjectivePoint& pt, size_t n) {
433 auto nx = pt.x();
434 auto ny = pt.y().mul2();
435 auto nz = pt.z();
436 auto w = nz.square().square();
437
438 // Conditional ok: loop iteration count is public
439 while(n > 0) {
440 const auto ny2 = ny.square();
441 const auto ny4 = ny2.square();
442 const auto t1 = (nx.square() - w).mul3();
443 const auto t2 = nx * ny2;
444 nx = t1.square() - t2.mul2();
445 nz *= ny;
446 ny = t1 * (t2 - nx).mul2() - ny4;
447 n--;
448 // Conditional ok: loop iteration count is public
449 if(n > 0) {
450 w *= ny4;
451 }
452 }
453 return ProjectivePoint(nx, ny.div2(), nz);
454}
455
456template <typename ProjectivePoint>
457inline constexpr ProjectivePoint dbl_n_a_zero(const ProjectivePoint& pt, size_t n) {
458 auto nx = pt.x();
459 auto ny = pt.y().mul2();
460 auto nz = pt.z();
461
462 // Conditional ok: loop iteration count is public
463 while(n > 0) {
464 const auto ny2 = ny.square();
465 const auto ny4 = ny2.square();
466 const auto t1 = nx.square().mul3();
467 const auto t2 = nx * ny2;
468 nx = t1.square() - t2.mul2();
469 nz *= ny;
470 ny = t1 * (t2 - nx).mul2() - ny4;
471 n--;
472 }
473 return ProjectivePoint(nx, ny.div2(), nz);
474}
475
476template <typename ProjectivePoint, typename FieldElement>
477inline constexpr ProjectivePoint dbl_n_generic(const ProjectivePoint& pt, const FieldElement& A, size_t n) {
478 auto nx = pt.x();
479 auto ny = pt.y().mul2();
480 auto nz = pt.z();
481 auto w = nz.square().square() * A;
482
483 // Conditional ok: loop iteration count is public
484 while(n > 0) {
485 const auto ny2 = ny.square();
486 const auto ny4 = ny2.square();
487 const auto t1 = nx.square().mul3() + w;
488 const auto t2 = nx * ny2;
489 nx = t1.square() - t2.mul2();
490 nz *= ny;
491 ny = t1 * (t2 - nx).mul2() - ny4;
492 n--;
493 // Conditional ok: loop iteration count is public
494 if(n > 0) {
495 w *= ny4;
496 }
497 }
498 return ProjectivePoint(nx, ny.div2(), nz);
499}
500
501} // namespace Botan
502
503#endif
static constexpr Choice no()
Definition ct_utils.h:307
constexpr bool as_bool() const
Definition ct_utils.h:329
auto to_affine_x(const typename C::ProjectivePoint &pt)
constexpr CT::Option< typename C::FieldElement > sqrt_field_element(const typename C::FieldElement &fe)
constexpr ProjectivePoint dbl_n_generic(const ProjectivePoint &pt, const FieldElement &A, size_t n)
constexpr ProjectivePoint dbl_a_minus_3(const ProjectivePoint &pt)
constexpr ProjectivePoint dbl_n_a_zero(const ProjectivePoint &pt, size_t n)
auto to_affine_batch(std::span< const typename C::ProjectivePoint > projective)
constexpr ProjectivePoint dbl_a_zero(const ProjectivePoint &pt)
constexpr auto to_affine(const typename C::ProjectivePoint &pt)
constexpr ProjectivePoint point_add_mixed(const ProjectivePoint &a, const AffinePoint &b, const FieldElement &one)
constexpr ProjectivePoint point_add_or_sub_mixed(const ProjectivePoint &a, const AffinePoint &b, CT::Choice sub, const FieldElement &one)
constexpr auto invert_field_element(const typename C::FieldElement &fe)
constexpr ProjectivePoint dbl_n_a_minus_3(const ProjectivePoint &pt, size_t n)
constexpr ProjectivePoint dbl_generic(const ProjectivePoint &pt, const FieldElement &A)
constexpr ProjectivePoint point_add(const ProjectivePoint &a, const ProjectivePoint &b)