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