Skip to main content

crypto/curve25519/
ed25519.rs

1use constant_time_eq::constant_time_eq;
2
3use super::curve25519::{FieldElement, U256};
4use crate::{EllipticCurveError, Hasher, sha2::Sha512};
5
6pub const SECRET_KEY_SIZE: usize = 32;
7pub const PUBLIC_KEY_SIZE: usize = 32;
8pub const SIGNATURE_SIZE: usize = 64;
9
10const MODULUS_L: U256 = U256::from_limbs([
11    0x5812_631a_5cf5_d3ed,
12    0x14de_f9de_a2f7_9cd6,
13    0x0000_0000_0000_0000,
14    0x1000_0000_0000_0000,
15]);
16
17// const P_PLUS_THREE_OVER_EIGHT: U256 = U256::from_limbs([
18//     0xffff_ffff_ffff_fffe,
19//     0xffff_ffff_ffff_ffff,
20//     0xffff_ffff_ffff_ffff,
21//     0x0fff_ffff_ffff_ffff,
22// ]);
23
24const EDWARDS_D: FieldElement = FieldElement(U256::from_limbs([
25    0x75eb_4dca_1359_78a3,
26    0x0070_0a4d_4141_d8ab,
27    0x8cc7_4079_7779_e898,
28    0x5203_6cee_2b6f_fe73,
29]));
30
31/// R = 2^256 mod L, used for fast scalar reduction.
32const R: U256 = U256::from_limbs([
33    0xd6ec_3174_8d98_951d,
34    0xc6ef_5bf4_737d_cf70,
35    0xffff_ffff_ffff_fffe,
36    0x0fff_ffff_ffff_ffff,
37]);
38
39/// Barrett µ = floor(2^512 / L), used for fast wide reduction modulo L.
40const BAR_MU: [u64; 5] = [
41    0xed9c_e5a3_0a2c_131b,
42    0x2106_215d_0863_29a7,
43    0xffff_ffff_ffff_ffeb,
44    0xffff_ffff_ffff_ffff,
45    0x0000_0000_0000_000f,
46];
47
48const EDWARDS_2D: FieldElement = FieldElement(U256::from_limbs([
49    0xebd6_9b94_26b2_f159,
50    0x00e0_149a_8283_b156,
51    0x198e_80f2_eef3_d130,
52    0x2406_d9dc_56df_fce7,
53]));
54
55const SQRT_M1: FieldElement = FieldElement(U256::from_limbs([
56    0xc4ee_1b27_4a0e_a0b0,
57    0x2f43_1806_ad2f_e478,
58    0x2b4d_0099_3dfb_d7a7,
59    0x2b83_2480_4fc1_df0b,
60]));
61
62const BASEPOINT: EdwardsPoint = EdwardsPoint {
63    x: FieldElement(U256::from_limbs([
64        0xc9562d608f25d51a,
65        0x692cc7609525a7b2,
66        0xc0a4e231fdd6dc5c,
67        0x216936d3cd6e53fe,
68    ])),
69    y: FieldElement(U256::from_limbs([
70        0x6666666666666658,
71        0x6666666666666666,
72        0x6666666666666666,
73        0x6666666666666666,
74    ])),
75    z: FieldElement(U256::from_limbs([
76        0x0000000000000001,
77        0x0000000000000000,
78        0x0000000000000000,
79        0x0000000000000000,
80    ])),
81    t: FieldElement(U256::from_limbs([
82        0x6dde8ab3a5b7dda3,
83        0x20f09f80775152f5,
84        0x66ea4e8e64abe37d,
85        0x67875f0fd78b7665,
86    ])),
87};
88
89/// Ed25519 signing key (RFC 8032).
90///
91/// # Generating a key
92///
93/// ```ignore
94/// use crypto::curve25519::ed25519::SecretKey;
95///
96/// let priv_key = SecretKey::generate().unwrap();
97/// let pub_key = priv_key.public_key();
98/// ```
99///
100/// # Signing and verification
101///
102/// ```ignore
103/// use crypto::curve25519::ed25519::SecretKey;
104///
105/// let priv_key = SecretKey::generate().unwrap();
106/// let pub_key = priv_key.public_key();
107/// let sig = priv_key.sign(b"message");
108/// assert!(pub_key.verify(b"message", &sig).is_ok());
109/// ```
110#[derive(Clone, PartialEq, Eq)]
111#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
112pub struct SecretKey {
113    seed: [u8; SECRET_KEY_SIZE],
114    scalar: Scalar,
115    prefix: [u8; 32],
116    public_point: EdwardsPoint,
117    public_bytes: [u8; PUBLIC_KEY_SIZE],
118}
119
120impl SecretKey {
121    /// Generates a fresh random secret key.
122    ///
123    /// Returns [`EllipticCurveError::Random`] when the operating system's
124    /// random number generator is unavailable or fails.
125    #[cfg(feature = "random")]
126    pub fn generate() -> Result<SecretKey, EllipticCurveError> {
127        let seed: [u8; SECRET_KEY_SIZE] = crate::random::bytes()?;
128        Ok(SecretKey::from_bytes(&seed))
129    }
130
131    pub fn from_bytes(seed: &[u8; SECRET_KEY_SIZE]) -> SecretKey {
132        let (scalar, prefix) = expand_secret(seed);
133        let public_point = scalar_mul_base(&scalar);
134        let public_bytes = public_point
135            .to_bytes()
136            .expect("basepoint multiplication must produce a valid point");
137        SecretKey {
138            seed: *seed,
139            scalar,
140            prefix,
141            public_point,
142            public_bytes,
143        }
144    }
145
146    // pub fn from_seed_unchecked(seed: &[u8; PRIVATE_KEY_SIZE]) -> SecretKey {
147    //     SecretKey::from_bytes(seed)
148    // }
149
150    pub fn sign(&self, message: &[u8]) -> [u8; SIGNATURE_SIZE] {
151        let r = hash_to_scalar(&[&self.prefix, message]);
152        let r_point = scalar_mul_base(&r)
153            .to_bytes()
154            .expect("basepoint multiplication must produce a valid point");
155
156        let k = hash_to_scalar(&[&r_point, &self.public_bytes, message]);
157        let s = r.add(k.mul(self.scalar));
158
159        let mut signature = [0u8; SIGNATURE_SIZE];
160        signature[..32].copy_from_slice(&r_point);
161        signature[32..].copy_from_slice(&s.to_bytes());
162        signature
163    }
164
165    #[inline]
166    pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE] {
167        self.seed
168    }
169
170    #[inline]
171    pub fn public_key(&self) -> PublicKey {
172        PublicKey {
173            point: self.public_point,
174            bytes: self.public_bytes,
175        }
176    }
177}
178
179impl From<&[u8; SECRET_KEY_SIZE]> for SecretKey {
180    fn from(bytes: &[u8; SECRET_KEY_SIZE]) -> Self {
181        Self::from_bytes(bytes)
182    }
183}
184
185impl TryFrom<&[u8]> for SecretKey {
186    type Error = EllipticCurveError;
187
188    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
189        Ok(Self::from_bytes(bytes.try_into().map_err(|_| EllipticCurveError::InvalidKey)?))
190    }
191}
192
193/// Ed25519 public key for signature verification (RFC 8032).
194///
195/// Public keys are decoded with the strict RFC 8032 rules: non-canonical
196/// encodings and small-order points (the identity and other low-order torsion
197/// points) are rejected, as the latter would otherwise permit trivial
198/// signature forgeries. Honest Ed25519 public keys are always in the
199/// prime-order subgroup and are accepted.
200///
201/// # Verifying a signature
202///
203/// ```ignore
204/// use crypto::curve25519::ed25519::{PublicKey, SecretKey};
205///
206/// let priv_key = SecretKey::generate().unwrap();
207/// let pub_key = priv_key.public_key();
208/// let sig = priv_key.sign(b"message");
209/// assert!(pub_key.verify(b"message", &sig).is_ok());
210/// ```
211///
212/// # Deserializing from bytes
213///
214/// ```ignore
215/// use crypto::curve25519::ed25519::PublicKey;
216///
217/// let bytes = [0u8; 32]; // replace with a real public key
218/// let pub_key = PublicKey::from_bytes(&bytes);
219/// ```
220#[derive(Clone, Debug, PartialEq, Eq)]
221pub struct PublicKey {
222    point: EdwardsPoint,
223    bytes: [u8; PUBLIC_KEY_SIZE],
224}
225
226impl PublicKey {
227    /// Decodes a public key from its 32-byte encoding.
228    ///
229    /// Returns [`EllipticCurveError::InvalidKey`] if the input is not a
230    /// canonically encoded curve point, or if it decodes to a small-order
231    /// point (which has a torsion component that can be used to forge
232    /// signatures).
233    pub fn from_bytes(key: &[u8; PUBLIC_KEY_SIZE]) -> Result<PublicKey, EllipticCurveError> {
234        let point = EdwardsPoint::from_bytes(key.try_into().unwrap()).ok_or(EllipticCurveError::InvalidKey)?;
235        if point.is_small_order() {
236            return Err(EllipticCurveError::InvalidKey);
237        }
238        Ok(PublicKey {
239            point,
240            bytes: *key,
241        })
242    }
243
244    pub fn verify(&self, message: &[u8], signature: &[u8; SIGNATURE_SIZE]) -> Result<(), EllipticCurveError> {
245        ed25519_verify(&self.point, message, signature)
246    }
247
248    #[inline]
249    pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE] {
250        self.bytes
251    }
252
253    pub fn to_montgomery_u(&self) -> Option<FieldElement> {
254        let inv_z = self.point.z.invert()?;
255        let y = self.point.y.mul(inv_z);
256        let one = FieldElement::ONE;
257        let u = (one.add(y)).mul((one.sub(y)).invert()?);
258        Some(u)
259    }
260}
261
262impl TryFrom<&[u8]> for PublicKey {
263    type Error = EllipticCurveError;
264
265    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
266        Self::from_bytes(bytes.try_into().map_err(|_| EllipticCurveError::InvalidKey)?)
267    }
268}
269
270#[derive(Clone, Copy, Debug, PartialEq, Eq)]
271#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))]
272struct Scalar(U256);
273
274impl Scalar {
275    fn from_canonical_bytes(bytes: &[u8; 32]) -> Option<Self> {
276        let value = U256::from_le_slice(bytes);
277        if value.ct_ge(&MODULUS_L) {
278            None
279        } else {
280            Some(Self(value))
281        }
282    }
283
284    /// Reduce an arbitrary-length byte sequence modulo L (the ed25519 subgroup order).
285    ///
286    /// Two fast paths:
287    /// - <= 32 bytes: parse as U256 and repeatedly conditionally subtract L (at most 16 times,
288    ///   since a 256-bit value is < 16·L). Constant-time via ct_select.
289    /// - > 32 bytes: Horner evaluation in base 2^256. Split into 32-byte chunks processed
290    ///   from most significant to least, using the precomputed R = 2^256 mod L for shifting
291    ///   and the Barrett constant µ for fast modular multiplication of the accumulator by R.
292    fn reduce_bytes_mod_l(bytes: &[u8]) -> Self {
293        let len = bytes.len();
294        if len <= 32 {
295            let mut padded = [0u8; 32];
296            padded[..len].copy_from_slice(bytes);
297            let val = U256::from_le_slice(&padded);
298            let mut result = val;
299            let mut i = 16;
300            while i > 0 {
301                let (diff, borrow) = result.sub_raw(&MODULUS_L);
302                result = U256::ct_select(&diff, &result, borrow == 0);
303                i -= 1;
304            }
305            Self(result)
306        } else {
307            let chunk_count = len.div_ceil(32);
308            let mut acc = U256::ZERO;
309            let mut chunk_idx = chunk_count;
310            while chunk_idx > 0 {
311                chunk_idx -= 1;
312                let chunk_start = chunk_idx * 32;
313                let chunk_end = usize::min(chunk_start + 32, len);
314                let chunk_len = chunk_end - chunk_start;
315
316                if !acc.is_zero() {
317                    acc = acc.mul_mod_barrett_w::<10>(&R, &MODULUS_L, &BAR_MU);
318                }
319
320                let mut padded = [0u8; 32];
321                padded[..chunk_len].copy_from_slice(&bytes[chunk_start..chunk_end]);
322                let mut chunk_reduced = U256::from_le_slice(&padded);
323                let mut j = 16;
324                while j > 0 {
325                    let (diff, borrow) = chunk_reduced.sub_raw(&MODULUS_L);
326                    chunk_reduced = U256::ct_select(&diff, &chunk_reduced, borrow == 0);
327                    j -= 1;
328                }
329
330                acc = acc.add_mod(&chunk_reduced, &MODULUS_L);
331            }
332            Self(acc)
333        }
334    }
335
336    #[inline]
337    fn to_bytes(self) -> [u8; 32] {
338        self.0.to_le_bytes_fixed::<32>()
339    }
340
341    #[inline]
342    fn add(self, rhs: Self) -> Self {
343        Self(self.0.add_mod(&rhs.0, &MODULUS_L))
344    }
345
346    #[inline]
347    fn mul(self, rhs: Self) -> Self {
348        Self(self.0.mul_mod_barrett_w::<10>(&rhs.0, &MODULUS_L, &BAR_MU))
349    }
350}
351
352#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))]
354struct EdwardsPoint {
355    x: FieldElement,
356    y: FieldElement,
357    z: FieldElement,
358    t: FieldElement,
359}
360
361impl EdwardsPoint {
362    #[inline]
363    fn identity() -> Self {
364        Self {
365            x: FieldElement::ZERO,
366            y: FieldElement::ONE,
367            z: FieldElement::ONE,
368            t: FieldElement::ZERO,
369        }
370    }
371
372    #[inline]
373    fn from_affine(x: FieldElement, y: FieldElement) -> Self {
374        Self {
375            x,
376            y,
377            z: FieldElement::ONE,
378            t: x.mul(y),
379        }
380    }
381
382    fn from_bytes(bytes: &[u8; 32]) -> Option<Self> {
383        let sign = (bytes[31] >> 7) == 1;
384        let mut y_bytes = *bytes;
385        y_bytes[31] &= 0x7f;
386        let y = FieldElement::from_canonical_bytes(&y_bytes)?;
387        let y2 = y.square();
388        let u = y2.sub(FieldElement::ONE);
389        let v = EDWARDS_D.mul(y2).add(FieldElement::ONE);
390        let x2 = u.mul(v.invert()?);
391        let mut x = sqrt(&x2)?;
392        if x.is_zero() && sign {
393            return None;
394        }
395        if x.is_odd() != sign {
396            x = x.negate();
397        }
398        Some(Self::from_affine(x, y))
399    }
400
401    fn to_bytes(self) -> Option<[u8; 32]> {
402        let inv_z = self.z.invert()?;
403        let x = self.x.mul(inv_z);
404        let y = self.y.mul(inv_z);
405        let mut out = y.to_bytes();
406        if x.is_odd() {
407            out[31] |= 0x80;
408        }
409        Some(out)
410    }
411
412    #[inline]
413    fn add(&self, rhs: &Self) -> Self {
414        let a = self.y.sub(self.x).mul(rhs.y.sub(rhs.x));
415        let b = self.y.add(self.x).mul(rhs.y.add(rhs.x));
416        let c = self.t.mul(rhs.t).mul(EDWARDS_2D);
417        let z1z2 = self.z.mul(rhs.z);
418        let d = z1z2.add(z1z2);
419        let e = b.sub(a);
420        let f = d.sub(c);
421        let g = d.add(c);
422        let h = b.add(a);
423        Self {
424            x: e.mul(f),
425            y: g.mul(h),
426            t: e.mul(h),
427            z: f.mul(g),
428        }
429    }
430
431    #[inline]
432    fn double(&self) -> Self {
433        let a = self.x.square();
434        let b = self.y.square();
435        let z2 = self.z.square();
436        let c = z2.add(z2);
437        let d = a.negate();
438        let e = self.x.add(self.y).square().sub(a).sub(b);
439        let g = d.add(b);
440        let f = g.sub(c);
441        let h = d.sub(b);
442        Self {
443            x: e.mul(f),
444            y: g.mul(h),
445            t: e.mul(h),
446            z: f.mul(g),
447        }
448    }
449
450    #[inline]
451    fn select(a: &Self, b: &Self, choice: bool) -> Self {
452        Self {
453            x: FieldElement::select(&a.x, &b.x, choice),
454            y: FieldElement::select(&a.y, &b.y, choice),
455            z: FieldElement::select(&a.z, &b.z, choice),
456            t: FieldElement::select(&a.t, &b.t, choice),
457        }
458    }
459
460    #[inline]
461    fn mul_by_cofactor(&self) -> Self {
462        self.double().double().double()
463    }
464
465    /// Returns `true` if this point has small order, i.e. `[8]P == identity`.
466    ///
467    /// Such points (the identity or one of the order-2/4/8 torsion points) are
468    /// never produced by Ed25519 key generation. Accepting them as public keys
469    /// is unsafe: the torsion component can be cancelled out of the
470    /// verification equation, allowing forgeries.
471    #[inline]
472    fn is_small_order(&self) -> bool {
473        let p = self.mul_by_cofactor();
474        p.x.is_zero() && p.y.ct_eq(&p.z)
475    }
476}
477
478#[inline]
479fn sqrt(a: &FieldElement) -> Option<FieldElement> {
480    let mut candidate = a.pow_sqrt_exponent();
481    if !candidate.square().ct_eq(a) {
482        candidate = candidate.mul(SQRT_M1);
483    }
484    if candidate.square().ct_eq(a) {
485        Some(candidate)
486    } else {
487        None
488    }
489}
490
491fn scalar_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {
492    let mut table = [EdwardsPoint::identity(); 16];
493    table[1] = *point;
494    let mut i = 2;
495    while i < 16 {
496        table[i] = table[i - 1].add(point);
497        i += 1;
498    }
499
500    scalar_mul_table(&table, scalar)
501}
502
503/// Optimized version of `scalar_mul` for `BASEPOINT` using a pre-computed table.
504#[cfg(feature = "std")]
505fn scalar_mul_base(scalar: &Scalar) -> EdwardsPoint {
506    use std::sync::LazyLock;
507    static TABLE: LazyLock<[EdwardsPoint; 16]> = LazyLock::new(|| {
508        let mut t = [EdwardsPoint::identity(); 16];
509        t[1] = BASEPOINT;
510        let mut i = 2;
511        while i < 16 {
512            t[i] = t[i - 1].add(&BASEPOINT);
513            i += 1;
514        }
515        t
516    });
517
518    scalar_mul_table(&TABLE, scalar)
519}
520
521#[cfg(not(feature = "std"))]
522#[inline]
523fn scalar_mul_base(scalar: &Scalar) -> EdwardsPoint {
524    scalar_mul(&BASEPOINT, scalar)
525}
526
527#[inline]
528fn scalar_mul_table(table: &[EdwardsPoint; 16], scalar: &Scalar) -> EdwardsPoint {
529    let mut result = EdwardsPoint::identity();
530    let mut win = 63;
531    loop {
532        let idx = scalar_window(scalar, win);
533        let selected = ct_select_from_table(table, idx);
534        result = result.add(&selected);
535        if win == 0 {
536            break;
537        }
538        result = result.double().double().double().double();
539        win -= 1;
540    }
541    result
542}
543
544#[inline]
545fn ct_select_from_table(table: &[EdwardsPoint; 16], index: usize) -> EdwardsPoint {
546    let mut result = table[0];
547    let mut i = 1;
548    while i < 16 {
549        let diff = i ^ index;
550        let choice = ((diff.wrapping_sub(1) >> (usize::BITS - 1)) & 1) != 0;
551        result = EdwardsPoint::select(&table[i], &result, choice);
552        i += 1;
553    }
554    result
555}
556
557#[inline]
558fn scalar_window(scalar: &Scalar, window: usize) -> usize {
559    let bit_pos = window * 4;
560    let limb_idx = bit_pos / 64;
561    let limb = scalar.0.limbs[limb_idx];
562    ((limb >> (bit_pos % 64)) & 0xf) as usize
563}
564
565fn hash_to_scalar(parts: &[&[u8]]) -> Scalar {
566    let mut hasher = Sha512::new();
567    let mut i = 0usize;
568    while i < parts.len() {
569        hasher.update(parts[i]);
570        i += 1;
571    }
572    let digest = hasher.sum();
573    Scalar::reduce_bytes_mod_l(digest.as_ref())
574}
575
576fn expand_secret(private_key: &[u8; SECRET_KEY_SIZE]) -> (Scalar, [u8; 32]) {
577    let mut digest = Sha512::hash(private_key);
578    let digest: &mut [u8; 64] = digest.as_mut().try_into().unwrap();
579
580    digest[0] &= 248;
581    digest[31] &= 63;
582    digest[31] |= 64;
583
584    let scalar = Scalar::reduce_bytes_mod_l(&digest[..32]);
585    let mut prefix = [0u8; 32];
586    prefix.copy_from_slice(&digest[32..]);
587    (scalar, prefix)
588}
589
590fn ed25519_verify(
591    point: &EdwardsPoint,
592    message: &[u8],
593    signature: &[u8; SIGNATURE_SIZE],
594) -> Result<(), EllipticCurveError> {
595    let r_bytes: &[u8; 32] = &signature[..32].try_into().unwrap();
596    let r = EdwardsPoint::from_bytes(r_bytes).ok_or(EllipticCurveError::InvalidSignature)?;
597
598    let s =
599        Scalar::from_canonical_bytes(&signature[32..].try_into().unwrap()).ok_or(EllipticCurveError::Unspecified)?;
600
601    let k = hash_to_scalar(&[
602        r_bytes,
603        &point.to_bytes().ok_or(EllipticCurveError::Unspecified)?,
604        message,
605    ]);
606
607    // RFC 8032 section 5.1.7 permits either the cofactored check
608    // `[8][S]B == [8]R + [8][k]A` or the stricter cofactorless check
609    // `[S]B == R + [k]A`. The cofactorless equation is used here, matching
610    // the common ref10-derived implementations and rejecting the
611    // `low_order_residue` edge cases.
612    let lhs = scalar_mul_base(&s);
613    let rhs = r.add(&scalar_mul(point, &k));
614
615    // Point encodings are public data, but the comparison is done in
616    // constant time for consistency with the rest of the crate.
617    match (lhs.to_bytes(), rhs.to_bytes()) {
618        (Some(lhs), Some(rhs)) if constant_time_eq(&lhs, &rhs) => Ok(()),
619        _ => Err(EllipticCurveError::InvalidSignature),
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    use super::*;
626    use crate::curve25519::x25519;
627
628    #[cfg(feature = "zeroize")]
629    #[test]
630    fn secret_key_zeroize_clears_seed() {
631        use zeroize::Zeroize;
632
633        let mut key = SecretKey::from_bytes(&[1u8; SECRET_KEY_SIZE]);
634        assert_ne!(key.to_bytes(), [0u8; SECRET_KEY_SIZE]);
635        key.zeroize();
636        assert_eq!(key.to_bytes(), [0u8; SECRET_KEY_SIZE]);
637    }
638
639    const BASEPOINT_COMPRESSED: [u8; 32] = [
640        0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
641        0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
642    ];
643
644    fn decode_hex<const N: usize>(hex_bytes: &str) -> [u8; N] {
645        let bytes = hex::decode(hex_bytes).unwrap();
646        assert_eq!(bytes.len(), N);
647        let mut out = [0u8; N];
648        out.copy_from_slice(&bytes);
649        out
650    }
651
652    fn decode_hex_vec(hex_bytes: &str) -> Vec<u8> {
653        hex::decode(hex_bytes).unwrap()
654    }
655
656    #[test]
657    fn sign_verify_roundtrip() {
658        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
659        let priv_key = SecretKey::from_bytes(&seed);
660        let pub_key = priv_key.public_key();
661
662        let messages: [&[u8]; 4] = [b"", b"hello", b"test message", &[0xffu8; 256]];
663        for msg in &messages {
664            let sig = priv_key.sign(msg);
665            assert!(pub_key.verify(msg, &sig).is_ok());
666            assert!(pub_key.verify(b"wrong", &sig).is_err());
667        }
668    }
669
670    #[test]
671    fn public_key_bytes_roundtrip() {
672        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
673        let priv_key = SecretKey::from_bytes(&seed);
674        let pub_key = priv_key.public_key();
675        let pub_bytes = pub_key.to_bytes();
676        let restored = PublicKey::from_bytes(&pub_bytes).unwrap();
677        assert_eq!(pub_bytes, restored.to_bytes());
678    }
679
680    #[test]
681    fn private_key_bytes_roundtrip() {
682        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
683        let priv_key = SecretKey::from_bytes(&seed);
684        assert_eq!(priv_key.to_bytes(), seed);
685    }
686
687    #[test]
688    fn generate_produces_valid_keys() {
689        let priv_key = SecretKey::generate().unwrap();
690        let pub_key = priv_key.public_key();
691        let sig = priv_key.sign(b"hello");
692        assert!(pub_key.verify(b"hello", &sig).is_ok());
693    }
694
695    #[test]
696    fn rejects_invalid_public_key() {
697        assert!(PublicKey::from_bytes(&[0xffu8; 32]).is_err());
698        let p_enc = decode_hex::<32>("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f");
699        assert!(PublicKey::from_bytes(&p_enc).is_err());
700    }
701
702    fn check_vector(
703        seed_hex: &'static str,
704        public_key_hex: &'static str,
705        message_hex: &'static str,
706        signature_hex: &'static str,
707    ) {
708        let seed = decode_hex::<32>(seed_hex);
709        let pk_expected = decode_hex::<32>(public_key_hex);
710        let sig_expected = decode_hex::<64>(signature_hex);
711        let msg = decode_hex_vec(message_hex);
712
713        let priv_key = SecretKey::from_bytes(&seed);
714        assert_eq!(priv_key.public_key().to_bytes(), pk_expected);
715        assert_eq!(priv_key.sign(&msg), sig_expected);
716        assert!(priv_key.public_key().verify(&msg, &sig_expected).is_ok());
717    }
718
719    #[test]
720    fn rfc8032_vectors() {
721        check_vector(
722            "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
723            "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
724            "",
725            "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
726        );
727        check_vector(
728            "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
729            "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
730            "72",
731            "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
732        );
733        check_vector(
734            "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
735            "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
736            "af82",
737            "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
738        );
739    }
740
741    #[test]
742    fn go_golden_vectors() {
743        let data = include_str!("../../testdata/ed25519/sign.input");
744
745        for line in data.lines() {
746            let mut parts = line.split(':');
747            let private_and_public = parts.next().unwrap();
748            let public_key = parts.next().unwrap();
749            let message = parts.next().unwrap();
750            let signature_with_message = parts.next().unwrap();
751            assert!(parts.next().is_some());
752            assert!(parts.next().is_none());
753
754            check_vector(&private_and_public[..64], public_key, message, &signature_with_message[..128]);
755        }
756    }
757
758    #[test]
759    fn verify_rejects_tampering_and_non_canonical_s() {
760        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
761        let pub_key = SecretKey::from_bytes(&seed).public_key();
762        let signature = SecretKey::from_bytes(&seed).sign(b"message");
763
764        assert!(pub_key.verify(b"message", &signature).is_ok());
765        assert!(pub_key.verify(b"tampered", &signature).is_err());
766
767        let mut bad_signature = signature;
768        bad_signature[0] ^= 0x80;
769        assert!(pub_key.verify(b"message", &bad_signature).is_err());
770
771        let mut non_canonical_s = signature;
772        non_canonical_s[32..].copy_from_slice(&[
773            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00,
774            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
775        ]);
776        assert!(pub_key.verify(b"message", &non_canonical_s).is_err());
777    }
778
779    #[test]
780    fn public_key_validation_rejects_invalid_encodings() {
781        let valid = decode_hex::<32>("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");
782        assert!(PublicKey::from_bytes(&valid).is_ok());
783
784        let mut invalid = [0xffu8; 32];
785        assert!(PublicKey::from_bytes(&invalid).is_err());
786
787        invalid = valid;
788        invalid[31] |= 0x80;
789        invalid[..31].fill(0);
790        assert!(PublicKey::from_bytes(&invalid).is_err());
791    }
792
793    #[test]
794    fn rejects_low_order_public_keys() {
795        // Canonical encodings of the edwards25519 low-order points (order 1, 2,
796        // 4 and 8), listed in the CCTV ed25519 vector documentation. None of
797        // them is a valid Ed25519 public key.
798        let low_order_points: [&str; 8] = [
799            "0000000000000000000000000000000000000000000000000000000000000000",
800            "0000000000000000000000000000000000000000000000000000000000000080",
801            "0100000000000000000000000000000000000000000000000000000000000000",
802            "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05",
803            "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85",
804            "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a",
805            "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
806            "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
807        ];
808
809        for hex_key in low_order_points {
810            let bytes = decode_hex::<32>(hex_key);
811            assert!(
812                PublicKey::from_bytes(&bytes).is_err(),
813                "low-order public key {hex_key} must be rejected",
814            );
815        }
816    }
817
818    #[test]
819    fn is_small_order_detects_torsion_points() {
820        assert!(!BASEPOINT.is_small_order());
821        assert!(EdwardsPoint::identity().is_small_order());
822
823        // Order-2 point y = p - 1.
824        let order_two = EdwardsPoint::from_bytes(&decode_hex::<32>(
825            "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
826        ))
827        .unwrap();
828        assert!(order_two.is_small_order());
829    }
830
831    #[test]
832    fn rejects_low_order_key_forgery() {
833        // H-03: with a small-order public key A, `R = [s]B` verifies for any
834        // `s` and message under the cofactored equation. Rejecting the key at
835        // import means the forgery never reaches verification.
836        let identity = decode_hex::<32>("0100000000000000000000000000000000000000000000000000000000000000");
837        let order_two = decode_hex::<32>("ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f");
838
839        for key in [identity, order_two] {
840            assert!(PublicKey::from_bytes(&key).is_err());
841        }
842
843        // The forged signature is well-formed, but only a small-order key would
844        // accept it; a genuine key rejects it.
845        let s = Scalar::from_canonical_bytes(&[7u8; 32]).expect("7...7 is a valid scalar");
846        let mut forged_signature = [0u8; SIGNATURE_SIZE];
847        forged_signature[..32].copy_from_slice(&scalar_mul_base(&s).to_bytes().unwrap());
848        forged_signature[32..].copy_from_slice(&s.to_bytes());
849
850        let genuine_seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
851        let genuine = SecretKey::from_bytes(&genuine_seed).public_key();
852        assert!(genuine.verify(b"forged message", &forged_signature).is_err());
853    }
854
855    #[test]
856    fn cctv_ed25519_vectors() {
857        let data = include_str!("../../testdata/ed25519/cctv_vectors.txt");
858
859        for line in data.lines() {
860            let parts: Vec<&str> = line.split(':').collect();
861            assert_eq!(parts.len(), 5, "malformed line: {line}");
862            let number = parts[0];
863            let key_hex = parts[1];
864            let sig_hex = parts[2];
865            let msg_hex = parts[3];
866            let flags_str = parts[4];
867
868            let flags: Vec<&str> = if flags_str.is_empty() {
869                vec![]
870            } else {
871                flags_str.split(',').collect()
872            };
873
874            let has_non_canonical_a = flags.contains(&"non_canonical_A");
875            let has_non_canonical_r = flags.contains(&"non_canonical_R");
876            // Small-order public keys are rejected at import (torsion forgery),
877            // and the cofactorless equation rejects the `low_order_residue`
878            // cases. This matches the strict profile of libsodium and
879            // ed25519-dalek's `verify_strict`.
880            let has_low_order_a = flags.contains(&"low_order_A");
881            let has_low_order_residue = flags.contains(&"low_order_residue");
882            let should_reject = has_non_canonical_a || has_non_canonical_r || has_low_order_a || has_low_order_residue;
883
884            let public_key = decode_hex::<32>(key_hex);
885            let signature = decode_hex::<64>(sig_hex);
886            let message = decode_hex_vec(msg_hex);
887
888            let pub_key = PublicKey::from_bytes(&public_key);
889            let result = pub_key.and_then(|pk| pk.verify(&message, &signature));
890
891            if should_reject {
892                assert!(
893                    result.is_err(),
894                    "vector #{number} should be rejected (flags: {flags_str}) but was accepted",
895                );
896            } else {
897                assert!(
898                    result.is_ok(),
899                    "vector #{number} should be accepted (flags: {flags_str}) but was rejected",
900                );
901            }
902        }
903    }
904
905    #[test]
906    fn rfc8032_extended_vectors() {
907        check_vector(
908            "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42",
909            "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf",
910            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
911            "dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b58909351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704",
912        );
913    }
914
915    #[test]
916    fn verify_rejects_all_zero_signature() {
917        let public_key = decode_hex::<32>("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");
918        let signature = [0u8; 64];
919        let pk = PublicKey::from_bytes(&public_key).unwrap();
920        let result = pk.verify(b"test", &signature);
921        assert!(result.is_err());
922    }
923
924    #[test]
925    fn verify_rejects_s_equals_l() {
926        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
927        let pub_key = SecretKey::from_bytes(&seed).public_key();
928        let signature = SecretKey::from_bytes(&seed).sign(b"test");
929
930        let mut bad_sig = signature;
931        bad_sig[32..].copy_from_slice(&[
932            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00,
933            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
934        ]);
935        assert!(pub_key.verify(b"test", &bad_sig).is_err());
936    }
937
938    #[test]
939    fn verify_rejects_non_canonical_point_encodings() {
940        let non_canonical_key = decode_hex::<32>("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f");
941        assert!(PublicKey::from_bytes(&non_canonical_key).is_err());
942
943        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
944        let pub_key = SecretKey::from_bytes(&seed).public_key();
945        let mut bad_sig = SecretKey::from_bytes(&seed).sign(b"test");
946        bad_sig[..32].copy_from_slice(&[
947            0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
948            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
949        ]);
950        assert!(pub_key.verify(b"test", &bad_sig).is_err());
951    }
952
953    /// The identity is a valid Edwards point and is deliberately accepted by the
954    /// low-level [`EdwardsPoint::from_bytes`] decoder. It is *not* a usable
955    /// public key: [`PublicKey::from_bytes`] rejects it (and every other
956    /// small-order point) to prevent torsion-based signature forgeries.
957    #[test]
958    fn edwards_identity_point_roundtrip() {
959        let id_bytes = [
960            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
961            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
962        ];
963        let point = EdwardsPoint::from_bytes(&id_bytes).unwrap();
964        let roundtripped = point.to_bytes().unwrap();
965        assert_eq!(roundtripped, id_bytes);
966    }
967
968    #[test]
969    fn ed25519_to_montgomery_u_conversion() {
970        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
971        let priv_key = SecretKey::from_bytes(&seed);
972        let ed_pub = priv_key.public_key();
973
974        let u = ed_pub
975            .to_montgomery_u()
976            .expect("valid ed point must convert to montgomery u");
977        let u_bytes = u.to_bytes();
978
979        let x_pub_from_ed = x25519::PublicKey::try_from(&ed_pub).unwrap();
980        assert_eq!(u_bytes, x_pub_from_ed.to_bytes());
981
982        assert_ne!(u_bytes, [0u8; 32], "montgomery u must be non-zero for non-identity point");
983    }
984
985    #[test]
986    fn rfc8032_test_1024() {
987        check_vector(
988            "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5",
989            "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e",
990            "08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0",
991            "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03",
992        );
993    }
994
995    #[test]
996    fn wycheproof_ed25519_vectors() {
997        #[derive(serde::Deserialize)]
998        struct TestJson {
999            #[serde(rename = "testGroups")]
1000            test_groups: Vec<TestGroup>,
1001        }
1002
1003        #[derive(serde::Deserialize)]
1004        struct TestGroup {
1005            #[serde(rename = "publicKey")]
1006            public_key: PublicKeyJson,
1007            tests: Vec<TestCase>,
1008        }
1009
1010        #[derive(serde::Deserialize)]
1011        struct PublicKeyJson {
1012            pk: String,
1013        }
1014
1015        #[derive(serde::Deserialize)]
1016        struct TestCase {
1017            #[serde(rename = "tcId")]
1018            tc_id: u32,
1019            // #[allow(dead_code)]
1020            // comment: String,
1021            msg: String,
1022            sig: String,
1023            result: String,
1024        }
1025
1026        let data = include_str!("../../testdata/wycheproof/testvectors_v1/ed25519_test.json");
1027        let parsed: TestJson = serde_json::from_str(data).unwrap();
1028
1029        let mut valid_tested = 0usize;
1030        let mut invalid_tested = 0usize;
1031        let mut skipped = 0usize;
1032
1033        for group in &parsed.test_groups {
1034            let public_key = decode_hex::<32>(&group.public_key.pk);
1035            let pk = PublicKey::from_bytes(&public_key).unwrap();
1036
1037            for test in &group.tests {
1038                let msg = decode_hex_vec(&test.msg);
1039                let sig_hex = &test.sig;
1040                if sig_hex.len() != 128 {
1041                    skipped += 1;
1042                    continue;
1043                }
1044                let signature = decode_hex::<64>(sig_hex);
1045                let should_be_valid = test.result == "valid";
1046
1047                let result = pk.verify(&msg, &signature);
1048                if should_be_valid {
1049                    assert!(
1050                        result.is_ok(),
1051                        "Wycheproof test #{}: expected valid but got {:?}",
1052                        test.tc_id,
1053                        result,
1054                    );
1055                    valid_tested += 1;
1056                } else {
1057                    assert!(result.is_err(), "Wycheproof test #{}: expected invalid but got ok", test.tc_id,);
1058                    invalid_tested += 1;
1059                }
1060            }
1061        }
1062
1063        assert!(valid_tested > 0, "must test at least one valid Wycheproof vector");
1064        assert!(invalid_tested > 0, "must test at least one invalid Wycheproof vector");
1065        assert!(skipped > 0, "some truncated signatures should be skipped");
1066
1067        eprintln!("Wycheproof ed25519: {valid_tested} valid, {invalid_tested} invalid, {skipped} skipped");
1068    }
1069
1070    #[test]
1071    fn sign_verify_roundtrip_various_lengths() {
1072        let seed = decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
1073        let priv_key = SecretKey::from_bytes(&seed);
1074        let pub_key = priv_key.public_key();
1075
1076        for len in [0, 1, 2, 16, 32, 64, 128, 255, 256, 1024] {
1077            let message: Vec<u8> = (0..len).map(|i| (i & 0xff) as u8).collect();
1078            let signature = priv_key.sign(&message);
1079            assert!(
1080                pub_key.verify(&message, &signature).is_ok(),
1081                "roundtrip failed for message length {len}"
1082            );
1083        }
1084    }
1085
1086    #[test]
1087    fn fixed_window_matches_scalar_mul() {
1088        for _ in 0..100 {
1089            let s = Scalar(U256::from_limbs([
1090                rand::random::<u64>(),
1091                rand::random::<u64>(),
1092                rand::random::<u64>(),
1093                rand::random::<u64>(),
1094            ]));
1095            let s = Scalar(s.0.add_mod(&U256::ZERO, &MODULUS_L));
1096            let old_result = scalar_mul(&BASEPOINT, &s);
1097            let new_result = scalar_mul_base(&s);
1098            assert_eq!(old_result.to_bytes(), new_result.to_bytes(), "mismatch for scalar={:x}", s.0,);
1099        }
1100    }
1101
1102    #[test]
1103    fn mul_mod_barrett_agrees_with_mul_mod() {
1104        for _ in 0..100 {
1105            let a = U256::from_limbs([
1106                rand::random::<u64>(),
1107                rand::random::<u64>(),
1108                rand::random::<u64>(),
1109                rand::random::<u64>(),
1110            ]);
1111            let b = U256::from_limbs([
1112                rand::random::<u64>(),
1113                rand::random::<u64>(),
1114                rand::random::<u64>(),
1115                rand::random::<u64>(),
1116            ]);
1117            let slow = a.mul_mod(&b, &MODULUS_L);
1118            let fast = a.mul_mod_barrett(&b, &MODULUS_L, &BAR_MU);
1119            assert_eq!(slow, fast, "mismatch for a={a:x}, b={b:x}");
1120        }
1121    }
1122
1123    #[test]
1124    fn basepoint() {
1125        assert_eq!(BASEPOINT, EdwardsPoint::from_bytes(&BASEPOINT_COMPRESSED).unwrap())
1126    }
1127}