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        ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
1001        let (scalar, prefix) = expand_seed(seed);
1002        let public = mul_basepoint(&scalar).compress();
1003        Ok(Self {
1004            scalar,
1005            prefix,
1006            public,
1007        })
1008    }
1009
1010    /// The public key, already derived.
1011    pub fn public_key(&self) -> &[u8; 32] {
1012        &self.public
1013    }
1014
1015    /// Sign `message`, performing one basepoint multiplication rather than two.
1016    pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1017        ensure!(
1018            signature.len() == 64,
1019            InvalidLength,
1020            "ed25519 signature buffer"
1021        );
1022
1023        // r = H(prefix || M), deterministic -- Ed25519 needs no RNG at signing
1024        // time, which removes an entire class of nonce-reuse failures.
1025        let mut r = hash_to_scalar(&[&self.prefix, message]);
1026        let big_r = mul_basepoint(&r).compress();
1027
1028        let k = hash_to_scalar(&[&big_r, &self.public, message]);
1029        let s = scalar::mul_add(&k, &self.scalar, &r);
1030
1031        signature[..32].copy_from_slice(&big_r);
1032        signature[32..].copy_from_slice(&s);
1033        r.zeroize();
1034        Ok(())
1035    }
1036}
1037
1038impl SignatureScheme for Ed25519 {
1039    const PRIVATE_KEY_LEN: usize = 32;
1040    const PUBLIC_KEY_LEN: usize = 32;
1041    const SIGNATURE_LEN: usize = 64;
1042
1043    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1044        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1045        ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1046        let (mut a, mut prefix) = expand_seed(private_key);
1047        out.copy_from_slice(&mul_basepoint(&a).compress());
1048        a.zeroize();
1049        prefix.zeroize();
1050        Ok(())
1051    }
1052
1053    fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1054        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1055        ensure!(
1056            signature.len() == 64,
1057            InvalidLength,
1058            "ed25519 signature buffer"
1059        );
1060
1061        // One shot: expand, derive the public key, sign, discard. A caller
1062        // signing more than once should hold an `Ed25519Key` instead and pay
1063        // the derivation once.
1064        Ed25519Key::from_seed(private_key)?.sign(message, signature)
1065    }
1066
1067    fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1068        // One shot: recover the point, verify, discard. A caller verifying
1069        // more than once against the same key should hold an
1070        // `Ed25519VerifyKey` and pay the decompression once.
1071        Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1072    }
1073}
1074
1075/// A public key with its point already recovered.
1076///
1077/// Verification needs the public key as a curve point, and decompressing one
1078/// is a field exponentiation -- about two microseconds, against the twenty a
1079/// verification takes. A key used more than once should not pay that more than
1080/// once, which is the same reason [`Ed25519Key`] exists on the signing side.
1081///
1082/// The point is stored negated, because the equation verification checks is
1083/// `[S]B + [k](-A) == R`, so that is the form every signature wants.
1084///
1085/// [`Ed25519::verify`] builds one of these and throws it away, which is the
1086/// right thing for a caller with one signature and the wrong thing for a
1087/// caller with many.
1088pub struct Ed25519VerifyKey {
1089    /// The compressed encoding, which the challenge hash needs verbatim.
1090    compressed: [u8; 32],
1091    /// `-A`, decompressed once.
1092    neg_a: Point,
1093}
1094
1095impl Ed25519VerifyKey {
1096    /// Decompress `public_key`, rejecting anything not on the curve.
1097    pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1098        ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1099        let mut compressed = [0u8; 32];
1100        compressed.copy_from_slice(public_key);
1101        let a = Point::decompress(&compressed).ok_or(ic_core::err!(
1102            MalformedEncoding,
1103            "ed25519 public key is not on the curve"
1104        ))?;
1105        Ok(Self {
1106            compressed,
1107            neg_a: a.negate(),
1108        })
1109    }
1110
1111    /// The key as it was given.
1112    pub fn as_bytes(&self) -> &[u8; 32] {
1113        &self.compressed
1114    }
1115
1116    /// Verify `signature` over `message`.
1117    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1118        ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1119
1120        let mut big_r = [0u8; 32];
1121        big_r.copy_from_slice(&signature[..32]);
1122        let mut s = [0u8; 32];
1123        s.copy_from_slice(&signature[32..]);
1124
1125        // RFC 8032 section 5.1.7: reject a non-canonical S. Without this check
1126        // the signature is malleable, and any system that treats a signature
1127        // as a unique identifier becomes attackable.
1128        ensure!(
1129            scalar::is_canonical(&s),
1130            MalformedEncoding,
1131            "ed25519 signature S is not reduced"
1132        );
1133
1134        let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
1135            MalformedEncoding,
1136            "ed25519 signature R is not on the curve"
1137        ))?;
1138
1139        let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1140
1141        // [S]B + [k](-A) == R, in one interleaved pass sharing a single chain
1142        // of doublings. Everything here is public, so neither multiplication
1143        // is constant time.
1144        #[cfg(feature = "std")]
1145        let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1146        #[cfg(not(feature = "std"))]
1147        let lhs = double_scalar_mul_vartime_no_table(&self.neg_a, &k, &s);
1148
1149        if lhs.eq_projective(&r_point) {
1150            Ok(())
1151        } else {
1152            Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1153        }
1154    }
1155}
1156
1157impl SelfTest for Ed25519 {
1158    fn self_test() -> Result<()> {
1159        // RFC 8032 §7.1 test vector 1: the empty message.
1160        let mut seed = [0u8; 32];
1161        ic_core::codec::hex_decode(
1162            b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1163            &mut seed,
1164        )?;
1165        let mut want_pk = [0u8; 32];
1166        ic_core::codec::hex_decode(
1167            b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1168            &mut want_pk,
1169        )?;
1170        let mut want_sig = [0u8; 64];
1171        ic_core::codec::hex_decode(
1172            b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1173            &mut want_sig,
1174        )?;
1175
1176        let mut pk = [0u8; 32];
1177        <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1178        ensure!(
1179            ic_core::ct::verify(&want_pk, &pk),
1180            SelfTestFailed,
1181            "ed25519"
1182        );
1183
1184        let mut sig = [0u8; 64];
1185        <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1186        ensure!(
1187            ic_core::ct::verify(&want_sig, &sig),
1188            SelfTestFailed,
1189            "ed25519"
1190        );
1191
1192        <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1193
1194        // A corrupted signature must be rejected.
1195        sig[0] ^= 1;
1196        ensure!(
1197            <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1198            SelfTestFailed,
1199            "ed25519"
1200        );
1201        Ok(())
1202    }
1203}
1204
1205#[cfg(test)]
1206mod tests {
1207    use super::*;
1208    use ic_core::codec::{hex, unhex};
1209
1210    #[test]
1211    fn curve_constants_are_correct() {
1212        // d = -121665 / 121666
1213        let d = Fe::from_u64(121_665)
1214            .neg()
1215            .mul(&Fe::from_u64(121_666).invert());
1216        assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1217        assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1218        // sqrt(-1) squares to -1.
1219        assert_eq!(
1220            hex(&SQRT_M1.square().to_bytes()),
1221            hex(&Fe::ONE.neg().to_bytes()),
1222            "sqrt(-1)"
1223        );
1224    }
1225
1226    #[test]
1227    fn basepoint_has_the_expected_coordinates() {
1228        let b = basepoint();
1229        // y = 4/5
1230        let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1231        let z_inv = b.z.invert();
1232        assert_eq!(
1233            hex(&b.y.mul(&z_inv).to_bytes()),
1234            hex(&expected_y.to_bytes())
1235        );
1236        assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1237    }
1238
1239    /// The dedicated doubling must agree with adding a point to itself.
1240    ///
1241    /// `add` is what RFC 8032's vectors validate, so it is the oracle here.
1242    /// The two formulas are different enough -- one reads `T`, the other does
1243    /// not -- that agreeing on the basepoint alone would not be convincing, so
1244    /// this walks a chain of multiples and doubles each one.
1245    #[test]
1246    fn doubling_agrees_with_adding_a_point_to_itself() {
1247        let mut p = basepoint();
1248        let mut checked = 0;
1249        for _ in 0..16 {
1250            assert_eq!(
1251                p.double().compress(),
1252                p.add(&p).compress(),
1253                "dedicated doubling and self-addition differ"
1254            );
1255            p = p.add(&basepoint());
1256            checked += 1;
1257        }
1258        assert_eq!(checked, 16, "the comparison did not run");
1259
1260        // The identity doubles to itself, which the formula has to get right
1261        // without a special case.
1262        assert_eq!(
1263            Point::IDENTITY.double().compress(),
1264            Point::IDENTITY.compress()
1265        );
1266    }
1267
1268    /// Projective equality must agree with comparing compressed encodings.
1269    ///
1270    /// The two answer the same question by different routes -- one divides by
1271    /// Z, the other cross-multiplies -- so agreement is the argument. It has to
1272    /// hold for equal points given *different* representatives, which is the
1273    /// case the whole optimisation rests on, so the test scales one side by a
1274    /// factor and checks it still compares equal.
1275    #[test]
1276    fn projective_equality_agrees_with_compressed_equality() {
1277        let b = basepoint();
1278        let mut points = std::vec![Point::IDENTITY, b];
1279        let mut p = b;
1280        for _ in 0..6 {
1281            p = p.double();
1282            points.push(p);
1283        }
1284
1285        let mut checked = 0;
1286        for (i, a) in points.iter().enumerate() {
1287            for (j, c) in points.iter().enumerate() {
1288                let projective = a.eq_projective(c);
1289                let compressed = a.compress() == c.compress();
1290                assert_eq!(
1291                    projective, compressed,
1292                    "projective and compressed equality differ for {i} vs {j}"
1293                );
1294                checked += 1;
1295            }
1296        }
1297        assert_eq!(checked, 64, "the comparison did not run");
1298
1299        // The case that matters: the same point with a different Z. Adding the
1300        // identity re-scales the representation without moving the point.
1301        let scaled = b.add(&Point::IDENTITY);
1302        assert!(b.eq_projective(&scaled), "equal points with different Z");
1303        assert_eq!(b.compress(), scaled.compress());
1304    }
1305
1306    #[test]
1307    fn group_law_is_consistent() {
1308        let b = basepoint();
1309        // P + 0 == P
1310        assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1311        // 2P via doubling equals 2P via scalar multiplication.
1312        let mut two = [0u8; 32];
1313        two[0] = 2;
1314        assert_eq!(
1315            hex(&b.double().compress()),
1316            hex(&b.mul_scalar(&two).compress())
1317        );
1318        // (P + P) + P == 3P
1319        let mut three = [0u8; 32];
1320        three[0] = 3;
1321        assert_eq!(
1322            hex(&b.double().add(&b).compress()),
1323            hex(&b.mul_scalar(&three).compress())
1324        );
1325    }
1326
1327    #[test]
1328    fn order_of_the_basepoint_is_l() {
1329        // [L]B must be the identity.
1330        assert_eq!(
1331            hex(&basepoint().mul_scalar(&scalar::L).compress()),
1332            hex(&Point::IDENTITY.compress())
1333        );
1334    }
1335
1336    #[test]
1337    fn compression_roundtrips() {
1338        let b = basepoint();
1339        for k in [1u8, 2, 3, 47, 200] {
1340            let mut s = [0u8; 32];
1341            s[0] = k;
1342            let p = b.mul_scalar(&s);
1343            let c = p.compress();
1344            let d = Point::decompress(&c).expect("valid point");
1345            assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1346        }
1347    }
1348
1349    #[test]
1350    fn decompression_rejects_non_curve_points() {
1351        // A y value with no corresponding x.
1352        let mut bad = [0u8; 32];
1353        bad[0] = 2;
1354        assert!(Point::decompress(&bad).is_none());
1355    }
1356
1357    /// RFC 8032 §7.1 test vectors.
1358    #[test]
1359    fn rfc8032_vectors() {
1360        let cases: [(&str, &str, &str, &str); 3] = [
1361            (
1362                "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1363                "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1364                "",
1365                "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1366            ),
1367            (
1368                "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1369                "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1370                "72",
1371                "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1372            ),
1373            (
1374                "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1375                "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1376                "af82",
1377                "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1378            ),
1379        ];
1380
1381        for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1382            let seed = unhex(seed_hex).unwrap();
1383            let msg = unhex(msg_hex).unwrap();
1384
1385            let mut pk = [0u8; 32];
1386            Ed25519::public_key(&seed, &mut pk).unwrap();
1387            assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1388
1389            let mut sig = [0u8; 64];
1390            Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1391            assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1392
1393            Ed25519::verify(&pk, &msg, &sig).unwrap();
1394        }
1395    }
1396
1397    #[test]
1398    fn verification_rejects_tampering() {
1399        let seed = [0x42u8; 32];
1400        let mut pk = [0u8; 32];
1401        Ed25519::public_key(&seed, &mut pk).unwrap();
1402        let mut sig = [0u8; 64];
1403        Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1404        Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1405
1406        // Wrong message.
1407        assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1408        // Corrupted R.
1409        let mut bad = sig;
1410        bad[0] ^= 1;
1411        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1412        // Corrupted S.
1413        let mut bad = sig;
1414        bad[40] ^= 1;
1415        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1416        // Wrong public key.
1417        let mut other_pk = [0u8; 32];
1418        Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1419        assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1420    }
1421
1422    /// A signature with `S >= L` must be rejected even though it would
1423    /// otherwise verify; this is the malleability check.
1424    #[test]
1425    fn rejects_non_canonical_s() {
1426        let seed = [0x42u8; 32];
1427        let mut pk = [0u8; 32];
1428        Ed25519::public_key(&seed, &mut pk).unwrap();
1429        let mut sig = [0u8; 64];
1430        Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1431
1432        // Add L to S. The verification equation still holds mod L, so only the
1433        // canonicality check can catch it.
1434        let mut carry = 0u16;
1435        for i in 0..32 {
1436            let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1437            sig[32 + i] = t as u8;
1438            carry = t >> 8;
1439        }
1440        assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1441    }
1442
1443    /// The cached key and the seed-only call must produce the same signature.
1444    ///
1445    /// They share a code path now, which is the point -- but that is the sort
1446    /// of thing a later refactor separates again, and the two would then differ
1447    /// only for callers who use one and verify with the other. RFC 8032's
1448    /// vectors exercise the trait method alone and would not notice.
1449    #[test]
1450    fn the_cached_key_signs_identically_to_the_seed() {
1451        let mut checked = 0;
1452        for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1453            for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1454                let mut from_seed = [0u8; 64];
1455                Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1456
1457                let key = Ed25519Key::from_seed(&seed).unwrap();
1458                let mut from_key = [0u8; 64];
1459                key.sign(message, &mut from_key).unwrap();
1460
1461                assert_eq!(from_seed, from_key, "the two signing paths diverged");
1462
1463                // And the cached public key is the one the trait derives.
1464                let mut derived = [0u8; 32];
1465                Ed25519::public_key(&seed, &mut derived).unwrap();
1466                assert_eq!(&derived, key.public_key());
1467
1468                // Both verify, so neither is consistently wrong.
1469                Ed25519::verify(&derived, message, &from_key).unwrap();
1470                checked += 1;
1471            }
1472        }
1473        assert_eq!(checked, 9, "the comparison did not run");
1474    }
1475
1476    /// Scalars that exercise the signed radix-16 recoding at its edges: digits
1477    /// of exactly 8, which carry; runs of 7 and 9 either side of that; the
1478    /// largest value the recoding accepts; the group order less one; and the
1479    /// shape of a clamped secret.
1480    fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
1481        let mut one = [0u8; 32];
1482        one[0] = 1;
1483        let mut eight = [0u8; 32];
1484        eight[0] = 8;
1485        let mut top = [0xffu8; 32];
1486        top[31] = 0x7f;
1487        let mut clamped = [0x9du8; 32];
1488        clamped[0] &= 248;
1489        clamped[31] &= 127;
1490        clamped[31] |= 64;
1491        let mut l_minus_1 = scalar::L;
1492        l_minus_1[0] -= 1;
1493        let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
1494        for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
1495            let mut s = [fill; 32];
1496            s[31] &= 0x7f;
1497            out.push(s);
1498        }
1499        out
1500    }
1501
1502    /// The `no_std` signing path against the bit-at-a-time ladder, on the
1503    /// basepoint and on a point that is not.
1504    ///
1505    /// Tests run with `std`, where signing takes the precomputed table, so
1506    /// without this the windowed path would be compiled into embedded builds
1507    /// and executed by nothing.
1508    /// Every coordinate, not just the compressed form: a wrong `T` still
1509    /// compresses correctly, since compression reads only `X`, `Y` and `Z`,
1510    /// and would corrupt every addition that reads it.
1511    #[test]
1512    fn the_basepoint_constant_is_the_decompressed_encoding() {
1513        let decoded = Point::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
1514        let zinv = decoded.z.invert();
1515        for (name, constant, from_encoding) in [
1516            ("x", BASEPOINT.x, decoded.x.mul(&zinv)),
1517            ("y", BASEPOINT.y, decoded.y.mul(&zinv)),
1518            ("t", BASEPOINT.t, decoded.t.mul(&zinv)),
1519        ] {
1520            assert_eq!(
1521                constant.to_bytes(),
1522                from_encoding.to_bytes(),
1523                "{name} differs"
1524            );
1525        }
1526        assert_eq!(BASEPOINT.z.to_bytes(), Fe::ONE.to_bytes());
1527        assert_eq!(BASEPOINT.compress(), BASEPOINT_COMPRESSED);
1528    }
1529
1530    #[test]
1531    fn the_windowed_multiplication_agrees_with_the_ladder() {
1532        let b = basepoint();
1533        let mut seven = [0u8; 32];
1534        seven[0] = 7;
1535        let p = b.mul_scalar(&seven);
1536
1537        let mut checked = 0;
1538        for point in [b, p] {
1539            for scalar in windowed_scalars() {
1540                assert_eq!(
1541                    mul_scalar_windowed(&point, &scalar).compress(),
1542                    point.mul_scalar(&scalar).compress(),
1543                    "windowed and ladder differ for {scalar:02x?}"
1544                );
1545                checked += 1;
1546            }
1547        }
1548        assert!(checked >= 20, "only {checked} comparisons ran");
1549    }
1550
1551    /// The `no_std` verification path against the tabled one and against two
1552    /// separate ladders, which share no code with either.
1553    #[test]
1554    fn the_untabled_double_multiplication_agrees() {
1555        let b = basepoint();
1556        let mut checked = 0;
1557        for (i, k) in windowed_scalars().into_iter().enumerate() {
1558            let mut seed = [0u8; 32];
1559            seed[0] = 3 + i as u8;
1560            let a = b.mul_scalar(&seed);
1561            for s in [k, [0xffu8; 32], [0x9du8; 32]] {
1562                let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
1563                let tabled = double_scalar_mul_vartime(&a, &k, &s);
1564                let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
1565                assert_eq!(
1566                    untabled.compress(),
1567                    tabled.compress(),
1568                    "k={k:02x?} s={s:02x?}"
1569                );
1570                assert_eq!(
1571                    untabled.compress(),
1572                    ladders.compress(),
1573                    "k={k:02x?} s={s:02x?}"
1574                );
1575                checked += 1;
1576            }
1577        }
1578        assert!(checked >= 30, "only {checked} comparisons ran");
1579    }
1580
1581    /// The recoding must represent the scalar, with the digits it promises.
1582    #[test]
1583    fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1584        for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1585            let naf = wnaf(&scalar, 5);
1586
1587            let mut previous_nonzero: Option<usize> = None;
1588            for (i, d) in naf.iter().enumerate() {
1589                if *d == 0 {
1590                    continue;
1591                }
1592                assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1593                assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1594                if let Some(j) = previous_nonzero {
1595                    assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1596                }
1597                previous_nonzero = Some(i);
1598            }
1599
1600            // And it evaluates back to the scalar, modulo a small prime that
1601            // has nothing to do with the curve.
1602            const M: u128 = 1_000_000_007;
1603            let mut from_digits = 0u128;
1604            let mut power = 1u128;
1605            for d in naf {
1606                let term = ((d as i128).rem_euclid(M as i128)) as u128;
1607                from_digits = (from_digits + term * power) % M;
1608                power = power * 2 % M;
1609            }
1610            let mut from_bytes = 0u128;
1611            let mut p = 1u128;
1612            for byte in scalar {
1613                from_bytes = (from_bytes + (byte as u128) * p) % M;
1614                p = p * 256 % M;
1615            }
1616            assert_eq!(from_digits, from_bytes, "recoding changed the value");
1617        }
1618    }
1619
1620    #[test]
1621    fn signing_is_deterministic() {
1622        let seed = [0x7fu8; 32];
1623        let mut a = [0u8; 64];
1624        let mut b = [0u8; 64];
1625        Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1626        Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1627        assert_eq!(a, b);
1628    }
1629
1630    #[test]
1631    fn rejects_wrong_lengths() {
1632        let mut out = [0u8; 32];
1633        assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1634        assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1635        assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1636    }
1637
1638    #[test]
1639    fn self_test_passes() {
1640        Ed25519::self_test().unwrap();
1641    }
1642
1643    /// A few points on the curve, for the formula tests below.
1644    fn sample_points(n: usize) -> Vec<Point> {
1645        let mut out = Vec::new();
1646        let mut p = basepoint();
1647        for _ in 0..n {
1648            out.push(p);
1649            p = p.double().add(&basepoint());
1650        }
1651        out
1652    }
1653
1654    /// The completed-coordinate doubling is the extended one.
1655    ///
1656    /// `Projective::double` produces four squarings and no multiplications,
1657    /// and the multiplications a doubling needs move into whichever conversion
1658    /// follows. That is only sound if the two routes agree, and the signs are
1659    /// where it would go wrong: the completed form this uses differs from the
1660    /// one the extended formula implies by a factor of -1 in two coordinates,
1661    /// which cancels projectively and would not cancel if one of them were
1662    /// dropped.
1663    #[test]
1664    fn the_completed_doubling_agrees_with_the_extended_one() {
1665        for p in sample_points(40) {
1666            let want = p.double();
1667            let got = p.to_projective().double().to_extended();
1668            assert!(got.eq_projective(&want), "doubling disagrees");
1669            // And through the projective form, which is the route a chain of
1670            // doublings actually takes.
1671            let chained = p.to_projective().double().to_projective().double();
1672            let twice = p.double().double();
1673            assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1674        }
1675    }
1676
1677    /// Niels addition is the nine-multiplication addition.
1678    #[test]
1679    fn niels_addition_agrees_with_the_general_one() {
1680        let pts = sample_points(20);
1681        for p in &pts {
1682            for q in &pts {
1683                let want = p.add(q);
1684                let got = p.add_niels(&q.to_niels()).to_extended();
1685                assert!(got.eq_projective(&want), "add_niels disagrees");
1686
1687                let want_sub = p.add(&q.negate());
1688                let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1689                assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1690            }
1691        }
1692    }
1693
1694    /// Affine-Niels addition is the general addition.
1695    ///
1696    /// It drops the `Z` multiply on the assumption that the stored point has
1697    /// `Z = 1`, which `to_affine_niels` arranges by inverting. If that
1698    /// inversion or the `2d·x·y` were wrong the result would still be a point
1699    /// on the curve, just the wrong one, so it is checked against the addition
1700    /// the published vectors validate.
1701    #[test]
1702    fn affine_niels_addition_agrees_with_the_general_one() {
1703        let pts = sample_points(20);
1704        for p in &pts {
1705            for q in &pts {
1706                let want = p.add(q);
1707                let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1708                assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1709
1710                // And the negated form, which the table's sign handling uses.
1711                let mut n = q.to_affine_niels();
1712                n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1713                let want_neg = p.add(&q.negate());
1714                let got_neg = p.add_affine_niels(&n).to_extended();
1715                assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1716            }
1717        }
1718    }
1719
1720    /// Where verification's time actually goes.
1721    ///
1722    /// Ignored: it is a measurement, not an assertion. Run it with
1723    /// `cargo test -p ic-ec --release -- --ignored --nocapture where_verify_spends`
1724    /// before changing anything here, because the answer decided what was
1725    /// worth doing and a guess would not have.
1726    #[test]
1727    #[ignore = "diagnostic, not a test"]
1728    fn where_verify_spends_its_time() {
1729        use std::time::Instant;
1730
1731        let seed = [7u8; 32];
1732        let key = Ed25519Key::from_seed(&seed).unwrap();
1733        let msg = b"benchmark message";
1734        let mut sig = [0u8; 64];
1735        key.sign(msg, &mut sig).unwrap();
1736        let pk = *key.public_key();
1737
1738        let mut big_r = [0u8; 32];
1739        big_r.copy_from_slice(&sig[..32]);
1740        let mut s_sc = [0u8; 32];
1741        s_sc.copy_from_slice(&sig[32..]);
1742
1743        let n = 2000;
1744        let time = |label: &str, f: &mut dyn FnMut()| {
1745            let mut best = f64::INFINITY;
1746            for _ in 0..5 {
1747                let t = Instant::now();
1748                for _ in 0..n {
1749                    f();
1750                }
1751                let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1752                if e < best {
1753                    best = e;
1754                }
1755            }
1756            println!("  {label:<34} {best:>9.2} us");
1757            best
1758        };
1759
1760        let a_point = Point::decompress(&pk).unwrap();
1761        let k = hash_to_scalar(&[&big_r, &pk, msg]);
1762
1763        println!(
1764            "
1765ed25519 verify, cost breakdown:"
1766        );
1767        let d = time("decompress (x2 per verify)", &mut || {
1768            core::hint::black_box(Point::decompress(&pk));
1769        });
1770        let h = time("hash_to_scalar", &mut || {
1771            core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1772        });
1773        let b = time("mul_basepoint (const time)", &mut || {
1774            core::hint::black_box(mul_basepoint(&s_sc));
1775        });
1776        let v = time("double_scalar_mul_vartime", &mut || {
1777            core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1778        });
1779        time("  of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1780            core::hint::black_box(wnaf(&k, 5));
1781            core::hint::black_box(wnaf(&s_sc, 8));
1782        });
1783        time("  of which: odd_a table build", &mut || {
1784            let twice = a_point.double();
1785            let mut odd = [a_point; 8];
1786            for i in 1..8 {
1787                odd[i] = odd[i - 1].add(&twice);
1788            }
1789            let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1790            core::hint::black_box(t);
1791        });
1792        time("  of which: 255 doublings", &mut || {
1793            let mut p = a_point;
1794            for _ in 0..255 {
1795                p = p.double();
1796            }
1797            core::hint::black_box(p);
1798        });
1799        time("  of which: 79 additions", &mut || {
1800            let mut p = a_point;
1801            for _ in 0..79 {
1802                p = p.add(&a_point);
1803            }
1804            core::hint::black_box(p);
1805        });
1806        time("compress (one inversion)", &mut || {
1807            core::hint::black_box(a_point.compress());
1808        });
1809        println!(
1810            "  {:<34} {:>9.2} us",
1811            "-- accounted for",
1812            2.0 * d + h + b + v
1813        );
1814
1815        // One level down: if the point ops are slow, the field ops are why.
1816        println!(
1817            "
1818field and point primitives, nanoseconds:"
1819        );
1820        let nn = 200_000;
1821        let ns = |label: &str, f: &mut dyn FnMut()| {
1822            let mut best = f64::INFINITY;
1823            for _ in 0..5 {
1824                let t = Instant::now();
1825                for _ in 0..nn {
1826                    f();
1827                }
1828                let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1829                if e < best {
1830                    best = e;
1831                }
1832            }
1833            println!("  {label:<34} {best:>9.2} ns");
1834        };
1835        let fx = a_point.x;
1836        let fy = a_point.y;
1837        ns("Fe::mul", &mut || {
1838            core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1839        });
1840        ns("Fe::square", &mut || {
1841            core::hint::black_box(core::hint::black_box(&fx).square());
1842        });
1843        ns("Fe::add", &mut || {
1844            core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1845        });
1846        ns("Fe::sub", &mut || {
1847            core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1848        });
1849        ns("Fe::neg", &mut || {
1850            core::hint::black_box(core::hint::black_box(&fx).neg());
1851        });
1852        let proj = a_point.to_projective();
1853        let comp = proj.double();
1854        let an = a_point.to_affine_niels();
1855        ns("Projective::double  (4S)", &mut || {
1856            core::hint::black_box(core::hint::black_box(&proj).double());
1857        });
1858        ns("Projective::double_projective", &mut || {
1859            core::hint::black_box(core::hint::black_box(proj).double_projective());
1860        });
1861        ns("Completed::to_projective (3M)", &mut || {
1862            core::hint::black_box(core::hint::black_box(&comp).to_projective());
1863        });
1864        ns("Completed::to_extended (4M)", &mut || {
1865            core::hint::black_box(core::hint::black_box(&comp).to_extended());
1866        });
1867        ns("Point::add_affine_niels (3M)", &mut || {
1868            core::hint::black_box(
1869                core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1870            );
1871        });
1872        ns("Point::double", &mut || {
1873            core::hint::black_box(core::hint::black_box(&a_point).double());
1874        });
1875        ns("Point::add", &mut || {
1876            core::hint::black_box(
1877                core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1878            );
1879        });
1880    }
1881
1882    /// How many point operations a verification actually performs.
1883    #[test]
1884    #[ignore = "diagnostic, not a test"]
1885    fn count_the_point_operations() {
1886        let mut doublings = 0usize;
1887        let mut adds_a = 0usize;
1888        let mut adds_b = 0usize;
1889        let mut state = 0x1234_5678_9abc_def0u64;
1890        let trials = 200;
1891        for _ in 0..trials {
1892            let mut kb = [0u8; 32];
1893            for c in kb.chunks_exact_mut(8) {
1894                state ^= state >> 12;
1895                state ^= state << 25;
1896                state ^= state >> 27;
1897                c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1898            }
1899            kb[31] &= 0x0f;
1900            let na = wnaf(&kb, 5);
1901            let nb = wnaf(&kb, 8);
1902            let mut i = 257;
1903            while i > 0 && na[i] == 0 && nb[i] == 0 {
1904                i -= 1;
1905            }
1906            doublings += i + 1;
1907            adds_a += na.iter().filter(|d| **d != 0).count();
1908            adds_b += nb.iter().filter(|d| **d != 0).count();
1909        }
1910        let d = doublings as f64 / trials as f64;
1911        let aa = adds_a as f64 / trials as f64;
1912        let ab = adds_b as f64 / trials as f64;
1913        println!(
1914            "
1915  per double-scalar multiplication, averaged over {trials} scalars:"
1916        );
1917        println!("    doublings                  {d:>8.1}");
1918        println!("    additions, w=5 table (A)   {aa:>8.1}");
1919        println!("    additions, w=8 table (B)   {ab:>8.1}");
1920        println!("    additions, building A      {:>8.1}", 8.0);
1921        println!("    ---");
1922        println!("    total additions            {:>8.1}", aa + ab + 8.0);
1923        println!(
1924            "    field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1925            d * 8.0 + (aa + ab + 8.0) * 9.0
1926        );
1927        println!(
1928            "    the same at dalek's 3M+4S and 7M:                      {:>6.0}",
1929            d * 7.0 + (aa + ab + 8.0) * 7.0
1930        );
1931    }
1932}