literate_crypto/pubkey/ecc/
curve.rs

1use {
2    super::num::Num,
3    docext::docext,
4    std::{fmt, marker::PhantomData, ops},
5};
6
7/// An elliptic curve.
8///
9/// Elliptic curves can be expressed in a few different forms, but the most
10/// common is
11///
12/// $$
13/// y^2 = x^3 + ax + b
14/// $$
15///
16/// For some $a, b$. When this equation is plotted over $\mathbb{R}^2$, it
17/// results in the following curve:
18///
19/// ![curve](img/curve.svg)
20///
21/// Two points on the curve can be added by drawing a line through them and
22/// finding the intersection. For example, $A + B = C$:
23///
24/// ![addition](img/curve-addition.svg)
25///
26/// A point can be added to itself by drawing a tangent instead of a line,
27/// referred to as _point doubling_. For example, $2A = B$:
28///
29/// ![doubling](img/curve-doubling.svg)
30///
31/// When a line is drawn through two points, it is possible that there is no
32/// intersection. A similar situation can happen with tangents as well.
33///
34/// ![doubling](img/curve-addition-inf.svg)
35///
36/// For this reason, there is a special point referred to as _the point at
37/// infinity_ and designated by $\infty$. By definition, for any point $A$, $A +
38/// \infty = A$. This rule also defines point negation: if $A + B = \infty$ then
39/// $A = -B$. Any line which does not intersect the curve must be parallel to
40/// the y-axis, hence for any point $(x, y)$ the negation is simply $(x, -y)$.
41/// The example above also demostrates this, since $A = -B$ and the line through
42/// $A$ and $B$ is parallel to the y-axis.
43///
44/// Finally, any point $P$ can be multiplied by a non-negative integer $k$, $Q =
45/// kP$. This _point multiplication_ is defined simply as repeated point
46/// addition. By definition, $0 \cdot P = \infty$.
47///
48/// The elliptic curve point arithmetic defined above constitutes a _group_.
49/// This means that the operations within this arithmetic behave in a
50/// mathematically reasonable way, similar to integer arithmetic.
51///
52/// In the graphical examples, the curve is defined over $\mathbb{R}$. The
53/// conclusions and laws above are true if the curve is defined over any
54/// _field_, not just $\mathbb{R}$. In practice (as well as in this
55/// implementation), usually a prime field is used and operations are carried
56/// out via modular arithmetic.
57#[docext]
58pub trait Curve: Sized {
59    /// Size of [`Curve::P`] and [`Curve::N`] in bytes.
60    const SIZE: usize;
61
62    /// Order of the prime field this curve is constructed over.
63    const P: Num;
64
65    /// Order of the [generator point](Curve::g).
66    ///
67    /// If the generator point keeps being added to itself, it will keep giving
68    /// unique points up until $N$ additions have been done, after which the
69    /// points will cyclically repeat.
70    #[docext]
71    const N: Num;
72
73    /// The $a$ parameter for the elliptic curve equation $y^2 = x^3 + ax + b$.
74    #[docext]
75    const A: Num;
76
77    /// The $b$ parameter for the elliptic curve equation $y^2 = x^3 + ax + b$.
78    #[docext]
79    const B: Num;
80
81    /// The generator point for this curve.
82    ///
83    /// During cryptographic operations, this point is used to generate all
84    /// other points on the curve via point multiplication. This point
85    /// must generate a cyclic subgroup of the curve. The [cardinality of the
86    /// subgroup](Curve::N) should be as large as possible.
87    fn g() -> Point<Self>;
88}
89
90/// A point on an elliptic curve curve, possibly at infinity.
91#[derive(Debug)]
92pub struct Point<C>(Coordinates, PhantomData<C>);
93
94impl<C> Clone for Point<C> {
95    fn clone(&self) -> Self {
96        *self
97    }
98}
99
100impl<C> Copy for Point<C> {}
101
102impl<C> PartialEq for Point<C> {
103    fn eq(&self, other: &Self) -> bool {
104        self.0 == other.0
105    }
106}
107
108impl<C> Eq for Point<C> {}
109
110/// Finite point coordinates $(x, y)$ or infinity $\infty$.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112#[docext]
113pub enum Coordinates {
114    /// The point at infinity.
115    Infinity,
116    Finite(Num, Num),
117}
118
119/// [Elliptic curve](Curve) points are added together by first constructing a
120/// line through the two points, then finding the intersection of that line with
121/// the curve. The intersection is the result. If the two points are equal, a
122/// tangent should be constructed instead of a line.
123///
124/// If the points are not equal:
125/// $$
126/// (x_1, y_1) + (x_2, y_2) = (x_3, y_3) \\
127/// H = \frac{y_2 - y_1}{x_2 - x_1} \\
128/// x_3 = H^2 - x_1 - x_2 \\
129/// y_3 = H(x_1 - x_3) - y_1 \\
130/// $$
131///
132/// If the points are equal:
133/// $$
134/// 2 \cdot (x_1, y_1) = (x_3, y_3) \\
135/// H = \frac{3x_1^2 + a}{2y_1} \\
136/// x_3 = H^2 - 2x_1 \\
137/// y_3 = H(x_1 - x_3) - y_1 \\
138/// $$
139#[docext]
140impl<C: Curve> ops::Add for Point<C> {
141    type Output = Self;
142
143    fn add(self, rhs: Self) -> Self::Output {
144        match (self.0, rhs.0) {
145            (Coordinates::Infinity, other) | (other, Coordinates::Infinity) => {
146                // Infinity is the identity element in the group.
147                Self(other, Default::default())
148            }
149            (Coordinates::Finite(x1, y1), Coordinates::Finite(x2, y2)) if x1 == x2 && y1 == y2 => {
150                // Special formula for adding a point to itself, aka point doubling.
151                let Some(inv) = Num::TWO.mul(y1, C::P).inv(C::P) else {
152                    return Self(Coordinates::Infinity, Default::default());
153                };
154                let h = Num::THREE.mul(x1, C::P).mul(x1, C::P).mul(inv, C::P);
155                let x = h.mul(h, C::P).sub(Num::TWO.mul(x1, C::P), C::P);
156                let s = x1.sub(x, C::P);
157                Self::new(x, h.mul(s, C::P).sub(y1, C::P)).unwrap()
158            }
159            (Coordinates::Finite(x1, y1), Coordinates::Finite(x2, y2)) => {
160                // Regular point addition formula.
161                let Some(inv) = x2.sub(x1, C::P).inv(C::P) else {
162                    return Self(Coordinates::Infinity, Default::default());
163                };
164                let h = y2.sub(y1, C::P).mul(inv, C::P);
165                let x = h.mul(h, C::P).sub(x1, C::P).sub(x2, C::P);
166                let s = x1.sub(x, C::P);
167                Self::new(x, h.mul(s, C::P).sub(y1, C::P)).unwrap()
168            }
169        }
170    }
171}
172
173impl<C: Curve> ops::AddAssign for Point<C> {
174    fn add_assign(&mut self, rhs: Self) {
175        *self = *self + rhs;
176    }
177}
178
179impl<C: Curve> Point<C> {
180    pub fn new(x: Num, y: Num) -> Result<Self, InvalidPoint> {
181        // Verify that (x, y) lies on the curve.
182        let y2 = y.mul(y, C::P);
183        let x3 = x.mul(x, C::P).mul(x, C::P);
184        let ax = C::A.mul(x, C::P);
185        if y2 == x3.add(ax, C::P).add(C::B, C::P) {
186            Ok(Self(Coordinates::Finite(x, y), Default::default()))
187        } else {
188            Err(InvalidPoint)
189        }
190    }
191
192    pub fn infinity() -> Self {
193        Self(Coordinates::Infinity, Default::default())
194    }
195
196    pub fn coordinates(&self) -> Coordinates {
197        self.0
198    }
199
200    pub(super) fn scale(&self, n: Num) -> Self {
201        let mut s = *self;
202        let mut result = Self::infinity();
203        for i in 0..Num::BITS {
204            if n.get_bit(i) {
205                result += s;
206            }
207            s += s;
208        }
209        result
210    }
211}
212
213#[derive(Debug, Clone, Copy)]
214pub struct InvalidPoint;
215
216impl fmt::Display for InvalidPoint {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        write!(f, "invalid point")
219    }
220}
221
222impl std::error::Error for InvalidPoint {}