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