dcrypt_algorithms/ec/b283k/
point.rs

1//! sect283k1 elliptic curve point operations
2
3use crate::ec::b283k::{
4    constants::{B283K_FIELD_ELEMENT_SIZE, B283K_POINT_COMPRESSED_SIZE},
5    field::FieldElement,
6    scalar::Scalar,
7};
8use crate::error::{validate, Error, Result};
9use subtle::{Choice, ConditionallySelectable};
10
11/// Format of a serialized elliptic curve point
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PointFormat {
14    /// The point at infinity (identity element)
15    Identity,
16    /// Uncompressed format: 0x04 || x || y
17    Uncompressed,
18    /// Compressed format: 0x02/0x03 || x
19    Compressed,
20}
21
22/// A point on the sect283k1 elliptic curve
23#[derive(Clone, Copy, Debug)]
24pub struct Point {
25    pub(crate) is_identity: Choice,
26    pub(crate) x: FieldElement,
27    pub(crate) y: FieldElement,
28}
29
30impl PartialEq for Point {
31    fn eq(&self, other: &Self) -> bool {
32        let self_is_identity: bool = self.is_identity.into();
33        let other_is_identity: bool = other.is_identity.into();
34
35        if self_is_identity || other_is_identity {
36            return self_is_identity == other_is_identity;
37        }
38
39        self.x == other.x && self.y == other.y
40    }
41}
42
43impl Eq for Point {}
44
45impl ConditionallySelectable for Point {
46    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
47        Self {
48            is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
49            x: FieldElement::conditional_select(&a.x, &b.x, choice),
50            y: FieldElement::conditional_select(&a.y, &b.y, choice),
51        }
52    }
53}
54
55impl Point {
56    /// Create a new point from uncompressed coordinates.
57    ///
58    /// Returns an error if the coordinates don't satisfy the curve equation y² + xy = x³ + 1.
59    pub fn new_uncompressed(
60        x: &[u8; B283K_FIELD_ELEMENT_SIZE],
61        y: &[u8; B283K_FIELD_ELEMENT_SIZE],
62    ) -> Result<Self> {
63        let x_fe = FieldElement::from_bytes(x)?;
64        let y_fe = FieldElement::from_bytes(y)?;
65        if !Self::is_on_curve(&x_fe, &y_fe) {
66            return Err(Error::param(
67                "B283k Point",
68                "Point coordinates do not satisfy curve equation",
69            ));
70        }
71        Ok(Point {
72            is_identity: Choice::from(0),
73            x: x_fe,
74            y: y_fe,
75        })
76    }
77
78    /// Create the identity point (point at infinity).
79    pub fn identity() -> Self {
80        Point {
81            is_identity: Choice::from(1),
82            x: FieldElement::zero(),
83            y: FieldElement::zero(),
84        }
85    }
86
87    /// Check if this point is the identity element.
88    pub fn is_identity(&self) -> bool {
89        self.is_identity.into()
90    }
91
92    /// Get the x-coordinate of this point as bytes.
93    pub fn x_coordinate_bytes(&self) -> [u8; B283K_FIELD_ELEMENT_SIZE] {
94        self.x.to_bytes()
95    }
96
97    /// Get the y-coordinate of this point as bytes.
98    pub fn y_coordinate_bytes(&self) -> [u8; B283K_FIELD_ELEMENT_SIZE] {
99        self.y.to_bytes()
100    }
101
102    /// Serialize this point in compressed format.
103    ///
104    /// The compressed format uses the trace to disambiguate the y-coordinate.
105    pub fn serialize_compressed(&self) -> [u8; B283K_POINT_COMPRESSED_SIZE] {
106        let mut out = [0u8; B283K_POINT_COMPRESSED_SIZE];
107        if self.is_identity() {
108            return out;
109        }
110
111        let y_tilde = self.x.invert().unwrap().mul(&self.y).trace();
112        out[0] = if y_tilde == 1 { 0x03 } else { 0x02 };
113        out[1..].copy_from_slice(&self.x.to_bytes());
114        out
115    }
116
117    /// Deserialize a point from compressed format.
118    ///
119    /// Recovers the y-coordinate from the x-coordinate and the compression flag.
120    /// Returns an error if the bytes don't represent a valid point.
121    pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
122        validate::length(
123            "B283k Compressed Point",
124            bytes.len(),
125            B283K_POINT_COMPRESSED_SIZE,
126        )?;
127        if bytes.iter().all(|&b| b == 0) {
128            return Ok(Self::identity());
129        }
130        let tag = bytes[0];
131        if tag != 0x02 && tag != 0x03 {
132            return Err(Error::param(
133                "B283k Point",
134                "Invalid compressed point prefix",
135            ));
136        }
137        let mut x_bytes = [0u8; B283K_FIELD_ELEMENT_SIZE];
138        x_bytes.copy_from_slice(&bytes[1..]);
139        let x = FieldElement::from_bytes(&x_bytes)?;
140        if x.is_zero() {
141            return Ok(Point {
142                is_identity: Choice::from(0),
143                x,
144                y: FieldElement::one().sqrt(),
145            });
146        }
147
148        let rhs = x.add(&x.square().invert().unwrap());
149
150        if rhs.trace() != 0 {
151            return Err(Error::param("B283k Point", "Cannot decompress point"));
152        }
153
154        let mut z = Self::half_trace(&rhs);
155
156        if z.trace() != (tag as u64 - 2) {
157            z = z.add(&FieldElement::one());
158        }
159
160        let y = x.mul(&z);
161        Ok(Point {
162            is_identity: Choice::from(0),
163            x,
164            y,
165        })
166    }
167
168    fn half_trace(a: &FieldElement) -> FieldElement {
169        let mut ht = *a;
170        let mut t = *a;
171        for _ in 0..141 {
172            t = t.square();
173            t = t.square();
174            ht = ht.add(&t);
175        }
176        ht
177    }
178
179    /// Constant-time Affine addition.
180    /// Computes both addition and doubling paths and selects valid one.
181    /// Handles division-by-zero safely via dummy inversion.
182    pub fn add(&self, other: &Self) -> Self {
183        // 1. Calculate flags
184        let x_eq = self.x == other.x;
185        let y_eq = self.y == other.y;
186        
187        // P == Q: x1 == x2 AND y1 == y2
188        let p_eq_q = Choice::from((x_eq && y_eq) as u8);
189        // P == -Q: x1 == x2 AND y1 != y2 (in binary field characteristic 2)
190        let p_eq_neg_q = Choice::from((x_eq && !y_eq) as u8);
191        
192        // 2. Compute Generic Addition (valid when x1 != x2)
193        // lambda = (y1 + y2) / (x1 + x2)
194        let sum_y = self.y.add(&other.y);
195        let sum_x = self.x.add(&other.x);
196        
197        // Safe inversion: if sum_x is zero (x1 == x2), invert 1 instead.
198        // This produces garbage result, but we won't select it in that case.
199        let sum_x_is_zero = Choice::from(sum_x.is_zero() as u8);
200        let denom = FieldElement::conditional_select(&sum_x, &FieldElement::one(), sum_x_is_zero);
201        let inv_denom = denom.invert().unwrap_or(FieldElement::zero());
202        
203        let lambda = sum_y.mul(&inv_denom);
204        let x3 = lambda.square().add(&lambda).add(&self.x).add(&other.x);
205        let y3 = lambda.mul(&(self.x.add(&x3))).add(&x3).add(&self.y);
206        
207        let generic = Point {
208            is_identity: Choice::from(0),
209            x: x3,
210            y: y3,
211        };
212
213        // 3. Compute Doubling (valid when P == Q)
214        let double = self.double();
215
216        // 4. Select Result
217        // Start with Generic. If P==Q, switch to Double.
218        let mut result = Self::conditional_select(&generic, &double, p_eq_q);
219        
220        // If P == -Q, the result must be Identity.
221        result = Self::conditional_select(&result, &Self::identity(), p_eq_neg_q);
222        
223        // If either input is identity, result is the other one.
224        result = Self::conditional_select(&result, other, self.is_identity);
225        result = Self::conditional_select(&result, self, other.is_identity);
226
227        result
228    }
229
230    /// Constant-time Affine doubling.
231    /// Computes doubling formula safely, handling the P at infinity case
232    /// without branching.
233    pub fn double(&self) -> Self {
234        // lambda = x + y/x
235        // Safe inversion: if x is zero, invert 1
236        let x_is_zero = Choice::from(self.x.is_zero() as u8);
237        let denom = FieldElement::conditional_select(&self.x, &FieldElement::one(), x_is_zero);
238        let inv_x = denom.invert().unwrap_or(FieldElement::zero());
239        
240        let term = self.y.mul(&inv_x);
241        let lambda = self.x.add(&term);
242        
243        let x2 = lambda.square().add(&lambda);
244        let y2 = self.x.square().add(&lambda.mul(&x2)).add(&x2);
245
246        let result = Point {
247            is_identity: Choice::from(0),
248            x: x2,
249            y: y2,
250        };
251
252        Self::conditional_select(&result, &Self::identity(), self.is_identity)
253    }
254
255    /// Scalar multiplication: compute scalar * self.
256    ///
257    /// Uses constant-time double-and-add algorithm.
258    pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
259        if scalar.is_zero() {
260            return Ok(Self::identity());
261        }
262        let scalar_bytes = scalar.as_secret_buffer().as_ref();
263        let mut res = Self::identity();
264        let mut temp = self.clone();
265
266        for byte in scalar_bytes.iter().rev() {
267            for i in 0..8 {
268                let bit = (byte >> i) & 1;
269                let choice = Choice::from(bit);
270
271                // Unconditionally compute addition
272                let res_added = res.add(&temp);
273                
274                // Constant-time select
275                res = Point::conditional_select(&res, &res_added, choice);
276
277                // Unconditionally double
278                temp = temp.double();
279            }
280        }
281        Ok(res)
282    }
283
284    fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
285        let y_sq = y.square();
286        let xy = x.mul(y);
287        let lhs = y_sq.add(&xy);
288
289        let x_cubed = x.square().mul(x);
290        let rhs = x_cubed.add(&FieldElement::one());
291
292        lhs == rhs
293    }
294}