sp1_curves/edwards/
ed25519.rs

1use std::str::FromStr;
2
3use generic_array::GenericArray;
4use num::{BigUint, Num, One};
5use serde::{Deserialize, Serialize};
6use typenum::{U32, U62};
7
8use crate::{
9    curve25519_dalek::CompressedEdwardsY,
10    edwards::{EdwardsCurve, EdwardsParameters},
11    params::{FieldParameters, NumLimbs},
12    AffinePoint, CurveType, EllipticCurveParameters,
13};
14
15pub type Ed25519 = EdwardsCurve<Ed25519Parameters>;
16
17#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
18pub struct Ed25519Parameters;
19
20#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
21pub struct Ed25519BaseField;
22
23impl FieldParameters for Ed25519BaseField {
24    const MODULUS: &'static [u8] = &[
25        237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
26        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127,
27    ];
28
29    const WITNESS_OFFSET: usize = 1usize << 14;
30
31    fn modulus() -> BigUint {
32        (BigUint::one() << 255) - BigUint::from(19u32)
33    }
34}
35
36impl NumLimbs for Ed25519BaseField {
37    type Limbs = U32;
38    type Witness = U62;
39}
40
41impl EllipticCurveParameters for Ed25519Parameters {
42    type BaseField = Ed25519BaseField;
43    const CURVE_TYPE: CurveType = CurveType::Ed25519;
44}
45
46impl EdwardsParameters for Ed25519Parameters {
47    const D: GenericArray<u8, U32> = GenericArray::from_array([
48        163, 120, 89, 19, 202, 77, 235, 117, 171, 216, 65, 65, 77, 10, 112, 0, 152, 232, 121, 119,
49        121, 64, 199, 140, 115, 254, 111, 43, 238, 108, 3, 82,
50    ]);
51
52    fn prime_group_order() -> BigUint {
53        BigUint::from(2u32).pow(252) + BigUint::from(27742317777372353535851937790883648493u128)
54    }
55
56    fn generator() -> (BigUint, BigUint) {
57        let x = BigUint::from_str_radix(
58            "15112221349535400772501151409588531511454012693041857206046113283949847762202",
59            10,
60        )
61        .unwrap();
62        let y = BigUint::from_str_radix(
63            "46316835694926478169428394003475163141307993866256225615783033603165251855960",
64            10,
65        )
66        .unwrap();
67        (x, y)
68    }
69}
70
71/// Computes the square root of a number in the base field of Ed25519.
72///
73/// This function always returns the nonnegative square root, in the sense that the least
74/// significant bit of the result is always 0.
75pub fn ed25519_sqrt(a: &BigUint) -> Option<BigUint> {
76    // Here is a description of how to calculate sqrt in the Curve25519 base field:
77    // ssh://git@github.com/succinctlabs/curve25519-dalek/blob/
78    // e2d1bd10d6d772af07cac5c8161cd7655016af6d/curve25519-dalek/src/field.rs#L256
79
80    let modulus = Ed25519BaseField::modulus();
81    // The exponent is (modulus+3)/8;
82    let mut beta = a.modpow(
83        &BigUint::from_str(
84            "7237005577332262213973186563042994240829374041602535252466099000494570602494",
85        )
86        .unwrap(),
87        &modulus,
88    );
89
90    // The square root of -1 in the field.
91    // Take from here:
92    // ssh://git@github.com/succinctlabs/curve25519-dalek/blob/
93    // e2d1bd10d6d772af07cac5c8161cd7655016af6d/curve25519-dalek/src/backend/serial/u64/constants.
94    // rs#L89
95    let sqrt_m1 = BigUint::from_str(
96        "19681161376707505956807079304988542015446066515923890162744021073123829784752",
97    )
98    .unwrap();
99
100    let beta_squared = &beta * &beta % &modulus;
101    let neg_a = &modulus - a;
102
103    if beta_squared == neg_a {
104        beta = (&beta * &sqrt_m1) % &modulus;
105    }
106
107    let correct_sign_sqrt = &beta_squared == a;
108    let flipped_sign_sqrt = beta_squared == neg_a;
109
110    if !correct_sign_sqrt && !flipped_sign_sqrt {
111        return None;
112    }
113
114    let beta_bytes = beta.to_bytes_le();
115    if (beta_bytes[0] & 1) == 1 {
116        beta = (&modulus - &beta) % &modulus;
117    }
118
119    Some(beta)
120}
121
122pub fn decompress(compressed_point: &CompressedEdwardsY) -> Option<AffinePoint<Ed25519>> {
123    let mut point_bytes = *compressed_point.as_bytes();
124    let sign = point_bytes[31] >> 7 == 1;
125    // mask out the sign bit
126    point_bytes[31] &= 0b0111_1111;
127    let modulus = &Ed25519BaseField::modulus();
128
129    let y = &BigUint::from_bytes_le(&point_bytes);
130    let yy = &((y * y) % modulus);
131    let u = (yy + modulus - BigUint::one()) % modulus; // u =  y²-1
132    let v = &((yy * &Ed25519Parameters::d_biguint()) + &BigUint::one()) % modulus; // v = dy²+1
133
134    let v_inv = v.modpow(&(modulus - BigUint::from(2u64)), modulus);
135    let u_div_v = (u * &v_inv) % modulus;
136
137    let mut x = ed25519_sqrt(&u_div_v)?;
138
139    // sqrt always returns the nonnegative square root,
140    // so we negate according to the supplied sign bit.
141    if sign {
142        x = (modulus - &x) % modulus;
143    }
144
145    Some(AffinePoint::new(x, y.clone()))
146}
147
148#[cfg(test)]
149mod tests {
150
151    use super::*;
152    use num::traits::ToBytes;
153
154    const NUM_TEST_CASES: usize = 100;
155
156    #[test]
157    fn test_ed25519_decompress() {
158        // This test checks that decompression of generator, 2x generator, 4x generator, etc. works.
159
160        // Get the generator point.
161        let mut point = {
162            let (x, y) = Ed25519Parameters::generator();
163            AffinePoint::<EdwardsCurve<Ed25519Parameters>>::new(x, y)
164        };
165        for _ in 0..NUM_TEST_CASES {
166            // Compress the point. The first 255 bits of a compressed point is the y-coordinate. The
167            // high bit of the 32nd byte gives the "sign" of x, which is the parity.
168            let compressed_point = {
169                let x = point.x.to_le_bytes();
170                let y = point.y.to_le_bytes();
171                let mut compressed = [0u8; 32];
172
173                // Copy y into compressed.
174                compressed[..y.len()].copy_from_slice(&y);
175
176                // Set the sign bit.
177                compressed[31] |= (x[0] & 1) << 7;
178
179                CompressedEdwardsY(compressed)
180            };
181            assert_eq!(point, decompress(&compressed_point).unwrap());
182
183            // Double the point to create a "random" point for the next iteration.
184            point = point.clone() + point.clone();
185        }
186    }
187}