dcrypt_algorithms/ec/p192/
point.rs

1//! P-192 elliptic curve point operations
2
3use crate::ec::p192::{
4    constants::{
5        P192_FIELD_ELEMENT_SIZE, P192_POINT_COMPRESSED_SIZE, P192_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    /// Identity point (all zeros)
17    Identity,
18    /// Uncompressed: 0x04 ∥ x ∥ y
19    Uncompressed,
20    /// Compressed: 0x02/0x03 ∥ x
21    Compressed,
22}
23
24/// Affine coordinates (x, y) or identity; 𝔽ₚ is built from FieldElement
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/// Jacobian coordinates (X:Y:Z) for efficient arithmetic
33#[derive(Clone, Debug)]
34pub(crate) struct ProjectivePoint {
35    pub(crate) is_identity: Choice,
36    pub(crate) x: FieldElement,
37    pub(crate) y: FieldElement,
38    pub(crate) z: FieldElement,
39}
40
41impl PartialEq for Point {
42    fn eq(&self, other: &Self) -> bool {
43        let a_id: bool = self.is_identity.into();
44        let b_id: bool = other.is_identity.into();
45        if a_id || b_id {
46            return a_id == b_id;
47        }
48        self.x == other.x && self.y == other.y
49    }
50}
51
52impl Point {
53    /// Create a new affine point from uncompressed byte coordinates
54    pub fn new_uncompressed(
55        x_bytes: &[u8; P192_FIELD_ELEMENT_SIZE],
56        y_bytes: &[u8; P192_FIELD_ELEMENT_SIZE],
57    ) -> Result<Self> {
58        let x_fe = FieldElement::from_bytes(x_bytes)?;
59        let y_fe = FieldElement::from_bytes(y_bytes)?;
60        if !Self::is_on_curve(&x_fe, &y_fe) {
61            return Err(Error::param("P-192 Point", "Point not on curve"));
62        }
63        Ok(Point {
64            is_identity: Choice::from(0),
65            x: x_fe,
66            y: y_fe,
67        })
68    }
69
70    /// The identity (point at infinity)
71    pub fn identity() -> Self {
72        Point {
73            is_identity: Choice::from(1),
74            x: FieldElement::zero(),
75            y: FieldElement::zero(),
76        }
77    }
78
79    /// Is this the identity point?
80    pub fn is_identity(&self) -> bool {
81        self.is_identity.into()
82    }
83
84    /// Extract x‐coordinate as big‐endian bytes
85    pub fn x_coordinate_bytes(&self) -> [u8; P192_FIELD_ELEMENT_SIZE] {
86        self.x.to_bytes()
87    }
88
89    /// Extract y‐coordinate as big‐endian bytes
90    pub fn y_coordinate_bytes(&self) -> [u8; P192_FIELD_ELEMENT_SIZE] {
91        self.y.to_bytes()
92    }
93
94    /// Detect serialized point format
95    pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
96        if bytes.is_empty() {
97            return Err(Error::param("P-192 Point", "Empty encoding"));
98        }
99        match (bytes[0], bytes.len()) {
100            (0x00, P192_POINT_UNCOMPRESSED_SIZE) => {
101                // all‐zeros encoding = identity
102                if bytes.iter().all(|&b| b == 0) {
103                    Ok(PointFormat::Identity)
104                } else {
105                    Err(Error::param("P-192 Point", "Invalid identity encoding"))
106                }
107            }
108            (0x04, P192_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
109            (0x02 | 0x03, P192_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
110            _ => Err(Error::param("P-192 Point", "Unknown or malformed format")),
111        }
112    }
113
114    /// Serialize this point as uncompressed: 0x04 ∥ x ∥ y
115    pub fn serialize_uncompressed(&self) -> [u8; P192_POINT_UNCOMPRESSED_SIZE] {
116        let mut out = [0u8; P192_POINT_UNCOMPRESSED_SIZE];
117        if self.is_identity() {
118            return out; // all zeros
119        }
120        out[0] = 0x04;
121        out[1..1 + P192_FIELD_ELEMENT_SIZE].copy_from_slice(&self.x.to_bytes());
122        out[1 + P192_FIELD_ELEMENT_SIZE..].copy_from_slice(&self.y.to_bytes());
123        out
124    }
125
126    /// Deserialize from uncompressed bytes (0x04 ∥ x ∥ y), or all‐zeros for identity
127    pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
128        validate::length("P-192 Point", bytes.len(), P192_POINT_UNCOMPRESSED_SIZE)?;
129        if bytes.iter().all(|&b| b == 0) {
130            return Ok(Self::identity());
131        }
132        if bytes[0] != 0x04 {
133            return Err(Error::param(
134                "P-192 Point",
135                "Invalid prefix for uncompressed",
136            ));
137        }
138        let mut xb = [0u8; P192_FIELD_ELEMENT_SIZE];
139        let mut yb = [0u8; P192_FIELD_ELEMENT_SIZE];
140        xb.copy_from_slice(&bytes[1..1 + P192_FIELD_ELEMENT_SIZE]);
141        yb.copy_from_slice(&bytes[1 + P192_FIELD_ELEMENT_SIZE..]);
142        Self::new_uncompressed(&xb, &yb)
143    }
144
145    /// Serialize this point in compressed form: 0x02/0x03 ∥ x
146    pub fn serialize_compressed(&self) -> [u8; P192_POINT_COMPRESSED_SIZE] {
147        let mut out = [0u8; P192_POINT_COMPRESSED_SIZE];
148        if self.is_identity() {
149            return out; // all zeros
150        }
151        out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
152        out[1..].copy_from_slice(&self.x.to_bytes());
153        out
154    }
155
156    /// Deserialize from compressed bytes (0x02/0x03 ∥ x) or all‐zeros for identity
157    pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
158        validate::length(
159            "P-192 Compressed Point",
160            bytes.len(),
161            P192_POINT_COMPRESSED_SIZE,
162        )?;
163        if bytes.iter().all(|&b| b == 0) {
164            return Ok(Self::identity());
165        }
166        let tag = bytes[0];
167        if tag != 0x02 && tag != 0x03 {
168            return Err(Error::param("P-192 Point", "Invalid compressed prefix"));
169        }
170        let mut xb = [0u8; P192_FIELD_ELEMENT_SIZE];
171        xb.copy_from_slice(&bytes[1..]);
172        let x_fe = FieldElement::from_bytes(&xb)
173            .map_err(|_| Error::param("P-192 Point", "Invalid compressed point: x not in field"))?;
174        // Compute rhs = x³ - 3x + b
175        let rhs = {
176            let x2 = x_fe.square();
177            let x3 = x2.mul(&x_fe);
178            let a = FieldElement(FieldElement::A_M3);
179            let b_coeff = FieldElement::from_bytes(&crate::ec::p192::field::B).unwrap();
180            x3.add(&a.mul(&x_fe)).add(&b_coeff)
181        };
182        let y_candidate = rhs
183            .sqrt()
184            .ok_or_else(|| Error::param("P-192 Point", "Invalid compressed point: no sqrt"))?;
185        let y_final =
186            if (y_candidate.is_odd() && tag == 0x03) || (!y_candidate.is_odd() && tag == 0x02) {
187                y_candidate
188            } else {
189                y_candidate.negate() // p - y (cleaner than FieldElement::zero().sub(&y_candidate))
190            };
191        Ok(Point {
192            is_identity: Choice::from(0),
193            x: x_fe,
194            y: y_final,
195        })
196    }
197
198    /// Add two points (group law)
199    pub fn add(&self, other: &Self) -> Self {
200        let p1 = self.to_projective();
201        let p2 = other.to_projective();
202        let sum = p1.add(&p2);
203        sum.to_affine()
204    }
205
206    /// Double this point: 2P
207    pub fn double(&self) -> Self {
208        let p = self.to_projective();
209        let d = p.double();
210        d.to_affine()
211    }
212
213    /// Scalar multiplication: P * scalar
214    /// Constant‐time double‐and‐add
215    pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
216        if scalar.is_zero() {
217            return Ok(Self::identity());
218        }
219        let base = self.to_projective();
220        let mut acc = ProjectivePoint::identity();
221        let bytes = scalar.as_secret_buffer().as_ref();
222        for &byte in bytes.iter() {
223            for i in (0..8).rev() {
224                acc = acc.double();
225                if ((byte >> i) & 1) == 1 {
226                    acc = acc.add(&base);
227                }
228            }
229        }
230        Ok(acc.to_affine())
231    }
232
233    /// Check that (x, y) satisfies y² = x³ - 3x + b
234    fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
235        let y2 = y.square();
236        let x2 = x.square();
237        let x3 = x2.mul(x);
238        let a = FieldElement(FieldElement::A_M3);
239        let b_coeff = FieldElement::from_bytes(&crate::ec::p192::field::B).unwrap();
240        let rhs = x3.add(&a.mul(x)).add(&b_coeff);
241        y2 == rhs
242    }
243
244    /// Convert affine to Jacobian for intermediate computations
245    fn to_projective(&self) -> ProjectivePoint {
246        if self.is_identity() {
247            ProjectivePoint::identity()
248        } else {
249            ProjectivePoint {
250                is_identity: Choice::from(0),
251                x: self.x.clone(),
252                y: self.y.clone(),
253                z: FieldElement::one(),
254            }
255        }
256    }
257}
258
259impl ProjectivePoint {
260    /// Identity in Jacobian form: (0 : 1 : 0)
261    pub fn identity() -> Self {
262        ProjectivePoint {
263            is_identity: Choice::from(1),
264            x: FieldElement::zero(),
265            y: FieldElement::one(),
266            z: FieldElement::zero(),
267        }
268    }
269
270    /// Constant‐time point addition (Jacobian coordinates)
271    pub fn add(&self, other: &Self) -> Self {
272        // Handle identity cases
273        if self.is_identity.into() {
274            return other.clone();
275        }
276        if other.is_identity.into() {
277            return self.clone();
278        }
279
280        // Z₁², Z₂², Z₁³, Z₂³
281        let z1_sq = self.z.square();
282        let z2_sq = other.z.square();
283        let z1_cu = z1_sq.mul(&self.z);
284        let z2_cu = z2_sq.mul(&other.z);
285
286        let u1 = self.x.mul(&z2_sq); // X₁·Z₂²
287        let u2 = other.x.mul(&z1_sq); // X₂·Z₁²
288        let s1 = self.y.mul(&z2_cu); // Y₁·Z₂³
289        let s2 = other.y.mul(&z1_cu); // Y₂·Z₁³
290
291        let h = u2.sub(&u1);
292        let r = s2.sub(&s1);
293
294        if h.is_zero() {
295            if r.is_zero() {
296                return self.double();
297            } else {
298                return ProjectivePoint::identity();
299            }
300        }
301
302        let h2 = h.square();
303        let h3 = h2.mul(&h);
304        let v = u1.mul(&h2);
305
306        // X₃ = r² - h³ - 2v
307        let r2 = r.square();
308        let two_v = v.add(&v);
309        let mut x3 = r2.sub(&h3);
310        x3 = x3.sub(&two_v);
311
312        // Y₃ = r·(v - X₃) - s1·h³
313        let v_minus_x3 = v.sub(&x3);
314        let r_times = r.mul(&v_minus_x3);
315        let s1_h3 = s1.mul(&h3);
316        let y3 = r_times.sub(&s1_h3);
317
318        // Z₃ = Z₁·Z₂·h
319        let z1z2 = self.z.mul(&other.z);
320        let z3 = z1z2.mul(&h);
321
322        ProjectivePoint {
323            is_identity: Choice::from(0),
324            x: x3,
325            y: y3,
326            z: z3,
327        }
328    }
329
330    /// Constant‐time point doubling (Jacobian coordinates)
331    pub fn double(&self) -> Self {
332        if self.is_identity.into() {
333            return self.clone();
334        }
335        if self.y.is_zero() {
336            return ProjectivePoint::identity();
337        }
338
339        // Standard SEC-1 formulas  (a = −3)
340        //
341        //   δ  = Z²
342        //   γ  = Y²
343        //   β  = X·γ
344        //   α  = 3·(X − δ)·(X + δ)
345        let delta = self.z.square();
346        let gamma = self.y.square();
347        let beta = self.x.mul(&gamma);
348
349        let t1 = self.x.add(&delta); // X + δ
350        let t2 = self.x.sub(&delta); // X − δ
351        let mut alpha = t1.mul(&t2); // (X − δ)(X + δ)
352        let three = FieldElement::from_u32(3);
353        alpha = alpha.mul(&three); // ×3
354
355        // X₃ = α² − 8·β
356        let eight_beta = {
357            let two_beta = beta.add(&beta);
358            let four_beta = two_beta.add(&two_beta);
359            four_beta.add(&four_beta) // 8·β
360        };
361        let x3 = alpha.square().sub(&eight_beta);
362
363        // Z₃ = (Y + Z)² − γ − δ
364        let z3 = self.y.add(&self.z).square().sub(&gamma).sub(&delta);
365
366        // Y₃ = α·(4·β − X₃) − 8·γ²
367        let four_beta = {
368            let two_beta = beta.add(&beta);
369            two_beta.add(&two_beta)
370        };
371        let mut y3 = four_beta.sub(&x3);
372        y3 = alpha.mul(&y3);
373
374        let eight_gamma_sq = {
375            let gamma_sq = gamma.square();
376            let two = gamma_sq.add(&gamma_sq);
377            let four = two.add(&two);
378            four.add(&four) // 8·γ²
379        };
380        let y3 = y3.sub(&eight_gamma_sq);
381
382        ProjectivePoint {
383            is_identity: Choice::from(0),
384            x: x3,
385            y: y3,
386            z: z3,
387        }
388    }
389
390    /// Convert Jacobian back to affine coordinates
391    pub fn to_affine(&self) -> Point {
392        if self.is_identity.into() {
393            return Point::identity();
394        }
395        let z_inv = self.z.invert().expect("Nonzero Z ⇒ invertible");
396        let z_inv_sq = z_inv.square();
397        let z_inv_cu = z_inv_sq.mul(&z_inv);
398        let x_aff = self.x.mul(&z_inv_sq);
399        let y_aff = self.y.mul(&z_inv_cu);
400        Point {
401            is_identity: Choice::from(0),
402            x: x_aff,
403            y: y_aff,
404        }
405    }
406}