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([
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([
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([
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/// The Ed25519 base point.
566/// `scalar * B`, through the precomputed table where there is one.
567///
568/// Every basepoint multiplication in this module goes through here rather than
569/// calling `mul_scalar` on the basepoint directly, so the two paths cannot
570/// drift apart and a caller cannot accidentally take the slow one.
571fn mul_basepoint(scalar: &[u8; 32]) -> Point {
572    #[cfg(feature = "std")]
573    {
574        basepoint_table::table().mul(scalar)
575    }
576    #[cfg(not(feature = "std"))]
577    {
578        mul_scalar_windowed(&basepoint(), scalar)
579    }
580}
581
582/// `[k]A + [s]B`, in one pass, variable time in both scalars.
583///
584/// Verification needs two scalar multiplications and then compares the
585/// results. Done separately that is two independent runs of doublings -- and
586/// the doublings are the whole cost, some two hundred and fifty-five of them
587/// against forty-odd additions. Run together they are shared: one chain of
588/// doublings, with each scalar contributing an addition at the positions where
589/// its own recoding is non-zero.
590///
591/// The basepoint half also stops paying for constant time here. `mul_basepoint`
592/// selects a table entry by reading all eight and moving conditionally, because
593/// a signing scalar is secret. Nothing in a verification is: the signature, the
594/// public key and the message are all in the clear, so the table is indexed
595/// directly and the window widened to eight, which is a table built once and
596/// about a third as many additions.
597///
598/// Both properties are why this is not the function signing calls.
599/// [`double_scalar_mul_vartime`], reachable from the benchmark.
600///
601/// The benchmark lives outside this workspace and cannot see private items,
602/// and comparing whole signatures cannot separate "our field arithmetic is
603/// slower" from "our scalar multiplication does more work". This is how that
604/// question gets answered rather than guessed at.
605#[cfg(feature = "bench-internals")]
606#[doc(hidden)]
607pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
608    double_scalar_mul_vartime(a, k, s)
609}
610
611/// `scalar * p` in constant time, four bits at a time, for scalars below
612/// `2^255`.
613///
614/// What `no_std` signing uses in place of the precomputed table, and the same
615/// algorithm with the table built per call: the scalar becomes 64 signed
616/// radix-16 digits, `1..=8` times `p` are computed on the stack, and each digit
617/// selects one of them by reading all eight with conditional moves. That is
618/// 252 doublings and 64 additions, against 256 of each for
619/// [`Point::mul_scalar`]'s bit-at-a-time ladder, and eight entries of 160 bytes
620/// that are gone when it returns -- which is the constraint `no_std` exists
621/// for.
622///
623/// Every scalar reaching it is a clamped secret or a value reduced modulo the
624/// group order, which is what [`signed_digits`] needs. [`Point::mul_scalar`]
625/// remains the public operation and takes any 32 bytes.
626#[cfg(any(not(feature = "std"), test))]
627fn mul_scalar_windowed(p: &Point, scalar: &[u8; 32]) -> Point {
628    let mut multiples = [*p; 8];
629    for i in 1..8 {
630        multiples[i] = multiples[i - 1].add(p);
631    }
632    let table: [Niels; 8] = core::array::from_fn(|i| multiples[i].to_niels());
633
634    // `digit * p` for a digit in `[-8, 8]`, without indexing by the digit.
635    let select = |digit: i8| -> Niels {
636        let negative = Choice::from_u8((digit as u8) >> 7);
637        let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u8;
638        let mut out = Niels::IDENTITY;
639        for (i, entry) in table.iter().enumerate() {
640            out.cmov(entry, Choice::from_u8(u8::from(magnitude == (i as u8 + 1))));
641        }
642        out.conditional_negate(negative);
643        out
644    };
645
646    let digits = signed_digits(scalar);
647    let mut acc = Point::IDENTITY.add_niels(&select(digits[63])).to_extended();
648    for i in (0..63).rev() {
649        // Times sixteen: three doublings that never need `T`, then one that
650        // does, since the addition after it reads `T`.
651        let mut q = acc.to_projective();
652        for _ in 0..3 {
653            q = q.double_projective();
654        }
655        acc = q.double().to_extended();
656        acc = acc.add_niels(&select(digits[i])).to_extended();
657    }
658    acc
659}
660
661/// `1, 3, 5 .. 15` times `p`, in Niels form.
662fn odd_multiples(p: &Point) -> [Niels; 8] {
663    let twice = p.double();
664    let mut odd = [*p; 8];
665    for i in 1..8 {
666        odd[i] = odd[i - 1].add(&twice);
667    }
668    core::array::from_fn(|i| odd[i].to_niels())
669}
670
671/// The scalar as 64 signed radix-16 digits, each in `[-8, 8]`.
672///
673/// A nibble above 8 becomes `nibble - 16` with a carry into the next digit,
674/// which is what keeps a table to the positive multiples.
675///
676/// Only the first 63 digits are recoded. The last one is left to absorb the
677/// final carry, because a carry *out* of the top would be a factor of `16^64`
678/// with nowhere to go -- silently dropping it would give the wrong point. That
679/// works because every scalar reaching here has its top byte at most 127: the
680/// clamped secret has bit 255 cleared by construction, and `r` and `s` are
681/// reduced modulo the group order and so are far smaller. The top nibble is
682/// then at most 7, one carry takes it to 8, and 8 is in range.
683///
684/// The first version of this recoded all 64 and dropped that carry. The
685/// agreement test below caught it.
686fn signed_digits(scalar: &[u8; 32]) -> [i8; 64] {
687    debug_assert!(
688        scalar[31] <= 127,
689        "the top digit can only absorb the final carry for scalars below 2^255"
690    );
691
692    let mut nibbles = [0i8; 64];
693    for (i, byte) in scalar.iter().enumerate() {
694        nibbles[i * 2] = (byte & 0x0f) as i8;
695        nibbles[i * 2 + 1] = (byte >> 4) as i8;
696    }
697
698    for i in 0..63 {
699        let carry = (nibbles[i] + 8) >> 4;
700        nibbles[i] -= carry << 4;
701        nibbles[i + 1] += carry;
702    }
703    nibbles
704}
705
706#[cfg(feature = "std")]
707fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
708    let odd_b = basepoint_table::odd_multiples();
709    shared_doublings(a, k, &wnaf(s, 8), |e, digit| {
710        let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
711        if digit > 0 {
712            e.add_affine_niels(n)
713        } else {
714            e.sub_affine_niels(n)
715        }
716    })
717}
718
719/// [`double_scalar_mul_vartime`] with no stored table: the basepoint's odd
720/// multiples are built per call, the same way `A`'s are, and read at width 5.
721///
722/// What `no_std` verification uses. It keeps the part that matters -- one chain
723/// of doublings shared by both scalars, where computing `[S]B` and `[k]A`
724/// separately would run two -- and gives up only the wider window, which is
725/// the part that needs storage.
726#[cfg(any(not(feature = "std"), test))]
727fn double_scalar_mul_vartime_no_table(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
728    let odd_b = odd_multiples(&basepoint());
729    shared_doublings(a, k, &wnaf(s, 5), |e, digit| {
730        let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
731        if digit > 0 {
732            e.add_niels(n)
733        } else {
734            e.sub_niels(n)
735        }
736    })
737}
738
739/// The loop both of those share: `[k]A` at width 5, plus whatever `add_b` adds
740/// at each non-zero digit of `naf_b`, over a single chain of doublings.
741#[inline(always)]
742fn shared_doublings(
743    a: &Point,
744    k: &[u8; 32],
745    naf_b: &[i8; 258],
746    add_b: impl Fn(&Point, i8) -> Completed,
747) -> Point {
748    // 1A, 3A, 5A .. 15A, in Niels form, built for this call.
749    let odd_a = odd_multiples(a);
750    let naf_a = wnaf(k, 5);
751
752    // Start at the highest position either recoding reaches, so the leading
753    // doublings of the identity are skipped.
754    let mut i = 257;
755    while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
756        i -= 1;
757    }
758
759    // The accumulator is carried in whichever form the next step wants: a
760    // doubling reads only X, Y and Z, and an addition is the only thing that
761    // reads T. So a position with no addition pays three multiplications to
762    // come out of the completed form instead of four.
763    let mut acc = Point::IDENTITY.to_projective();
764    loop {
765        // Nothing to add here, which is the common case: double straight back
766        // to projective without materialising the completed form.
767        if naf_a[i] == 0 && naf_b[i] == 0 {
768            acc = acc.double_projective();
769            if i == 0 {
770                return acc.to_extended_from_projective();
771            }
772            i -= 1;
773            continue;
774        }
775        let mut t = acc.double();
776        if naf_a[i] != 0 {
777            let e = t.to_extended();
778            let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
779            t = if naf_a[i] > 0 {
780                e.add_niels(n)
781            } else {
782                e.sub_niels(n)
783            };
784        }
785        if naf_b[i] != 0 {
786            t = add_b(&t.to_extended(), naf_b[i]);
787        }
788        if i == 0 {
789            return t.to_extended();
790        }
791        acc = t.to_projective();
792        i -= 1;
793    }
794}
795
796/// [`mul_basepoint`], reachable from the benchmark. See
797/// [`double_scalar_mul_vartime_for_bench`].
798#[cfg(feature = "bench-internals")]
799#[doc(hidden)]
800pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
801    mul_basepoint(scalar)
802}
803
804fn basepoint() -> Point {
805    BASEPOINT
806}
807
808/// The basepoint in extended coordinates, `Z = 1` and `T = XY`.
809///
810/// It used to be decompressed from [`BASEPOINT_COMPRESSED`] on every call,
811/// which is a field square root -- about a tenth of a signature on the `no_std`
812/// path, where nothing caches it. The limbs were computed outside this crate
813/// from `y = 4/5` and the curve equation, and
814/// `the_basepoint_constant_is_the_decompressed_encoding` holds them to what
815/// decompressing the RFC 8032 encoding gives.
816const BASEPOINT: Point = Point {
817    x: Fe([
818        1_738_742_601_995_546,
819        1_146_398_526_822_698,
820        2_070_867_633_025_821,
821        562_264_141_797_630,
822        587_772_402_128_613,
823    ]),
824    y: Fe([
825        1_801_439_850_948_184,
826        1_351_079_888_211_148,
827        450_359_962_737_049,
828        900_719_925_474_099,
829        1_801_439_850_948_198,
830    ]),
831    z: Fe::ONE,
832    t: Fe([
833        1_841_354_044_333_475,
834        16_398_895_984_059,
835        755_974_180_946_558,
836        900_171_276_175_154,
837        1_821_297_809_914_039,
838    ]),
839};
840
841/// RFC 8032 Ed25519 (PureEdDSA over Curve25519 with SHA-512).
842pub struct Ed25519;
843
844impl Algorithm for Ed25519 {
845    const ID: &'static str = "ed25519";
846    const NAME: &'static str = "Ed25519";
847}
848
849/// Expand a 32-byte seed into the clamped scalar and the nonce prefix.
850fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
851    let h = Sha512::digest(seed);
852    let mut a = [0u8; 32];
853    let mut prefix = [0u8; 32];
854    a.copy_from_slice(&h.as_ref()[..32]);
855    prefix.copy_from_slice(&h.as_ref()[32..]);
856    a[0] &= 248;
857    a[31] &= 127;
858    a[31] |= 64;
859    (a, prefix)
860}
861
862/// Width-`w` non-adjacent form of a 256-bit scalar.
863///
864/// Each non-zero digit is odd and lies in `[-(2^(w-1) - 1), 2^(w-1) - 1]`, and
865/// no two non-zero digits are within `w` places of each other, which puts the
866/// density near `1/(w+1)`. A wider window means fewer additions and a bigger
867/// table: width 5 for an arbitrary point, whose table has to be built on the
868/// spot, and width 8 for the basepoint, whose table is built once.
869///
870/// `w` must be at most 8, so that every digit fits an `i8`.
871fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
872    debug_assert!((2..=8).contains(&w), "window width out of range");
873    let half = 1i64 << (w - 1);
874    let full = 1i64 << w;
875    let mask = (full - 1) as u64;
876
877    let mut naf = [0i8; 258];
878    // Five limbs for a four-limb scalar. A negative digit adds to `k`, and for
879    // a scalar near 2^256 that carries out of the top: on four limbs it wraps
880    // to zero, the loop stops early, and the representation is silently short.
881    // The scalars that reach this are reduced modulo the group order and could
882    // not trigger it -- which is exactly the assumption that was wrong for the
883    // NIST recoding, so the room is given rather than argued for.
884    let mut k = [0u64; 5];
885    for (i, limb) in k.iter_mut().take(4).enumerate() {
886        let mut b = [0u8; 8];
887        b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
888        *limb = u64::from_le_bytes(b);
889    }
890
891    let mut i = 0;
892    while k.iter().any(|&x| x != 0) {
893        if k[0] & 1 == 1 {
894            let mut d = (k[0] & mask) as i64;
895            if d >= half {
896                d -= full;
897            }
898            naf[i] = d as i8;
899            if d > 0 {
900                sub_u64(&mut k, d as u64);
901            } else {
902                add_u64(&mut k, d.unsigned_abs());
903            }
904        }
905        shr1(&mut k);
906        i += 1;
907    }
908    naf
909}
910
911/// `k -= v`, for `v` small enough not to borrow past the top.
912fn sub_u64(k: &mut [u64; 5], v: u64) {
913    let (d, mut borrow) = k[0].overflowing_sub(v);
914    k[0] = d;
915    for limb in k.iter_mut().skip(1) {
916        if !borrow {
917            break;
918        }
919        let (d, b) = limb.overflowing_sub(1);
920        *limb = d;
921        borrow = b;
922    }
923}
924
925/// `k += v`, for `v` small enough not to carry past the top.
926fn add_u64(k: &mut [u64; 5], v: u64) {
927    let (d, mut carry) = k[0].overflowing_add(v);
928    k[0] = d;
929    for limb in k.iter_mut().skip(1) {
930        if !carry {
931            break;
932        }
933        let (d, c) = limb.overflowing_add(1);
934        *limb = d;
935        carry = c;
936    }
937}
938
939/// `k >>= 1`.
940fn shr1(k: &mut [u64; 5]) {
941    for i in 0..4 {
942        k[i] = (k[i] >> 1) | (k[i + 1] << 63);
943    }
944    k[4] >>= 1;
945}
946
947/// `SHA-512(parts...)` reduced modulo the group order.
948fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
949    let mut h = Sha512::new();
950    for p in parts {
951        h.update(p);
952    }
953    let digest = h.finalize();
954    let mut wide = [0u8; 64];
955    wide.copy_from_slice(digest.as_ref());
956    scalar::reduce_wide(&wide)
957}
958
959/// A signing key with its public key already derived.
960///
961/// # Why this exists
962///
963/// RFC 8032 signing needs the public key: it goes into the hash that produces
964/// `k`. [`Ed25519::sign`] takes only the 32-byte seed, so it has to derive the
965/// public key on every call -- a second basepoint multiplication, and with the
966/// table in place that is most of what a signature now costs.
967///
968/// A key that is used more than once should derive it once. That is what a TLS
969/// server does with a certificate key, and what dalek's `SigningKey` does,
970/// which is why comparing `Ed25519::sign` against it was comparing two
971/// different amounts of work.
972///
973/// The trait method still exists and still takes a seed. This changes nothing
974/// for a caller signing once; it halves the cost for a caller signing twice.
975pub struct Ed25519Key {
976    /// The clamped scalar from the seed's hash.
977    scalar: [u8; 32],
978    /// The second half of that hash, which seeds the deterministic nonce.
979    prefix: [u8; 32],
980    /// `scalar * B`, compressed. Derived once, here.
981    public: [u8; 32],
982}
983
984impl Drop for Ed25519Key {
985    fn drop(&mut self) {
986        self.scalar.zeroize();
987        self.prefix.zeroize();
988        // `public` is public, and is left alone.
989    }
990}
991
992impl Ed25519Key {
993    /// Expand a 32-byte seed and derive its public key.
994    pub fn from_seed(seed: &[u8]) -> Result<Self> {
995        ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
996        let (scalar, prefix) = expand_seed(seed);
997        let public = mul_basepoint(&scalar).compress();
998        Ok(Self {
999            scalar,
1000            prefix,
1001            public,
1002        })
1003    }
1004
1005    /// The public key, already derived.
1006    pub fn public_key(&self) -> &[u8; 32] {
1007        &self.public
1008    }
1009
1010    /// Sign `message`, performing one basepoint multiplication rather than two.
1011    pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1012        ensure!(
1013            signature.len() == 64,
1014            InvalidLength,
1015            "ed25519 signature buffer"
1016        );
1017
1018        // r = H(prefix || M), deterministic -- Ed25519 needs no RNG at signing
1019        // time, which removes an entire class of nonce-reuse failures.
1020        let mut r = hash_to_scalar(&[&self.prefix, message]);
1021        let big_r = mul_basepoint(&r).compress();
1022
1023        let k = hash_to_scalar(&[&big_r, &self.public, message]);
1024        let s = scalar::mul_add(&k, &self.scalar, &r);
1025
1026        signature[..32].copy_from_slice(&big_r);
1027        signature[32..].copy_from_slice(&s);
1028        r.zeroize();
1029        Ok(())
1030    }
1031}
1032
1033impl SignatureScheme for Ed25519 {
1034    const PRIVATE_KEY_LEN: usize = 32;
1035    const PUBLIC_KEY_LEN: usize = 32;
1036    const SIGNATURE_LEN: usize = 64;
1037
1038    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1039        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1040        ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1041        let (mut a, mut prefix) = expand_seed(private_key);
1042        out.copy_from_slice(&mul_basepoint(&a).compress());
1043        a.zeroize();
1044        prefix.zeroize();
1045        Ok(())
1046    }
1047
1048    fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1049        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1050        ensure!(
1051            signature.len() == 64,
1052            InvalidLength,
1053            "ed25519 signature buffer"
1054        );
1055
1056        // One shot: expand, derive the public key, sign, discard. A caller
1057        // signing more than once should hold an `Ed25519Key` instead and pay
1058        // the derivation once.
1059        Ed25519Key::from_seed(private_key)?.sign(message, signature)
1060    }
1061
1062    fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1063        // One shot: recover the point, verify, discard. A caller verifying
1064        // more than once against the same key should hold an
1065        // `Ed25519VerifyKey` and pay the decompression once.
1066        Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1067    }
1068}
1069
1070/// A public key with its point already recovered.
1071///
1072/// Verification needs the public key as a curve point, and decompressing one
1073/// is a field exponentiation -- about two microseconds, against the twenty a
1074/// verification takes. A key used more than once should not pay that more than
1075/// once, which is the same reason [`Ed25519Key`] exists on the signing side.
1076///
1077/// The point is stored negated, because the equation verification checks is
1078/// `[S]B + [k](-A) == R`, so that is the form every signature wants.
1079///
1080/// [`Ed25519::verify`] builds one of these and throws it away, which is the
1081/// right thing for a caller with one signature and the wrong thing for a
1082/// caller with many.
1083pub struct Ed25519VerifyKey {
1084    /// The compressed encoding, which the challenge hash needs verbatim.
1085    compressed: [u8; 32],
1086    /// `-A`, decompressed once.
1087    neg_a: Point,
1088}
1089
1090impl Ed25519VerifyKey {
1091    /// Decompress `public_key`, rejecting anything not on the curve.
1092    pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1093        ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1094        let mut compressed = [0u8; 32];
1095        compressed.copy_from_slice(public_key);
1096        let a = Point::decompress(&compressed).ok_or(ic_core::err!(
1097            MalformedEncoding,
1098            "ed25519 public key is not on the curve"
1099        ))?;
1100        Ok(Self {
1101            compressed,
1102            neg_a: a.negate(),
1103        })
1104    }
1105
1106    /// The key as it was given.
1107    pub fn as_bytes(&self) -> &[u8; 32] {
1108        &self.compressed
1109    }
1110
1111    /// Verify `signature` over `message`.
1112    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1113        ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1114
1115        let mut big_r = [0u8; 32];
1116        big_r.copy_from_slice(&signature[..32]);
1117        let mut s = [0u8; 32];
1118        s.copy_from_slice(&signature[32..]);
1119
1120        // RFC 8032 section 5.1.7: reject a non-canonical S. Without this check
1121        // the signature is malleable, and any system that treats a signature
1122        // as a unique identifier becomes attackable.
1123        ensure!(
1124            scalar::is_canonical(&s),
1125            MalformedEncoding,
1126            "ed25519 signature S is not reduced"
1127        );
1128
1129        let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
1130            MalformedEncoding,
1131            "ed25519 signature R is not on the curve"
1132        ))?;
1133
1134        let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1135
1136        // [S]B + [k](-A) == R, in one interleaved pass sharing a single chain
1137        // of doublings. Everything here is public, so neither multiplication
1138        // is constant time.
1139        #[cfg(feature = "std")]
1140        let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1141        #[cfg(not(feature = "std"))]
1142        let lhs = double_scalar_mul_vartime_no_table(&self.neg_a, &k, &s);
1143
1144        if lhs.eq_projective(&r_point) {
1145            Ok(())
1146        } else {
1147            Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1148        }
1149    }
1150}
1151
1152impl SelfTest for Ed25519 {
1153    fn self_test() -> Result<()> {
1154        // RFC 8032 §7.1 test vector 1: the empty message.
1155        let mut seed = [0u8; 32];
1156        ic_core::codec::hex_decode(
1157            b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1158            &mut seed,
1159        )?;
1160        let mut want_pk = [0u8; 32];
1161        ic_core::codec::hex_decode(
1162            b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1163            &mut want_pk,
1164        )?;
1165        let mut want_sig = [0u8; 64];
1166        ic_core::codec::hex_decode(
1167            b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1168            &mut want_sig,
1169        )?;
1170
1171        let mut pk = [0u8; 32];
1172        <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1173        ensure!(
1174            ic_core::ct::verify(&want_pk, &pk),
1175            SelfTestFailed,
1176            "ed25519"
1177        );
1178
1179        let mut sig = [0u8; 64];
1180        <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1181        ensure!(
1182            ic_core::ct::verify(&want_sig, &sig),
1183            SelfTestFailed,
1184            "ed25519"
1185        );
1186
1187        <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1188
1189        // A corrupted signature must be rejected.
1190        sig[0] ^= 1;
1191        ensure!(
1192            <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1193            SelfTestFailed,
1194            "ed25519"
1195        );
1196        Ok(())
1197    }
1198}
1199
1200#[cfg(test)]
1201mod tests {
1202    use super::*;
1203    use ic_core::codec::{hex, unhex};
1204
1205    #[test]
1206    fn curve_constants_are_correct() {
1207        // d = -121665 / 121666
1208        let d = Fe::from_u64(121_665)
1209            .neg()
1210            .mul(&Fe::from_u64(121_666).invert());
1211        assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1212        assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1213        // sqrt(-1) squares to -1.
1214        assert_eq!(
1215            hex(&SQRT_M1.square().to_bytes()),
1216            hex(&Fe::ONE.neg().to_bytes()),
1217            "sqrt(-1)"
1218        );
1219    }
1220
1221    #[test]
1222    fn basepoint_has_the_expected_coordinates() {
1223        let b = basepoint();
1224        // y = 4/5
1225        let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1226        let z_inv = b.z.invert();
1227        assert_eq!(
1228            hex(&b.y.mul(&z_inv).to_bytes()),
1229            hex(&expected_y.to_bytes())
1230        );
1231        assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1232    }
1233
1234    /// The dedicated doubling must agree with adding a point to itself.
1235    ///
1236    /// `add` is what RFC 8032's vectors validate, so it is the oracle here.
1237    /// The two formulas are different enough -- one reads `T`, the other does
1238    /// not -- that agreeing on the basepoint alone would not be convincing, so
1239    /// this walks a chain of multiples and doubles each one.
1240    #[test]
1241    fn doubling_agrees_with_adding_a_point_to_itself() {
1242        let mut p = basepoint();
1243        let mut checked = 0;
1244        for _ in 0..16 {
1245            assert_eq!(
1246                p.double().compress(),
1247                p.add(&p).compress(),
1248                "dedicated doubling and self-addition differ"
1249            );
1250            p = p.add(&basepoint());
1251            checked += 1;
1252        }
1253        assert_eq!(checked, 16, "the comparison did not run");
1254
1255        // The identity doubles to itself, which the formula has to get right
1256        // without a special case.
1257        assert_eq!(
1258            Point::IDENTITY.double().compress(),
1259            Point::IDENTITY.compress()
1260        );
1261    }
1262
1263    /// Projective equality must agree with comparing compressed encodings.
1264    ///
1265    /// The two answer the same question by different routes -- one divides by
1266    /// Z, the other cross-multiplies -- so agreement is the argument. It has to
1267    /// hold for equal points given *different* representatives, which is the
1268    /// case the whole optimisation rests on, so the test scales one side by a
1269    /// factor and checks it still compares equal.
1270    #[test]
1271    fn projective_equality_agrees_with_compressed_equality() {
1272        let b = basepoint();
1273        let mut points = std::vec![Point::IDENTITY, b];
1274        let mut p = b;
1275        for _ in 0..6 {
1276            p = p.double();
1277            points.push(p);
1278        }
1279
1280        let mut checked = 0;
1281        for (i, a) in points.iter().enumerate() {
1282            for (j, c) in points.iter().enumerate() {
1283                let projective = a.eq_projective(c);
1284                let compressed = a.compress() == c.compress();
1285                assert_eq!(
1286                    projective, compressed,
1287                    "projective and compressed equality differ for {i} vs {j}"
1288                );
1289                checked += 1;
1290            }
1291        }
1292        assert_eq!(checked, 64, "the comparison did not run");
1293
1294        // The case that matters: the same point with a different Z. Adding the
1295        // identity re-scales the representation without moving the point.
1296        let scaled = b.add(&Point::IDENTITY);
1297        assert!(b.eq_projective(&scaled), "equal points with different Z");
1298        assert_eq!(b.compress(), scaled.compress());
1299    }
1300
1301    #[test]
1302    fn group_law_is_consistent() {
1303        let b = basepoint();
1304        // P + 0 == P
1305        assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1306        // 2P via doubling equals 2P via scalar multiplication.
1307        let mut two = [0u8; 32];
1308        two[0] = 2;
1309        assert_eq!(
1310            hex(&b.double().compress()),
1311            hex(&b.mul_scalar(&two).compress())
1312        );
1313        // (P + P) + P == 3P
1314        let mut three = [0u8; 32];
1315        three[0] = 3;
1316        assert_eq!(
1317            hex(&b.double().add(&b).compress()),
1318            hex(&b.mul_scalar(&three).compress())
1319        );
1320    }
1321
1322    #[test]
1323    fn order_of_the_basepoint_is_l() {
1324        // [L]B must be the identity.
1325        assert_eq!(
1326            hex(&basepoint().mul_scalar(&scalar::L).compress()),
1327            hex(&Point::IDENTITY.compress())
1328        );
1329    }
1330
1331    #[test]
1332    fn compression_roundtrips() {
1333        let b = basepoint();
1334        for k in [1u8, 2, 3, 47, 200] {
1335            let mut s = [0u8; 32];
1336            s[0] = k;
1337            let p = b.mul_scalar(&s);
1338            let c = p.compress();
1339            let d = Point::decompress(&c).expect("valid point");
1340            assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1341        }
1342    }
1343
1344    #[test]
1345    fn decompression_rejects_non_curve_points() {
1346        // A y value with no corresponding x.
1347        let mut bad = [0u8; 32];
1348        bad[0] = 2;
1349        assert!(Point::decompress(&bad).is_none());
1350    }
1351
1352    /// RFC 8032 §7.1 test vectors.
1353    #[test]
1354    fn rfc8032_vectors() {
1355        let cases: [(&str, &str, &str, &str); 3] = [
1356            (
1357                "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1358                "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1359                "",
1360                "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1361            ),
1362            (
1363                "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1364                "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1365                "72",
1366                "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1367            ),
1368            (
1369                "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1370                "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1371                "af82",
1372                "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1373            ),
1374        ];
1375
1376        for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1377            let seed = unhex(seed_hex).unwrap();
1378            let msg = unhex(msg_hex).unwrap();
1379
1380            let mut pk = [0u8; 32];
1381            Ed25519::public_key(&seed, &mut pk).unwrap();
1382            assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1383
1384            let mut sig = [0u8; 64];
1385            Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1386            assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1387
1388            Ed25519::verify(&pk, &msg, &sig).unwrap();
1389        }
1390    }
1391
1392    #[test]
1393    fn verification_rejects_tampering() {
1394        let seed = [0x42u8; 32];
1395        let mut pk = [0u8; 32];
1396        Ed25519::public_key(&seed, &mut pk).unwrap();
1397        let mut sig = [0u8; 64];
1398        Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1399        Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1400
1401        // Wrong message.
1402        assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1403        // Corrupted R.
1404        let mut bad = sig;
1405        bad[0] ^= 1;
1406        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1407        // Corrupted S.
1408        let mut bad = sig;
1409        bad[40] ^= 1;
1410        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1411        // Wrong public key.
1412        let mut other_pk = [0u8; 32];
1413        Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1414        assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1415    }
1416
1417    /// A signature with `S >= L` must be rejected even though it would
1418    /// otherwise verify; this is the malleability check.
1419    #[test]
1420    fn rejects_non_canonical_s() {
1421        let seed = [0x42u8; 32];
1422        let mut pk = [0u8; 32];
1423        Ed25519::public_key(&seed, &mut pk).unwrap();
1424        let mut sig = [0u8; 64];
1425        Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1426
1427        // Add L to S. The verification equation still holds mod L, so only the
1428        // canonicality check can catch it.
1429        let mut carry = 0u16;
1430        for i in 0..32 {
1431            let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1432            sig[32 + i] = t as u8;
1433            carry = t >> 8;
1434        }
1435        assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1436    }
1437
1438    /// The cached key and the seed-only call must produce the same signature.
1439    ///
1440    /// They share a code path now, which is the point -- but that is the sort
1441    /// of thing a later refactor separates again, and the two would then differ
1442    /// only for callers who use one and verify with the other. RFC 8032's
1443    /// vectors exercise the trait method alone and would not notice.
1444    #[test]
1445    fn the_cached_key_signs_identically_to_the_seed() {
1446        let mut checked = 0;
1447        for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1448            for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1449                let mut from_seed = [0u8; 64];
1450                Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1451
1452                let key = Ed25519Key::from_seed(&seed).unwrap();
1453                let mut from_key = [0u8; 64];
1454                key.sign(message, &mut from_key).unwrap();
1455
1456                assert_eq!(from_seed, from_key, "the two signing paths diverged");
1457
1458                // And the cached public key is the one the trait derives.
1459                let mut derived = [0u8; 32];
1460                Ed25519::public_key(&seed, &mut derived).unwrap();
1461                assert_eq!(&derived, key.public_key());
1462
1463                // Both verify, so neither is consistently wrong.
1464                Ed25519::verify(&derived, message, &from_key).unwrap();
1465                checked += 1;
1466            }
1467        }
1468        assert_eq!(checked, 9, "the comparison did not run");
1469    }
1470
1471    /// Scalars that exercise the signed radix-16 recoding at its edges: digits
1472    /// of exactly 8, which carry; runs of 7 and 9 either side of that; the
1473    /// largest value the recoding accepts; the group order less one; and the
1474    /// shape of a clamped secret.
1475    fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
1476        let mut one = [0u8; 32];
1477        one[0] = 1;
1478        let mut eight = [0u8; 32];
1479        eight[0] = 8;
1480        let mut top = [0xffu8; 32];
1481        top[31] = 0x7f;
1482        let mut clamped = [0x9du8; 32];
1483        clamped[0] &= 248;
1484        clamped[31] &= 127;
1485        clamped[31] |= 64;
1486        let mut l_minus_1 = scalar::L;
1487        l_minus_1[0] -= 1;
1488        let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
1489        for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
1490            let mut s = [fill; 32];
1491            s[31] &= 0x7f;
1492            out.push(s);
1493        }
1494        out
1495    }
1496
1497    /// The `no_std` signing path against the bit-at-a-time ladder, on the
1498    /// basepoint and on a point that is not.
1499    ///
1500    /// Tests run with `std`, where signing takes the precomputed table, so
1501    /// without this the windowed path would be compiled into embedded builds
1502    /// and executed by nothing.
1503    /// Every coordinate, not just the compressed form: a wrong `T` still
1504    /// compresses correctly, since compression reads only `X`, `Y` and `Z`,
1505    /// and would corrupt every addition that reads it.
1506    #[test]
1507    fn the_basepoint_constant_is_the_decompressed_encoding() {
1508        let decoded = Point::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
1509        let zinv = decoded.z.invert();
1510        for (name, constant, from_encoding) in [
1511            ("x", BASEPOINT.x, decoded.x.mul(&zinv)),
1512            ("y", BASEPOINT.y, decoded.y.mul(&zinv)),
1513            ("t", BASEPOINT.t, decoded.t.mul(&zinv)),
1514        ] {
1515            assert_eq!(
1516                constant.to_bytes(),
1517                from_encoding.to_bytes(),
1518                "{name} differs"
1519            );
1520        }
1521        assert_eq!(BASEPOINT.z.to_bytes(), Fe::ONE.to_bytes());
1522        assert_eq!(BASEPOINT.compress(), BASEPOINT_COMPRESSED);
1523    }
1524
1525    #[test]
1526    fn the_windowed_multiplication_agrees_with_the_ladder() {
1527        let b = basepoint();
1528        let mut seven = [0u8; 32];
1529        seven[0] = 7;
1530        let p = b.mul_scalar(&seven);
1531
1532        let mut checked = 0;
1533        for point in [b, p] {
1534            for scalar in windowed_scalars() {
1535                assert_eq!(
1536                    mul_scalar_windowed(&point, &scalar).compress(),
1537                    point.mul_scalar(&scalar).compress(),
1538                    "windowed and ladder differ for {scalar:02x?}"
1539                );
1540                checked += 1;
1541            }
1542        }
1543        assert!(checked >= 20, "only {checked} comparisons ran");
1544    }
1545
1546    /// The `no_std` verification path against the tabled one and against two
1547    /// separate ladders, which share no code with either.
1548    #[test]
1549    fn the_untabled_double_multiplication_agrees() {
1550        let b = basepoint();
1551        let mut checked = 0;
1552        for (i, k) in windowed_scalars().into_iter().enumerate() {
1553            let mut seed = [0u8; 32];
1554            seed[0] = 3 + i as u8;
1555            let a = b.mul_scalar(&seed);
1556            for s in [k, [0xffu8; 32], [0x9du8; 32]] {
1557                let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
1558                let tabled = double_scalar_mul_vartime(&a, &k, &s);
1559                let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
1560                assert_eq!(
1561                    untabled.compress(),
1562                    tabled.compress(),
1563                    "k={k:02x?} s={s:02x?}"
1564                );
1565                assert_eq!(
1566                    untabled.compress(),
1567                    ladders.compress(),
1568                    "k={k:02x?} s={s:02x?}"
1569                );
1570                checked += 1;
1571            }
1572        }
1573        assert!(checked >= 30, "only {checked} comparisons ran");
1574    }
1575
1576    /// The recoding must represent the scalar, with the digits it promises.
1577    #[test]
1578    fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1579        for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1580            let naf = wnaf(&scalar, 5);
1581
1582            let mut previous_nonzero: Option<usize> = None;
1583            for (i, d) in naf.iter().enumerate() {
1584                if *d == 0 {
1585                    continue;
1586                }
1587                assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1588                assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1589                if let Some(j) = previous_nonzero {
1590                    assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1591                }
1592                previous_nonzero = Some(i);
1593            }
1594
1595            // And it evaluates back to the scalar, modulo a small prime that
1596            // has nothing to do with the curve.
1597            const M: u128 = 1_000_000_007;
1598            let mut from_digits = 0u128;
1599            let mut power = 1u128;
1600            for d in naf {
1601                let term = ((d as i128).rem_euclid(M as i128)) as u128;
1602                from_digits = (from_digits + term * power) % M;
1603                power = power * 2 % M;
1604            }
1605            let mut from_bytes = 0u128;
1606            let mut p = 1u128;
1607            for byte in scalar {
1608                from_bytes = (from_bytes + (byte as u128) * p) % M;
1609                p = p * 256 % M;
1610            }
1611            assert_eq!(from_digits, from_bytes, "recoding changed the value");
1612        }
1613    }
1614
1615    #[test]
1616    fn signing_is_deterministic() {
1617        let seed = [0x7fu8; 32];
1618        let mut a = [0u8; 64];
1619        let mut b = [0u8; 64];
1620        Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1621        Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1622        assert_eq!(a, b);
1623    }
1624
1625    #[test]
1626    fn rejects_wrong_lengths() {
1627        let mut out = [0u8; 32];
1628        assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1629        assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1630        assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1631    }
1632
1633    #[test]
1634    fn self_test_passes() {
1635        Ed25519::self_test().unwrap();
1636    }
1637
1638    /// A few points on the curve, for the formula tests below.
1639    fn sample_points(n: usize) -> Vec<Point> {
1640        let mut out = Vec::new();
1641        let mut p = basepoint();
1642        for _ in 0..n {
1643            out.push(p);
1644            p = p.double().add(&basepoint());
1645        }
1646        out
1647    }
1648
1649    /// The completed-coordinate doubling is the extended one.
1650    ///
1651    /// `Projective::double` produces four squarings and no multiplications,
1652    /// and the multiplications a doubling needs move into whichever conversion
1653    /// follows. That is only sound if the two routes agree, and the signs are
1654    /// where it would go wrong: the completed form this uses differs from the
1655    /// one the extended formula implies by a factor of -1 in two coordinates,
1656    /// which cancels projectively and would not cancel if one of them were
1657    /// dropped.
1658    #[test]
1659    fn the_completed_doubling_agrees_with_the_extended_one() {
1660        for p in sample_points(40) {
1661            let want = p.double();
1662            let got = p.to_projective().double().to_extended();
1663            assert!(got.eq_projective(&want), "doubling disagrees");
1664            // And through the projective form, which is the route a chain of
1665            // doublings actually takes.
1666            let chained = p.to_projective().double().to_projective().double();
1667            let twice = p.double().double();
1668            assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1669        }
1670    }
1671
1672    /// Niels addition is the nine-multiplication addition.
1673    #[test]
1674    fn niels_addition_agrees_with_the_general_one() {
1675        let pts = sample_points(20);
1676        for p in &pts {
1677            for q in &pts {
1678                let want = p.add(q);
1679                let got = p.add_niels(&q.to_niels()).to_extended();
1680                assert!(got.eq_projective(&want), "add_niels disagrees");
1681
1682                let want_sub = p.add(&q.negate());
1683                let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1684                assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1685            }
1686        }
1687    }
1688
1689    /// Affine-Niels addition is the general addition.
1690    ///
1691    /// It drops the `Z` multiply on the assumption that the stored point has
1692    /// `Z = 1`, which `to_affine_niels` arranges by inverting. If that
1693    /// inversion or the `2d·x·y` were wrong the result would still be a point
1694    /// on the curve, just the wrong one, so it is checked against the addition
1695    /// the published vectors validate.
1696    #[test]
1697    fn affine_niels_addition_agrees_with_the_general_one() {
1698        let pts = sample_points(20);
1699        for p in &pts {
1700            for q in &pts {
1701                let want = p.add(q);
1702                let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1703                assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1704
1705                // And the negated form, which the table's sign handling uses.
1706                let mut n = q.to_affine_niels();
1707                n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1708                let want_neg = p.add(&q.negate());
1709                let got_neg = p.add_affine_niels(&n).to_extended();
1710                assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1711            }
1712        }
1713    }
1714
1715    /// Where verification's time actually goes.
1716    ///
1717    /// Ignored: it is a measurement, not an assertion. Run it with
1718    /// `cargo test -p ic-ec --release -- --ignored --nocapture where_verify_spends`
1719    /// before changing anything here, because the answer decided what was
1720    /// worth doing and a guess would not have.
1721    #[test]
1722    #[ignore = "diagnostic, not a test"]
1723    fn where_verify_spends_its_time() {
1724        use std::time::Instant;
1725
1726        let seed = [7u8; 32];
1727        let key = Ed25519Key::from_seed(&seed).unwrap();
1728        let msg = b"benchmark message";
1729        let mut sig = [0u8; 64];
1730        key.sign(msg, &mut sig).unwrap();
1731        let pk = *key.public_key();
1732
1733        let mut big_r = [0u8; 32];
1734        big_r.copy_from_slice(&sig[..32]);
1735        let mut s_sc = [0u8; 32];
1736        s_sc.copy_from_slice(&sig[32..]);
1737
1738        let n = 2000;
1739        let time = |label: &str, f: &mut dyn FnMut()| {
1740            let mut best = f64::INFINITY;
1741            for _ in 0..5 {
1742                let t = Instant::now();
1743                for _ in 0..n {
1744                    f();
1745                }
1746                let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1747                if e < best {
1748                    best = e;
1749                }
1750            }
1751            println!("  {label:<34} {best:>9.2} us");
1752            best
1753        };
1754
1755        let a_point = Point::decompress(&pk).unwrap();
1756        let k = hash_to_scalar(&[&big_r, &pk, msg]);
1757
1758        println!(
1759            "
1760ed25519 verify, cost breakdown:"
1761        );
1762        let d = time("decompress (x2 per verify)", &mut || {
1763            core::hint::black_box(Point::decompress(&pk));
1764        });
1765        let h = time("hash_to_scalar", &mut || {
1766            core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1767        });
1768        let b = time("mul_basepoint (const time)", &mut || {
1769            core::hint::black_box(mul_basepoint(&s_sc));
1770        });
1771        let v = time("double_scalar_mul_vartime", &mut || {
1772            core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1773        });
1774        time("  of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1775            core::hint::black_box(wnaf(&k, 5));
1776            core::hint::black_box(wnaf(&s_sc, 8));
1777        });
1778        time("  of which: odd_a table build", &mut || {
1779            let twice = a_point.double();
1780            let mut odd = [a_point; 8];
1781            for i in 1..8 {
1782                odd[i] = odd[i - 1].add(&twice);
1783            }
1784            let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1785            core::hint::black_box(t);
1786        });
1787        time("  of which: 255 doublings", &mut || {
1788            let mut p = a_point;
1789            for _ in 0..255 {
1790                p = p.double();
1791            }
1792            core::hint::black_box(p);
1793        });
1794        time("  of which: 79 additions", &mut || {
1795            let mut p = a_point;
1796            for _ in 0..79 {
1797                p = p.add(&a_point);
1798            }
1799            core::hint::black_box(p);
1800        });
1801        time("compress (one inversion)", &mut || {
1802            core::hint::black_box(a_point.compress());
1803        });
1804        println!(
1805            "  {:<34} {:>9.2} us",
1806            "-- accounted for",
1807            2.0 * d + h + b + v
1808        );
1809
1810        // One level down: if the point ops are slow, the field ops are why.
1811        println!(
1812            "
1813field and point primitives, nanoseconds:"
1814        );
1815        let nn = 200_000;
1816        let ns = |label: &str, f: &mut dyn FnMut()| {
1817            let mut best = f64::INFINITY;
1818            for _ in 0..5 {
1819                let t = Instant::now();
1820                for _ in 0..nn {
1821                    f();
1822                }
1823                let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1824                if e < best {
1825                    best = e;
1826                }
1827            }
1828            println!("  {label:<34} {best:>9.2} ns");
1829        };
1830        let fx = a_point.x;
1831        let fy = a_point.y;
1832        ns("Fe::mul", &mut || {
1833            core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1834        });
1835        ns("Fe::square", &mut || {
1836            core::hint::black_box(core::hint::black_box(&fx).square());
1837        });
1838        ns("Fe::add", &mut || {
1839            core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1840        });
1841        ns("Fe::sub", &mut || {
1842            core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1843        });
1844        ns("Fe::neg", &mut || {
1845            core::hint::black_box(core::hint::black_box(&fx).neg());
1846        });
1847        let proj = a_point.to_projective();
1848        let comp = proj.double();
1849        let an = a_point.to_affine_niels();
1850        ns("Projective::double  (4S)", &mut || {
1851            core::hint::black_box(core::hint::black_box(&proj).double());
1852        });
1853        ns("Projective::double_projective", &mut || {
1854            core::hint::black_box(core::hint::black_box(proj).double_projective());
1855        });
1856        ns("Completed::to_projective (3M)", &mut || {
1857            core::hint::black_box(core::hint::black_box(&comp).to_projective());
1858        });
1859        ns("Completed::to_extended (4M)", &mut || {
1860            core::hint::black_box(core::hint::black_box(&comp).to_extended());
1861        });
1862        ns("Point::add_affine_niels (3M)", &mut || {
1863            core::hint::black_box(
1864                core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1865            );
1866        });
1867        ns("Point::double", &mut || {
1868            core::hint::black_box(core::hint::black_box(&a_point).double());
1869        });
1870        ns("Point::add", &mut || {
1871            core::hint::black_box(
1872                core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1873            );
1874        });
1875    }
1876
1877    /// How many point operations a verification actually performs.
1878    #[test]
1879    #[ignore = "diagnostic, not a test"]
1880    fn count_the_point_operations() {
1881        let mut doublings = 0usize;
1882        let mut adds_a = 0usize;
1883        let mut adds_b = 0usize;
1884        let mut state = 0x1234_5678_9abc_def0u64;
1885        let trials = 200;
1886        for _ in 0..trials {
1887            let mut kb = [0u8; 32];
1888            for c in kb.chunks_exact_mut(8) {
1889                state ^= state >> 12;
1890                state ^= state << 25;
1891                state ^= state >> 27;
1892                c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1893            }
1894            kb[31] &= 0x0f;
1895            let na = wnaf(&kb, 5);
1896            let nb = wnaf(&kb, 8);
1897            let mut i = 257;
1898            while i > 0 && na[i] == 0 && nb[i] == 0 {
1899                i -= 1;
1900            }
1901            doublings += i + 1;
1902            adds_a += na.iter().filter(|d| **d != 0).count();
1903            adds_b += nb.iter().filter(|d| **d != 0).count();
1904        }
1905        let d = doublings as f64 / trials as f64;
1906        let aa = adds_a as f64 / trials as f64;
1907        let ab = adds_b as f64 / trials as f64;
1908        println!(
1909            "
1910  per double-scalar multiplication, averaged over {trials} scalars:"
1911        );
1912        println!("    doublings                  {d:>8.1}");
1913        println!("    additions, w=5 table (A)   {aa:>8.1}");
1914        println!("    additions, w=8 table (B)   {ab:>8.1}");
1915        println!("    additions, building A      {:>8.1}", 8.0);
1916        println!("    ---");
1917        println!("    total additions            {:>8.1}", aa + ab + 8.0);
1918        println!(
1919            "    field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1920            d * 8.0 + (aa + ab + 8.0) * 9.0
1921        );
1922        println!(
1923            "    the same at dalek's 3M+4S and 7M:                      {:>6.0}",
1924            d * 7.0 + (aa + ab + 8.0) * 7.0
1925        );
1926    }
1927}