Skip to main content

arcana/ecc/
eddsa.rs

1//! EdDSA digital signatures (RFC 8032).
2//!
3//! # Algorithms
4//!
5//! - **Ed25519** — Edwards curve over `GF(2^255 - 19)` (pure +
6//!   `Ed25519ctx` + `Ed25519ph`).
7//! - **Ed448** — planned (RFC 8032 Appendix A port pending).
8//!
9//! This module implements `Fe25519` field arithmetic, extended-
10//! coordinate point operations on the twisted Edwards form, and
11//! the full sign / verify protocol for Ed25519.
12//!
13//! # Side-channel posture
14//!
15//! Per `arcana/doc/sca/countermeasures/eddsa.rst`:
16//!
17//! | Threat                                    | Status     | Roadmap item                                                            |
18//! |-------------------------------------------|------------|-------------------------------------------------------------------------|
19//! | SPA on scalar mul                         | partial    | `T1-F` — audit pass mirroring Weierstrass commit `76191c1`              |
20//! | DPA on scalar mul                         | vulnerable | `T2-A` (Z-rerandomization, shared with Weierstrass plan)                |
21//! | **Single-fault on RFC 8032 deterministic**| vulnerable | `T1-D` — hedged Ed25519 (CFRG `det-sigs-with-noise`, Romailler 2017)    |
22//! | Template attacks (Samwel et al. 2018)     | vulnerable | `T2-A` + `T2-B` (Z-rerand + scalar blinding)                            |
23//! | DPA on intermediate SHA-512 keyed digest  | partial    | `T2-D` — masked SHA-512, shared with HMAC consumer                      |
24//!
25//! ## Romailler-Pelissier 2017 — single-fault key recovery
26//!
27//! RFC 8032 derives the per-signature nonce `r` deterministically
28//! from `(seed, message)`. Two signatures of the same message
29//! produce the same `r`, which is fine cryptographically but
30//! **fragile under fault** (FDTC 2017,
31//! `romailler2017eddsa_fault`):
32//!
33//! ```text
34//!   Sign M  →  (R, s)         (normal)
35//!   Sign M  →  (R', s')       (one fault during SHA-512 → r' ≠ r)
36//!
37//!   k  = H(R  ‖ A ‖ M),   s  = r  + k * a mod ℓ
38//!   k' = H(R' ‖ A ‖ M),   s' = r' + k' * a mod ℓ
39//!
40//!   →  a = (s - s') / (k - k') mod ℓ
41//! ```
42//!
43//! One well-placed fault recovers the whole secret scalar `a`.
44//! The standard fix is **hedged signing**: derive `r` from
45//! `H(prefix ‖ ρ ‖ M)` with `ρ` 32 bytes of fresh randomness;
46//! deterministic mode (`ρ = 0^32`) stays available for KAT
47//! determinism. Roadmap item `T1-D`.
48//!
49//! # Zeroize-on-Drop
50//!
51//! [`Ed25519SecretKey`] currently does **not** implement `Drop`
52//! with `silentops::ct_zeroize`. Roadmap item `T2-E`.
53
54use crate::Hasher;
55use crate::hash::sha512::Sha512;
56
57// ============================================================
58// Field arithmetic for GF(2^255 - 19)
59// ============================================================
60
61/// Element of GF(2^255 - 19), stored as four 64-bit limbs in little-endian order.
62#[derive(Clone, Copy, Debug)]
63struct Fe25519([u64; 4]);
64
65/// p = 2^255 - 19
66const P: [u64; 4] = [
67    0xFFFF_FFFF_FFFF_FFED,
68    0xFFFF_FFFF_FFFF_FFFF,
69    0xFFFF_FFFF_FFFF_FFFF,
70    0x7FFF_FFFF_FFFF_FFFF,
71];
72
73impl Fe25519 {
74    const ZERO: Fe25519 = Fe25519([0, 0, 0, 0]);
75    const ONE: Fe25519 = Fe25519([1, 0, 0, 0]);
76
77    /// Reduce a 4-limb value mod p.  The input must be < 2*p.
78    fn reduce(mut limbs: [u64; 4]) -> Self {
79        // Try subtracting p; if it underflows, keep the original.
80        let (r0, borrow) = limbs[0].overflowing_sub(P[0]);
81        let (r1, borrow) = sbb(limbs[1], P[1], borrow);
82        let (r2, borrow) = sbb(limbs[2], P[2], borrow);
83        let (r3, borrow) = sbb(limbs[3], P[3], borrow);
84
85        // If borrow, the subtraction underflowed => limbs < p, keep limbs.
86        let mask = if borrow { u64::MAX } else { 0 };
87        limbs[0] = (limbs[0] & mask) | (r0 & !mask);
88        limbs[1] = (limbs[1] & mask) | (r1 & !mask);
89        limbs[2] = (limbs[2] & mask) | (r2 & !mask);
90        limbs[3] = (limbs[3] & mask) | (r3 & !mask);
91
92        Fe25519(limbs)
93    }
94
95    fn add(self, rhs: Self) -> Self {
96        let (r0, carry) = self.0[0].overflowing_add(rhs.0[0]);
97        let (r1, carry) = adc(self.0[1], rhs.0[1], carry);
98        let (r2, carry) = adc(self.0[2], rhs.0[2], carry);
99        let (r3, _carry) = adc(self.0[3], rhs.0[3], carry);
100        Fe25519::reduce([r0, r1, r2, r3])
101    }
102
103    fn sub(self, rhs: Self) -> Self {
104        let (r0, borrow) = self.0[0].overflowing_sub(rhs.0[0]);
105        let (r1, borrow) = sbb(self.0[1], rhs.0[1], borrow);
106        let (r2, borrow) = sbb(self.0[2], rhs.0[2], borrow);
107        let (r3, borrow) = sbb(self.0[3], rhs.0[3], borrow);
108
109        // If borrow, add p.
110        let mask = if borrow { u64::MAX } else { 0 };
111        let (r0, carry) = r0.overflowing_add(P[0] & mask);
112        let (r1, carry) = adc(r1, P[1] & mask, carry);
113        let (r2, carry) = adc(r2, P[2] & mask, carry);
114        let (r3, _) = adc(r3, P[3] & mask, carry);
115
116        Fe25519([r0, r1, r2, r3])
117    }
118
119    fn neg(self) -> Self {
120        Fe25519::ZERO.sub(self)
121    }
122
123    /// Multiply two field elements: schoolbook 256x256 -> 512, then reduce mod p.
124    fn mul(self, rhs: Self) -> Self {
125        let wide = mul256(self.0, rhs.0);
126        fe_reduce_wide(wide)
127    }
128
129    fn square(self) -> Self {
130        self.mul(self)
131    }
132
133    /// Compute self^exp mod p via square-and-multiply.
134    fn pow(self, exp: [u64; 4]) -> Self {
135        let mut result = Fe25519::ONE;
136        let mut base = self;
137        for i in 0..4 {
138            let mut e = exp[i];
139            for _ in 0..64 {
140                if e & 1 == 1 {
141                    result = result.mul(base);
142                }
143                base = base.square();
144                e >>= 1;
145            }
146        }
147        result
148    }
149
150    /// Modular inverse via Fermat's little theorem: a^(p-2) mod p.
151    fn inv(self) -> Self {
152        // p - 2 = 2^255 - 21
153        let pm2: [u64; 4] = [
154            0xFFFF_FFFF_FFFF_FFEB,
155            0xFFFF_FFFF_FFFF_FFFF,
156            0xFFFF_FFFF_FFFF_FFFF,
157            0x7FFF_FFFF_FFFF_FFFF,
158        ];
159        self.pow(pm2)
160    }
161
162    /// Constant-time equality (on fully reduced values).
163    fn equals(self, other: Self) -> bool {
164        let a = Fe25519::reduce(self.0);
165        let b = Fe25519::reduce(other.0);
166        let mut acc = 0u64;
167        for i in 0..4 {
168            acc |= a.0[i] ^ b.0[i];
169        }
170        acc == 0
171    }
172
173    fn is_zero(self) -> bool {
174        self.equals(Fe25519::ZERO)
175    }
176
177    /// Encode to 32 bytes, little-endian.
178    fn to_bytes(self) -> [u8; 32] {
179        let r = Fe25519::reduce(self.0);
180        let mut out = [0u8; 32];
181        out[0..8].copy_from_slice(&r.0[0].to_le_bytes());
182        out[8..16].copy_from_slice(&r.0[1].to_le_bytes());
183        out[16..24].copy_from_slice(&r.0[2].to_le_bytes());
184        out[24..32].copy_from_slice(&r.0[3].to_le_bytes());
185        out
186    }
187
188    /// Decode from 32 bytes, little-endian.  Clears bit 255.
189    fn from_bytes(bytes: &[u8; 32]) -> Self {
190        let mut limbs = [0u64; 4];
191        limbs[0] = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
192        limbs[1] = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
193        limbs[2] = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
194        limbs[3] = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
195        limbs[3] &= 0x7FFF_FFFF_FFFF_FFFF;
196        Fe25519::reduce(limbs)
197    }
198
199    fn from_u64(v: u64) -> Self {
200        Fe25519([v, 0, 0, 0])
201    }
202
203    /// Return 1 if the fully-reduced value is odd, 0 otherwise.
204    fn is_odd(self) -> u8 {
205        let r = Fe25519::reduce(self.0);
206        (r.0[0] & 1) as u8
207    }
208
209    /// Constant-time conditional select: choice=0 -> a, choice=1 -> b.
210    fn ct_select(a: Self, b: Self, choice: u8) -> Self {
211        let mask = (choice as u64).wrapping_neg();
212        Fe25519([
213            a.0[0] ^ (mask & (a.0[0] ^ b.0[0])),
214            a.0[1] ^ (mask & (a.0[1] ^ b.0[1])),
215            a.0[2] ^ (mask & (a.0[2] ^ b.0[2])),
216            a.0[3] ^ (mask & (a.0[3] ^ b.0[3])),
217        ])
218    }
219}
220
221// ---- Low-level helpers ----
222
223#[inline(always)]
224fn adc(a: u64, b: u64, carry_in: bool) -> (u64, bool) {
225    let (s1, c1) = a.overflowing_add(b);
226    let (s2, c2) = s1.overflowing_add(carry_in as u64);
227    (s2, c1 | c2)
228}
229
230#[inline(always)]
231fn sbb(a: u64, b: u64, borrow_in: bool) -> (u64, bool) {
232    let (s1, b1) = a.overflowing_sub(b);
233    let (s2, b2) = s1.overflowing_sub(borrow_in as u64);
234    (s2, b1 | b2)
235}
236
237#[inline(always)]
238fn mul64(a: u64, b: u64) -> u128 {
239    (a as u128) * (b as u128)
240}
241
242/// Schoolbook 256x256 -> 512 bit multiply.
243fn mul256(a: [u64; 4], b: [u64; 4]) -> [u64; 8] {
244    let mut r = [0u64; 8];
245    for i in 0..4 {
246        let mut carry = 0u64;
247        for j in 0..4 {
248            let uv = mul64(a[i], b[j]) + r[i + j] as u128 + carry as u128;
249            r[i + j] = uv as u64;
250            carry = (uv >> 64) as u64;
251        }
252        r[i + 4] = carry;
253    }
254    r
255}
256
257/// Reduce a 512-bit result mod p = 2^255 - 19.
258///
259/// Since 2^255 = 19 (mod p), we split at bit 255 and fold with factor 19.
260fn fe_reduce_wide(w: [u64; 8]) -> Fe25519 {
261    // Extract bit 255 from w[3].
262    let low3_top_bit = (w[3] >> 63) & 1;
263    let low = [w[0], w[1], w[2], w[3] & 0x7FFF_FFFF_FFFF_FFFF];
264
265    // high = bits [255..512) of w, shifted down.
266    let high = [
267        (w[4] << 1) | low3_top_bit,
268        (w[5] << 1) | (w[4] >> 63),
269        (w[6] << 1) | (w[5] >> 63),
270        (w[7] << 1) | (w[6] >> 63),
271        w[7] >> 63,
272    ];
273
274    // w = low + high * 2^255 = low + 19 * high (mod p).
275    let mut acc = [0u64; 5];
276    let mut carry = 0u128;
277    for i in 0..5 {
278        carry += low.get(i).copied().unwrap_or(0) as u128 + 19u128 * high[i] as u128;
279        acc[i] = carry as u64;
280        carry >>= 64;
281    }
282
283    // Split at bit 255 again.
284    let top_bit = (acc[3] >> 63) & 1;
285    acc[3] &= 0x7FFF_FFFF_FFFF_FFFF;
286    let extra = (acc[4] << 1) | top_bit;
287
288    // Add 19 * extra to lower 255 bits.
289    let add = extra as u128 * 19;
290    let (r0, c) = acc[0].overflowing_add(add as u64);
291    let (r1, c) = adc(acc[1], (add >> 64) as u64, c);
292    let (r2, c) = adc(acc[2], 0, c);
293    let (r3, _) = adc(acc[3], 0, c);
294
295    Fe25519::reduce([r0, r1, r2, r3])
296}
297
298// ============================================================
299// Scalar arithmetic mod L (group order)
300// ============================================================
301
302/// L = 2^252 + 27742317777372353535851937790883648493
303const L: [u64; 4] = [
304    0x5812631A5CF5D3ED,
305    0x14DEF9DEA2F79CD6,
306    0x0000000000000000,
307    0x1000000000000000,
308];
309
310/// Reduce a 64-byte (512-bit) little-endian value mod L.
311fn sc_reduce(input: &[u8; 64]) -> [u8; 32] {
312    let mut a = [0u64; 8];
313    for i in 0..8 {
314        a[i] = u64::from_le_bytes(input[i * 8..(i + 1) * 8].try_into().unwrap());
315    }
316
317    let result = bn_mod(&a, 8);
318
319    let mut out = [0u8; 32];
320    for i in 0..4 {
321        out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
322    }
323    out
324}
325
326/// Add two scalars mod L.  Both inputs must already be < L.
327fn sc_add(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
328    let mut al = [0u64; 4];
329    let mut bl = [0u64; 4];
330    for i in 0..4 {
331        al[i] = u64::from_le_bytes(a[i * 8..(i + 1) * 8].try_into().unwrap());
332        bl[i] = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
333    }
334
335    let (r0, carry) = al[0].overflowing_add(bl[0]);
336    let (r1, carry) = adc(al[1], bl[1], carry);
337    let (r2, carry) = adc(al[2], bl[2], carry);
338    let (r3, carry) = adc(al[3], bl[3], carry);
339
340    let mut result = [r0, r1, r2, r3];
341
342    // Subtract L if result >= L (carry set, or no borrow on subtraction).
343    let (s0, borrow) = result[0].overflowing_sub(L[0]);
344    let (s1, borrow) = sbb(result[1], L[1], borrow);
345    let (s2, borrow) = sbb(result[2], L[2], borrow);
346    let (s3, borrow) = sbb(result[3], L[3], borrow);
347
348    let use_sub = carry | !borrow;
349    let mask = if use_sub { 0u64 } else { u64::MAX };
350    result[0] = (result[0] & mask) | (s0 & !mask);
351    result[1] = (result[1] & mask) | (s1 & !mask);
352    result[2] = (result[2] & mask) | (s2 & !mask);
353    result[3] = (result[3] & mask) | (s3 & !mask);
354
355    let mut out = [0u8; 32];
356    for i in 0..4 {
357        out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
358    }
359    out
360}
361
362/// Multiply two 256-bit scalars mod L.
363fn sc_mul(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
364    let mut al = [0u64; 4];
365    let mut bl = [0u64; 4];
366    for i in 0..4 {
367        al[i] = u64::from_le_bytes(a[i * 8..(i + 1) * 8].try_into().unwrap());
368        bl[i] = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
369    }
370
371    let wide = mul256(al, bl);
372    let result = bn_mod(&wide, 8);
373
374    let mut out = [0u8; 32];
375    for i in 0..4 {
376        out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
377    }
378    out
379}
380
381/// Big-number modular reduction: compute a[0..limbs] mod L via bit-by-bit
382/// shift-and-subtract.  Simple but obviously correct.
383fn bn_mod(a: &[u64], limbs: usize) -> [u64; 4] {
384    // Working register: 5 limbs (320 bits) is enough since L is 253 bits and
385    // we only ever hold a value < 2*L during the loop.
386    let mut r = [0u64; 5];
387
388    // Process input bits from MSB down to LSB.
389    for word_idx in (0..limbs).rev() {
390        for bit_idx in (0..64).rev() {
391            // Shift r left by 1.
392            let mut carry = 0u64;
393            for i in 0..5 {
394                let new_carry = r[i] >> 63;
395                r[i] = (r[i] << 1) | carry;
396                carry = new_carry;
397            }
398            // Bring in the next bit from a.
399            r[0] |= (a[word_idx] >> bit_idx) & 1;
400
401            // If r >= L, subtract L.
402            let (s0, b) = r[0].overflowing_sub(L[0]);
403            let (s1, b) = sbb(r[1], L[1], b);
404            let (s2, b) = sbb(r[2], L[2], b);
405            let (s3, b) = sbb(r[3], L[3], b);
406            let (s4, b) = sbb(r[4], 0, b);
407            if !b {
408                r[0] = s0;
409                r[1] = s1;
410                r[2] = s2;
411                r[3] = s3;
412                r[4] = s4;
413            }
414        }
415    }
416
417    [r[0], r[1], r[2], r[3]]
418}
419
420// ============================================================
421// Extended Edwards point (X : Y : Z : T) with T = X*Y/Z
422// ============================================================
423
424/// Point on the Ed25519 curve in extended coordinates.
425#[derive(Clone, Copy, Debug)]
426struct ExtPoint {
427    x: Fe25519,
428    y: Fe25519,
429    z: Fe25519,
430    t: Fe25519,
431}
432
433/// d = -121665/121666 mod p
434/// = 37095705934669439343138083508754565189542113879843219016388785533085940283555
435const D: Fe25519 = Fe25519([
436    0x75EB4DCA135978A3,
437    0x00700A4D4141D8AB,
438    0x8CC740797779E898,
439    0x52036CEE2B6FFE73,
440]);
441
442/// 2*d mod p (kept for reference/tests).
443#[cfg(test)]
444const D2: Fe25519 = Fe25519([
445    0xEBD69B9426B2F159,
446    0x00E0149A8283B156,
447    0x198E80F2EEF3D130,
448    0x2406D9DC56DFFCE7,
449]);
450
451/// Base point x-coordinate.
452/// Bx = 15112221349535807912866137220509078750507884956996801397906370244768422529236
453const B_X: Fe25519 = Fe25519([
454    0xC9562D608F25D51A,
455    0x692CC7609525A7B2,
456    0xC0A4E231FDD6DC5C,
457    0x216936D3CD6E53FE,
458]);
459
460/// Base point y-coordinate.
461/// By = 46316835694926478169428394003475163141307993866256225615783033890098355573289
462const B_Y: Fe25519 = Fe25519([
463    0x6666666666666658,
464    0x6666666666666666,
465    0x6666666666666666,
466    0x6666666666666666,
467]);
468
469impl ExtPoint {
470    const IDENTITY: ExtPoint = ExtPoint {
471        x: Fe25519::ZERO,
472        y: Fe25519::ONE,
473        z: Fe25519::ONE,
474        t: Fe25519::ZERO,
475    };
476
477    fn from_affine(x: Fe25519, y: Fe25519) -> Self {
478        ExtPoint {
479            x,
480            y,
481            z: Fe25519::ONE,
482            t: x.mul(y),
483        }
484    }
485
486    fn basepoint() -> Self {
487        ExtPoint::from_affine(B_X, B_Y)
488    }
489
490    /// Point doubling using the unified doubling formula for twisted Edwards
491    /// curves with a = -1.  Reference: HHCD08 (Hisil et al.), Section 4.
492    fn double(self) -> Self {
493        let a = self.x.square();
494        let b = self.y.square();
495        let c = self.z.square().add(self.z.square()); // 2*Z^2
496        let d = a.neg(); // -X^2  (a = -1 in the twisted Edwards equation)
497        let e = self.x.add(self.y).square().sub(a).sub(b);
498        let g = d.add(b);
499        let f = g.sub(c);
500        let h = d.sub(b);
501
502        ExtPoint {
503            x: e.mul(f),
504            y: g.mul(h),
505            t: e.mul(h),
506            z: f.mul(g),
507        }
508    }
509
510    /// Point addition using the unified addition formula for twisted Edwards
511    /// curves with a = -1.  Reference: HHCD08, Section 3.1.
512    fn add(self, other: Self) -> Self {
513        let a = self.x.mul(other.x);
514        let b = self.y.mul(other.y);
515        let c = self.t.mul(other.t).mul(D);
516        let dd = self.z.mul(other.z);
517
518        let e = (self.x.add(self.y)).mul(other.x.add(other.y)).sub(a).sub(b);
519        let f = dd.sub(c);
520        let g = dd.add(c);
521        let h = b.add(a); // b - (-1)*a = b + a, since curve coeff a = -1
522
523        ExtPoint {
524            x: e.mul(f),
525            y: g.mul(h),
526            t: e.mul(h),
527            z: f.mul(g),
528        }
529    }
530
531    /// Constant-time scalar multiplication (left-to-right double-and-add with
532    /// conditional select on every bit).
533    fn scalar_mul(self, scalar: &[u8; 32]) -> Self {
534        let mut result = ExtPoint::IDENTITY;
535        for byte_idx in (0..32).rev() {
536            for bit_idx in (0..8).rev() {
537                result = result.double();
538                let bit = (scalar[byte_idx] >> bit_idx) & 1;
539                let candidate = result.add(self);
540                result = ExtPoint::ct_select(result, candidate, bit);
541            }
542        }
543        result
544    }
545
546    fn ct_select(a: Self, b: Self, choice: u8) -> Self {
547        ExtPoint {
548            x: Fe25519::ct_select(a.x, b.x, choice),
549            y: Fe25519::ct_select(a.y, b.y, choice),
550            z: Fe25519::ct_select(a.z, b.z, choice),
551            t: Fe25519::ct_select(a.t, b.t, choice),
552        }
553    }
554
555    /// Compress a point to 32 bytes (RFC 8032 encoding):
556    /// encode y in little-endian, put the sign of x into the top bit.
557    fn compress(self) -> [u8; 32] {
558        let z_inv = self.z.inv();
559        let x = self.x.mul(z_inv);
560        let y = self.y.mul(z_inv);
561        let mut bytes = y.to_bytes();
562        bytes[31] |= x.is_odd() << 7;
563        bytes
564    }
565
566    /// Decompress a 32-byte encoded point.
567    fn decompress(bytes: &[u8; 32]) -> Option<Self> {
568        let x_sign = (bytes[31] >> 7) & 1;
569        let mut y_bytes = *bytes;
570        y_bytes[31] &= 0x7F;
571        let y = Fe25519::from_bytes(&y_bytes);
572
573        // x^2 = (y^2 - 1) / (d * y^2 + 1)
574        let y2 = y.square();
575        let u = y2.sub(Fe25519::ONE); // numerator
576        let v = D.mul(y2).add(Fe25519::ONE); // denominator
577
578        // Compute candidate: x = u * v^3 * (u * v^7)^((p-5)/8)
579        let v3 = v.square().mul(v);
580        let v7 = v3.square().mul(v);
581        let uv7 = u.mul(v7);
582
583        // (p-5)/8 = (2^255 - 24) / 8 = 2^252 - 3
584        let exp: [u64; 4] = [
585            0xFFFF_FFFF_FFFF_FFFD,
586            0xFFFF_FFFF_FFFF_FFFF,
587            0xFFFF_FFFF_FFFF_FFFF,
588            0x0FFF_FFFF_FFFF_FFFF,
589        ];
590        let uv7_pow = uv7.pow(exp);
591        let mut x = u.mul(v3).mul(uv7_pow);
592
593        // Verify: v * x^2 should equal u.
594        let check = v.mul(x.square());
595        if check.equals(u) {
596            // ok
597        } else if check.equals(u.neg()) {
598            // Multiply x by sqrt(-1) = 2^((p-1)/4).
599            let sqrt_m1 = compute_sqrt_m1();
600            x = x.mul(sqrt_m1);
601        } else {
602            return None;
603        }
604
605        if x.is_zero() && x_sign == 1 {
606            return None;
607        }
608
609        if x.is_odd() != x_sign {
610            x = x.neg();
611        }
612
613        let t = x.mul(y);
614        Some(ExtPoint {
615            x,
616            y,
617            z: Fe25519::ONE,
618            t,
619        })
620    }
621
622    /// Check projective equality: X1*Z2 == X2*Z1 and Y1*Z2 == Y2*Z1.
623    fn equals(self, other: Self) -> bool {
624        let lhs_x = self.x.mul(other.z);
625        let rhs_x = other.x.mul(self.z);
626        let lhs_y = self.y.mul(other.z);
627        let rhs_y = other.y.mul(self.z);
628        lhs_x.equals(rhs_x) && lhs_y.equals(rhs_y)
629    }
630}
631
632/// Compute sqrt(-1) mod p = 2^((p-1)/4).
633fn compute_sqrt_m1() -> Fe25519 {
634    // (p-1)/4 = (2^255 - 20)/4 = 2^253 - 5
635    let exp: [u64; 4] = [
636        0xFFFF_FFFF_FFFF_FFFB,
637        0xFFFF_FFFF_FFFF_FFFF,
638        0xFFFF_FFFF_FFFF_FFFF,
639        0x1FFF_FFFF_FFFF_FFFF,
640    ];
641    Fe25519::from_u64(2).pow(exp)
642}
643
644// ============================================================
645// Ed25519 public API
646// ============================================================
647
648/// Ed25519 public key (32 bytes, compressed point encoding).
649#[derive(Clone, Debug, PartialEq, Eq)]
650pub struct Ed25519PublicKey(pub [u8; 32]);
651
652/// Ed25519 secret key (the original 32-byte seed).
653#[derive(Clone)]
654pub struct Ed25519SecretKey(pub [u8; 32]);
655
656/// Ed25519 signature (64 bytes: R || S).
657#[derive(Clone, Debug, PartialEq, Eq)]
658pub struct Ed25519Signature(pub [u8; 64]);
659
660/// Generate an Ed25519 key pair from a 32-byte secret seed.
661///
662/// Returns (public_key, secret_key).
663pub fn ed25519_keygen(secret: &[u8; 32]) -> (Ed25519PublicKey, Ed25519SecretKey) {
664    let hash = Sha512::hash(secret);
665
666    let mut a = [0u8; 32];
667    a.copy_from_slice(&hash[..32]);
668    a[0] &= 248;
669    a[31] &= 127;
670    a[31] |= 64;
671
672    let big_a = ExtPoint::basepoint().scalar_mul(&a);
673    let pk_bytes = big_a.compress();
674
675    (Ed25519PublicKey(pk_bytes), Ed25519SecretKey(*secret))
676}
677
678// ----------------------------------------------------------------------------
679// Generic sign / verify core (RFC 8032 §5.1)
680//
681// The three Ed25519 variants (pure, ctx, ph) all share the same algebraic
682// structure -- they differ only in two places:
683//
684//  1. Whether a `dom2(F, C)` prefix is prepended to the SHA-512 inputs.
685//     (None for pure Ed25519, Some for ctx and ph.)
686//  2. Whether the "message" bytes fed to the two SHA-512 calls are the
687//     raw application message (pure / ctx) or a 64-byte SHA-512 digest
688//     of it (ph). The implementation is the same either way: the caller
689//     is responsible for hashing first when in ph mode and just passes
690//     a 64-byte slice as `message_or_hash`.
691//
692// Both helpers are kept private to the module: the public surface is the
693// six wrappers below (sign / verify / ctx_sign / ctx_verify / ph_sign /
694// ph_verify) which give each variant a clear, unambiguous name.
695// ----------------------------------------------------------------------------
696
697/// Update a SHA-512 hasher with the optional `dom2` domain-separation
698/// prefix (RFC 8032 §5.1.6). For pure Ed25519 (`dom2 == None`) this is
699/// a no-op, preserving byte-for-byte the existing test vectors.
700fn update_dom2(hasher: &mut Sha512, dom2: Option<&[u8]>) {
701    if let Some(prefix) = dom2 {
702        hasher.update(prefix);
703    }
704}
705
706/// Generic Ed25519 sign core. Used by all three variants.
707fn ed25519_sign_internal(sk: &Ed25519SecretKey, message_or_hash: &[u8], dom2: Option<&[u8]>) -> Ed25519Signature {
708    let hash = Sha512::hash(&sk.0);
709
710    let mut a = [0u8; 32];
711    a.copy_from_slice(&hash[..32]);
712    a[0] &= 248;
713    a[31] &= 127;
714    a[31] |= 64;
715
716    let prefix = &hash[32..64];
717
718    // Public key A = a * B.
719    let big_a = ExtPoint::basepoint().scalar_mul(&a);
720    let pk_bytes = big_a.compress();
721
722    // r = SHA-512(dom2 || prefix || M) mod L  -- M is `message_or_hash`.
723    let mut hasher = Sha512::new();
724    update_dom2(&mut hasher, dom2);
725    hasher.update(prefix);
726    hasher.update(message_or_hash);
727    let r_hash = hasher.finalize();
728    let mut r_wide = [0u8; 64];
729    r_wide.copy_from_slice(&r_hash);
730    let r_scalar = sc_reduce(&r_wide);
731
732    // R = r * B.
733    let big_r = ExtPoint::basepoint().scalar_mul(&r_scalar);
734    let r_bytes = big_r.compress();
735
736    // S = (r + SHA-512(dom2 || R || A || M) * a) mod L.
737    let mut hasher = Sha512::new();
738    update_dom2(&mut hasher, dom2);
739    hasher.update(&r_bytes);
740    hasher.update(&pk_bytes);
741    hasher.update(message_or_hash);
742    let k_hash = hasher.finalize();
743    let mut k_wide = [0u8; 64];
744    k_wide.copy_from_slice(&k_hash);
745    let k = sc_reduce(&k_wide);
746
747    let ka = sc_mul(&k, &a);
748    let s = sc_add(&r_scalar, &ka);
749
750    let mut sig = [0u8; 64];
751    sig[..32].copy_from_slice(&r_bytes);
752    sig[32..].copy_from_slice(&s);
753
754    Ed25519Signature(sig)
755}
756
757/// Generic Ed25519 verify core. Used by all three variants.
758fn ed25519_verify_internal(
759    pk: &Ed25519PublicKey,
760    message_or_hash: &[u8],
761    sig: &Ed25519Signature,
762    dom2: Option<&[u8]>,
763) -> bool {
764    let r_bytes: [u8; 32] = match sig.0[..32].try_into() {
765        Ok(b) => b,
766        Err(_) => return false,
767    };
768    let big_r = match ExtPoint::decompress(&r_bytes) {
769        Some(p) => p,
770        None => return false,
771    };
772
773    let big_a = match ExtPoint::decompress(&pk.0) {
774        Some(p) => p,
775        None => return false,
776    };
777
778    let s_bytes: [u8; 32] = match sig.0[32..].try_into() {
779        Ok(b) => b,
780        Err(_) => return false,
781    };
782
783    // Check S < L.
784    {
785        let mut sl = [0u64; 4];
786        for i in 0..4 {
787            sl[i] = u64::from_le_bytes(s_bytes[i * 8..(i + 1) * 8].try_into().unwrap());
788        }
789        let (_, borrow) = sl[0].overflowing_sub(L[0]);
790        let (_, borrow) = sbb(sl[1], L[1], borrow);
791        let (_, borrow) = sbb(sl[2], L[2], borrow);
792        let (_, borrow) = sbb(sl[3], L[3], borrow);
793        if !borrow {
794            return false; // S >= L
795        }
796    }
797
798    // k = SHA-512(dom2 || R || A || M) mod L.
799    let mut hasher = Sha512::new();
800    update_dom2(&mut hasher, dom2);
801    hasher.update(&r_bytes);
802    hasher.update(&pk.0);
803    hasher.update(message_or_hash);
804    let k_hash = hasher.finalize();
805    let mut k_wide = [0u8; 64];
806    k_wide.copy_from_slice(&k_hash);
807    let k = sc_reduce(&k_wide);
808
809    // Cofactored verification: [8][S]B == [8]R + [8][k]A.
810    let sb = ExtPoint::basepoint().scalar_mul(&s_bytes);
811    let ka = big_a.scalar_mul(&k);
812    let rhs = big_r.add(ka);
813
814    // Multiply both sides by cofactor 8 (three doublings).
815    let lhs = sb.double().double().double();
816    let rhs = rhs.double().double().double();
817
818    lhs.equals(rhs)
819}
820
821// ----------------------------------------------------------------------------
822// dom2 prefix construction (RFC 8032 §5.1.6 / §5.1.7)
823//
824//   dom2(F, C) = "SigEd25519 no Ed25519 collisions" || octet(F) || octet(len(C)) || C
825//
826// where F = 0 for Ed25519ctx and F = 1 for Ed25519ph. The literal string
827// is exactly 32 bytes; F and len(C) take one byte each; the context C
828// follows verbatim. Maximum context length is 255 bytes.
829// ----------------------------------------------------------------------------
830
831const DOM2_LITERAL: &[u8] = b"SigEd25519 no Ed25519 collisions";
832
833/// Build the `dom2(F, C)` prefix used by Ed25519ctx (F=0) and
834/// Ed25519ph (F=1). Returns `None` if `context.len() > 255` (the
835/// max RFC 8032 allows since `len(C)` must fit in one octet).
836fn build_dom2(f: u8, context: &[u8]) -> Option<Vec<u8>> {
837    if context.len() > 255 {
838        return None;
839    }
840    let mut out = Vec::with_capacity(DOM2_LITERAL.len() + 2 + context.len());
841    out.extend_from_slice(DOM2_LITERAL);
842    out.push(f);
843    out.push(context.len() as u8);
844    out.extend_from_slice(context);
845    Some(out)
846}
847
848// ----------------------------------------------------------------------------
849// Ed25519 (pure) -- public API. RFC 8032 §5.1
850// ----------------------------------------------------------------------------
851
852/// Sign a message with Ed25519 (pure mode, no prehashing).
853///
854/// This is RFC 8032 §5.1 -- the original / canonical Ed25519 mode.
855/// **Identical bytes** to what was produced before this commit's
856/// generic-core refactor; the existing pinned RFC 8032 §7.1 test
857/// vectors still pass byte-exact.
858pub fn ed25519_sign(sk: &Ed25519SecretKey, msg: &[u8]) -> Ed25519Signature {
859    ed25519_sign_internal(sk, msg, None)
860}
861
862/// Verify an Ed25519 signature (pure mode).
863pub fn ed25519_verify(pk: &Ed25519PublicKey, msg: &[u8], sig: &Ed25519Signature) -> bool {
864    ed25519_verify_internal(pk, msg, sig, None)
865}
866
867// ----------------------------------------------------------------------------
868// Ed25519ctx -- RFC 8032 §5.1.6
869//
870// Same as pure Ed25519 except both SHA-512 calls are prefixed by
871// `dom2(0, context)`. The context is a domain-separation tag chosen
872// by the calling protocol (max 255 bytes); a non-empty context is
873// REQUIRED by the RFC. Two ctx signatures with different contexts
874// are guaranteed to be unrelated even if the message bytes are
875// identical -- this is the property protocol designers want when
876// the same key is used for multiple purposes.
877// ----------------------------------------------------------------------------
878
879/// Sign a message with Ed25519ctx (RFC 8032 §5.1.6).
880///
881/// `context` is a non-empty domain-separation tag (max 255 bytes).
882/// Returns `None` if the context is empty (the RFC mandates a
883/// non-empty context for ctx mode) or longer than 255 bytes.
884pub fn ed25519ctx_sign(sk: &Ed25519SecretKey, msg: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
885    if context.is_empty() {
886        return None;
887    }
888    let dom2 = build_dom2(0, context)?;
889    Some(ed25519_sign_internal(sk, msg, Some(&dom2)))
890}
891
892/// Verify an Ed25519ctx signature.
893///
894/// The same `context` used at sign time must be supplied here. A
895/// signature produced under one context will NOT verify under a
896/// different context, even if the (sk, msg) are the same -- that's
897/// the whole point of ctx mode.
898pub fn ed25519ctx_verify(pk: &Ed25519PublicKey, msg: &[u8], context: &[u8], sig: &Ed25519Signature) -> bool {
899    if context.is_empty() || context.len() > 255 {
900        return false;
901    }
902    let dom2 = match build_dom2(0, context) {
903        Some(d) => d,
904        None => return false,
905    };
906    ed25519_verify_internal(pk, msg, sig, Some(&dom2))
907}
908
909// ----------------------------------------------------------------------------
910// Ed25519ph -- RFC 8032 §5.1.7
911//
912// "Pre-hashed" Ed25519: the message is hashed with SHA-512 first, and
913// the resulting 64-byte digest is what gets fed into the two internal
914// SHA-512 calls (with the `dom2(1, context)` prefix). This lets a
915// signer sign very large messages without making two passes over them
916// in the application layer (e.g. file signing, where the application
917// already has a streaming SHA-512 of the file at hand).
918//
919// The context is OPTIONAL in Ed25519ph (zero-length context is allowed).
920// ----------------------------------------------------------------------------
921
922/// Sign a message with Ed25519ph (RFC 8032 §5.1.7, pre-hashed mode).
923///
924/// The message is internally hashed with SHA-512 before being fed
925/// into the Ed25519 algebraic core. `context` may be empty (unlike
926/// ctx mode); maximum context length is still 255 bytes.
927pub fn ed25519ph_sign(sk: &Ed25519SecretKey, msg: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
928    let dom2 = build_dom2(1, context)?;
929    let m_prime = Sha512::hash(msg);
930    Some(ed25519_sign_internal(sk, &m_prime, Some(&dom2)))
931}
932
933/// Verify an Ed25519ph signature.
934pub fn ed25519ph_verify(pk: &Ed25519PublicKey, msg: &[u8], context: &[u8], sig: &Ed25519Signature) -> bool {
935    if context.len() > 255 {
936        return false;
937    }
938    let dom2 = match build_dom2(1, context) {
939        Some(d) => d,
940        None => return false,
941    };
942    let m_prime = Sha512::hash(msg);
943    ed25519_verify_internal(pk, &m_prime, sig, Some(&dom2))
944}
945
946/// Variant of [`ed25519ph_sign`] that takes a precomputed SHA-512
947/// digest of the message instead of the message itself. Useful when
948/// the caller already has the digest from a streaming hash (e.g. a
949/// file scanner that produced the digest as it read the file).
950///
951/// The `prehashed` argument **must** be exactly 64 bytes (the
952/// SHA-512 output length). Returns `None` for any other length.
953pub fn ed25519ph_sign_prehashed(sk: &Ed25519SecretKey, prehashed: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
954    if prehashed.len() != 64 {
955        return None;
956    }
957    let dom2 = build_dom2(1, context)?;
958    Some(ed25519_sign_internal(sk, prehashed, Some(&dom2)))
959}
960
961/// Verify a precomputed-digest Ed25519ph signature. Counterpart to
962/// [`ed25519ph_sign_prehashed`].
963pub fn ed25519ph_verify_prehashed(
964    pk: &Ed25519PublicKey,
965    prehashed: &[u8],
966    context: &[u8],
967    sig: &Ed25519Signature,
968) -> bool {
969    if prehashed.len() != 64 || context.len() > 255 {
970        return false;
971    }
972    let dom2 = match build_dom2(1, context) {
973        Some(d) => d,
974        None => return false,
975    };
976    ed25519_verify_internal(pk, prehashed, sig, Some(&dom2))
977}
978
979// ============================================================
980// Ed448 (TODO)
981// ============================================================
982
983// TODO: Implement Ed448 (RFC 8032 Section 5.2) using the Goldilocks curve.
984// Ed448-Goldilocks: -x^2 + y^2 = 1 - 39081*x^2*y^2 over GF(2^448 - 2^224 - 1).
985// Requirements:
986//   - Field arithmetic for GF(2^448 - 2^224 - 1)
987//   - SHAKE256 instead of SHA-512
988//   - Different scalar clamping and point encoding
989//   - 57-byte keys and 114-byte signatures
990
991// ============================================================
992// Tests
993// ============================================================
994
995#[cfg(test)]
996mod tests {
997    use super::*;
998
999    fn hex_to_bytes(s: &str) -> Vec<u8> {
1000        (0..s.len())
1001            .step_by(2)
1002            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
1003            .collect()
1004    }
1005
1006    #[test]
1007    fn test_fe25519_basic() {
1008        let a = Fe25519::ONE;
1009        let b = Fe25519::ONE;
1010        let c = a.add(b);
1011        assert_eq!(c.0[0], 2);
1012        assert_eq!(c.0[1], 0);
1013
1014        let d = c.sub(a);
1015        assert!(d.equals(Fe25519::ONE));
1016    }
1017
1018    #[test]
1019    fn test_fe25519_mul() {
1020        let a = Fe25519::from_u64(7);
1021        let b = Fe25519::from_u64(13);
1022        let c = a.mul(b);
1023        assert!(c.equals(Fe25519::from_u64(91)));
1024    }
1025
1026    #[test]
1027    fn test_fe25519_inv() {
1028        let a = Fe25519::from_u64(42);
1029        let b = a.inv();
1030        let c = a.mul(b);
1031        assert!(c.equals(Fe25519::ONE));
1032    }
1033
1034    #[test]
1035    fn test_basepoint_on_curve() {
1036        // Verify: -x^2 + y^2 == 1 + d*x^2*y^2
1037        let x2 = B_X.square();
1038        let y2 = B_Y.square();
1039        let lhs = y2.sub(x2);
1040        let rhs = Fe25519::ONE.add(D.mul(x2).mul(y2));
1041        assert!(lhs.equals(rhs), "Base point not on curve!");
1042    }
1043
1044    #[test]
1045    fn test_d_constant() {
1046        // d = -121665 * inv(121666) mod p
1047        let n = Fe25519::from_u64(121665).neg();
1048        let d_inv = Fe25519::from_u64(121666).inv();
1049        let d_computed = n.mul(d_inv);
1050        assert!(d_computed.equals(D), "D constant is wrong");
1051    }
1052
1053    #[test]
1054    fn test_d2_constant() {
1055        let d2_computed = D.add(D);
1056        assert!(d2_computed.equals(D2), "D2 constant is wrong");
1057    }
1058
1059    #[test]
1060    fn test_basepoint_compress_decompress() {
1061        let bp = ExtPoint::basepoint();
1062        let compressed = bp.compress();
1063        let decompressed = ExtPoint::decompress(&compressed).expect("decompress failed");
1064        assert!(bp.equals(decompressed));
1065    }
1066
1067    fn naive_scalar_mul(p: ExtPoint, scalar: &[u8; 32]) -> ExtPoint {
1068        // Compute by iterating MSB to LSB, doubling and conditionally
1069        // adding -- but using a simple non-CT branch (for testing only).
1070        let mut acc = ExtPoint::IDENTITY;
1071        let mut started = false;
1072        for byte_idx in (0..32).rev() {
1073            for bit_idx in (0..8).rev() {
1074                if started {
1075                    acc = acc.double();
1076                }
1077                let bit = (scalar[byte_idx] >> bit_idx) & 1;
1078                if bit == 1 {
1079                    if !started {
1080                        acc = p;
1081                        started = true;
1082                    } else {
1083                        acc = acc.add(p);
1084                    }
1085                }
1086            }
1087        }
1088        acc
1089    }
1090
1091    #[test]
1092    fn test_scalar_mul_vs_naive() {
1093        let bp = ExtPoint::basepoint();
1094        let mut s = [0u8; 32];
1095        // Some pseudo-random scalar with many bits set.
1096        for i in 0..32 {
1097            s[i] = (i as u8) ^ 0xa5;
1098        }
1099        s[31] &= 0x7f;
1100        let r1 = bp.scalar_mul(&s);
1101        let r2 = naive_scalar_mul(bp, &s);
1102        assert!(r1.equals(r2));
1103    }
1104
1105    #[test]
1106    fn test_sc_reduce_basic() {
1107        // Test 1: input = 1
1108        let mut x = [0u8; 64];
1109        x[0] = 1;
1110        let r = sc_reduce(&x);
1111        assert_eq!(r[0], 1);
1112        for i in 1..32 {
1113            assert_eq!(r[i], 0);
1114        }
1115
1116        // Test 2: input = L (encoded LE in 64 bytes)
1117        let mut x = [0u8; 64];
1118        x[0..8].copy_from_slice(&L[0].to_le_bytes());
1119        x[8..16].copy_from_slice(&L[1].to_le_bytes());
1120        x[16..24].copy_from_slice(&L[2].to_le_bytes());
1121        x[24..32].copy_from_slice(&L[3].to_le_bytes());
1122        let r = sc_reduce(&x);
1123        for i in 0..32 {
1124            assert_eq!(r[i], 0, "L mod L should be 0, byte {}", i);
1125        }
1126
1127        // Test 3: input = L+1 → result = 1
1128        let mut x = [0u8; 64];
1129        x[0..8].copy_from_slice(&(L[0] + 1).to_le_bytes());
1130        x[8..16].copy_from_slice(&L[1].to_le_bytes());
1131        x[16..24].copy_from_slice(&L[2].to_le_bytes());
1132        x[24..32].copy_from_slice(&L[3].to_le_bytes());
1133        let r = sc_reduce(&x);
1134        assert_eq!(r[0], 1);
1135    }
1136
1137    #[test]
1138    fn test_fe_pow_fermat() {
1139        // a^(p-1) == 1 mod p for a != 0.
1140        let a = Fe25519::from_u64(7);
1141        let pm1: [u64; 4] = [
1142            0xFFFF_FFFF_FFFF_FFEC,
1143            0xFFFF_FFFF_FFFF_FFFF,
1144            0xFFFF_FFFF_FFFF_FFFF,
1145            0x7FFF_FFFF_FFFF_FFFF,
1146        ];
1147        let r = a.pow(pm1);
1148        assert!(r.equals(Fe25519::ONE));
1149    }
1150
1151    #[test]
1152    fn test_scalar_mul_two() {
1153        let bp = ExtPoint::basepoint();
1154        let mut two = [0u8; 32];
1155        two[0] = 2;
1156        let r = bp.scalar_mul(&two);
1157        assert!(r.equals(bp.double()));
1158    }
1159
1160    #[test]
1161    fn test_scalar_mul_three() {
1162        let bp = ExtPoint::basepoint();
1163        let mut three = [0u8; 32];
1164        three[0] = 3;
1165        let r = bp.scalar_mul(&three);
1166        assert!(r.equals(bp.double().add(bp)));
1167    }
1168
1169    #[test]
1170    fn test_scalar_mul_identity() {
1171        let bp = ExtPoint::basepoint();
1172        let mut one = [0u8; 32];
1173        one[0] = 1;
1174        let result = bp.scalar_mul(&one);
1175        assert!(result.equals(bp));
1176    }
1177
1178    #[test]
1179    fn test_point_add_double() {
1180        let bp = ExtPoint::basepoint();
1181        let bp2_add = bp.add(bp);
1182        let bp2_dbl = bp.double();
1183        assert!(bp2_add.equals(bp2_dbl));
1184    }
1185
1186    /// RFC 8032, Section 7.1 - Test Vector 1 (empty message).
1187    #[test]
1188    fn test_ed25519_vector1() {
1189        let sk_hex = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
1190        let pk_hex = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
1191        let sig_hex = "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155\
1192                        5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b";
1193
1194        let sk_bytes = hex_to_bytes(sk_hex);
1195        let pk_bytes = hex_to_bytes(pk_hex);
1196        let sig_bytes = hex_to_bytes(sig_hex);
1197
1198        let mut sk = [0u8; 32];
1199        sk.copy_from_slice(&sk_bytes);
1200
1201        let (pk, secret) = ed25519_keygen(&sk);
1202        assert_eq!(&pk.0[..], &pk_bytes[..], "Public key mismatch");
1203
1204        let signature = ed25519_sign(&secret, b"");
1205        assert_eq!(&signature.0[..], &sig_bytes[..], "Signature mismatch");
1206
1207        assert!(ed25519_verify(&pk, b"", &signature), "Verification failed");
1208    }
1209
1210    /// RFC 8032, Section 7.1 - Test Vector 2 (message = 0x72).
1211    #[test]
1212    fn test_ed25519_vector2() {
1213        let sk_hex = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb";
1214        let pk_hex = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
1215        let sig_hex = "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da\
1216                        085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00";
1217
1218        let sk_bytes = hex_to_bytes(sk_hex);
1219        let pk_bytes = hex_to_bytes(pk_hex);
1220        let sig_bytes = hex_to_bytes(sig_hex);
1221
1222        let mut sk = [0u8; 32];
1223        sk.copy_from_slice(&sk_bytes);
1224
1225        let (pk, secret) = ed25519_keygen(&sk);
1226        assert_eq!(&pk.0[..], &pk_bytes[..], "Public key mismatch");
1227
1228        let msg = [0x72u8];
1229        let signature = ed25519_sign(&secret, &msg);
1230        assert_eq!(&signature.0[..], &sig_bytes[..], "Signature mismatch");
1231
1232        assert!(ed25519_verify(&pk, &msg, &signature), "Verification failed");
1233    }
1234
1235    // ----- RFC 8032 §7.2 -- Ed25519ctx test vector ("foo" context) ---------
1236    //
1237    //  SECRET KEY: 0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6
1238    //  PUBLIC KEY: dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292
1239    //  MESSAGE   : f726936d19c800494e3fdaff20b276a8
1240    //  CONTEXT   : 666f6f                  (= "foo")
1241    //  SIGNATURE : 55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a
1242    //              8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d
1243    #[test]
1244    fn rfc8032_section_7_2_ed25519ctx_vector() {
1245        let sk_bytes = hex_to_bytes("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6");
1246        let pk_bytes = hex_to_bytes("dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292");
1247        let msg = hex_to_bytes("f726936d19c800494e3fdaff20b276a8");
1248        let context = hex_to_bytes("666f6f"); // "foo"
1249        let sig_bytes = hex_to_bytes(
1250            "55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a\
1251             8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d",
1252        );
1253
1254        let mut sk_arr = [0u8; 32];
1255        sk_arr.copy_from_slice(&sk_bytes);
1256        let (pk, secret) = ed25519_keygen(&sk_arr);
1257        assert_eq!(pk.0.as_slice(), pk_bytes.as_slice(), "pk mismatch");
1258
1259        // Sign and check byte-exact against the pinned signature.
1260        let signature = ed25519ctx_sign(&secret, &msg, &context).expect("ctx_sign");
1261        assert_eq!(
1262            signature.0.as_slice(),
1263            sig_bytes.as_slice(),
1264            "Ed25519ctx signature mismatch"
1265        );
1266
1267        // Verify must accept with the same context.
1268        assert!(ed25519ctx_verify(&pk, &msg, &context, &signature));
1269    }
1270
1271    // ----- RFC 8032 §7.3 -- Ed25519ph test vector ("abc", no context) ------
1272    //
1273    //  SECRET KEY: 833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42
1274    //  PUBLIC KEY: ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf
1275    //  MESSAGE   : 616263                   (= "abc")
1276    //  CONTEXT   : (empty)
1277    //  SIGNATURE : 98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae41
1278    //              31f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406
1279    #[test]
1280    fn rfc8032_section_7_3_ed25519ph_vector() {
1281        let sk_bytes = hex_to_bytes("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42");
1282        let pk_bytes = hex_to_bytes("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf");
1283        let msg = hex_to_bytes("616263"); // "abc"
1284        let sig_bytes = hex_to_bytes(
1285            "98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae41\
1286             31f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406",
1287        );
1288
1289        let mut sk_arr = [0u8; 32];
1290        sk_arr.copy_from_slice(&sk_bytes);
1291        let (pk, secret) = ed25519_keygen(&sk_arr);
1292        assert_eq!(pk.0.as_slice(), pk_bytes.as_slice(), "pk mismatch");
1293
1294        // Empty context is allowed in ph mode.
1295        let signature = ed25519ph_sign(&secret, &msg, b"").expect("ph_sign");
1296        assert_eq!(
1297            signature.0.as_slice(),
1298            sig_bytes.as_slice(),
1299            "Ed25519ph signature mismatch"
1300        );
1301
1302        assert!(ed25519ph_verify(&pk, &msg, b"", &signature));
1303    }
1304
1305    /// Pre-hashed entry point: feeding the SHA-512 of `"abc"` to
1306    /// `ed25519ph_sign_prehashed` must produce the same byte-exact
1307    /// signature as `ed25519ph_sign(b"abc", _)`. Same for verify.
1308    #[test]
1309    fn ed25519ph_prehashed_matches_message_form() {
1310        let sk_bytes = hex_to_bytes("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42");
1311        let mut sk_arr = [0u8; 32];
1312        sk_arr.copy_from_slice(&sk_bytes);
1313        let (pk, secret) = ed25519_keygen(&sk_arr);
1314
1315        let from_msg = ed25519ph_sign(&secret, b"abc", b"").expect("ph_sign");
1316
1317        // Pre-hash with SHA-512 ourselves and feed the digest in.
1318        let digest = Sha512::hash(b"abc");
1319        let from_digest = ed25519ph_sign_prehashed(&secret, &digest, b"").expect("ph_sign_prehashed");
1320
1321        assert_eq!(from_msg.0, from_digest.0);
1322
1323        // Both forms verify equivalently.
1324        assert!(ed25519ph_verify(&pk, b"abc", b"", &from_msg));
1325        assert!(ed25519ph_verify_prehashed(&pk, &digest, b"", &from_msg));
1326    }
1327
1328    // ----- Cross-mode rejection tests --------------------------------------
1329    //
1330    // The dom2 prefix is what makes pure / ctx / ph mutually unforgeable.
1331    // A signature produced under one mode must NOT verify under another --
1332    // and ctx signatures with different `context` values must be mutually
1333    // unforgeable too. These tests catch any regression in the prefix
1334    // handling that would silently let one mode's signature pass another
1335    // mode's verifier.
1336
1337    fn make_test_keys() -> (Ed25519PublicKey, Ed25519SecretKey) {
1338        let sk_bytes = hex_to_bytes("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6");
1339        let mut sk = [0u8; 32];
1340        sk.copy_from_slice(&sk_bytes);
1341        ed25519_keygen(&sk)
1342    }
1343
1344    #[test]
1345    fn ed25519ctx_requires_nonempty_context() {
1346        let (_pk, sk) = make_test_keys();
1347        // ctx_sign with empty context must return None per RFC 8032 §5.1.
1348        assert!(ed25519ctx_sign(&sk, b"any message", b"").is_none());
1349    }
1350
1351    #[test]
1352    fn ed25519ctx_signature_does_not_verify_as_pure() {
1353        let (pk, sk) = make_test_keys();
1354        let msg = b"shared message";
1355        let sig = ed25519ctx_sign(&sk, msg, b"appA").expect("ctx_sign");
1356        // The same (pk, msg, sig) must be REJECTED by pure verify
1357        // because pure verify doesn't apply the dom2 prefix.
1358        assert!(!ed25519_verify(&pk, msg, &sig));
1359    }
1360
1361    #[test]
1362    fn pure_signature_does_not_verify_as_ctx() {
1363        let (pk, sk) = make_test_keys();
1364        let msg = b"shared message";
1365        let sig = ed25519_sign(&sk, msg);
1366        // Pure signature presented to ctx verify must be rejected
1367        // (ctx verify will hash with the dom2 prefix).
1368        assert!(!ed25519ctx_verify(&pk, msg, b"appA", &sig));
1369    }
1370
1371    #[test]
1372    fn ed25519ctx_different_contexts_dont_cross() {
1373        let (pk, sk) = make_test_keys();
1374        let msg = b"shared message";
1375        let sig_a = ed25519ctx_sign(&sk, msg, b"appA").expect("ctx_sign A");
1376        let sig_b = ed25519ctx_sign(&sk, msg, b"appB").expect("ctx_sign B");
1377
1378        // Each verifies under its own context.
1379        assert!(ed25519ctx_verify(&pk, msg, b"appA", &sig_a));
1380        assert!(ed25519ctx_verify(&pk, msg, b"appB", &sig_b));
1381
1382        // Cross-context must fail.
1383        assert!(!ed25519ctx_verify(&pk, msg, b"appB", &sig_a));
1384        assert!(!ed25519ctx_verify(&pk, msg, b"appA", &sig_b));
1385
1386        // And the two signatures must actually be different (otherwise
1387        // the previous assertion would just be a fluke).
1388        assert_ne!(sig_a.0, sig_b.0);
1389    }
1390
1391    #[test]
1392    fn ed25519ph_signature_does_not_verify_as_pure() {
1393        let (pk, sk) = make_test_keys();
1394        let msg = b"shared message";
1395        let sig = ed25519ph_sign(&sk, msg, b"").expect("ph_sign");
1396        // ph verify pre-hashes the message; pure verify doesn't.
1397        // The same signature presented to pure verify must reject.
1398        assert!(!ed25519_verify(&pk, msg, &sig));
1399    }
1400
1401    #[test]
1402    fn ed25519ph_signature_does_not_verify_as_ctx() {
1403        let (pk, sk) = make_test_keys();
1404        let msg = b"shared message";
1405        let sig = ed25519ph_sign(&sk, msg, b"context").expect("ph_sign");
1406        // ph and ctx use different F bytes (1 vs 0) in the dom2 prefix,
1407        // so even with the same context they don't cross.
1408        assert!(!ed25519ctx_verify(&pk, msg, b"context", &sig));
1409    }
1410
1411    #[test]
1412    fn ed25519ctx_max_context_length() {
1413        let (pk, sk) = make_test_keys();
1414        let msg = b"x";
1415        // 255-byte context is allowed.
1416        let ctx_max = vec![0xa5u8; 255];
1417        let sig = ed25519ctx_sign(&sk, msg, &ctx_max).expect("ctx max len");
1418        assert!(ed25519ctx_verify(&pk, msg, &ctx_max, &sig));
1419
1420        // 256-byte context is NOT allowed (the length octet would
1421        // overflow).
1422        let ctx_too_long = vec![0xa5u8; 256];
1423        assert!(ed25519ctx_sign(&sk, msg, &ctx_too_long).is_none());
1424        assert!(!ed25519ctx_verify(&pk, msg, &ctx_too_long, &sig));
1425    }
1426}