Botan 3.13.0
Crypto and TLS for C&
ed448_internal.cpp
Go to the documentation of this file.
1
2/*
3 * Ed448 Internals
4 * (C) 2024 Jack Lloyd
5 * 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
6 *
7 * Botan is released under the Simplified BSD License (see license.txt)
8 */
9#include <botan/internal/ed448_internal.h>
10
11#include <botan/exceptn.h>
12#include <botan/types.h>
13#include <botan/xof.h>
14#include <botan/internal/buffer_slicer.h>
15#include <botan/internal/buffer_stuffer.h>
16#include <botan/internal/concat_util.h>
17#include <botan/internal/ct_utils.h>
18#include <botan/internal/loadstor.h>
19
20namespace Botan {
21namespace {
22
23std::vector<uint8_t> dom4(uint8_t x, std::span<const uint8_t> y) {
24 // RFC 8032 2. Notation and Conventions
25 // dom4(x, y) The octet string "SigEd448" || octet(x) ||
26 // octet(OLEN(y)) || y, where x is in range 0-255 and y
27 // is an octet string of at most 255 octets. "SigEd448"
28 // is in ASCII (8 octets).
29 BOTAN_ARG_CHECK(y.size() < 256, "y is too long");
30 return concat<std::vector<uint8_t>>(std::array<uint8_t, 8>{'S', 'i', 'g', 'E', 'd', '4', '4', '8'},
31 store_le(x),
32 store_le(static_cast<uint8_t>(y.size())),
33 y);
34}
35
36template <ranges::spanable_range... Ts>
37std::array<uint8_t, 2 * ED448_LEN> shake(bool f, std::span<const uint8_t> context, Ts... xs) {
38 auto shake_xof = XOF::create_or_throw("SHAKE-256");
39 shake_xof->update(dom4(static_cast<uint8_t>(f), context));
40 (shake_xof->update(std::span(xs)), ...);
41 std::array<uint8_t, 2 * ED448_LEN> res{};
42 shake_xof->output(res);
43 return res;
44}
45
46std::pair<std::span<const uint8_t, 57>, std::span<const uint8_t, 57>> split(std::span<const uint8_t, 114> arr) {
47 BufferSlicer bs(arr);
48 auto lhs = bs.take<57>();
49 auto rhs = bs.take<57>();
50 return {lhs, rhs};
51}
52
53Scalar448 scalar_from_xof(XOF& shake_xof) {
54 // 5.2.5. Key Generation
55 // The 57-byte public key is generated by the following steps:
56 // 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the
57 // digest in a 114-octet large buffer, denoted h. Only the lower 57
58 // bytes are used for generating the public key.
59 std::array<uint8_t, ED448_LEN> raw_s{};
60 shake_xof.output(raw_s);
61 // 2. Prune the buffer: The two least significant bits of the first
62 // octet are cleared, all eight bits the last octet are cleared, and
63 // the highest bit of the second to last octet is set.
64 raw_s[0] &= 0xFC;
65 raw_s[55] |= 0x80;
66 raw_s[56] = 0;
67
68 return Scalar448(raw_s);
69}
70
71} // namespace
72
73Ed448Point Ed448Point::decode(std::span<const uint8_t, ED448_LEN> enc) {
74 // RFC 8032 5.2.3 Decoding
75 // 1. First, interpret the string as an integer in little-endian
76 // representation. Bit 455 of this number is the least significant
77 // bit of the x-coordinate, and denote this value x_0. The
78 // y-coordinate is recovered simply by clearing this bit. If the
79 // resulting value is >= p, decoding fails.
80 if((enc.back() & 0x7F) != 0) { // last byte is either 0x00 or 0x80
81 throw Decoding_Error("Ed448 point has unacceptable x-distinguisher");
82 }
83 std::array<uint8_t, ED448_LEN> identity_element{};
84 identity_element[0] = 1;
85 if(CT::is_equal(enc.data(), identity_element.data(), ED448_LEN).as_bool()) {
86 throw Decoding_Error("Ed448 point is the identity element");
87 }
88 const bool x_distinguisher = enc.back() != 0;
89 const auto y_data = std::span(enc).first<56>();
91 throw Decoding_Error("Ed448 y-coordinate is not smaller than p");
92 }
93 const auto y = Gf448Elem(y_data);
94
95 // 2. To recover the x-coordinate, the curve equation implies
96 // x^2 = (y^2 - 1) / (d y^2 - 1) (mod p). The denominator is always
97 // non-zero mod p. Let u = y^2 - 1 and v = d y^2 - 1. To compute
98 // the square root of (u/v), the first step is to compute the
99 // candidate root x = (u/v)^((p+1)/4). This can be done using the
100 // following trick, to use a single modular powering for both the
101 // inversion of v and the square root:
102 // (p+1)/4 3 (p-3)/4
103 // x = (u/v) = u v (u^5 v^3) (mod p)
104 const auto u = square(Gf448Elem(y)) - Gf448Elem::one();
105 const auto v = -mul_a24(square(Gf448Elem(y))) - Gf448Elem::one();
106 const auto maybe_x = (u * square(u)) * v * root((square(square(u)) * u) * square(v) * v);
107
108 // 3. If v * x^2 = u, the recovered x-coordinate is x. Otherwise, no
109 // square root exists, and the decoding fails.
110 if(v * square(maybe_x) != u) {
111 throw Decoding_Error("Square root does not exist");
112 }
113 // 4. Finally, use the x_0 bit to select the right square root. If
114 // x = 0, and x_0 = 1, decoding fails. Otherwise, if x_0 != x mod
115 // 2, set x <-- p - x. Return the decoded point (x,y).
116 if(maybe_x.is_zero() && x_distinguisher) {
117 throw Decoding_Error("Square root of zero cannot be odd");
118 }
119 const bool maybe_x_parity = maybe_x.is_odd();
120 std::array<uint64_t, WORDS_448> x_data{};
121 CT::Mask<uint64_t>::expand_bool(maybe_x_parity == x_distinguisher)
122 .select_n(x_data.data(), maybe_x.words().data(), (-maybe_x).words().data(), WORDS_448);
123
124 return {Gf448Elem(x_data), y};
125}
126
128 constexpr std::array<const uint64_t, WORDS_448> x = {0x2626a82bc70cc05e,
129 0x433b80e18b00938e,
130 0x12ae1af72ab66511,
131 0xea6de324a3d3a464,
132 0x9e146570470f1767,
133 0x221d15a622bf36da,
134 0x4f1970c66bed0ded};
135 constexpr std::array<const uint64_t, WORDS_448> y = {0x9808795bf230fa14,
136 0xfdbd132c4ed7c8ad,
137 0x3ad3ff1ce67c39c4,
138 0x87789c1e05a0c2d7,
139 0x4bea73736ca39840,
140 0x8876203756c9c762,
141 0x693f46716eb6bc24};
142 return Ed448Point(Gf448Elem(x), Gf448Elem(y));
143}
144
145std::array<uint8_t, ED448_LEN> Ed448Point::encode() const {
146 std::array<uint8_t, ED448_LEN> res_buf = {0};
147
148 // RFC 8032 5.2.2
149 // All values are coded as octet strings, and integers are coded using
150 // little-endian convention. [...]
151 // First, encode the y-coordinate as a little-endian string of 57 octets.
152 // The final octet is always zero.
153 y().to_bytes(std::span(res_buf).first<56>());
154
155 // To form the encoding of the point, copy the least significant bit of
156 // the x-coordinate to the most significant bit of the final octet.
157 res_buf.back() = (static_cast<uint8_t>(x().is_odd()) << 7);
158
159 return res_buf;
160}
161
163 // RFC 8032 5.2.4. - Point Addition (Add)
164 const Gf448Elem A = m_z * other.m_z;
165 const Gf448Elem B = square(A);
166 const Gf448Elem C = m_x * other.m_x;
167 const Gf448Elem D = m_y * other.m_y;
168 const Gf448Elem E = -mul_a24(C * D);
169 const Gf448Elem F = B - E;
170 const Gf448Elem G = B + E;
171 const Gf448Elem H = (m_x + m_y) * (other.m_x + other.m_y);
172 const Gf448Elem X3 = A * F * (H - C - D);
173 const Gf448Elem Y3 = A * G * (D - C);
174 const Gf448Elem Z3 = F * G;
175
176 return Ed448Point(X3, Y3, Z3);
177}
178
180 // RFC 8032 5.2.4. - Point Addition (Double)
181 const Gf448Elem B = square(m_x + m_y);
182 const Gf448Elem C = square(m_x);
183 const Gf448Elem D = square(m_y);
184 const Gf448Elem E = C + D;
185 const Gf448Elem H = square(m_z);
186 const Gf448Elem J = E - (H + H);
187 const Gf448Elem X3 = (B - E) * J;
188 const Gf448Elem Y3 = E * (C - D);
189 const Gf448Elem Z3 = E * J;
190
191 return Ed448Point(X3, Y3, Z3);
192}
193
195 // 4-bit windowed scalar multiplication.
196 std::array<Ed448Point, 16> table = {Ed448Point::identity(),
197 *this,
212
213 for(size_t i = 2; i < 16; ++i) {
214 if(i % 2 == 0) {
215 table[i] = table[i / 2].double_point();
216 } else {
217 table[i] = table[i - 1] + *this;
218 }
219 }
220
221 // Process 448 bits (446-bit scalar + 2 leading zero bits) in 112 4-bit windows
222 auto res = Ed448Point::identity();
223
224 for(int window = 111; window >= 0; --window) {
225 // Double 4 times
226 res = res.double_point();
227 res = res.double_point();
228 res = res.double_point();
229 res = res.double_point();
230
231 // Extract 4-bit window value. Bits at position >= 446 are zero.
232 const uint64_t w = s.get_window(static_cast<size_t>(window) * 4, 4);
233
234 // Constant-time table lookup
235 auto selected = Ed448Point::identity();
236 for(size_t i = 0; i < 16; ++i) {
237 const auto correct_idx = CT::Mask<uint64_t>::is_equal(static_cast<uint64_t>(i), w);
238 selected.ct_conditional_assign(correct_idx, table[i]);
239 }
240
241 res = res + selected;
242 }
243
244 return res;
245}
246
248 /*
249 Fixed base point multiplication
250
251 Same idea as base point multiply used in pcurves
252 */
253 constexpr size_t W = 4;
254 constexpr size_t WindowElements = (1 << W) - 1; // 15
255 constexpr size_t Windows = (448 + W - 1) / W; // 112
256 constexpr size_t TableSize = Windows * WindowElements; // 1680
257
258 static const auto table = []() {
259 std::vector<Ed448Point> tbl(TableSize, Ed448Point::identity());
260
261 auto accum = Ed448Point::base_point();
262
263 for(size_t i = 0; i < TableSize; i += WindowElements) {
264 tbl[i] = accum;
265
266 for(size_t j = 1; j < WindowElements; ++j) {
267 if(j % 2 == 1) {
268 tbl[i + j] = tbl[i + j / 2].double_point();
269 } else {
270 tbl[i + j] = tbl[i + j - 1] + tbl[i];
271 }
272 }
273
274 accum = tbl[i + (WindowElements / 2)].double_point();
275 }
276
277 return tbl;
278 }();
279
280 auto res = Ed448Point::identity();
281
282 for(size_t i = 0; i != Windows; ++i) {
283 const uint8_t w = static_cast<uint8_t>(scalar.get_window(i * W, W));
284
285 // Constant-time table lookup from this window's 15-entry subtable
286 auto selected = Ed448Point::identity();
287 for(size_t j = 0; j != WindowElements; ++j) {
288 const auto assign = CT::Mask<uint64_t>::is_equal(j + 1, w);
289 selected.ct_conditional_assign(assign, table[i * WindowElements + j]);
290 }
291
292 res = res + selected;
293 }
294
295 return res;
296}
297
299 const Ed448Point& p1,
300 const Scalar448& s2,
301 const Ed448Point& p2) {
302 // 2-bit 2-ary Shamir's trick (variable time)
303 // Process 2 bits from each scalar per iteration, using a 16-entry table.
304 // table[w1 | (w2 << 2)] = w1*p1 + w2*p2, for w1,w2 in 0..3.
305
306 // Precompute small multiples of each point
307 const auto p1x2 = p1.double_point();
308 const auto p1x3 = p1x2 + p1;
309 const auto p2x2 = p2.double_point();
310 const auto p2x3 = p2x2 + p2;
311
312 // Build table indexed by (w2 << 2) | w1, excluding identity at index 0
313 const std::array<Ed448Point, 15> table = {
314 p1, // 1*p1 + 0*p2
315 p1x2, // 2*p1 + 0*p2
316 p1x3, // 3*p1 + 0*p2
317 p2, // 0*p1 + 1*p2
318 p1 + p2, // 1*p1 + 1*p2
319 p1x2 + p2, // 2*p1 + 1*p2
320 p1x3 + p2, // 3*p1 + 1*p2
321 p2x2, // 0*p1 + 2*p2
322 p1 + p2x2, // 1*p1 + 2*p2
323 p1x2 + p2x2, // 2*p1 + 2*p2
324 p1x3 + p2x2, // 3*p1 + 2*p2
325 p2x3, // 0*p1 + 3*p2
326 p1 + p2x3, // 1*p1 + 3*p2
327 p1x2 + p2x3, // 2*p1 + 3*p2
328 p1x3 + p2x3, // 3*p1 + 3*p2
329 };
330
331 auto res = Ed448Point::identity();
332
333 // 446 bits / 2 = 223 windows, covering bit positions 0..445
334 for(int window = 222; window >= 0; --window) {
335 res = res.double_point();
336 res = res.double_point();
337
338 const size_t bit_pos = static_cast<size_t>(window) * 2;
339 const size_t idx = s1.get_window(bit_pos, 2) | (s2.get_window(bit_pos, 2) << 2);
340
341 if(idx > 0) {
342 res = res + table[idx - 1];
343 }
344 }
345
346 return res;
347}
348
349bool Ed448Point::operator==(const Ed448Point& other) const {
350 // Compare in projective coordinates: (X1:Y1:Z1) == (X2:Y2:Z2)
351 // iff X1*Z2 == X2*Z1 && Y1*Z2 == Y2*Z1
352 // This avoids two field inversions that x() and y() would require.
353 const auto lhs_x = m_x * other.m_z;
354 const auto rhs_x = other.m_x * m_z;
355 const auto lhs_y = m_y * other.m_z;
356 const auto rhs_y = other.m_y * m_z;
357
358 const auto mask_x = CT::Mask<uint8_t>::expand_bool(lhs_x == rhs_x);
359 const auto mask_y = CT::Mask<uint8_t>::expand_bool(lhs_y == rhs_y);
360
361 return (mask_x & mask_y).as_bool();
362}
363
365 m_x.ct_cond_assign(mask, other.m_x);
366 m_y.ct_cond_assign(mask, other.m_y);
367 m_z.ct_cond_assign(mask, other.m_z);
368}
369
370Ed448Point operator*(const Scalar448& lhs, const Ed448Point& rhs) {
371 return rhs.scalar_mul(lhs);
372}
373
374std::array<uint8_t, ED448_LEN> create_pk_from_sk(std::span<const uint8_t, ED448_LEN> sk) {
375 // 5.2.5. Key Generation
376 // The 57-byte public key is generated by the following steps:
377 auto shake_xof = XOF::create_or_throw("SHAKE-256");
378 shake_xof->update(sk);
379
380 const Scalar448 s = scalar_from_xof(*shake_xof);
381 // 3. Interpret the buffer as the little-endian integer, forming a
382 // secret scalar s. Perform a known-base-point scalar
383 // multiplication [s]B.
385}
386
387std::array<uint8_t, 2 * ED448_LEN> sign_message(std::span<const uint8_t, ED448_LEN> sk,
388 std::span<const uint8_t, ED448_LEN> pk,
389 bool pgflag,
390 std::span<const uint8_t> context,
391 std::span<const uint8_t> msg) {
392 // 5.2.6. Signature Generation
393 // The inputs to the signing procedure is the private key, a 57-octet
394 // string, a flag F, which is 0 for Ed448, 1 for Ed448ph, context C of
395 // at most 255 octets, and a message M of arbitrary size.
396 // 1. Hash the private key, 57 octets, using SHAKE256(x, 114). Let h
397 // denote the resulting digest. Construct the secret scalar s from
398 // the first half of the digest, and the corresponding public key A,
399 // as described in the previous section. Let prefix denote the
400 // second half of the hash digest, h[57],...,h[113].
401 auto shake_xof = XOF::create_or_throw("SHAKE-256");
402 shake_xof->update(sk);
403 const Scalar448 s = scalar_from_xof(*shake_xof);
404 std::array<uint8_t, ED448_LEN> prefix{};
405 shake_xof->output(prefix);
406 // 2. Compute SHAKE256(dom4(F, C) || prefix || PH(M), 114), where M is
407 // the message to be signed, F is 1 for Ed448ph, 0 for Ed448, and C
408 // is the context to use. Interpret the 114-octet digest as a
409 // little-endian integer r.
410 const Scalar448 r(shake(pgflag, context, prefix, msg));
411 // 3. Compute the point [r]B. For efficiency, do this by first
412 // reducing r modulo L, the group order of B. Let the string R be
413 // the encoding of this point.
414 const auto big_r = Ed448Point::base_point_mul(r).encode();
415 // 4. Compute SHAKE256(dom4(F, C) || R || A || PH(M), 114), and
416 // interpret the 114-octet digest as a little-endian integer k.
417 const Scalar448 k(shake(pgflag, context, big_r, pk, msg));
418 // 5. Compute S = (r + k * s) mod L. For efficiency, again reduce k
419 // modulo L first.
420 const auto big_s = r + k * s; //r_plus_ks_mod_L(r, k, s);
421 // 6. Form the signature of the concatenation of R (57 octets) and the
422 // little-endian encoding of S (57 octets; the ten most significant
423 // bits of the final octets are always zero).
424 std::array<uint8_t, 2 * ED448_LEN> sig{};
425 BufferStuffer stuf(sig);
426 stuf.append(big_r);
427 stuf.append(big_s.to_bytes<ED448_LEN>());
428 BOTAN_ASSERT(stuf.full(), "Buffer is full");
429
430 return sig;
431}
432
433bool verify_signature(std::span<const uint8_t, ED448_LEN> pk,
434 bool phflag,
435 std::span<const uint8_t> context,
436 std::span<const uint8_t> sig,
437 std::span<const uint8_t> msg) {
438 // RFC 8032 5.2.7. Verify
439 // 1. To verify a signature on a message M using context C and public
440 // key A, with F being 0 for Ed448 and 1 for Ed448ph, first split
441 // the signature into two 57-octet halves. Decode the first half as
442 // a point R, and the second half as an integer S, in the range 0 <=
443 // s < L. Decode the public key A as point A’. If any of the
444 // decodings fail (including S being out of range), the signature is
445 // invalid.
446 if(sig.size() != 2 * ED448_LEN) {
447 // Wrong signature size
448 throw Decoding_Error("Ed448 signature has wrong size");
449 }
450 const auto [big_r_bytes, big_s_bytes] = split(sig.first<2 * ED448_LEN>());
451 const auto big_r = Ed448Point::decode(big_r_bytes);
452 if(!Scalar448::bytes_are_reduced(big_s_bytes)) {
453 // S not in range 0 <= s < L
454 throw Decoding_Error("Ed448 signature has invalid S");
455 }
456 const Scalar448 big_s(big_s_bytes);
457 // 2. Compute SHAKE256(dom4(F, C) || R || A || PH(M), 114), and
458 // interpret the 114-octet digest as a little-endian integer k.
459 const Scalar448 k(shake(phflag, context, big_r_bytes, pk, msg));
460 // 3. Check the group equation [4][S]B = [4]R + [4][k]A’. It’s
461 // sufficient, but not required, to instead check [S]B = R + [k]A’.
462 // Rearranged as [S]B + [k](-A’) = R, computed via Shamir’s trick.
463 const auto neg_A = Ed448Point::decode(pk).negate();
464 return Ed448Point::double_scalar_mul_vartime(big_s, Ed448Point::base_point(), k, neg_A) == big_r;
465}
466
467} // namespace Botan
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
#define BOTAN_ASSERT(expr, assertion_made)
Definition assert.h:62
Helper class to ease in-place marshalling of concatenated fixed-length values.
constexpr void append(std::span< const uint8_t > buffer)
constexpr bool full() const
static constexpr Mask< T > is_equal(T x, T y)
Definition ct_utils.h:442
static constexpr Mask< T > expand_bool(bool v)
Definition ct_utils.h:397
Representation of a point on the Ed448 curve.
Ed448Point negate() const
Negate the point.
Gf448Elem y() const
Getter for point coordinate y.
static Ed448Point identity()
Return the identity element.
static Ed448Point base_point_mul(const Scalar448 &scalar)
Fixed base point scalar multiplication (precomputed table, no doublings).
Ed448Point(const Gf448Elem &x, const Gf448Elem &y, const Gf448Elem &z)
Create a point from its projective coordinates X, Y, Z.
static Ed448Point double_scalar_mul_vartime(const Scalar448 &s1, const Ed448Point &p1, const Scalar448 &s2, const Ed448Point &p2)
Variable-time double scalar multiplication using Shamir's trick: [s1]P + [s2]Q.
static Ed448Point decode(std::span< const uint8_t, ED448_LEN > enc)
Decode a point from its 57-byte encoding (RFC 8032 5.2.3).
Ed448Point scalar_mul(const Scalar448 &scalar) const
Scalar multiplication.
Ed448Point double_point() const
Double a point (RFC 8032 5.2.4).
static Ed448Point base_point()
Create the curve's base point ('B' in RFC 8032 5.2).
void ct_conditional_assign(CT::Mask< uint64_t > mask, const Ed448Point &other)
Assign other to this if mask is set (constant time).
bool operator==(const Ed448Point &other) const
Check if two points are equal (constant time).
Ed448Point operator+(const Ed448Point &other) const
Add two points (RFC 8032 5.2.4).
Gf448Elem x() const
Getter for point coordinate x.
std::array< uint8_t, ED448_LEN > encode() const
Encode the point to its 57-byte representation (RFC 8032 5.2.2).
void to_bytes(std::span< uint8_t, BYTES_448 > out) const
Store the canonical representation of the GF element as 56 bytes in little-endian order.
static bool bytes_are_canonical_representation(std::span< const uint8_t, BYTES_448 > x)
Given 56 bytes, checks that the (little endian) number from this bytes is a valid GF element,...
bool is_odd() const
Return true iff this element is odd. Constant time.
static Gf448Elem one()
Definition curve448_gf.h:64
Representation of a scalar for X448.
uint32_t get_window(size_t starting_pos, size_t width) const
Extract a window of width bits starting at bit position starting_pos. Bits beyond position 445 are tr...
static bool bytes_are_reduced(std::span< const uint8_t > x)
static std::unique_ptr< XOF > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition xof.cpp:54
constexpr CT::Mask< T > is_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:798
Gf448Elem mul_a24(const Gf448Elem &a)
Multiply a field element by the Curve448 constant a24 = 39081.
Gf448Elem root(const Gf448Elem &elem)
Compute the root of elem in the field.
BigInt operator*(const BigInt &x, const BigInt &y)
Definition big_ops3.cpp:57
std::array< uint8_t, ED448_LEN > create_pk_from_sk(std::span< const uint8_t, ED448_LEN > sk)
Create a public key point from a secret key (RFC 8032 5.2.5).
BigInt square(const BigInt &x)
Definition numthry.cpp:184
constexpr size_t ED448_LEN
constexpr auto store_le(ParamTs &&... params)
Definition loadstor.h:736
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90
bool verify_signature(std::span< const uint8_t, ED448_LEN > pk, bool phflag, std::span< const uint8_t > context, std::span< const uint8_t > sig, std::span< const uint8_t > msg)
Verify a signature(RFC 8032 5.2.7).
constexpr size_t WORDS_448
Definition curve448_gf.h:23
std::array< uint8_t, 2 *ED448_LEN > sign_message(std::span< const uint8_t, ED448_LEN > sk, std::span< const uint8_t, ED448_LEN > pk, bool pgflag, std::span< const uint8_t > context, std::span< const uint8_t > msg)
Sign a message using a keypair (RFC 8032 5.2.6).