Skip to main content

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, ConditionallySelectable};
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                let acc_added = acc.add(&base);
226                let choice = Choice::from((byte >> i) & 1);
227                acc = ProjectivePoint::conditional_select(&acc, &acc_added, choice);
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        // Z₁², Z₂², Z₁³, Z₂³
273        let z1_sq = self.z.square();
274        let z2_sq = other.z.square();
275        let z1_cu = z1_sq.mul(&self.z);
276        let z2_cu = z2_sq.mul(&other.z);
277
278        let u1 = self.x.mul(&z2_sq); // X₁·Z₂²
279        let u2 = other.x.mul(&z1_sq); // X₂·Z₁²
280        let s1 = self.y.mul(&z2_cu); // Y₁·Z₂³
281        let s2 = other.y.mul(&z1_cu); // Y₂·Z₁³
282
283        let h = u2.sub(&u1);
284        let r = s2.sub(&s1);
285
286        let h2 = h.square();
287        let h3 = h2.mul(&h);
288        let v = u1.mul(&h2);
289
290        // X₃ = r² - h³ - 2v
291        let r2 = r.square();
292        let two_v = v.add(&v);
293        let mut x3 = r2.sub(&h3);
294        x3 = x3.sub(&two_v);
295
296        // Y₃ = r·(v - X₃) - s1·h³
297        let v_minus_x3 = v.sub(&x3);
298        let r_times = r.mul(&v_minus_x3);
299        let s1_h3 = s1.mul(&h3);
300        let y3 = r_times.sub(&s1_h3);
301
302        // Z₃ = Z₁·Z₂·h
303        let z1z2 = self.z.mul(&other.z);
304        let z3 = z1z2.mul(&h);
305
306        let generic = ProjectivePoint {
307            is_identity: Choice::from(0),
308            x: x3,
309            y: y3,
310            z: z3,
311        };
312
313        let double_point = self.double();
314        let h_is_zero = Choice::from(h.is_zero() as u8);
315        let r_is_zero = Choice::from(r.is_zero() as u8);
316        let p_eq_q = h_is_zero & r_is_zero;
317        let p_eq_neg_q = h_is_zero & !r_is_zero;
318
319        let mut result = Self::conditional_select(&generic, &double_point, p_eq_q);
320        result = Self::conditional_select(&result, &Self::identity(), p_eq_neg_q);
321        result = Self::conditional_select(&result, other, self.is_identity);
322        result = Self::conditional_select(&result, self, other.is_identity);
323        result
324    }
325
326    /// Constant‐time point doubling (Jacobian coordinates)
327    pub fn double(&self) -> Self {
328        // Standard SEC-1 formulas  (a = −3)
329        //
330        //   δ  = Z²
331        //   γ  = Y²
332        //   β  = X·γ
333        //   α  = 3·(X − δ)·(X + δ)
334        let delta = self.z.square();
335        let gamma = self.y.square();
336        let beta = self.x.mul(&gamma);
337
338        let t1 = self.x.add(&delta); // X + δ
339        let t2 = self.x.sub(&delta); // X − δ
340        let mut alpha = t1.mul(&t2); // (X − δ)(X + δ)
341        let three = FieldElement::from_u32(3);
342        alpha = alpha.mul(&three); // ×3
343
344        // X₃ = α² − 8·β
345        let eight_beta = {
346            let two_beta = beta.add(&beta);
347            let four_beta = two_beta.add(&two_beta);
348            four_beta.add(&four_beta) // 8·β
349        };
350        let x3 = alpha.square().sub(&eight_beta);
351
352        // Z₃ = (Y + Z)² − γ − δ
353        let z3 = self.y.add(&self.z).square().sub(&gamma).sub(&delta);
354
355        // Y₃ = α·(4·β − X₃) − 8·γ²
356        let four_beta = {
357            let two_beta = beta.add(&beta);
358            two_beta.add(&two_beta)
359        };
360        let mut y3 = four_beta.sub(&x3);
361        y3 = alpha.mul(&y3);
362
363        let eight_gamma_sq = {
364            let gamma_sq = gamma.square();
365            let two = gamma_sq.add(&gamma_sq);
366            let four = two.add(&two);
367            four.add(&four) // 8·γ²
368        };
369        let y3 = y3.sub(&eight_gamma_sq);
370
371        let result = ProjectivePoint {
372            is_identity: Choice::from(0),
373            x: x3,
374            y: y3,
375            z: z3,
376        };
377
378        let return_identity = self.is_identity | Choice::from(self.y.is_zero() as u8);
379        Self::conditional_select(&result, &Self::identity(), return_identity)
380    }
381
382    /// Convert Jacobian back to affine coordinates
383    pub fn to_affine(&self) -> Point {
384        if self.is_identity.into() {
385            return Point::identity();
386        }
387        let z_inv = self.z.invert().expect("Nonzero Z ⇒ invertible");
388        let z_inv_sq = z_inv.square();
389        let z_inv_cu = z_inv_sq.mul(&z_inv);
390        let x_aff = self.x.mul(&z_inv_sq);
391        let y_aff = self.y.mul(&z_inv_cu);
392        Point {
393            is_identity: Choice::from(0),
394            x: x_aff,
395            y: y_aff,
396        }
397    }
398
399    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
400        let select_field = |lhs: &FieldElement, rhs: &FieldElement| {
401            let mut out = [0u32; 6];
402            for (i, limb) in out.iter_mut().enumerate() {
403                *limb = u32::conditional_select(&lhs.0[i], &rhs.0[i], choice);
404            }
405            FieldElement(out)
406        };
407        Self {
408            is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
409            x: select_field(&a.x, &b.x),
410            y: select_field(&a.y, &b.y),
411            z: select_field(&a.z, &b.z),
412        }
413    }
414}