lambdaworks_math/elliptic_curve/edwards/
point.rs

1use crate::{
2    cyclic_group::IsGroup,
3    elliptic_curve::{
4        point::ProjectivePoint,
5        traits::{EllipticCurveError, FromAffine, IsEllipticCurve},
6    },
7    field::element::FieldElement,
8};
9
10use super::traits::IsEdwards;
11
12#[derive(Clone, Debug)]
13pub struct EdwardsProjectivePoint<E: IsEllipticCurve>(ProjectivePoint<E>);
14
15impl<E: IsEllipticCurve + IsEdwards> EdwardsProjectivePoint<E> {
16    /// Creates an elliptic curve point giving the projective [x: y: z] coordinates.
17    pub fn new(value: [FieldElement<E::BaseField>; 3]) -> Result<Self, EllipticCurveError> {
18        let (x, y, z) = (&value[0], &value[1], &value[2]);
19
20        // The point at infinity is (0, 1, 1).
21        // We convert every (0, y, y) into the infinity.
22        if x == &FieldElement::<E::BaseField>::zero() && z == y {
23            return Ok(Self(ProjectivePoint::new([
24                FieldElement::<E::BaseField>::zero(),
25                FieldElement::<E::BaseField>::one(),
26                FieldElement::<E::BaseField>::one(),
27            ])));
28        }
29        if z != &FieldElement::<E::BaseField>::zero()
30            && E::defining_equation_projective(x, y, z) == FieldElement::<E::BaseField>::zero()
31        {
32            Ok(Self(ProjectivePoint::new(value)))
33        } else {
34            Err(EllipticCurveError::InvalidPoint)
35        }
36    }
37
38    /// Returns the `x` coordinate of the point.
39    pub fn x(&self) -> &FieldElement<E::BaseField> {
40        self.0.x()
41    }
42
43    /// Returns the `y` coordinate of the point.
44    pub fn y(&self) -> &FieldElement<E::BaseField> {
45        self.0.y()
46    }
47
48    /// Returns the `z` coordinate of the point.
49    pub fn z(&self) -> &FieldElement<E::BaseField> {
50        self.0.z()
51    }
52
53    /// Returns a tuple [x, y, z] with the coordinates of the point.
54    pub fn coordinates(&self) -> &[FieldElement<E::BaseField>; 3] {
55        self.0.coordinates()
56    }
57
58    /// Creates the same point in affine coordinates. That is,
59    /// returns [x / z: y / z: 1] where `self` is [x: y: z].
60    /// Panics if `self` is the point at infinity.
61    pub fn to_affine(&self) -> Self {
62        Self(self.0.to_affine())
63    }
64}
65
66impl<E: IsEllipticCurve> PartialEq for EdwardsProjectivePoint<E> {
67    fn eq(&self, other: &Self) -> bool {
68        self.0 == other.0
69    }
70}
71
72impl<E: IsEdwards> FromAffine<E::BaseField> for EdwardsProjectivePoint<E> {
73    fn from_affine(
74        x: FieldElement<E::BaseField>,
75        y: FieldElement<E::BaseField>,
76    ) -> Result<Self, EllipticCurveError> {
77        let coordinates = [x, y, FieldElement::one()];
78        EdwardsProjectivePoint::new(coordinates)
79    }
80}
81
82impl<E: IsEllipticCurve> Eq for EdwardsProjectivePoint<E> {}
83
84impl<E: IsEdwards> IsGroup for EdwardsProjectivePoint<E> {
85    /// Returns the point at infinity (neutral element) in projective coordinates.
86    ///
87    /// # Safety
88    ///
89    /// - The values `[0, 1, 1]` are the **canonical representation** of the neutral element
90    ///   in the Edwards curve, meaning they are guaranteed to be a valid point.
91    /// - `unwrap()` is used because this point is **known** to be valid, so
92    ///   there is no need for additional runtime checks.
93    fn neutral_element() -> Self {
94        // SAFETY:
95        // - `[0, 1, 1]` is a mathematically verified neutral element in Edwards curves.
96        // - `unwrap()` is safe because this point is **always valid**.
97        let point = Self::new([
98            FieldElement::zero(),
99            FieldElement::one(),
100            FieldElement::one(),
101        ]);
102        point.unwrap()
103    }
104
105    fn is_neutral_element(&self) -> bool {
106        let [px, py, pz] = self.coordinates();
107        px == &FieldElement::zero() && py == pz
108    }
109
110    /// Computes the addition of `self` and `other` using the Edwards curve addition formula.
111    ///
112    /// This implementation follows Equation (5.38) from "Moonmath" (page 97).
113    ///
114    /// # Safety
115    ///
116    /// - The function assumes both `self` and `other` are valid points on the curve.
117    /// - The resulting coordinates are computed using a well-defined formula that
118    ///   maintains the elliptic curve invariants.
119    /// - `unwrap()` is safe because the formula guarantees the result is valid.
120    fn operate_with(&self, other: &Self) -> Self {
121        // This avoids dropping, which in turn saves us from having to clone the coordinates.
122        let (s_affine, o_affine) = (self.to_affine(), other.to_affine());
123
124        let [x1, y1, _] = s_affine.coordinates();
125        let [x2, y2, _] = o_affine.coordinates();
126
127        let one = FieldElement::one();
128        let (x1y2, y1x2) = (x1 * y2, y1 * x2);
129        let (x1x2, y1y2) = (x1 * x2, y1 * y2);
130        let dx1x2y1y2 = E::d() * &x1x2 * &y1y2;
131
132        let num_s1 = &x1y2 + &y1x2;
133        let den_s1 = &one + &dx1x2y1y2;
134
135        let num_s2 = &y1y2 - E::a() * &x1x2;
136        let den_s2 = &one - &dx1x2y1y2;
137        // SAFETY: The creation of the result point is safe because the inputs are always points that belong to the curve.
138        // We are using that den_s1 and den_s2 aren't zero.
139        // See Theorem 3.3 from https://eprint.iacr.org/2007/286.pdf.
140        let x_coord = (&num_s1 / &den_s1).unwrap();
141        let y_coord = (&num_s2 / &den_s2).unwrap();
142        let point = Self::new([x_coord, y_coord, one]);
143        point.unwrap()
144    }
145
146    /// Returns the additive inverse of the projective point `p`
147    ///  
148    /// # Safety
149    ///
150    /// - Negating the x-coordinate of a valid Edwards point results in another valid point.
151    /// - `unwrap()` is safe because negation does not break the curve equation.
152    fn neg(&self) -> Self {
153        let [px, py, pz] = self.coordinates();
154        // SAFETY:
155        // - The negation formula for Edwards curves is well-defined.
156        // - The result remains a valid curve point.
157        let point = Self::new([-px, py.clone(), pz.clone()]);
158        point.unwrap()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use crate::{
165        cyclic_group::IsGroup,
166        elliptic_curve::{
167            edwards::{curves::tiny_jub_jub::TinyJubJubEdwards, point::EdwardsProjectivePoint},
168            traits::{EllipticCurveError, IsEllipticCurve},
169        },
170        field::element::FieldElement,
171    };
172
173    fn create_point(x: u64, y: u64) -> EdwardsProjectivePoint<TinyJubJubEdwards> {
174        TinyJubJubEdwards::create_point_from_affine(FieldElement::from(x), FieldElement::from(y))
175            .unwrap()
176    }
177
178    #[test]
179    fn create_valid_point_works() {
180        let p = TinyJubJubEdwards::create_point_from_affine(
181            FieldElement::from(5),
182            FieldElement::from(5),
183        )
184        .unwrap();
185        assert_eq!(p.x(), &FieldElement::from(5));
186        assert_eq!(p.y(), &FieldElement::from(5));
187        assert_eq!(p.z(), &FieldElement::from(1));
188    }
189
190    #[test]
191    fn create_invalid_point_returns_invalid_point_error() {
192        let result = TinyJubJubEdwards::create_point_from_affine(
193            FieldElement::from(5),
194            FieldElement::from(4),
195        );
196        assert_eq!(result.unwrap_err(), EllipticCurveError::InvalidPoint);
197    }
198
199    #[test]
200    fn operate_with_works_for_points_in_tiny_jub_jub() {
201        let p = EdwardsProjectivePoint::<TinyJubJubEdwards>::new([
202            FieldElement::from(5),
203            FieldElement::from(5),
204            FieldElement::from(1),
205        ])
206        .unwrap();
207        let q = EdwardsProjectivePoint::<TinyJubJubEdwards>::new([
208            FieldElement::from(8),
209            FieldElement::from(5),
210            FieldElement::from(1),
211        ])
212        .unwrap();
213        let expected = EdwardsProjectivePoint::<TinyJubJubEdwards>::new([
214            FieldElement::from(0),
215            FieldElement::from(1),
216            FieldElement::from(1),
217        ])
218        .unwrap();
219        assert_eq!(p.operate_with(&q), expected);
220    }
221
222    #[test]
223    fn test_negation_in_edwards() {
224        let a = create_point(5, 5);
225        let b = create_point(13 - 5, 5);
226
227        assert_eq!(a.neg(), b);
228        assert!(a.operate_with(&b).is_neutral_element());
229    }
230
231    #[test]
232    fn operate_with_works_and_cycles_in_tiny_jub_jub() {
233        let g = create_point(12, 11);
234        assert_eq!(g.operate_with_self(0_u16), create_point(0, 1));
235        assert_eq!(g.operate_with_self(1_u16), create_point(12, 11));
236        assert_eq!(g.operate_with_self(2_u16), create_point(8, 5));
237        assert_eq!(g.operate_with_self(3_u16), create_point(11, 6));
238        assert_eq!(g.operate_with_self(4_u16), create_point(6, 9));
239        assert_eq!(g.operate_with_self(5_u16), create_point(10, 0));
240        assert_eq!(g.operate_with_self(6_u16), create_point(6, 4));
241        assert_eq!(g.operate_with_self(7_u16), create_point(11, 7));
242        assert_eq!(g.operate_with_self(8_u16), create_point(8, 8));
243        assert_eq!(g.operate_with_self(9_u16), create_point(12, 2));
244        assert_eq!(g.operate_with_self(10_u16), create_point(0, 12));
245        assert_eq!(g.operate_with_self(11_u16), create_point(1, 2));
246        assert_eq!(g.operate_with_self(12_u16), create_point(5, 8));
247        assert_eq!(g.operate_with_self(13_u16), create_point(2, 7));
248        assert_eq!(g.operate_with_self(14_u16), create_point(7, 4));
249        assert_eq!(g.operate_with_self(15_u16), create_point(3, 0));
250        assert_eq!(g.operate_with_self(16_u16), create_point(7, 9));
251        assert_eq!(g.operate_with_self(17_u16), create_point(2, 6));
252        assert_eq!(g.operate_with_self(18_u16), create_point(5, 5));
253        assert_eq!(g.operate_with_self(19_u16), create_point(1, 11));
254        assert_eq!(g.operate_with_self(20_u16), create_point(0, 1));
255    }
256}