Skip to main content

ic_ec/
ed25519.rs

1//! RFC 8032 Ed25519 signatures.
2//!
3//! Points use extended twisted Edwards coordinates `(X : Y : Z : T)` with
4//! `a = -1`. Because `d` is a non-square in GF(2^255-19), the
5//! `add-2008-hwcd-3` formula is *complete*: it is correct for every input pair,
6//! including doubling and the identity. That is what lets scalar multiplication
7//! be a single branch-free loop with no exceptional cases to special-case, and
8//! no timing signal from the shape of the scalar.
9
10use crate::field::Fe;
11use crate::scalar;
12use ic_core::ct::Choice;
13
14// The precomputed basepoint table. See the module for why it is `std` only.
15#[cfg(feature = "std")]
16mod basepoint_table;
17use ic_core::traits::{Algorithm, Digest, SelfTest, SignatureScheme};
18use ic_core::{ensure, Result, Zeroize};
19use ic_hash::Sha512;
20
21/// The compressed encoding of the Ed25519 base point.
22#[cfg(test)]
23const BASEPOINT_COMPRESSED: [u8; 32] = [
24    0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
25    0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
26];
27
28/// The curve constant `d = -121665/121666`, as 51-bit limbs.
29const D: Fe = Fe::from_limbs51([
30    929_955_233_495_203,
31    466_365_720_129_213,
32    1_662_059_464_998_953,
33    2_033_849_074_728_123,
34    1_442_794_654_840_575,
35]);
36
37/// `2*d`, used directly by the addition formula.
38const D2: Fe = Fe::from_limbs51([
39    1_859_910_466_990_425,
40    932_731_440_258_426,
41    1_072_319_116_312_658,
42    1_815_898_335_770_999,
43    633_789_495_995_903,
44]);
45
46/// A square root of -1 in GF(2^255-19), needed for point decompression.
47const SQRT_M1: Fe = Fe::from_limbs51([
48    1_718_705_420_411_056,
49    234_908_883_556_509,
50    2_233_514_472_574_048,
51    2_117_202_627_021_982,
52    765_476_049_583_133,
53]);
54
55/// A point in extended twisted Edwards coordinates.
56#[derive(Clone, Copy, Debug)]
57pub struct Point {
58    x: Fe,
59    y: Fe,
60    z: Fe,
61    t: Fe,
62}
63
64/// A point part-way through a group operation: `(X : Y : Z : T)` standing for
65/// the affine point `(X/Z, Y/T)`.
66///
67/// Both the doubling and the addition formulas naturally produce this form --
68/// it is what they compute before the four multiplications that put the result
69/// back into extended coordinates. Keeping it is what makes a chain of
70/// doublings cheaper: a doubling reads only `X`, `Y` and `Z`, so on the way to
71/// another doubling the `T` those four multiplications would produce is never
72/// read, and three of them suffice instead of four.
73#[derive(Clone, Copy)]
74pub(crate) struct Completed {
75    x: Fe,
76    y: Fe,
77    z: Fe,
78    t: Fe,
79}
80
81/// `(X : Y : Z)`, standing for `(X/Z, Y/Z)`. No `T`.
82///
83/// What a doubling needs and all it needs.
84#[derive(Clone, Copy)]
85pub(crate) struct Projective {
86    x: Fe,
87    y: Fe,
88    z: Fe,
89}
90
91/// A point rearranged for addition: `(Y+X, Y-X, Z, 2d·T)`.
92///
93/// The addition formula wants those four quantities and nothing else, so a
94/// point that will be added many times -- every entry of every table here --
95/// stores them instead of `(X, Y, Z, T)`. That turns an addition from nine
96/// multiplications into four: the two sums and differences are already formed,
97/// and `2d·T` has already been scaled.
98#[derive(Clone, Copy)]
99pub(crate) struct Niels {
100    ypx: Fe,
101    ymx: Fe,
102    z: Fe,
103    t2d: Fe,
104}
105
106/// A point with `Z = 1`, rearranged for addition: `(y+x, y-x, 2d·x·y)`.
107///
108/// [`Niels`] without the `Z`, which removes the one multiplication that used
109/// it. Worth the field inversion it costs to build, for a table entry that
110/// will be added sixty-four times per signature and never changes.
111#[cfg(feature = "std")]
112#[derive(Clone, Copy)]
113pub(crate) struct AffineNiels {
114    ypx: Fe,
115    ymx: Fe,
116    t2d: Fe,
117}
118
119/// The constant-time table operations the windowed multiplication needs. They
120/// mirror [`AffineNiels`]'s, for a table built per call and so never worth the
121/// inversions that would make it affine.
122#[cfg(any(not(feature = "std"), test))]
123impl Niels {
124    /// The neutral element: `Y + X = Y - X = Z = 1`, `2d·T = 0`.
125    const IDENTITY: Niels = Niels {
126        ypx: Fe::ONE,
127        ymx: Fe::ONE,
128        z: Fe::ONE,
129        t2d: Fe::ZERO,
130    };
131
132    /// Negation swaps the sums and differences and negates `2d·T`; `Z` is
133    /// unchanged.
134    fn conditional_negate(&mut self, choice: Choice) {
135        let swapped_p = self.ymx;
136        let swapped_m = self.ypx;
137        let nt = self.t2d.neg();
138        Fe::cmov(&mut self.ypx, &swapped_p, choice);
139        Fe::cmov(&mut self.ymx, &swapped_m, choice);
140        Fe::cmov(&mut self.t2d, &nt, choice);
141    }
142
143    fn cmov(&mut self, other: &Niels, choice: Choice) {
144        Fe::cmov(&mut self.ypx, &other.ypx, choice);
145        Fe::cmov(&mut self.ymx, &other.ymx, choice);
146        Fe::cmov(&mut self.z, &other.z, choice);
147        Fe::cmov(&mut self.t2d, &other.t2d, choice);
148    }
149}
150
151#[cfg(feature = "std")]
152impl AffineNiels {
153    /// The neutral element: `x = 0`, `y = 1`.
154    pub(crate) const IDENTITY: AffineNiels = AffineNiels {
155        ypx: Fe::ONE,
156        ymx: Fe::ONE,
157        t2d: Fe::ZERO,
158    };
159
160    /// Negation swaps the sums and differences and negates `2d·x·y`, which is
161    /// what `-(x, y) = (-x, y)` comes to in this form.
162    pub(crate) fn conditional_negate(&mut self, choice: Choice) {
163        let swapped_p = self.ymx;
164        let swapped_m = self.ypx;
165        let nt = self.t2d.neg();
166        Fe::cmov(&mut self.ypx, &swapped_p, choice);
167        Fe::cmov(&mut self.ymx, &swapped_m, choice);
168        Fe::cmov(&mut self.t2d, &nt, choice);
169    }
170
171    pub(crate) fn cmov(&mut self, other: &AffineNiels, choice: Choice) {
172        Fe::cmov(&mut self.ypx, &other.ypx, choice);
173        Fe::cmov(&mut self.ymx, &other.ymx, choice);
174        Fe::cmov(&mut self.t2d, &other.t2d, choice);
175    }
176}
177
178impl Completed {
179    /// Drop to `(X : Y : Z)`, which is three multiplications.
180    fn to_projective(self) -> Projective {
181        Projective {
182            x: self.x.mul(&self.t),
183            y: self.y.mul(&self.z),
184            z: self.z.mul(&self.t),
185        }
186    }
187
188    /// Back to extended coordinates, which is four.
189    ///
190    /// Only needed before an addition, since that is the only operation that
191    /// reads `T`.
192    fn to_extended(self) -> Point {
193        Point {
194            x: self.x.mul(&self.t),
195            y: self.y.mul(&self.z),
196            z: self.z.mul(&self.t),
197            t: self.x.mul(&self.y),
198        }
199    }
200}
201
202impl Projective {
203    /// Recover extended coordinates from projective ones.
204    ///
205    /// `(X : Y : Z)` stands for `(X/Z, Y/Z)`, and extended coordinates want
206    /// `T` with `X*Y = T*Z`, so `T = X*Y/Z`. Only needed once, at the end.
207    fn to_extended_from_projective(self) -> Point {
208        // Four multiplications, not an inversion. `(X : Y : Z)` stands for
209        // `(X/Z, Y/Z)`, and extended coordinates want `T` with `X*Y = T*Z`;
210        // scaling every coordinate by `Z` gives `(XZ : YZ : Z^2 : XY)`, which
211        // satisfies that directly. Dividing through by `Z` instead would be an
212        // exponentiation -- about two hundred and fifty squarings -- to reach
213        // the same point in a representation nothing here needs.
214        Point {
215            x: self.x.mul(&self.z),
216            y: self.y.mul(&self.z),
217            z: self.z.square(),
218            t: self.x.mul(&self.y),
219        }
220    }
221
222    /// Double and stay projective, without writing the completed form out.
223    ///
224    /// The same arithmetic as `double()` followed by
225    /// [`Completed::to_projective`], with the intermediate kept in locals. A
226    /// `Completed` is four field elements, 160 bytes, and in a chain of
227    /// doublings it exists only to be consumed by the very next statement;
228    /// spilling and reloading it is pure traffic. This is the path taken at
229    /// every position where the recoding has nothing to add, which is most of
230    /// them.
231    fn double_projective(self) -> Projective {
232        let xx = self.x.square();
233        let yy = self.y.square();
234        let zz2 = {
235            let t = self.z.square();
236            t.add(&t)
237        };
238        let xy_sq = self.x.add(&self.y).square();
239        let yy_plus_xx = yy.add(&xx);
240        let yy_minus_xx = yy.sub(&xx);
241
242        let cx = xy_sq.sub(&yy_plus_xx);
243        let cy = yy_plus_xx;
244        let cz = yy_minus_xx;
245        let ct = zz2.sub(&yy_minus_xx);
246
247        Projective {
248            x: cx.mul(&ct),
249            y: cy.mul(&cz),
250            z: cz.mul(&ct),
251        }
252    }
253
254    /// `dbl-2008-hwcd` for `a = -1`, stopping at the completed form.
255    ///
256    /// Four squarings and no multiplications at all: every multiplication in a
257    /// doubling belongs to the conversion out of the completed form, which is
258    /// why it is worth not doing that conversion in full.
259    fn double(&self) -> Completed {
260        let xx = self.x.square();
261        let yy = self.y.square();
262        let zz2 = {
263            let t = self.z.square();
264            t.add(&t)
265        };
266        let xy_sq = self.x.add(&self.y).square();
267        let yy_plus_xx = yy.add(&xx);
268        let yy_minus_xx = yy.sub(&xx);
269        Completed {
270            x: xy_sq.sub(&yy_plus_xx),
271            y: yy_plus_xx,
272            z: yy_minus_xx,
273            t: zz2.sub(&yy_minus_xx),
274        }
275    }
276}
277
278impl Point {
279    /// The neutral element `(0, 1)`.
280    pub const IDENTITY: Point = Point {
281        x: Fe::ZERO,
282        y: Fe::ONE,
283        z: Fe::ONE,
284        t: Fe::ZERO,
285    };
286
287    /// The complete `add-2008-hwcd-3` group law for `a = -1`.
288    pub fn add(&self, other: &Point) -> Point {
289        let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
290        let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
291        let c = self.t.mul(&D2).mul(&other.t);
292        let d = self.z.mul(&other.z);
293        let d = d.add(&d);
294
295        let e = b.sub(&a);
296        let f = d.sub(&c);
297        let g = d.add(&c);
298        let h = b.add(&a);
299
300        Point {
301            x: e.mul(&f),
302            y: g.mul(&h),
303            t: e.mul(&h),
304            z: f.mul(&g),
305        }
306    }
307
308    /// Point doubling, `dbl-2008-hwcd` for `a = -1`.
309    ///
310    /// Adding a point to itself works and was what this did, but the general
311    /// addition costs nine multiplications and needs both operands' `T`. The
312    /// dedicated formula is four multiplications and four squarings, and does
313    /// not read `T` at all -- doubling is a function of `X`, `Y` and `Z` alone.
314    ///
315    /// Worth the separate formula because scalar multiplication is doublings
316    /// almost entirely: the non-adjacent form leaves about forty additions
317    /// against two hundred and fifty-six doublings.
318    pub fn double(&self) -> Point {
319        let aa = self.x.square();
320        let bb = self.y.square();
321        let c = self.z.square();
322        let c = c.add(&c);
323        // a = -1, so D = a*A = -A.
324        let d = aa.neg();
325        // E = (X+Y)^2 - A - B, which is 2*X*Y without a multiplication.
326        let xy = self.x.add(&self.y);
327        let e = xy.square().sub(&aa).sub(&bb);
328        let g = d.add(&bb);
329        let f = g.sub(&c);
330        let h = d.sub(&bb);
331
332        Point {
333            x: e.mul(&f),
334            y: g.mul(&h),
335            t: e.mul(&h),
336            z: f.mul(&g),
337        }
338    }
339
340    /// Drop `T`, which a doubling does not read.
341    fn to_projective(self) -> Projective {
342        Projective {
343            x: self.x,
344            y: self.y,
345            z: self.z,
346        }
347    }
348
349    /// Rearrange for repeated addition. See [`Niels`].
350    fn to_niels(self) -> Niels {
351        Niels {
352            ypx: self.y.add(&self.x),
353            ymx: self.y.sub(&self.x),
354            z: self.z,
355            t2d: self.t.mul(&D2),
356        }
357    }
358
359    /// `self + other`, in four multiplications, stopping at the completed form.
360    ///
361    /// The same `add-2008-hwcd-3` group law [`Point::add`] uses. It costs four
362    /// rather than nine because `other` arrives with its sums, differences and
363    /// `2d·T` already formed, and because the result is left completed rather
364    /// than converted back.
365    fn add_niels(&self, other: &Niels) -> Completed {
366        let pp = self.y.add(&self.x).mul(&other.ypx);
367        let mm = self.y.sub(&self.x).mul(&other.ymx);
368        let tt2d = self.t.mul(&other.t2d);
369        let zz = self.z.mul(&other.z);
370        let zz2 = zz.add(&zz);
371        Completed {
372            x: pp.sub(&mm),
373            y: pp.add(&mm),
374            z: zz2.add(&tt2d),
375            t: zz2.sub(&tt2d),
376        }
377    }
378
379    /// `self - other`.
380    ///
381    /// Negating a Niels point swaps its sums and differences and negates
382    /// `2d·T`, which is cheaper than negating the point it came from and
383    /// rebuilding it.
384    fn sub_niels(&self, other: &Niels) -> Completed {
385        let pp = self.y.add(&self.x).mul(&other.ymx);
386        let mm = self.y.sub(&self.x).mul(&other.ypx);
387        let tt2d = self.t.mul(&other.t2d);
388        let zz = self.z.mul(&other.z);
389        let zz2 = zz.add(&zz);
390        Completed {
391            x: pp.sub(&mm),
392            y: pp.add(&mm),
393            z: zz2.sub(&tt2d),
394            t: zz2.add(&tt2d),
395        }
396    }
397
398    /// Rearrange for repeated addition, with `Z` divided out. See
399    /// [`AffineNiels`].
400    ///
401    /// Costs a field inversion, which is why it is done when a table is built
402    /// and never on a hot path.
403    #[cfg(feature = "std")]
404    pub(crate) fn to_affine_niels(self) -> AffineNiels {
405        let z_inv = self.z.invert();
406        let x = self.x.mul(&z_inv);
407        let y = self.y.mul(&z_inv);
408        AffineNiels {
409            ypx: y.add(&x),
410            ymx: y.sub(&x),
411            t2d: x.mul(&y).mul(&D2),
412        }
413    }
414
415    /// `self + other`, in three multiplications.
416    ///
417    /// One fewer than [`Point::add_niels`]: `other` has `Z = 1`, so the
418    /// product of the two `Z`s is just this one's, doubled.
419    #[cfg(feature = "std")]
420    pub(crate) fn add_affine_niels(&self, other: &AffineNiels) -> Completed {
421        let pp = self.y.add(&self.x).mul(&other.ypx);
422        let mm = self.y.sub(&self.x).mul(&other.ymx);
423        let tt2d = self.t.mul(&other.t2d);
424        let zz2 = self.z.add(&self.z);
425        Completed {
426            x: pp.sub(&mm),
427            y: pp.add(&mm),
428            z: zz2.add(&tt2d),
429            t: zz2.sub(&tt2d),
430        }
431    }
432
433    /// `self - other`, for an affine Niels point.
434    ///
435    /// Negating one of these swaps its sums and differences and negates
436    /// `2d·x·y`, so the subtraction is the addition with two operands
437    /// exchanged and one sign flipped. Doing it here rather than by negating a
438    /// copy of the table entry avoids copying it at all -- an entry is three
439    /// field elements, and the variable-time path has no reason to touch it
440    /// with conditional moves.
441    #[cfg(feature = "std")]
442    pub(crate) fn sub_affine_niels(&self, other: &AffineNiels) -> Completed {
443        let pp = self.y.add(&self.x).mul(&other.ymx);
444        let mm = self.y.sub(&self.x).mul(&other.ypx);
445        let tt2d = self.t.mul(&other.t2d);
446        let zz2 = self.z.add(&self.z);
447        Completed {
448            x: pp.sub(&mm),
449            y: pp.add(&mm),
450            z: zz2.sub(&tt2d),
451            t: zz2.add(&tt2d),
452        }
453    }
454
455    /// Constant-time conditional move.
456    fn cmov(&mut self, other: &Point, choice: Choice) {
457        Fe::cmov(&mut self.x, &other.x, choice);
458        Fe::cmov(&mut self.y, &other.y, choice);
459        Fe::cmov(&mut self.z, &other.z, choice);
460        Fe::cmov(&mut self.t, &other.t, choice);
461    }
462
463    /// Scalar multiplication, constant-time in the scalar.
464    ///
465    /// Every iteration performs a doubling *and* an addition, selecting between
466    /// the two results with a conditional move, so the instruction trace is
467    /// identical for every scalar.
468    pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
469        let mut acc = Point::IDENTITY;
470        for i in (0..256).rev() {
471            acc = acc.double();
472            let sum = acc.add(self);
473            let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
474            acc.cmov(&sum, bit);
475        }
476        acc
477    }
478
479    /// Negate: `-(x, y, z, t)` is `(-x, y, z, -t)`.
480    fn negate(&self) -> Point {
481        Point {
482            x: self.x.neg(),
483            y: self.y,
484            z: self.z,
485            t: self.t.neg(),
486        }
487    }
488
489    /// Whether two points are the same, without leaving projective space.
490    ///
491    /// `(X : Y : Z)` stands for the affine point `(X/Z, Y/Z)`, so two are equal
492    /// exactly when `X1*Z2 == X2*Z1` and `Y1*Z2 == Y2*Z1`. That is four
493    /// multiplications.
494    ///
495    /// The obvious alternative is to compress both and compare the bytes, and
496    /// that is what verification used to do -- but compression divides by `Z`,
497    /// and a division here is an exponentiation: roughly two hundred and fifty
498    /// squarings each, five hundred to answer a question four multiplications
499    /// settle.
500    ///
501    /// `to_bytes` is used only to canonicalise the two sides before comparing,
502    /// which costs a carry chain and no inversion.
503    fn eq_projective(&self, other: &Point) -> bool {
504        self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
505            && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
506    }
507
508    /// Compress to the 32-byte RFC 8032 encoding.
509    pub fn compress(&self) -> [u8; 32] {
510        let z_inv = self.z.invert();
511        let x = self.x.mul(&z_inv);
512        let y = self.y.mul(&z_inv);
513        let mut out = y.to_bytes();
514        // The sign of x rides in the top bit.
515        out[31] |= x.is_negative().unwrap_u8() << 7;
516        out
517    }
518
519    /// Decompress a 32-byte encoding, rejecting non-curve points.
520    pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
521        let sign = Choice::from_u8(bytes[31] >> 7);
522        let mut y_bytes = *bytes;
523        y_bytes[31] &= 0x7f;
524        let y = Fe::from_bytes(&y_bytes);
525
526        // Solve x^2 = (y^2 - 1) / (d*y^2 + 1).
527        let y2 = y.square();
528        let u = y2.sub(&Fe::ONE);
529        let v = y2.mul(&D).add(&Fe::ONE);
530
531        // x = u*v^3 * (u*v^7)^((p-5)/8)
532        let v3 = v.square().mul(&v);
533        let v7 = v3.square().mul(&v);
534        let mut x = u.mul(&v3).mul(&u.mul(&v7).pow22523());
535
536        let check = v.mul(&x.square());
537        let correct = check.ct_eq(&u);
538        let flipped = check.ct_eq(&u.neg());
539        if !bool::from(correct.or(flipped)) {
540            // No square root exists: the encoding is not a curve point.
541            return None;
542        }
543        // When only the flipped case matched, multiply by sqrt(-1).
544        let alt = x.mul(&SQRT_M1);
545        Fe::cmov(&mut x, &alt, flipped.and(correct.not()));
546
547        // x = 0 with a set sign bit is the one non-canonical encoding.
548        if bool::from(x.is_zero()) && bool::from(sign) {
549            return None;
550        }
551        // Match the requested sign.
552        let neg = x.neg();
553        let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
554        Fe::cmov(&mut x, &neg, wrong_sign);
555
556        Some(Point {
557            x,
558            y,
559            z: Fe::ONE,
560            t: x.mul(&y),
561        })
562    }
563}
564
565/// Fill the basepoint tables now; see [`crate::prepare`].
566#[cfg(feature = "std")]
567pub(crate) fn prepare_tables() {
568    basepoint_table::prepare();
569}
570
571/// `scalar * B`, through the precomputed table where there is one.
572///
573/// Every basepoint multiplication in this module goes through here rather than
574/// calling `mul_scalar` on the basepoint directly, so the two paths cannot
575/// drift apart and a caller cannot accidentally take the slow one.
576fn mul_basepoint(scalar: &[u8; 32]) -> Point {
577    #[cfg(feature = "std")]
578    {
579        basepoint_table::mul(scalar)
580    }
581    #[cfg(not(feature = "std"))]
582    {
583        mul_scalar_windowed(&basepoint(), scalar)
584    }
585}
586
587/// `[k]A + [s]B`, in one pass, variable time in both scalars.
588///
589/// Verification needs two scalar multiplications and then compares the
590/// results. Done separately that is two independent runs of doublings -- and
591/// the doublings are the whole cost, some two hundred and fifty-five of them
592/// against forty-odd additions. Run together they are shared: one chain of
593/// doublings, with each scalar contributing an addition at the positions where
594/// its own recoding is non-zero.
595///
596/// The basepoint half also stops paying for constant time here. `mul_basepoint`
597/// selects a table entry by reading all eight and moving conditionally, because
598/// a signing scalar is secret. Nothing in a verification is: the signature, the
599/// public key and the message are all in the clear, so the table is indexed
600/// directly and the window widened to eight, which is a table built once and
601/// about a third as many additions.
602///
603/// Both properties are why this is not the function signing calls.
604/// [`double_scalar_mul_vartime`], reachable from the benchmark.
605///
606/// The benchmark lives outside this workspace and cannot see private items,
607/// and comparing whole signatures cannot separate "our field arithmetic is
608/// slower" from "our scalar multiplication does more work". This is how that
609/// question gets answered rather than guessed at.
610#[cfg(feature = "bench-internals")]
611#[doc(hidden)]
612pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
613    double_scalar_mul_vartime(a, k, s)
614}
615
616/// `scalar * p` in constant time, four bits at a time, for scalars below
617/// `2^255`.
618///
619/// What `no_std` signing uses in place of the precomputed table, and the same
620/// algorithm with the table built per call: the scalar becomes 64 signed
621/// radix-16 digits, `1..=8` times `p` are computed on the stack, and each digit
622/// selects one of them by reading all eight with conditional moves. That is
623/// 252 doublings and 64 additions, against 256 of each for
624/// [`Point::mul_scalar`]'s bit-at-a-time ladder, and eight entries of 160 bytes
625/// that are gone when it returns -- which is the constraint `no_std` exists
626/// for.
627///
628/// Every scalar reaching it is a clamped secret or a value reduced modulo the
629/// group order, which is what [`signed_digits`] needs. [`Point::mul_scalar`]
630/// remains the public operation and takes any 32 bytes.
631#[cfg(any(not(feature = "std"), test))]
632fn mul_scalar_windowed(p: &Point, scalar: &[u8; 32]) -> Point {
633    let mut multiples = [*p; 8];
634    for i in 1..8 {
635        multiples[i] = multiples[i - 1].add(p);
636    }
637    let table: [Niels; 8] = core::array::from_fn(|i| multiples[i].to_niels());
638
639    // `digit * p` for a digit in `[-8, 8]`, without indexing by the digit.
640    let select = |digit: i8| -> Niels {
641        let negative = Choice::from_u8((digit as u8) >> 7);
642        let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u8;
643        let mut out = Niels::IDENTITY;
644        for (i, entry) in table.iter().enumerate() {
645            out.cmov(entry, Choice::from_u8(u8::from(magnitude == (i as u8 + 1))));
646        }
647        out.conditional_negate(negative);
648        out
649    };
650
651    let digits = signed_digits(scalar);
652    let mut acc = Point::IDENTITY.add_niels(&select(digits[63])).to_extended();
653    for i in (0..63).rev() {
654        // Times sixteen: three doublings that never need `T`, then one that
655        // does, since the addition after it reads `T`.
656        let mut q = acc.to_projective();
657        for _ in 0..3 {
658            q = q.double_projective();
659        }
660        acc = q.double().to_extended();
661        acc = acc.add_niels(&select(digits[i])).to_extended();
662    }
663    acc
664}
665
666/// `1, 3, 5 .. 15` times `p`, in Niels form.
667fn odd_multiples(p: &Point) -> [Niels; 8] {
668    let twice = p.double();
669    let mut odd = [*p; 8];
670    for i in 1..8 {
671        odd[i] = odd[i - 1].add(&twice);
672    }
673    core::array::from_fn(|i| odd[i].to_niels())
674}
675
676/// The scalar as 64 signed radix-16 digits, each in `[-8, 8]`.
677///
678/// A nibble above 8 becomes `nibble - 16` with a carry into the next digit,
679/// which is what keeps a table to the positive multiples.
680///
681/// Only the first 63 digits are recoded. The last one is left to absorb the
682/// final carry, because a carry *out* of the top would be a factor of `16^64`
683/// with nowhere to go -- silently dropping it would give the wrong point. That
684/// works because every scalar reaching here has its top byte at most 127: the
685/// clamped secret has bit 255 cleared by construction, and `r` and `s` are
686/// reduced modulo the group order and so are far smaller. The top nibble is
687/// then at most 7, one carry takes it to 8, and 8 is in range.
688///
689/// The first version of this recoded all 64 and dropped that carry. The
690/// agreement test below caught it.
691fn signed_digits(scalar: &[u8; 32]) -> [i8; 64] {
692    debug_assert!(
693        scalar[31] <= 127,
694        "the top digit can only absorb the final carry for scalars below 2^255"
695    );
696
697    let mut nibbles = [0i8; 64];
698    for (i, byte) in scalar.iter().enumerate() {
699        nibbles[i * 2] = (byte & 0x0f) as i8;
700        nibbles[i * 2 + 1] = (byte >> 4) as i8;
701    }
702
703    for i in 0..63 {
704        let carry = (nibbles[i] + 8) >> 4;
705        nibbles[i] -= carry << 4;
706        nibbles[i + 1] += carry;
707    }
708    nibbles
709}
710
711#[cfg(feature = "std")]
712fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
713    basepoint_table::prepare();
714    shared_doublings(a, k, &wnaf(s, 8), |e, digit| {
715        let n = basepoint_table::odd_multiple((digit.unsigned_abs() as usize) / 2);
716        if digit > 0 {
717            e.add_affine_niels(n)
718        } else {
719            e.sub_affine_niels(n)
720        }
721    })
722}
723
724/// [`double_scalar_mul_vartime`] with no stored table: the basepoint's odd
725/// multiples are built per call, the same way `A`'s are, and read at width 5.
726///
727/// What `no_std` verification uses. It keeps the part that matters -- one chain
728/// of doublings shared by both scalars, where computing `[S]B` and `[k]A`
729/// separately would run two -- and gives up only the wider window, which is
730/// the part that needs storage.
731#[cfg(any(not(feature = "std"), test))]
732fn double_scalar_mul_vartime_no_table(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
733    let odd_b = odd_multiples(&basepoint());
734    shared_doublings(a, k, &wnaf(s, 5), |e, digit| {
735        let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
736        if digit > 0 {
737            e.add_niels(n)
738        } else {
739            e.sub_niels(n)
740        }
741    })
742}
743
744/// The loop both of those share: `[k]A` at width 5, plus whatever `add_b` adds
745/// at each non-zero digit of `naf_b`, over a single chain of doublings.
746#[inline(always)]
747fn shared_doublings(
748    a: &Point,
749    k: &[u8; 32],
750    naf_b: &[i8; 258],
751    add_b: impl Fn(&Point, i8) -> Completed,
752) -> Point {
753    // 1A, 3A, 5A .. 15A, in Niels form, built for this call.
754    let odd_a = odd_multiples(a);
755    let naf_a = wnaf(k, 5);
756
757    // Start at the highest position either recoding reaches, so the leading
758    // doublings of the identity are skipped.
759    let mut i = 257;
760    while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
761        i -= 1;
762    }
763
764    // The accumulator is carried in whichever form the next step wants: a
765    // doubling reads only X, Y and Z, and an addition is the only thing that
766    // reads T. So a position with no addition pays three multiplications to
767    // come out of the completed form instead of four.
768    let mut acc = Point::IDENTITY.to_projective();
769    loop {
770        // Nothing to add here, which is the common case: double straight back
771        // to projective without materialising the completed form.
772        if naf_a[i] == 0 && naf_b[i] == 0 {
773            acc = acc.double_projective();
774            if i == 0 {
775                return acc.to_extended_from_projective();
776            }
777            i -= 1;
778            continue;
779        }
780        let mut t = acc.double();
781        if naf_a[i] != 0 {
782            let e = t.to_extended();
783            let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
784            t = if naf_a[i] > 0 {
785                e.add_niels(n)
786            } else {
787                e.sub_niels(n)
788            };
789        }
790        if naf_b[i] != 0 {
791            t = add_b(&t.to_extended(), naf_b[i]);
792        }
793        if i == 0 {
794            return t.to_extended();
795        }
796        acc = t.to_projective();
797        i -= 1;
798    }
799}
800
801/// [`mul_basepoint`], reachable from the benchmark. See
802/// [`double_scalar_mul_vartime_for_bench`].
803#[cfg(feature = "bench-internals")]
804#[doc(hidden)]
805pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
806    mul_basepoint(scalar)
807}
808
809fn basepoint() -> Point {
810    BASEPOINT
811}
812
813/// The basepoint in extended coordinates, `Z = 1` and `T = XY`.
814///
815/// It used to be decompressed from [`BASEPOINT_COMPRESSED`] on every call,
816/// which is a field square root -- about a tenth of a signature on the `no_std`
817/// path, where nothing caches it. The limbs were computed outside this crate
818/// from `y = 4/5` and the curve equation, and
819/// `the_basepoint_constant_is_the_decompressed_encoding` holds them to what
820/// decompressing the RFC 8032 encoding gives.
821const BASEPOINT: Point = Point {
822    x: Fe::from_limbs51([
823        1_738_742_601_995_546,
824        1_146_398_526_822_698,
825        2_070_867_633_025_821,
826        562_264_141_797_630,
827        587_772_402_128_613,
828    ]),
829    y: Fe::from_limbs51([
830        1_801_439_850_948_184,
831        1_351_079_888_211_148,
832        450_359_962_737_049,
833        900_719_925_474_099,
834        1_801_439_850_948_198,
835    ]),
836    z: Fe::ONE,
837    t: Fe::from_limbs51([
838        1_841_354_044_333_475,
839        16_398_895_984_059,
840        755_974_180_946_558,
841        900_171_276_175_154,
842        1_821_297_809_914_039,
843    ]),
844};
845
846/// RFC 8032 Ed25519 (PureEdDSA over Curve25519 with SHA-512).
847pub struct Ed25519;
848
849impl Algorithm for Ed25519 {
850    const ID: &'static str = "ed25519";
851    const NAME: &'static str = "Ed25519";
852}
853
854/// Expand a 32-byte seed into the clamped scalar and the nonce prefix.
855fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
856    let h = Sha512::digest(seed);
857    let mut a = [0u8; 32];
858    let mut prefix = [0u8; 32];
859    a.copy_from_slice(&h.as_ref()[..32]);
860    prefix.copy_from_slice(&h.as_ref()[32..]);
861    a[0] &= 248;
862    a[31] &= 127;
863    a[31] |= 64;
864    (a, prefix)
865}
866
867/// Width-`w` non-adjacent form of a 256-bit scalar.
868///
869/// Each non-zero digit is odd and lies in `[-(2^(w-1) - 1), 2^(w-1) - 1]`, and
870/// no two non-zero digits are within `w` places of each other, which puts the
871/// density near `1/(w+1)`. A wider window means fewer additions and a bigger
872/// table: width 5 for an arbitrary point, whose table has to be built on the
873/// spot, and width 8 for the basepoint, whose table is built once.
874///
875/// `w` must be at most 8, so that every digit fits an `i8`.
876fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
877    debug_assert!((2..=8).contains(&w), "window width out of range");
878    let half = 1i64 << (w - 1);
879    let full = 1i64 << w;
880    let mask = (full - 1) as u64;
881
882    let mut naf = [0i8; 258];
883    // Five limbs for a four-limb scalar. A negative digit adds to `k`, and for
884    // a scalar near 2^256 that carries out of the top: on four limbs it wraps
885    // to zero, the loop stops early, and the representation is silently short.
886    // The scalars that reach this are reduced modulo the group order and could
887    // not trigger it -- which is exactly the assumption that was wrong for the
888    // NIST recoding, so the room is given rather than argued for.
889    let mut k = [0u64; 5];
890    for (i, limb) in k.iter_mut().take(4).enumerate() {
891        let mut b = [0u8; 8];
892        b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
893        *limb = u64::from_le_bytes(b);
894    }
895
896    let mut i = 0;
897    while k.iter().any(|&x| x != 0) {
898        if k[0] & 1 == 1 {
899            let mut d = (k[0] & mask) as i64;
900            if d >= half {
901                d -= full;
902            }
903            naf[i] = d as i8;
904            if d > 0 {
905                sub_u64(&mut k, d as u64);
906            } else {
907                add_u64(&mut k, d.unsigned_abs());
908            }
909        }
910        shr1(&mut k);
911        i += 1;
912    }
913    naf
914}
915
916/// `k -= v`, for `v` small enough not to borrow past the top.
917fn sub_u64(k: &mut [u64; 5], v: u64) {
918    let (d, mut borrow) = k[0].overflowing_sub(v);
919    k[0] = d;
920    for limb in k.iter_mut().skip(1) {
921        if !borrow {
922            break;
923        }
924        let (d, b) = limb.overflowing_sub(1);
925        *limb = d;
926        borrow = b;
927    }
928}
929
930/// `k += v`, for `v` small enough not to carry past the top.
931fn add_u64(k: &mut [u64; 5], v: u64) {
932    let (d, mut carry) = k[0].overflowing_add(v);
933    k[0] = d;
934    for limb in k.iter_mut().skip(1) {
935        if !carry {
936            break;
937        }
938        let (d, c) = limb.overflowing_add(1);
939        *limb = d;
940        carry = c;
941    }
942}
943
944/// `k >>= 1`.
945fn shr1(k: &mut [u64; 5]) {
946    for i in 0..4 {
947        k[i] = (k[i] >> 1) | (k[i + 1] << 63);
948    }
949    k[4] >>= 1;
950}
951
952/// `SHA-512(parts...)` reduced modulo the group order.
953fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
954    let mut h = Sha512::new();
955    for p in parts {
956        h.update(p);
957    }
958    let digest = h.finalize();
959    let mut wide = [0u8; 64];
960    wide.copy_from_slice(digest.as_ref());
961    scalar::reduce_wide(&wide)
962}
963
964/// A signing key with its public key already derived.
965///
966/// # Why this exists
967///
968/// RFC 8032 signing needs the public key: it goes into the hash that produces
969/// `k`. [`Ed25519::sign`] takes only the 32-byte seed, so it has to derive the
970/// public key on every call -- a second basepoint multiplication, and with the
971/// table in place that is most of what a signature now costs.
972///
973/// A key that is used more than once should derive it once. That is what a TLS
974/// server does with a certificate key, and what dalek's `SigningKey` does,
975/// which is why comparing `Ed25519::sign` against it was comparing two
976/// different amounts of work.
977///
978/// The trait method still exists and still takes a seed. This changes nothing
979/// for a caller signing once; it halves the cost for a caller signing twice.
980pub struct Ed25519Key {
981    /// The clamped scalar from the seed's hash.
982    scalar: [u8; 32],
983    /// The second half of that hash, which seeds the deterministic nonce.
984    prefix: [u8; 32],
985    /// `scalar * B`, compressed. Derived once, here.
986    public: [u8; 32],
987}
988
989impl Drop for Ed25519Key {
990    fn drop(&mut self) {
991        self.scalar.zeroize();
992        self.prefix.zeroize();
993        // `public` is public, and is left alone.
994    }
995}
996
997impl Ed25519Key {
998    /// Expand a 32-byte seed and derive its public key.
999    pub fn from_seed(seed: &[u8]) -> Result<Self> {
1000        ic_core::module::operational()?;
1001        ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
1002        let (scalar, prefix) = expand_seed(seed);
1003        let public = mul_basepoint(&scalar).compress();
1004        Ok(Self {
1005            scalar,
1006            prefix,
1007            public,
1008        })
1009    }
1010
1011    /// The public key, already derived.
1012    pub fn public_key(&self) -> &[u8; 32] {
1013        &self.public
1014    }
1015
1016    /// Sign `message`, performing one basepoint multiplication rather than two.
1017    pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1018        ic_core::module::operational()?;
1019        ensure!(
1020            signature.len() == 64,
1021            InvalidLength,
1022            "ed25519 signature buffer"
1023        );
1024
1025        // r = H(prefix || M), deterministic -- Ed25519 needs no RNG at signing
1026        // time, which removes an entire class of nonce-reuse failures.
1027        let mut r = hash_to_scalar(&[&self.prefix, message]);
1028        let big_r = mul_basepoint(&r).compress();
1029
1030        let k = hash_to_scalar(&[&big_r, &self.public, message]);
1031        let s = scalar::mul_add(&k, &self.scalar, &r);
1032
1033        signature[..32].copy_from_slice(&big_r);
1034        signature[32..].copy_from_slice(&s);
1035        r.zeroize();
1036        Ok(())
1037    }
1038}
1039
1040impl SignatureScheme for Ed25519 {
1041    const PRIVATE_KEY_LEN: usize = 32;
1042    const PUBLIC_KEY_LEN: usize = 32;
1043    const SIGNATURE_LEN: usize = 64;
1044
1045    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1046        ic_core::module::operational()?;
1047        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1048        ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1049        let (mut a, mut prefix) = expand_seed(private_key);
1050        out.copy_from_slice(&mul_basepoint(&a).compress());
1051        a.zeroize();
1052        prefix.zeroize();
1053        Ok(())
1054    }
1055
1056    fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1057        ic_core::module::operational()?;
1058        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1059        ensure!(
1060            signature.len() == 64,
1061            InvalidLength,
1062            "ed25519 signature buffer"
1063        );
1064
1065        // One shot: expand, derive the public key, sign, discard. A caller
1066        // signing more than once should hold an `Ed25519Key` instead and pay
1067        // the derivation once.
1068        Ed25519Key::from_seed(private_key)?.sign(message, signature)
1069    }
1070
1071    fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1072        ic_core::module::operational()?;
1073        // One shot: recover the point, verify, discard. A caller verifying
1074        // more than once against the same key should hold an
1075        // `Ed25519VerifyKey` and pay the decompression once.
1076        Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1077    }
1078}
1079
1080/// A public key with its point already recovered.
1081///
1082/// Verification needs the public key as a curve point, and decompressing one
1083/// is a field exponentiation -- about two microseconds, against the twenty a
1084/// verification takes. A key used more than once should not pay that more than
1085/// once, which is the same reason [`Ed25519Key`] exists on the signing side.
1086///
1087/// The point is stored negated, because the equation verification checks is
1088/// `[S]B + [k](-A) == R`, so that is the form every signature wants.
1089///
1090/// [`Ed25519::verify`] builds one of these and throws it away, which is the
1091/// right thing for a caller with one signature and the wrong thing for a
1092/// caller with many.
1093pub struct Ed25519VerifyKey {
1094    /// The compressed encoding, which the challenge hash needs verbatim.
1095    compressed: [u8; 32],
1096    /// `-A`, decompressed once.
1097    neg_a: Point,
1098}
1099
1100impl Ed25519VerifyKey {
1101    /// Decompress `public_key`, rejecting anything not on the curve.
1102    pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1103        ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1104        let mut compressed = [0u8; 32];
1105        compressed.copy_from_slice(public_key);
1106        let a = Point::decompress(&compressed).ok_or(ic_core::err!(
1107            MalformedEncoding,
1108            "ed25519 public key is not on the curve"
1109        ))?;
1110        Ok(Self {
1111            compressed,
1112            neg_a: a.negate(),
1113        })
1114    }
1115
1116    /// The key as it was given.
1117    pub fn as_bytes(&self) -> &[u8; 32] {
1118        &self.compressed
1119    }
1120
1121    /// Verify `signature` over `message`.
1122    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1123        ic_core::module::operational()?;
1124        ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1125
1126        let mut big_r = [0u8; 32];
1127        big_r.copy_from_slice(&signature[..32]);
1128        let mut s = [0u8; 32];
1129        s.copy_from_slice(&signature[32..]);
1130
1131        // RFC 8032 section 5.1.7: reject a non-canonical S. Without this check
1132        // the signature is malleable, and any system that treats a signature
1133        // as a unique identifier becomes attackable.
1134        ensure!(
1135            scalar::is_canonical(&s),
1136            MalformedEncoding,
1137            "ed25519 signature S is not reduced"
1138        );
1139
1140        let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
1141            MalformedEncoding,
1142            "ed25519 signature R is not on the curve"
1143        ))?;
1144
1145        let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1146
1147        // [S]B + [k](-A) == R, in one interleaved pass sharing a single chain
1148        // of doublings. Everything here is public, so neither multiplication
1149        // is constant time.
1150        #[cfg(feature = "std")]
1151        let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1152        #[cfg(not(feature = "std"))]
1153        let lhs = double_scalar_mul_vartime_no_table(&self.neg_a, &k, &s);
1154
1155        if lhs.eq_projective(&r_point) {
1156            Ok(())
1157        } else {
1158            Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1159        }
1160    }
1161}
1162
1163impl SelfTest for Ed25519 {
1164    fn self_test() -> Result<()> {
1165        // RFC 8032 §7.1 test vector 1: the empty message.
1166        let mut seed = [0u8; 32];
1167        ic_core::codec::hex_decode(
1168            b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1169            &mut seed,
1170        )?;
1171        let mut want_pk = [0u8; 32];
1172        ic_core::codec::hex_decode(
1173            b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1174            &mut want_pk,
1175        )?;
1176        let mut want_sig = [0u8; 64];
1177        ic_core::codec::hex_decode(
1178            b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1179            &mut want_sig,
1180        )?;
1181
1182        let mut pk = [0u8; 32];
1183        <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1184        ensure!(
1185            ic_core::ct::verify(&want_pk, &pk),
1186            SelfTestFailed,
1187            "ed25519"
1188        );
1189
1190        let mut sig = [0u8; 64];
1191        <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1192        ensure!(
1193            ic_core::ct::verify(&want_sig, &sig),
1194            SelfTestFailed,
1195            "ed25519"
1196        );
1197
1198        <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1199
1200        // A corrupted signature must be rejected.
1201        sig[0] ^= 1;
1202        ensure!(
1203            <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1204            SelfTestFailed,
1205            "ed25519"
1206        );
1207        Ok(())
1208    }
1209}
1210
1211#[cfg(test)]
1212mod tests {
1213    use super::*;
1214    use ic_core::codec::{hex, unhex};
1215
1216    #[test]
1217    fn curve_constants_are_correct() {
1218        // d = -121665 / 121666
1219        let d = Fe::from_u64(121_665)
1220            .neg()
1221            .mul(&Fe::from_u64(121_666).invert());
1222        assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1223        assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1224        // sqrt(-1) squares to -1.
1225        assert_eq!(
1226            hex(&SQRT_M1.square().to_bytes()),
1227            hex(&Fe::ONE.neg().to_bytes()),
1228            "sqrt(-1)"
1229        );
1230    }
1231
1232    #[test]
1233    fn basepoint_has_the_expected_coordinates() {
1234        let b = basepoint();
1235        // y = 4/5
1236        let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1237        let z_inv = b.z.invert();
1238        assert_eq!(
1239            hex(&b.y.mul(&z_inv).to_bytes()),
1240            hex(&expected_y.to_bytes())
1241        );
1242        assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1243    }
1244
1245    /// The dedicated doubling must agree with adding a point to itself.
1246    ///
1247    /// `add` is what RFC 8032's vectors validate, so it is the oracle here.
1248    /// The two formulas are different enough -- one reads `T`, the other does
1249    /// not -- that agreeing on the basepoint alone would not be convincing, so
1250    /// this walks a chain of multiples and doubles each one.
1251    #[test]
1252    fn doubling_agrees_with_adding_a_point_to_itself() {
1253        let mut p = basepoint();
1254        let mut checked = 0;
1255        for _ in 0..16 {
1256            assert_eq!(
1257                p.double().compress(),
1258                p.add(&p).compress(),
1259                "dedicated doubling and self-addition differ"
1260            );
1261            p = p.add(&basepoint());
1262            checked += 1;
1263        }
1264        assert_eq!(checked, 16, "the comparison did not run");
1265
1266        // The identity doubles to itself, which the formula has to get right
1267        // without a special case.
1268        assert_eq!(
1269            Point::IDENTITY.double().compress(),
1270            Point::IDENTITY.compress()
1271        );
1272    }
1273
1274    /// Projective equality must agree with comparing compressed encodings.
1275    ///
1276    /// The two answer the same question by different routes -- one divides by
1277    /// Z, the other cross-multiplies -- so agreement is the argument. It has to
1278    /// hold for equal points given *different* representatives, which is the
1279    /// case the whole optimisation rests on, so the test scales one side by a
1280    /// factor and checks it still compares equal.
1281    #[test]
1282    fn projective_equality_agrees_with_compressed_equality() {
1283        let b = basepoint();
1284        let mut points = std::vec![Point::IDENTITY, b];
1285        let mut p = b;
1286        for _ in 0..6 {
1287            p = p.double();
1288            points.push(p);
1289        }
1290
1291        let mut checked = 0;
1292        for (i, a) in points.iter().enumerate() {
1293            for (j, c) in points.iter().enumerate() {
1294                let projective = a.eq_projective(c);
1295                let compressed = a.compress() == c.compress();
1296                assert_eq!(
1297                    projective, compressed,
1298                    "projective and compressed equality differ for {i} vs {j}"
1299                );
1300                checked += 1;
1301            }
1302        }
1303        assert_eq!(checked, 64, "the comparison did not run");
1304
1305        // The case that matters: the same point with a different Z. Adding the
1306        // identity re-scales the representation without moving the point.
1307        let scaled = b.add(&Point::IDENTITY);
1308        assert!(b.eq_projective(&scaled), "equal points with different Z");
1309        assert_eq!(b.compress(), scaled.compress());
1310    }
1311
1312    #[test]
1313    fn group_law_is_consistent() {
1314        let b = basepoint();
1315        // P + 0 == P
1316        assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1317        // 2P via doubling equals 2P via scalar multiplication.
1318        let mut two = [0u8; 32];
1319        two[0] = 2;
1320        assert_eq!(
1321            hex(&b.double().compress()),
1322            hex(&b.mul_scalar(&two).compress())
1323        );
1324        // (P + P) + P == 3P
1325        let mut three = [0u8; 32];
1326        three[0] = 3;
1327        assert_eq!(
1328            hex(&b.double().add(&b).compress()),
1329            hex(&b.mul_scalar(&three).compress())
1330        );
1331    }
1332
1333    #[test]
1334    fn order_of_the_basepoint_is_l() {
1335        // [L]B must be the identity.
1336        assert_eq!(
1337            hex(&basepoint().mul_scalar(&scalar::L).compress()),
1338            hex(&Point::IDENTITY.compress())
1339        );
1340    }
1341
1342    #[test]
1343    fn compression_roundtrips() {
1344        let b = basepoint();
1345        for k in [1u8, 2, 3, 47, 200] {
1346            let mut s = [0u8; 32];
1347            s[0] = k;
1348            let p = b.mul_scalar(&s);
1349            let c = p.compress();
1350            let d = Point::decompress(&c).expect("valid point");
1351            assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1352        }
1353    }
1354
1355    #[test]
1356    fn decompression_rejects_non_curve_points() {
1357        // A y value with no corresponding x.
1358        let mut bad = [0u8; 32];
1359        bad[0] = 2;
1360        assert!(Point::decompress(&bad).is_none());
1361    }
1362
1363    /// RFC 8032 §7.1 test vectors.
1364    #[test]
1365    fn rfc8032_vectors() {
1366        let cases: [(&str, &str, &str, &str); 3] = [
1367            (
1368                "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1369                "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1370                "",
1371                "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1372            ),
1373            (
1374                "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1375                "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1376                "72",
1377                "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1378            ),
1379            (
1380                "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1381                "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1382                "af82",
1383                "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1384            ),
1385        ];
1386
1387        for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1388            let seed = unhex(seed_hex).unwrap();
1389            let msg = unhex(msg_hex).unwrap();
1390
1391            let mut pk = [0u8; 32];
1392            Ed25519::public_key(&seed, &mut pk).unwrap();
1393            assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1394
1395            let mut sig = [0u8; 64];
1396            Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1397            assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1398
1399            Ed25519::verify(&pk, &msg, &sig).unwrap();
1400        }
1401    }
1402
1403    #[test]
1404    fn verification_rejects_tampering() {
1405        let seed = [0x42u8; 32];
1406        let mut pk = [0u8; 32];
1407        Ed25519::public_key(&seed, &mut pk).unwrap();
1408        let mut sig = [0u8; 64];
1409        Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1410        Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1411
1412        // Wrong message.
1413        assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1414        // Corrupted R.
1415        let mut bad = sig;
1416        bad[0] ^= 1;
1417        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1418        // Corrupted S.
1419        let mut bad = sig;
1420        bad[40] ^= 1;
1421        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1422        // Wrong public key.
1423        let mut other_pk = [0u8; 32];
1424        Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1425        assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1426    }
1427
1428    /// A signature with `S >= L` must be rejected even though it would
1429    /// otherwise verify; this is the malleability check.
1430    #[test]
1431    fn rejects_non_canonical_s() {
1432        let seed = [0x42u8; 32];
1433        let mut pk = [0u8; 32];
1434        Ed25519::public_key(&seed, &mut pk).unwrap();
1435        let mut sig = [0u8; 64];
1436        Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1437
1438        // Add L to S. The verification equation still holds mod L, so only the
1439        // canonicality check can catch it.
1440        let mut carry = 0u16;
1441        for i in 0..32 {
1442            let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1443            sig[32 + i] = t as u8;
1444            carry = t >> 8;
1445        }
1446        assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1447    }
1448
1449    /// The cached key and the seed-only call must produce the same signature.
1450    ///
1451    /// They share a code path now, which is the point -- but that is the sort
1452    /// of thing a later refactor separates again, and the two would then differ
1453    /// only for callers who use one and verify with the other. RFC 8032's
1454    /// vectors exercise the trait method alone and would not notice.
1455    #[test]
1456    fn the_cached_key_signs_identically_to_the_seed() {
1457        let mut checked = 0;
1458        for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1459            for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1460                let mut from_seed = [0u8; 64];
1461                Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1462
1463                let key = Ed25519Key::from_seed(&seed).unwrap();
1464                let mut from_key = [0u8; 64];
1465                key.sign(message, &mut from_key).unwrap();
1466
1467                assert_eq!(from_seed, from_key, "the two signing paths diverged");
1468
1469                // And the cached public key is the one the trait derives.
1470                let mut derived = [0u8; 32];
1471                Ed25519::public_key(&seed, &mut derived).unwrap();
1472                assert_eq!(&derived, key.public_key());
1473
1474                // Both verify, so neither is consistently wrong.
1475                Ed25519::verify(&derived, message, &from_key).unwrap();
1476                checked += 1;
1477            }
1478        }
1479        assert_eq!(checked, 9, "the comparison did not run");
1480    }
1481
1482    /// Scalars that exercise the signed radix-16 recoding at its edges: digits
1483    /// of exactly 8, which carry; runs of 7 and 9 either side of that; the
1484    /// largest value the recoding accepts; the group order less one; and the
1485    /// shape of a clamped secret.
1486    fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
1487        let mut one = [0u8; 32];
1488        one[0] = 1;
1489        let mut eight = [0u8; 32];
1490        eight[0] = 8;
1491        let mut top = [0xffu8; 32];
1492        top[31] = 0x7f;
1493        let mut clamped = [0x9du8; 32];
1494        clamped[0] &= 248;
1495        clamped[31] &= 127;
1496        clamped[31] |= 64;
1497        let mut l_minus_1 = scalar::L;
1498        l_minus_1[0] -= 1;
1499        let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
1500        for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
1501            let mut s = [fill; 32];
1502            s[31] &= 0x7f;
1503            out.push(s);
1504        }
1505        out
1506    }
1507
1508    /// The `no_std` signing path against the bit-at-a-time ladder, on the
1509    /// basepoint and on a point that is not.
1510    ///
1511    /// Tests run with `std`, where signing takes the precomputed table, so
1512    /// without this the windowed path would be compiled into embedded builds
1513    /// and executed by nothing.
1514    /// Every coordinate, not just the compressed form: a wrong `T` still
1515    /// compresses correctly, since compression reads only `X`, `Y` and `Z`,
1516    /// and would corrupt every addition that reads it.
1517    #[test]
1518    fn the_basepoint_constant_is_the_decompressed_encoding() {
1519        let decoded = Point::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
1520        let zinv = decoded.z.invert();
1521        for (name, constant, from_encoding) in [
1522            ("x", BASEPOINT.x, decoded.x.mul(&zinv)),
1523            ("y", BASEPOINT.y, decoded.y.mul(&zinv)),
1524            ("t", BASEPOINT.t, decoded.t.mul(&zinv)),
1525        ] {
1526            assert_eq!(
1527                constant.to_bytes(),
1528                from_encoding.to_bytes(),
1529                "{name} differs"
1530            );
1531        }
1532        assert_eq!(BASEPOINT.z.to_bytes(), Fe::ONE.to_bytes());
1533        assert_eq!(BASEPOINT.compress(), BASEPOINT_COMPRESSED);
1534    }
1535
1536    #[test]
1537    fn the_windowed_multiplication_agrees_with_the_ladder() {
1538        let b = basepoint();
1539        let mut seven = [0u8; 32];
1540        seven[0] = 7;
1541        let p = b.mul_scalar(&seven);
1542
1543        let mut checked = 0;
1544        for point in [b, p] {
1545            for scalar in windowed_scalars() {
1546                assert_eq!(
1547                    mul_scalar_windowed(&point, &scalar).compress(),
1548                    point.mul_scalar(&scalar).compress(),
1549                    "windowed and ladder differ for {scalar:02x?}"
1550                );
1551                checked += 1;
1552            }
1553        }
1554        assert!(checked >= 20, "only {checked} comparisons ran");
1555    }
1556
1557    /// The `no_std` verification path against the tabled one and against two
1558    /// separate ladders, which share no code with either.
1559    #[test]
1560    fn the_untabled_double_multiplication_agrees() {
1561        let b = basepoint();
1562        let mut checked = 0;
1563        for (i, k) in windowed_scalars().into_iter().enumerate() {
1564            let mut seed = [0u8; 32];
1565            seed[0] = 3 + i as u8;
1566            let a = b.mul_scalar(&seed);
1567            for s in [k, [0xffu8; 32], [0x9du8; 32]] {
1568                let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
1569                let tabled = double_scalar_mul_vartime(&a, &k, &s);
1570                let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
1571                assert_eq!(
1572                    untabled.compress(),
1573                    tabled.compress(),
1574                    "k={k:02x?} s={s:02x?}"
1575                );
1576                assert_eq!(
1577                    untabled.compress(),
1578                    ladders.compress(),
1579                    "k={k:02x?} s={s:02x?}"
1580                );
1581                checked += 1;
1582            }
1583        }
1584        assert!(checked >= 30, "only {checked} comparisons ran");
1585    }
1586
1587    /// The recoding must represent the scalar, with the digits it promises.
1588    #[test]
1589    fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1590        for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1591            let naf = wnaf(&scalar, 5);
1592
1593            let mut previous_nonzero: Option<usize> = None;
1594            for (i, d) in naf.iter().enumerate() {
1595                if *d == 0 {
1596                    continue;
1597                }
1598                assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1599                assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1600                if let Some(j) = previous_nonzero {
1601                    assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1602                }
1603                previous_nonzero = Some(i);
1604            }
1605
1606            // And it evaluates back to the scalar, modulo a small prime that
1607            // has nothing to do with the curve.
1608            const M: u128 = 1_000_000_007;
1609            let mut from_digits = 0u128;
1610            let mut power = 1u128;
1611            for d in naf {
1612                let term = ((d as i128).rem_euclid(M as i128)) as u128;
1613                from_digits = (from_digits + term * power) % M;
1614                power = power * 2 % M;
1615            }
1616            let mut from_bytes = 0u128;
1617            let mut p = 1u128;
1618            for byte in scalar {
1619                from_bytes = (from_bytes + (byte as u128) * p) % M;
1620                p = p * 256 % M;
1621            }
1622            assert_eq!(from_digits, from_bytes, "recoding changed the value");
1623        }
1624    }
1625
1626    #[test]
1627    fn signing_is_deterministic() {
1628        let seed = [0x7fu8; 32];
1629        let mut a = [0u8; 64];
1630        let mut b = [0u8; 64];
1631        Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1632        Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1633        assert_eq!(a, b);
1634    }
1635
1636    #[test]
1637    fn rejects_wrong_lengths() {
1638        let mut out = [0u8; 32];
1639        assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1640        assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1641        assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1642    }
1643
1644    #[test]
1645    fn self_test_passes() {
1646        Ed25519::self_test().unwrap();
1647    }
1648
1649    /// A few points on the curve, for the formula tests below.
1650    fn sample_points(n: usize) -> Vec<Point> {
1651        let mut out = Vec::new();
1652        let mut p = basepoint();
1653        for _ in 0..n {
1654            out.push(p);
1655            p = p.double().add(&basepoint());
1656        }
1657        out
1658    }
1659
1660    /// The completed-coordinate doubling is the extended one.
1661    ///
1662    /// `Projective::double` produces four squarings and no multiplications,
1663    /// and the multiplications a doubling needs move into whichever conversion
1664    /// follows. That is only sound if the two routes agree, and the signs are
1665    /// where it would go wrong: the completed form this uses differs from the
1666    /// one the extended formula implies by a factor of -1 in two coordinates,
1667    /// which cancels projectively and would not cancel if one of them were
1668    /// dropped.
1669    #[test]
1670    fn the_completed_doubling_agrees_with_the_extended_one() {
1671        for p in sample_points(40) {
1672            let want = p.double();
1673            let got = p.to_projective().double().to_extended();
1674            assert!(got.eq_projective(&want), "doubling disagrees");
1675            // And through the projective form, which is the route a chain of
1676            // doublings actually takes.
1677            let chained = p.to_projective().double().to_projective().double();
1678            let twice = p.double().double();
1679            assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1680        }
1681    }
1682
1683    /// Niels addition is the nine-multiplication addition.
1684    #[test]
1685    fn niels_addition_agrees_with_the_general_one() {
1686        let pts = sample_points(20);
1687        for p in &pts {
1688            for q in &pts {
1689                let want = p.add(q);
1690                let got = p.add_niels(&q.to_niels()).to_extended();
1691                assert!(got.eq_projective(&want), "add_niels disagrees");
1692
1693                let want_sub = p.add(&q.negate());
1694                let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1695                assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1696            }
1697        }
1698    }
1699
1700    /// Affine-Niels addition is the general addition.
1701    ///
1702    /// It drops the `Z` multiply on the assumption that the stored point has
1703    /// `Z = 1`, which `to_affine_niels` arranges by inverting. If that
1704    /// inversion or the `2d·x·y` were wrong the result would still be a point
1705    /// on the curve, just the wrong one, so it is checked against the addition
1706    /// the published vectors validate.
1707    #[test]
1708    fn affine_niels_addition_agrees_with_the_general_one() {
1709        let pts = sample_points(20);
1710        for p in &pts {
1711            for q in &pts {
1712                let want = p.add(q);
1713                let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1714                assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1715
1716                // And the negated form, which the table's sign handling uses.
1717                let mut n = q.to_affine_niels();
1718                n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1719                let want_neg = p.add(&q.negate());
1720                let got_neg = p.add_affine_niels(&n).to_extended();
1721                assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1722            }
1723        }
1724    }
1725
1726    /// Where verification's time actually goes.
1727    ///
1728    /// Ignored: it is a measurement, not an assertion. Run it with
1729    /// `cargo test -p ic-ec --release -- --ignored --nocapture where_verify_spends`
1730    /// before changing anything here, because the answer decided what was
1731    /// worth doing and a guess would not have.
1732    #[test]
1733    #[ignore = "diagnostic, not a test"]
1734    fn where_verify_spends_its_time() {
1735        use std::time::Instant;
1736
1737        let seed = [7u8; 32];
1738        let key = Ed25519Key::from_seed(&seed).unwrap();
1739        let msg = b"benchmark message";
1740        let mut sig = [0u8; 64];
1741        key.sign(msg, &mut sig).unwrap();
1742        let pk = *key.public_key();
1743
1744        let mut big_r = [0u8; 32];
1745        big_r.copy_from_slice(&sig[..32]);
1746        let mut s_sc = [0u8; 32];
1747        s_sc.copy_from_slice(&sig[32..]);
1748
1749        let n = 2000;
1750        let time = |label: &str, f: &mut dyn FnMut()| {
1751            let mut best = f64::INFINITY;
1752            for _ in 0..5 {
1753                let t = Instant::now();
1754                for _ in 0..n {
1755                    f();
1756                }
1757                let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1758                if e < best {
1759                    best = e;
1760                }
1761            }
1762            println!("  {label:<34} {best:>9.2} us");
1763            best
1764        };
1765
1766        let a_point = Point::decompress(&pk).unwrap();
1767        let k = hash_to_scalar(&[&big_r, &pk, msg]);
1768
1769        println!(
1770            "
1771ed25519 verify, cost breakdown:"
1772        );
1773        let d = time("decompress (x2 per verify)", &mut || {
1774            core::hint::black_box(Point::decompress(&pk));
1775        });
1776        let h = time("hash_to_scalar", &mut || {
1777            core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1778        });
1779        let b = time("mul_basepoint (const time)", &mut || {
1780            core::hint::black_box(mul_basepoint(&s_sc));
1781        });
1782        let v = time("double_scalar_mul_vartime", &mut || {
1783            core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1784        });
1785        time("  of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1786            core::hint::black_box(wnaf(&k, 5));
1787            core::hint::black_box(wnaf(&s_sc, 8));
1788        });
1789        time("  of which: odd_a table build", &mut || {
1790            let twice = a_point.double();
1791            let mut odd = [a_point; 8];
1792            for i in 1..8 {
1793                odd[i] = odd[i - 1].add(&twice);
1794            }
1795            let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1796            core::hint::black_box(t);
1797        });
1798        time("  of which: 255 doublings", &mut || {
1799            let mut p = a_point;
1800            for _ in 0..255 {
1801                p = p.double();
1802            }
1803            core::hint::black_box(p);
1804        });
1805        time("  of which: 79 additions", &mut || {
1806            let mut p = a_point;
1807            for _ in 0..79 {
1808                p = p.add(&a_point);
1809            }
1810            core::hint::black_box(p);
1811        });
1812        time("compress (one inversion)", &mut || {
1813            core::hint::black_box(a_point.compress());
1814        });
1815        println!(
1816            "  {:<34} {:>9.2} us",
1817            "-- accounted for",
1818            2.0 * d + h + b + v
1819        );
1820
1821        // One level down: if the point ops are slow, the field ops are why.
1822        println!(
1823            "
1824field and point primitives, nanoseconds:"
1825        );
1826        let nn = 200_000;
1827        let ns = |label: &str, f: &mut dyn FnMut()| {
1828            let mut best = f64::INFINITY;
1829            for _ in 0..5 {
1830                let t = Instant::now();
1831                for _ in 0..nn {
1832                    f();
1833                }
1834                let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1835                if e < best {
1836                    best = e;
1837                }
1838            }
1839            println!("  {label:<34} {best:>9.2} ns");
1840        };
1841        let fx = a_point.x;
1842        let fy = a_point.y;
1843        ns("Fe::mul", &mut || {
1844            core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1845        });
1846        ns("Fe::square", &mut || {
1847            core::hint::black_box(core::hint::black_box(&fx).square());
1848        });
1849        ns("Fe::add", &mut || {
1850            core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1851        });
1852        ns("Fe::sub", &mut || {
1853            core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1854        });
1855        ns("Fe::neg", &mut || {
1856            core::hint::black_box(core::hint::black_box(&fx).neg());
1857        });
1858        let proj = a_point.to_projective();
1859        let comp = proj.double();
1860        let an = a_point.to_affine_niels();
1861        ns("Projective::double  (4S)", &mut || {
1862            core::hint::black_box(core::hint::black_box(&proj).double());
1863        });
1864        ns("Projective::double_projective", &mut || {
1865            core::hint::black_box(core::hint::black_box(proj).double_projective());
1866        });
1867        ns("Completed::to_projective (3M)", &mut || {
1868            core::hint::black_box(core::hint::black_box(&comp).to_projective());
1869        });
1870        ns("Completed::to_extended (4M)", &mut || {
1871            core::hint::black_box(core::hint::black_box(&comp).to_extended());
1872        });
1873        ns("Point::add_affine_niels (3M)", &mut || {
1874            core::hint::black_box(
1875                core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1876            );
1877        });
1878        ns("Point::double", &mut || {
1879            core::hint::black_box(core::hint::black_box(&a_point).double());
1880        });
1881        ns("Point::add", &mut || {
1882            core::hint::black_box(
1883                core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1884            );
1885        });
1886    }
1887
1888    /// How many point operations a verification actually performs.
1889    #[test]
1890    #[ignore = "diagnostic, not a test"]
1891    fn count_the_point_operations() {
1892        let mut doublings = 0usize;
1893        let mut adds_a = 0usize;
1894        let mut adds_b = 0usize;
1895        let mut state = 0x1234_5678_9abc_def0u64;
1896        let trials = 200;
1897        for _ in 0..trials {
1898            let mut kb = [0u8; 32];
1899            for c in kb.chunks_exact_mut(8) {
1900                state ^= state >> 12;
1901                state ^= state << 25;
1902                state ^= state >> 27;
1903                c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1904            }
1905            kb[31] &= 0x0f;
1906            let na = wnaf(&kb, 5);
1907            let nb = wnaf(&kb, 8);
1908            let mut i = 257;
1909            while i > 0 && na[i] == 0 && nb[i] == 0 {
1910                i -= 1;
1911            }
1912            doublings += i + 1;
1913            adds_a += na.iter().filter(|d| **d != 0).count();
1914            adds_b += nb.iter().filter(|d| **d != 0).count();
1915        }
1916        let d = doublings as f64 / trials as f64;
1917        let aa = adds_a as f64 / trials as f64;
1918        let ab = adds_b as f64 / trials as f64;
1919        println!(
1920            "
1921  per double-scalar multiplication, averaged over {trials} scalars:"
1922        );
1923        println!("    doublings                  {d:>8.1}");
1924        println!("    additions, w=5 table (A)   {aa:>8.1}");
1925        println!("    additions, w=8 table (B)   {ab:>8.1}");
1926        println!("    additions, building A      {:>8.1}", 8.0);
1927        println!("    ---");
1928        println!("    total additions            {:>8.1}", aa + ab + 8.0);
1929        println!(
1930            "    field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1931            d * 8.0 + (aa + ab + 8.0) * 9.0
1932        );
1933        println!(
1934            "    the same at dalek's 3M+4S and 7M:                      {:>6.0}",
1935            d * 7.0 + (aa + ab + 8.0) * 7.0
1936        );
1937    }
1938}