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
62impl Point {
63    /// The neutral element `(0, 1)`.
64    pub const IDENTITY: Point = Point {
65        x: Fe::ZERO,
66        y: Fe::ONE,
67        z: Fe::ONE,
68        t: Fe::ZERO,
69    };
70
71    /// The complete `add-2008-hwcd-3` group law for `a = -1`.
72    pub fn add(&self, other: &Point) -> Point {
73        let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
74        let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
75        let c = self.t.mul(&D2).mul(&other.t);
76        let d = self.z.mul(&other.z);
77        let d = d.add(&d);
78
79        let e = b.sub(&a);
80        let f = d.sub(&c);
81        let g = d.add(&c);
82        let h = b.add(&a);
83
84        Point {
85            x: e.mul(&f),
86            y: g.mul(&h),
87            t: e.mul(&h),
88            z: f.mul(&g),
89        }
90    }
91
92    /// Point doubling, `dbl-2008-hwcd` for `a = -1`.
93    ///
94    /// Adding a point to itself works and was what this did, but the general
95    /// addition costs nine multiplications and needs both operands' `T`. The
96    /// dedicated formula is four multiplications and four squarings, and does
97    /// not read `T` at all -- doubling is a function of `X`, `Y` and `Z` alone.
98    ///
99    /// Worth the separate formula because scalar multiplication is doublings
100    /// almost entirely: the non-adjacent form leaves about forty additions
101    /// against two hundred and fifty-six doublings.
102    pub fn double(&self) -> Point {
103        let aa = self.x.square();
104        let bb = self.y.square();
105        let c = self.z.square();
106        let c = c.add(&c);
107        // a = -1, so D = a*A = -A.
108        let d = aa.neg();
109        // E = (X+Y)^2 - A - B, which is 2*X*Y without a multiplication.
110        let xy = self.x.add(&self.y);
111        let e = xy.square().sub(&aa).sub(&bb);
112        let g = d.add(&bb);
113        let f = g.sub(&c);
114        let h = d.sub(&bb);
115
116        Point {
117            x: e.mul(&f),
118            y: g.mul(&h),
119            t: e.mul(&h),
120            z: f.mul(&g),
121        }
122    }
123
124    /// Constant-time conditional move.
125    /// Negate in place when `choice` is set.
126    ///
127    /// On a twisted Edwards curve `-(x, y, z, t)` is `(-x, y, z, -t)`, so this
128    /// is two field negations and a pair of conditional moves. Used by the
129    /// signed-digit basepoint table, which stores only positive multiples.
130    fn conditional_negate(&mut self, choice: Choice) {
131        let nx = self.x.neg();
132        let nt = self.t.neg();
133        Fe::cmov(&mut self.x, &nx, choice);
134        Fe::cmov(&mut self.t, &nt, choice);
135    }
136
137    fn cmov(&mut self, other: &Point, choice: Choice) {
138        Fe::cmov(&mut self.x, &other.x, choice);
139        Fe::cmov(&mut self.y, &other.y, choice);
140        Fe::cmov(&mut self.z, &other.z, choice);
141        Fe::cmov(&mut self.t, &other.t, choice);
142    }
143
144    /// Scalar multiplication, constant-time in the scalar.
145    ///
146    /// Every iteration performs a doubling *and* an addition, selecting between
147    /// the two results with a conditional move, so the instruction trace is
148    /// identical for every scalar.
149    pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
150        let mut acc = Point::IDENTITY;
151        for i in (0..256).rev() {
152            acc = acc.double();
153            let sum = acc.add(self);
154            let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
155            acc.cmov(&sum, bit);
156        }
157        acc
158    }
159
160    /// Negate: `-(x, y, z, t)` is `(-x, y, z, -t)`.
161    fn negate(&self) -> Point {
162        Point {
163            x: self.x.neg(),
164            y: self.y,
165            z: self.z,
166            t: self.t.neg(),
167        }
168    }
169
170    /// Scalar multiplication that is **not** constant time.
171    ///
172    /// # When this is allowed
173    ///
174    /// Only on values an attacker already has. Verification is the case: the
175    /// signature, the public key and the message are all public, so there is no
176    /// secret whose timing could leak, and the constant-time ladder buys
177    /// nothing there but work. Signing must never call this -- the scalar is
178    /// derived from the seed.
179    ///
180    /// # What it does instead
181    ///
182    /// A width-5 non-adjacent form. Recoding the scalar into signed odd digits
183    /// leaves roughly one position in six non-zero, so the additions drop from
184    /// one per bit to about forty in total; the doublings remain, because an
185    /// arbitrary point has no precomputed table to take them away. Only odd
186    /// multiples are stored, eight of them, since a negative digit negates on
187    /// the way out.
188    ///
189    /// The saving is real but bounded: the doublings dominate and they cannot
190    /// be avoided here. The basepoint half of verification is the one that got
191    /// a table.
192    pub fn mul_scalar_vartime(&self, scalar: &[u8; 32]) -> Point {
193        // 1P, 3P, 5P .. 15P.
194        let twice = self.double();
195        let mut odd = [*self; 8];
196        for i in 1..8 {
197            odd[i] = odd[i - 1].add(&twice);
198        }
199
200        let naf = wnaf5(scalar);
201        let mut acc = Point::IDENTITY;
202        for digit in naf.iter().rev() {
203            acc = acc.double();
204            if *digit != 0 {
205                // digit is odd and in [-15, 15]; |digit|/2 indexes the table.
206                let entry = &odd[(digit.unsigned_abs() as usize) / 2];
207                acc = if *digit > 0 {
208                    acc.add(entry)
209                } else {
210                    acc.add(&entry.negate())
211                };
212            }
213        }
214        acc
215    }
216
217    /// Whether two points are the same, without leaving projective space.
218    ///
219    /// `(X : Y : Z)` stands for the affine point `(X/Z, Y/Z)`, so two are equal
220    /// exactly when `X1*Z2 == X2*Z1` and `Y1*Z2 == Y2*Z1`. That is four
221    /// multiplications.
222    ///
223    /// The obvious alternative is to compress both and compare the bytes, and
224    /// that is what verification used to do -- but compression divides by `Z`,
225    /// and a division here is an exponentiation: roughly two hundred and fifty
226    /// squarings each, five hundred to answer a question four multiplications
227    /// settle.
228    ///
229    /// `to_bytes` is used only to canonicalise the two sides before comparing,
230    /// which costs a carry chain and no inversion.
231    fn eq_projective(&self, other: &Point) -> bool {
232        self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
233            && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
234    }
235
236    /// Compress to the 32-byte RFC 8032 encoding.
237    pub fn compress(&self) -> [u8; 32] {
238        let z_inv = self.z.invert();
239        let x = self.x.mul(&z_inv);
240        let y = self.y.mul(&z_inv);
241        let mut out = y.to_bytes();
242        // The sign of x rides in the top bit.
243        out[31] |= x.is_negative().unwrap_u8() << 7;
244        out
245    }
246
247    /// Decompress a 32-byte encoding, rejecting non-curve points.
248    pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
249        let sign = Choice::from_u8(bytes[31] >> 7);
250        let mut y_bytes = *bytes;
251        y_bytes[31] &= 0x7f;
252        let y = Fe::from_bytes(&y_bytes);
253
254        // Solve x^2 = (y^2 - 1) / (d*y^2 + 1).
255        let y2 = y.square();
256        let u = y2.sub(&Fe::ONE);
257        let v = y2.mul(&D).add(&Fe::ONE);
258
259        // x = u*v^3 * (u*v^7)^((p-5)/8)
260        let v3 = v.square().mul(&v);
261        let v7 = v3.square().mul(&v);
262        let mut x = u.mul(&v3).mul(&u.mul(&v7).pow22523());
263
264        let check = v.mul(&x.square());
265        let correct = check.ct_eq(&u);
266        let flipped = check.ct_eq(&u.neg());
267        if !bool::from(correct.or(flipped)) {
268            // No square root exists: the encoding is not a curve point.
269            return None;
270        }
271        // When only the flipped case matched, multiply by sqrt(-1).
272        let alt = x.mul(&SQRT_M1);
273        Fe::cmov(&mut x, &alt, flipped.and(correct.not()));
274
275        // x = 0 with a set sign bit is the one non-canonical encoding.
276        if bool::from(x.is_zero()) && bool::from(sign) {
277            return None;
278        }
279        // Match the requested sign.
280        let neg = x.neg();
281        let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
282        Fe::cmov(&mut x, &neg, wrong_sign);
283
284        Some(Point {
285            x,
286            y,
287            z: Fe::ONE,
288            t: x.mul(&y),
289        })
290    }
291}
292
293/// The Ed25519 base point.
294/// `scalar * B`, through the precomputed table where there is one.
295///
296/// Every basepoint multiplication in this module goes through here rather than
297/// calling `mul_scalar` on the basepoint directly, so the two paths cannot
298/// drift apart and a caller cannot accidentally take the slow one.
299fn mul_basepoint(scalar: &[u8; 32]) -> Point {
300    #[cfg(feature = "std")]
301    {
302        basepoint_table::table().mul(scalar)
303    }
304    #[cfg(not(feature = "std"))]
305    {
306        basepoint().mul_scalar(scalar)
307    }
308}
309
310/// `[k]A + [s]B`, in one pass, variable time in both scalars.
311///
312/// Verification needs two scalar multiplications and then compares the
313/// results. Done separately that is two independent runs of doublings -- and
314/// the doublings are the whole cost, some two hundred and fifty-five of them
315/// against forty-odd additions. Run together they are shared: one chain of
316/// doublings, with each scalar contributing an addition at the positions where
317/// its own recoding is non-zero.
318///
319/// The basepoint half also stops paying for constant time here. `mul_basepoint`
320/// selects a table entry by reading all eight and moving conditionally, because
321/// a signing scalar is secret. Nothing in a verification is: the signature, the
322/// public key and the message are all in the clear, so the table is indexed
323/// directly and the window widened to eight, which is a table built once and
324/// about a third as many additions.
325///
326/// Both properties are why this is not the function signing calls.
327#[cfg(feature = "std")]
328fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
329    // 1A, 3A, 5A .. 15A, built for this call.
330    let twice = a.double();
331    let mut odd_a = [*a; 8];
332    for i in 1..8 {
333        odd_a[i] = odd_a[i - 1].add(&twice);
334    }
335    let odd_b = basepoint_table::odd_multiples();
336
337    let naf_a = wnaf(k, 5);
338    let naf_b = wnaf(s, 8);
339
340    // Start at the highest position either recoding reaches, so the leading
341    // doublings of the identity are skipped.
342    let mut i = 257;
343    while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
344        i -= 1;
345    }
346
347    let mut acc = Point::IDENTITY;
348    loop {
349        acc = acc.double();
350        if naf_a[i] != 0 {
351            let e = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
352            acc = if naf_a[i] > 0 {
353                acc.add(e)
354            } else {
355                acc.add(&e.negate())
356            };
357        }
358        if naf_b[i] != 0 {
359            let e = &odd_b[(naf_b[i].unsigned_abs() as usize) / 2];
360            acc = if naf_b[i] > 0 {
361                acc.add(e)
362            } else {
363                acc.add(&e.negate())
364            };
365        }
366        if i == 0 {
367            break;
368        }
369        i -= 1;
370    }
371    acc
372}
373
374fn basepoint() -> Point {
375    // The encoding is a compile-time constant and is known to be valid, so the
376    // decompression cannot fail.
377    Point::decompress(&BASEPOINT_COMPRESSED).unwrap_or(Point::IDENTITY)
378}
379
380/// RFC 8032 Ed25519 (PureEdDSA over Curve25519 with SHA-512).
381pub struct Ed25519;
382
383impl Algorithm for Ed25519 {
384    const ID: &'static str = "ed25519";
385    const NAME: &'static str = "Ed25519";
386}
387
388/// Expand a 32-byte seed into the clamped scalar and the nonce prefix.
389fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
390    let h = Sha512::digest(seed);
391    let mut a = [0u8; 32];
392    let mut prefix = [0u8; 32];
393    a.copy_from_slice(&h.as_ref()[..32]);
394    prefix.copy_from_slice(&h.as_ref()[32..]);
395    a[0] &= 248;
396    a[31] &= 127;
397    a[31] |= 64;
398    (a, prefix)
399}
400
401/// Width-5 non-adjacent form of a 256-bit scalar.
402///
403/// Each non-zero digit is odd and lies in `[-15, 15]`, and no two non-zero
404/// digits are adjacent, which is what keeps the density near one in six. The
405/// array has room to run past the top of the scalar.
406///
407/// Variable time by construction: the loop length and the digit pattern depend
408/// on the scalar. See [`Point::mul_scalar_vartime`] for when that is allowed.
409fn wnaf5(scalar: &[u8; 32]) -> [i8; 258] {
410    wnaf(scalar, 5)
411}
412
413/// Width-`w` non-adjacent form of a 256-bit scalar.
414///
415/// Each non-zero digit is odd and lies in `[-(2^(w-1) - 1), 2^(w-1) - 1]`, and
416/// no two non-zero digits are within `w` places of each other, which puts the
417/// density near `1/(w+1)`. A wider window means fewer additions and a bigger
418/// table: width 5 for an arbitrary point, whose table has to be built on the
419/// spot, and width 8 for the basepoint, whose table is built once.
420///
421/// `w` must be at most 8, so that every digit fits an `i8`.
422fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
423    debug_assert!((2..=8).contains(&w), "window width out of range");
424    let half = 1i64 << (w - 1);
425    let full = 1i64 << w;
426    let mask = (full - 1) as u64;
427
428    let mut naf = [0i8; 258];
429    // Five limbs for a four-limb scalar. A negative digit adds to `k`, and for
430    // a scalar near 2^256 that carries out of the top: on four limbs it wraps
431    // to zero, the loop stops early, and the representation is silently short.
432    // The scalars that reach this are reduced modulo the group order and could
433    // not trigger it -- which is exactly the assumption that was wrong for the
434    // NIST recoding, so the room is given rather than argued for.
435    let mut k = [0u64; 5];
436    for (i, limb) in k.iter_mut().take(4).enumerate() {
437        let mut b = [0u8; 8];
438        b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
439        *limb = u64::from_le_bytes(b);
440    }
441
442    let mut i = 0;
443    while k.iter().any(|&x| x != 0) {
444        if k[0] & 1 == 1 {
445            let mut d = (k[0] & mask) as i64;
446            if d >= half {
447                d -= full;
448            }
449            naf[i] = d as i8;
450            if d > 0 {
451                sub_u64(&mut k, d as u64);
452            } else {
453                add_u64(&mut k, d.unsigned_abs());
454            }
455        }
456        shr1(&mut k);
457        i += 1;
458    }
459    naf
460}
461
462/// `k -= v`, for `v` small enough not to borrow past the top.
463fn sub_u64(k: &mut [u64; 5], v: u64) {
464    let (d, mut borrow) = k[0].overflowing_sub(v);
465    k[0] = d;
466    for limb in k.iter_mut().skip(1) {
467        if !borrow {
468            break;
469        }
470        let (d, b) = limb.overflowing_sub(1);
471        *limb = d;
472        borrow = b;
473    }
474}
475
476/// `k += v`, for `v` small enough not to carry past the top.
477fn add_u64(k: &mut [u64; 5], v: u64) {
478    let (d, mut carry) = k[0].overflowing_add(v);
479    k[0] = d;
480    for limb in k.iter_mut().skip(1) {
481        if !carry {
482            break;
483        }
484        let (d, c) = limb.overflowing_add(1);
485        *limb = d;
486        carry = c;
487    }
488}
489
490/// `k >>= 1`.
491fn shr1(k: &mut [u64; 5]) {
492    for i in 0..4 {
493        k[i] = (k[i] >> 1) | (k[i + 1] << 63);
494    }
495    k[4] >>= 1;
496}
497
498/// `SHA-512(parts...)` reduced modulo the group order.
499fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
500    let mut h = Sha512::new();
501    for p in parts {
502        h.update(p);
503    }
504    let digest = h.finalize();
505    let mut wide = [0u8; 64];
506    wide.copy_from_slice(digest.as_ref());
507    scalar::reduce_wide(&wide)
508}
509
510/// A signing key with its public key already derived.
511///
512/// # Why this exists
513///
514/// RFC 8032 signing needs the public key: it goes into the hash that produces
515/// `k`. [`Ed25519::sign`] takes only the 32-byte seed, so it has to derive the
516/// public key on every call -- a second basepoint multiplication, and with the
517/// table in place that is most of what a signature now costs.
518///
519/// A key that is used more than once should derive it once. That is what a TLS
520/// server does with a certificate key, and what dalek's `SigningKey` does,
521/// which is why comparing `Ed25519::sign` against it was comparing two
522/// different amounts of work.
523///
524/// The trait method still exists and still takes a seed. This changes nothing
525/// for a caller signing once; it halves the cost for a caller signing twice.
526pub struct Ed25519Key {
527    /// The clamped scalar from the seed's hash.
528    scalar: [u8; 32],
529    /// The second half of that hash, which seeds the deterministic nonce.
530    prefix: [u8; 32],
531    /// `scalar * B`, compressed. Derived once, here.
532    public: [u8; 32],
533}
534
535impl Drop for Ed25519Key {
536    fn drop(&mut self) {
537        self.scalar.zeroize();
538        self.prefix.zeroize();
539        // `public` is public, and is left alone.
540    }
541}
542
543impl Ed25519Key {
544    /// Expand a 32-byte seed and derive its public key.
545    pub fn from_seed(seed: &[u8]) -> Result<Self> {
546        ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
547        let (scalar, prefix) = expand_seed(seed);
548        let public = mul_basepoint(&scalar).compress();
549        Ok(Self {
550            scalar,
551            prefix,
552            public,
553        })
554    }
555
556    /// The public key, already derived.
557    pub fn public_key(&self) -> &[u8; 32] {
558        &self.public
559    }
560
561    /// Sign `message`, performing one basepoint multiplication rather than two.
562    pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
563        ensure!(
564            signature.len() == 64,
565            InvalidLength,
566            "ed25519 signature buffer"
567        );
568
569        // r = H(prefix || M), deterministic -- Ed25519 needs no RNG at signing
570        // time, which removes an entire class of nonce-reuse failures.
571        let mut r = hash_to_scalar(&[&self.prefix, message]);
572        let big_r = mul_basepoint(&r).compress();
573
574        let k = hash_to_scalar(&[&big_r, &self.public, message]);
575        let s = scalar::mul_add(&k, &self.scalar, &r);
576
577        signature[..32].copy_from_slice(&big_r);
578        signature[32..].copy_from_slice(&s);
579        r.zeroize();
580        Ok(())
581    }
582}
583
584impl SignatureScheme for Ed25519 {
585    const PRIVATE_KEY_LEN: usize = 32;
586    const PUBLIC_KEY_LEN: usize = 32;
587    const SIGNATURE_LEN: usize = 64;
588
589    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
590        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
591        ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
592        let (mut a, mut prefix) = expand_seed(private_key);
593        out.copy_from_slice(&mul_basepoint(&a).compress());
594        a.zeroize();
595        prefix.zeroize();
596        Ok(())
597    }
598
599    fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
600        ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
601        ensure!(
602            signature.len() == 64,
603            InvalidLength,
604            "ed25519 signature buffer"
605        );
606
607        // One shot: expand, derive the public key, sign, discard. A caller
608        // signing more than once should hold an `Ed25519Key` instead and pay
609        // the derivation once.
610        Ed25519Key::from_seed(private_key)?.sign(message, signature)
611    }
612
613    fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
614        ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
615        ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
616
617        let mut big_r = [0u8; 32];
618        big_r.copy_from_slice(&signature[..32]);
619        let mut s = [0u8; 32];
620        s.copy_from_slice(&signature[32..]);
621
622        // RFC 8032 §5.1.7: reject a non-canonical S. Without this check the
623        // signature is malleable, and any system that treats a signature as a
624        // unique identifier becomes attackable.
625        ensure!(
626            scalar::is_canonical(&s),
627            MalformedEncoding,
628            "ed25519 signature S is not reduced"
629        );
630
631        let mut pk_bytes = [0u8; 32];
632        pk_bytes.copy_from_slice(public_key);
633        let a_point = Point::decompress(&pk_bytes).ok_or(ic_core::err!(
634            MalformedEncoding,
635            "ed25519 public key is not on the curve"
636        ))?;
637        let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
638            MalformedEncoding,
639            "ed25519 signature R is not on the curve"
640        ))?;
641
642        let k = hash_to_scalar(&[&big_r, &pk_bytes, message]);
643
644        // The check is [S]B == R + [k]A, rearranged to [S]B + [k](-A) == R so
645        // that both multiplications are one interleaved pass sharing a single
646        // chain of doublings. Negating A is two field negations; running the
647        // two scalars separately would be a second two hundred and fifty-five
648        // doublings.
649        //
650        // Everything here is public -- the signature, the key, the message --
651        // so neither multiplication has to be constant time, and neither is.
652        #[cfg(feature = "std")]
653        let lhs = double_scalar_mul_vartime(&a_point.negate(), &k, &s);
654        #[cfg(not(feature = "std"))]
655        let lhs = mul_basepoint(&s).add(&a_point.negate().mul_scalar_vartime(&k));
656        let rhs = r_point;
657
658        // Compared projectively rather than by compressing both sides, which
659        // would be two field inversions to answer a question four
660        // multiplications settle. Nothing here is secret, so the comparison
661        // need not be constant time either.
662        if lhs.eq_projective(&rhs) {
663            Ok(())
664        } else {
665            Err(ic_core::err!(AuthenticationFailed, "ed25519"))
666        }
667    }
668}
669
670impl SelfTest for Ed25519 {
671    fn self_test() -> Result<()> {
672        // RFC 8032 §7.1 test vector 1: the empty message.
673        let mut seed = [0u8; 32];
674        ic_core::codec::hex_decode(
675            b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
676            &mut seed,
677        )?;
678        let mut want_pk = [0u8; 32];
679        ic_core::codec::hex_decode(
680            b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
681            &mut want_pk,
682        )?;
683        let mut want_sig = [0u8; 64];
684        ic_core::codec::hex_decode(
685            b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
686            &mut want_sig,
687        )?;
688
689        let mut pk = [0u8; 32];
690        <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
691        ensure!(
692            ic_core::ct::verify(&want_pk, &pk),
693            SelfTestFailed,
694            "ed25519"
695        );
696
697        let mut sig = [0u8; 64];
698        <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
699        ensure!(
700            ic_core::ct::verify(&want_sig, &sig),
701            SelfTestFailed,
702            "ed25519"
703        );
704
705        <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
706
707        // A corrupted signature must be rejected.
708        sig[0] ^= 1;
709        ensure!(
710            <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
711            SelfTestFailed,
712            "ed25519"
713        );
714        Ok(())
715    }
716}
717
718#[cfg(test)]
719mod tests {
720    use super::*;
721    use ic_core::codec::{hex, unhex};
722
723    #[test]
724    fn curve_constants_are_correct() {
725        // d = -121665 / 121666
726        let d = Fe::from_u64(121_665)
727            .neg()
728            .mul(&Fe::from_u64(121_666).invert());
729        assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
730        assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
731        // sqrt(-1) squares to -1.
732        assert_eq!(
733            hex(&SQRT_M1.square().to_bytes()),
734            hex(&Fe::ONE.neg().to_bytes()),
735            "sqrt(-1)"
736        );
737    }
738
739    #[test]
740    fn basepoint_has_the_expected_coordinates() {
741        let b = basepoint();
742        // y = 4/5
743        let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
744        let z_inv = b.z.invert();
745        assert_eq!(
746            hex(&b.y.mul(&z_inv).to_bytes()),
747            hex(&expected_y.to_bytes())
748        );
749        assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
750    }
751
752    /// The dedicated doubling must agree with adding a point to itself.
753    ///
754    /// `add` is what RFC 8032's vectors validate, so it is the oracle here.
755    /// The two formulas are different enough -- one reads `T`, the other does
756    /// not -- that agreeing on the basepoint alone would not be convincing, so
757    /// this walks a chain of multiples and doubles each one.
758    #[test]
759    fn doubling_agrees_with_adding_a_point_to_itself() {
760        let mut p = basepoint();
761        let mut checked = 0;
762        for _ in 0..16 {
763            assert_eq!(
764                p.double().compress(),
765                p.add(&p).compress(),
766                "dedicated doubling and self-addition differ"
767            );
768            p = p.add(&basepoint());
769            checked += 1;
770        }
771        assert_eq!(checked, 16, "the comparison did not run");
772
773        // The identity doubles to itself, which the formula has to get right
774        // without a special case.
775        assert_eq!(
776            Point::IDENTITY.double().compress(),
777            Point::IDENTITY.compress()
778        );
779    }
780
781    /// Projective equality must agree with comparing compressed encodings.
782    ///
783    /// The two answer the same question by different routes -- one divides by
784    /// Z, the other cross-multiplies -- so agreement is the argument. It has to
785    /// hold for equal points given *different* representatives, which is the
786    /// case the whole optimisation rests on, so the test scales one side by a
787    /// factor and checks it still compares equal.
788    #[test]
789    fn projective_equality_agrees_with_compressed_equality() {
790        let b = basepoint();
791        let mut points = std::vec![Point::IDENTITY, b];
792        let mut p = b;
793        for _ in 0..6 {
794            p = p.double();
795            points.push(p);
796        }
797
798        let mut checked = 0;
799        for (i, a) in points.iter().enumerate() {
800            for (j, c) in points.iter().enumerate() {
801                let projective = a.eq_projective(c);
802                let compressed = a.compress() == c.compress();
803                assert_eq!(
804                    projective, compressed,
805                    "projective and compressed equality differ for {i} vs {j}"
806                );
807                checked += 1;
808            }
809        }
810        assert_eq!(checked, 64, "the comparison did not run");
811
812        // The case that matters: the same point with a different Z. Adding the
813        // identity re-scales the representation without moving the point.
814        let scaled = b.add(&Point::IDENTITY);
815        assert!(b.eq_projective(&scaled), "equal points with different Z");
816        assert_eq!(b.compress(), scaled.compress());
817    }
818
819    #[test]
820    fn group_law_is_consistent() {
821        let b = basepoint();
822        // P + 0 == P
823        assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
824        // 2P via doubling equals 2P via scalar multiplication.
825        let mut two = [0u8; 32];
826        two[0] = 2;
827        assert_eq!(
828            hex(&b.double().compress()),
829            hex(&b.mul_scalar(&two).compress())
830        );
831        // (P + P) + P == 3P
832        let mut three = [0u8; 32];
833        three[0] = 3;
834        assert_eq!(
835            hex(&b.double().add(&b).compress()),
836            hex(&b.mul_scalar(&three).compress())
837        );
838    }
839
840    #[test]
841    fn order_of_the_basepoint_is_l() {
842        // [L]B must be the identity.
843        assert_eq!(
844            hex(&basepoint().mul_scalar(&scalar::L).compress()),
845            hex(&Point::IDENTITY.compress())
846        );
847    }
848
849    #[test]
850    fn compression_roundtrips() {
851        let b = basepoint();
852        for k in [1u8, 2, 3, 47, 200] {
853            let mut s = [0u8; 32];
854            s[0] = k;
855            let p = b.mul_scalar(&s);
856            let c = p.compress();
857            let d = Point::decompress(&c).expect("valid point");
858            assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
859        }
860    }
861
862    #[test]
863    fn decompression_rejects_non_curve_points() {
864        // A y value with no corresponding x.
865        let mut bad = [0u8; 32];
866        bad[0] = 2;
867        assert!(Point::decompress(&bad).is_none());
868    }
869
870    /// RFC 8032 §7.1 test vectors.
871    #[test]
872    fn rfc8032_vectors() {
873        let cases: [(&str, &str, &str, &str); 3] = [
874            (
875                "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
876                "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
877                "",
878                "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
879            ),
880            (
881                "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
882                "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
883                "72",
884                "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
885            ),
886            (
887                "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
888                "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
889                "af82",
890                "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
891            ),
892        ];
893
894        for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
895            let seed = unhex(seed_hex).unwrap();
896            let msg = unhex(msg_hex).unwrap();
897
898            let mut pk = [0u8; 32];
899            Ed25519::public_key(&seed, &mut pk).unwrap();
900            assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
901
902            let mut sig = [0u8; 64];
903            Ed25519::sign(&seed, &msg, &mut sig).unwrap();
904            assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
905
906            Ed25519::verify(&pk, &msg, &sig).unwrap();
907        }
908    }
909
910    #[test]
911    fn verification_rejects_tampering() {
912        let seed = [0x42u8; 32];
913        let mut pk = [0u8; 32];
914        Ed25519::public_key(&seed, &mut pk).unwrap();
915        let mut sig = [0u8; 64];
916        Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
917        Ed25519::verify(&pk, b"authentic", &sig).unwrap();
918
919        // Wrong message.
920        assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
921        // Corrupted R.
922        let mut bad = sig;
923        bad[0] ^= 1;
924        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
925        // Corrupted S.
926        let mut bad = sig;
927        bad[40] ^= 1;
928        assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
929        // Wrong public key.
930        let mut other_pk = [0u8; 32];
931        Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
932        assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
933    }
934
935    /// A signature with `S >= L` must be rejected even though it would
936    /// otherwise verify; this is the malleability check.
937    #[test]
938    fn rejects_non_canonical_s() {
939        let seed = [0x42u8; 32];
940        let mut pk = [0u8; 32];
941        Ed25519::public_key(&seed, &mut pk).unwrap();
942        let mut sig = [0u8; 64];
943        Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
944
945        // Add L to S. The verification equation still holds mod L, so only the
946        // canonicality check can catch it.
947        let mut carry = 0u16;
948        for i in 0..32 {
949            let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
950            sig[32 + i] = t as u8;
951            carry = t >> 8;
952        }
953        assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
954    }
955
956    /// The cached key and the seed-only call must produce the same signature.
957    ///
958    /// They share a code path now, which is the point -- but that is the sort
959    /// of thing a later refactor separates again, and the two would then differ
960    /// only for callers who use one and verify with the other. RFC 8032's
961    /// vectors exercise the trait method alone and would not notice.
962    #[test]
963    fn the_cached_key_signs_identically_to_the_seed() {
964        let mut checked = 0;
965        for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
966            for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
967                let mut from_seed = [0u8; 64];
968                Ed25519::sign(&seed, message, &mut from_seed).unwrap();
969
970                let key = Ed25519Key::from_seed(&seed).unwrap();
971                let mut from_key = [0u8; 64];
972                key.sign(message, &mut from_key).unwrap();
973
974                assert_eq!(from_seed, from_key, "the two signing paths diverged");
975
976                // And the cached public key is the one the trait derives.
977                let mut derived = [0u8; 32];
978                Ed25519::public_key(&seed, &mut derived).unwrap();
979                assert_eq!(&derived, key.public_key());
980
981                // Both verify, so neither is consistently wrong.
982                Ed25519::verify(&derived, message, &from_key).unwrap();
983                checked += 1;
984            }
985        }
986        assert_eq!(checked, 9, "the comparison did not run");
987    }
988
989    /// The variable-time path must agree with the constant-time one.
990    ///
991    /// RFC 8032's vectors reach it with a handful of scalars, which says little
992    /// about a recoding whose digit pattern is different for every scalar. This
993    /// drives both over scalars picked to stress the recoding: zero, one, a
994    /// value that carries at every position, alternating bits, and the top of
995    /// the range.
996    #[test]
997    fn the_vartime_multiplication_agrees_with_the_ladder() {
998        let p = basepoint();
999
1000        let mut one = [0u8; 32];
1001        one[0] = 1;
1002        let mut two = [0u8; 32];
1003        two[0] = 2;
1004        let mut top = [0xffu8; 32];
1005        top[31] = 0x7f;
1006
1007        let mut checked = 0;
1008        for scalar in [
1009            [0u8; 32],
1010            one,
1011            two,
1012            [0xffu8; 32],
1013            [0x55u8; 32],
1014            [0xaau8; 32],
1015            top,
1016            [0x9du8; 32],
1017        ] {
1018            let fast = p.mul_scalar_vartime(&scalar);
1019            let slow = p.mul_scalar(&scalar);
1020            assert_eq!(
1021                fast.compress(),
1022                slow.compress(),
1023                "vartime and ladder differ for {scalar:02x?}"
1024            );
1025            checked += 1;
1026        }
1027        assert_eq!(checked, 8, "the comparison did not run");
1028    }
1029
1030    /// The recoding must represent the scalar, with the digits it promises.
1031    #[test]
1032    fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1033        for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1034            let naf = wnaf5(&scalar);
1035
1036            let mut previous_nonzero: Option<usize> = None;
1037            for (i, d) in naf.iter().enumerate() {
1038                if *d == 0 {
1039                    continue;
1040                }
1041                assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1042                assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1043                if let Some(j) = previous_nonzero {
1044                    assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1045                }
1046                previous_nonzero = Some(i);
1047            }
1048
1049            // And it evaluates back to the scalar, modulo a small prime that
1050            // has nothing to do with the curve.
1051            const M: u128 = 1_000_000_007;
1052            let mut from_digits = 0u128;
1053            let mut power = 1u128;
1054            for d in naf {
1055                let term = ((d as i128).rem_euclid(M as i128)) as u128;
1056                from_digits = (from_digits + term * power) % M;
1057                power = power * 2 % M;
1058            }
1059            let mut from_bytes = 0u128;
1060            let mut p = 1u128;
1061            for byte in scalar {
1062                from_bytes = (from_bytes + (byte as u128) * p) % M;
1063                p = p * 256 % M;
1064            }
1065            assert_eq!(from_digits, from_bytes, "recoding changed the value");
1066        }
1067    }
1068
1069    #[test]
1070    fn signing_is_deterministic() {
1071        let seed = [0x7fu8; 32];
1072        let mut a = [0u8; 64];
1073        let mut b = [0u8; 64];
1074        Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1075        Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1076        assert_eq!(a, b);
1077    }
1078
1079    #[test]
1080    fn rejects_wrong_lengths() {
1081        let mut out = [0u8; 32];
1082        assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1083        assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1084        assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1085    }
1086
1087    #[test]
1088    fn self_test_passes() {
1089        Ed25519::self_test().unwrap();
1090    }
1091
1092    /// Where verification's time actually goes.
1093    ///
1094    /// Ignored: it is a measurement, not an assertion. Run it with
1095    /// `cargo test -p ic-ec --release -- --ignored --nocapture where_verify_spends`
1096    /// before changing anything here, because the answer decided what was
1097    /// worth doing and a guess would not have.
1098    #[test]
1099    #[ignore = "diagnostic, not a test"]
1100    fn where_verify_spends_its_time() {
1101        use std::time::Instant;
1102
1103        let seed = [7u8; 32];
1104        let key = Ed25519Key::from_seed(&seed).unwrap();
1105        let msg = b"benchmark message";
1106        let mut sig = [0u8; 64];
1107        key.sign(msg, &mut sig).unwrap();
1108        let pk = *key.public_key();
1109
1110        let mut big_r = [0u8; 32];
1111        big_r.copy_from_slice(&sig[..32]);
1112        let mut s_sc = [0u8; 32];
1113        s_sc.copy_from_slice(&sig[32..]);
1114
1115        let n = 2000;
1116        let time = |label: &str, f: &mut dyn FnMut()| {
1117            let mut best = f64::INFINITY;
1118            for _ in 0..5 {
1119                let t = Instant::now();
1120                for _ in 0..n {
1121                    f();
1122                }
1123                let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1124                if e < best {
1125                    best = e;
1126                }
1127            }
1128            println!("  {label:<34} {best:>9.2} us");
1129            best
1130        };
1131
1132        let a_point = Point::decompress(&pk).unwrap();
1133        let k = hash_to_scalar(&[&big_r, &pk, msg]);
1134
1135        println!(
1136            "
1137ed25519 verify, cost breakdown:"
1138        );
1139        let d = time("decompress (x2 per verify)", &mut || {
1140            core::hint::black_box(Point::decompress(&pk));
1141        });
1142        let h = time("hash_to_scalar", &mut || {
1143            core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1144        });
1145        let b = time("mul_basepoint (const time)", &mut || {
1146            core::hint::black_box(mul_basepoint(&s_sc));
1147        });
1148        let v = time("mul_scalar_vartime", &mut || {
1149            core::hint::black_box(a_point.mul_scalar_vartime(&k));
1150        });
1151        println!(
1152            "  {:<34} {:>9.2} us",
1153            "-- accounted for",
1154            2.0 * d + h + b + v
1155        );
1156
1157        // One level down: if the point ops are slow, the field ops are why.
1158        println!(
1159            "
1160field and point primitives, nanoseconds:"
1161        );
1162        let nn = 200_000;
1163        let ns = |label: &str, f: &mut dyn FnMut()| {
1164            let mut best = f64::INFINITY;
1165            for _ in 0..5 {
1166                let t = Instant::now();
1167                for _ in 0..nn {
1168                    f();
1169                }
1170                let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1171                if e < best {
1172                    best = e;
1173                }
1174            }
1175            println!("  {label:<34} {best:>9.2} ns");
1176        };
1177        let fx = a_point.x;
1178        let fy = a_point.y;
1179        ns("Fe::mul", &mut || {
1180            core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1181        });
1182        ns("Fe::square", &mut || {
1183            core::hint::black_box(core::hint::black_box(&fx).square());
1184        });
1185        ns("Fe::add", &mut || {
1186            core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1187        });
1188        ns("Point::double", &mut || {
1189            core::hint::black_box(core::hint::black_box(&a_point).double());
1190        });
1191        ns("Point::add", &mut || {
1192            core::hint::black_box(
1193                core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1194            );
1195        });
1196    }
1197}