dcrypt_algorithms/ec/k256/
point.rs

1//! secp256k1 elliptic curve point operations
2
3use crate::ec::k256::{
4    constants::{
5        K256_FIELD_ELEMENT_SIZE, K256_POINT_COMPRESSED_SIZE, K256_POINT_UNCOMPRESSED_SIZE,
6    },
7    field::FieldElement,
8    scalar::Scalar,
9};
10use crate::error::{validate, Error, Result};
11use subtle::Choice;
12
13/// Format of a serialized elliptic curve point
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum PointFormat {
16    /// The point at infinity (identity element)
17    Identity,
18    /// Uncompressed format: 0x04 || x || y
19    Uncompressed,
20    /// Compressed format: 0x02/0x03 || x
21    Compressed,
22}
23
24/// A point on the secp256k1 elliptic curve in affine coordinates
25#[derive(Clone, Debug)]
26pub struct Point {
27    pub(crate) is_identity: Choice,
28    pub(crate) x: FieldElement,
29    pub(crate) y: FieldElement,
30}
31
32#[derive(Clone, Debug)]
33pub(crate) struct ProjectivePoint {
34    is_identity: Choice,
35    x: FieldElement,
36    y: FieldElement,
37    z: FieldElement,
38}
39
40impl PartialEq for Point {
41    fn eq(&self, other: &Self) -> bool {
42        let self_is_identity: bool = self.is_identity.into();
43        let other_is_identity: bool = other.is_identity.into();
44        if self_is_identity || other_is_identity {
45            return self_is_identity == other_is_identity;
46        }
47        self.x == other.x && self.y == other.y
48    }
49}
50
51impl Point {
52    /// Create a new point from uncompressed coordinates.
53    ///
54    /// Returns an error if the coordinates don't satisfy the curve equation.
55    pub fn new_uncompressed(
56        x: &[u8; K256_FIELD_ELEMENT_SIZE],
57        y: &[u8; K256_FIELD_ELEMENT_SIZE],
58    ) -> Result<Self> {
59        let x_fe = FieldElement::from_bytes(x)?;
60        let y_fe = FieldElement::from_bytes(y)?;
61        if !Self::is_on_curve(&x_fe, &y_fe) {
62            return Err(Error::param(
63                "K256 Point",
64                "Point coordinates do not satisfy curve equation",
65            ));
66        }
67        Ok(Point {
68            is_identity: Choice::from(0),
69            x: x_fe,
70            y: y_fe,
71        })
72    }
73
74    /// Create the identity point (point at infinity).
75    pub fn identity() -> Self {
76        Point {
77            is_identity: Choice::from(1),
78            x: FieldElement::zero(),
79            y: FieldElement::zero(),
80        }
81    }
82
83    /// Check if this point is the identity element.
84    pub fn is_identity(&self) -> bool {
85        self.is_identity.into()
86    }
87
88    /// Check if this point is valid (on the curve).
89    pub fn is_valid(&self) -> bool {
90        if self.is_identity() {
91            return true;
92        }
93        Self::is_on_curve(&self.x, &self.y)
94    }
95
96    /// Get the x-coordinate of this point as bytes.
97    pub fn x_coordinate_bytes(&self) -> [u8; K256_FIELD_ELEMENT_SIZE] {
98        self.x.to_bytes()
99    }
100
101    /// Get the y-coordinate of this point as bytes.
102    pub fn y_coordinate_bytes(&self) -> [u8; K256_FIELD_ELEMENT_SIZE] {
103        self.y.to_bytes()
104    }
105
106    /// Serialize this point in uncompressed format.
107    pub fn serialize_uncompressed(&self) -> [u8; K256_POINT_UNCOMPRESSED_SIZE] {
108        let mut out = [0u8; K256_POINT_UNCOMPRESSED_SIZE];
109        if self.is_identity() {
110            return out;
111        }
112        out[0] = 0x04;
113        out[1..33].copy_from_slice(&self.x.to_bytes());
114        out[33..].copy_from_slice(&self.y.to_bytes());
115        out
116    }
117
118    /// Deserialize a point from uncompressed format.
119    ///
120    /// Returns an error if the bytes don't represent a valid point.
121    pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
122        validate::length(
123            "K256 Uncompressed Point",
124            bytes.len(),
125            K256_POINT_UNCOMPRESSED_SIZE,
126        )?;
127
128        // Check for identity (all zeros)
129        if bytes.iter().all(|&b| b == 0) {
130            return Ok(Self::identity());
131        }
132
133        // Check format byte
134        if bytes[0] != 0x04 {
135            return Err(Error::param(
136                "K256 Point",
137                "Invalid uncompressed point prefix (expected 0x04)",
138            ));
139        }
140
141        // Extract coordinates
142        let mut x_bytes = [0u8; K256_FIELD_ELEMENT_SIZE];
143        let mut y_bytes = [0u8; K256_FIELD_ELEMENT_SIZE];
144        x_bytes.copy_from_slice(&bytes[1..33]);
145        y_bytes.copy_from_slice(&bytes[33..65]);
146
147        // Create point and validate it's on the curve
148        Self::new_uncompressed(&x_bytes, &y_bytes)
149    }
150
151    /// Serialize this point in compressed format.
152    pub fn serialize_compressed(&self) -> [u8; K256_POINT_COMPRESSED_SIZE] {
153        let mut out = [0u8; K256_POINT_COMPRESSED_SIZE];
154        if self.is_identity() {
155            return out;
156        }
157        out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
158        out[1..].copy_from_slice(&self.x.to_bytes());
159        out
160    }
161
162    /// Deserialize a point from compressed format.
163    ///
164    /// Returns an error if the bytes don't represent a valid point.
165    pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
166        validate::length(
167            "K256 Compressed Point",
168            bytes.len(),
169            K256_POINT_COMPRESSED_SIZE,
170        )?;
171        if bytes.iter().all(|&b| b == 0) {
172            return Ok(Self::identity());
173        }
174        let tag = bytes[0];
175        if tag != 0x02 && tag != 0x03 {
176            return Err(Error::param(
177                "K256 Point",
178                "Invalid compressed point prefix",
179            ));
180        }
181        let mut x_bytes = [0u8; K256_FIELD_ELEMENT_SIZE];
182        x_bytes.copy_from_slice(&bytes[1..]);
183        let x_fe = FieldElement::from_bytes(&x_bytes)
184            .map_err(|_| Error::param("K256 Point", "Invalid x-coordinate"))?;
185        // y^2 = x^3 + 7
186        let rhs = {
187            let x3 = x_fe.square().mul(&x_fe);
188            let mut seven = [0u32; 8];
189            seven[0] = 7;
190            let b = FieldElement(seven);
191            x3.add(&b)
192        };
193        let y_fe = rhs
194            .sqrt()
195            .ok_or_else(|| Error::param("K256 Point", "Invalid compressed point: no sqrt"))?;
196        let y_final = if (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02) {
197            y_fe
198        } else {
199            y_fe.negate()
200        };
201        Ok(Point {
202            is_identity: Choice::from(0),
203            x: x_fe,
204            y: y_final,
205        })
206    }
207
208    /// Add two points using the group law.
209    pub fn add(&self, other: &Self) -> Self {
210        self.to_projective().add(&other.to_projective()).to_affine()
211    }
212
213    /// Double a point (add it to itself).
214    pub fn double(&self) -> Self {
215        // Identity or Y = 0 are special-cases
216        if self.is_identity() || self.y.is_zero() {
217            return Self::identity();
218        }
219
220        // λ = (3·x²) / (2·y)
221        let x_sq = self.x.square();
222        let three_x_sq = x_sq.add(&x_sq).add(&x_sq); // 3·x²
223        let two_y = self.y.double(); // 2·y
224        let inv_two_y = two_y
225            .invert() // constant-time
226            .expect("2·y ≠ 0 for non-identity point");
227        let lambda = three_x_sq.mul(&inv_two_y);
228
229        // x₂ = λ² − 2·x₁
230        let x3 = lambda.square().sub(&self.x.double());
231
232        // y₂ = λ·(x₁ − x₂) − y₁
233        let y3 = lambda.mul(&self.x.sub(&x3)).sub(&self.y);
234
235        Point {
236            is_identity: Choice::from(0),
237            x: x3,
238            y: y3,
239        }
240    }
241
242    /// Scalar multiplication: compute scalar * self.
243    ///
244    /// Uses constant-time double-and-add algorithm.
245    pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
246        if scalar.is_zero() {
247            return Ok(Self::identity());
248        }
249        let scalar_bytes = scalar.as_secret_buffer().as_ref();
250        let base = self.to_projective();
251        let mut result = ProjectivePoint::identity();
252        for byte in scalar_bytes.iter() {
253            for bit_pos in (0..8).rev() {
254                result = result.double();
255                if (byte >> bit_pos) & 1 == 1 {
256                    result = result.add(&base);
257                }
258            }
259        }
260        Ok(result.to_affine())
261    }
262
263    fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
264        let y_squared = y.square();
265        let x_cubed = x.square().mul(x);
266        let mut seven_limbs = [0u32; 8];
267        seven_limbs[0] = 7;
268        let seven = FieldElement(seven_limbs);
269        let rhs = x_cubed.add(&seven);
270        y_squared == rhs
271    }
272
273    fn to_projective(&self) -> ProjectivePoint {
274        if self.is_identity() {
275            return ProjectivePoint::identity();
276        }
277        ProjectivePoint {
278            is_identity: Choice::from(0),
279            x: self.x.clone(),
280            y: self.y.clone(),
281            z: FieldElement::one(),
282        }
283    }
284}
285
286impl ProjectivePoint {
287    pub fn identity() -> Self {
288        ProjectivePoint {
289            is_identity: Choice::from(1),
290            x: FieldElement::zero(),
291            y: FieldElement::one(),
292            z: FieldElement::zero(),
293        }
294    }
295
296    pub fn add(&self, other: &Self) -> Self {
297        if self.is_identity.into() {
298            return other.clone();
299        }
300        if other.is_identity.into() {
301            return self.clone();
302        }
303
304        let z1_sq = self.z.square();
305        let z2_sq = other.z.square();
306        let u1 = self.x.mul(&z2_sq);
307        let u2 = other.x.mul(&z1_sq);
308        let s1 = self.y.mul(&z2_sq).mul(&other.z);
309        let s2 = other.y.mul(&z1_sq).mul(&self.z);
310
311        let h = u2.sub(&u1);
312        if h.is_zero() {
313            if s1 == s2 {
314                return self.double();
315            } else {
316                return Self::identity();
317            }
318        }
319
320        let r = s2.sub(&s1);
321        let h_sq = h.square();
322        let h_cu = h_sq.mul(&h);
323        let v = u1.mul(&h_sq);
324
325        let r_sq = r.square();
326        let two_v = v.add(&v);
327        let x3 = r_sq.sub(&h_cu).sub(&two_v);
328
329        let v_minus_x3 = v.sub(&x3);
330        let y3 = r.mul(&v_minus_x3).sub(&s1.mul(&h_cu));
331
332        let z3 = self.z.mul(&other.z).mul(&h);
333
334        ProjectivePoint {
335            is_identity: Choice::from(0),
336            x: x3,
337            y: y3,
338            z: z3,
339        }
340    }
341
342    pub fn double(&self) -> Self {
343        // Reuse the *correct* affine doubling
344        self.to_affine().double().to_projective()
345    }
346
347    pub fn to_affine(&self) -> Point {
348        if self.is_identity.into() {
349            return Point::identity();
350        }
351        let z_inv = self.z.invert().expect("Nonzero Z should be invertible");
352        let z_inv_sq = z_inv.square();
353        let z_inv_cu = z_inv_sq.mul(&z_inv);
354        let x_aff = self.x.mul(&z_inv_sq);
355        let y_aff = self.y.mul(&z_inv_cu);
356        Point {
357            is_identity: Choice::from(0),
358            x: x_aff,
359            y: y_aff,
360        }
361    }
362}