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;
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, 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 Point {
46    /// Create a new point from uncompressed coordinates.
47    ///
48    /// Returns an error if the coordinates don't satisfy the curve equation y² + xy = x³ + 1.
49    pub fn new_uncompressed(
50        x: &[u8; B283K_FIELD_ELEMENT_SIZE],
51        y: &[u8; B283K_FIELD_ELEMENT_SIZE],
52    ) -> Result<Self> {
53        let x_fe = FieldElement::from_bytes(x)?;
54        let y_fe = FieldElement::from_bytes(y)?;
55        if !Self::is_on_curve(&x_fe, &y_fe) {
56            return Err(Error::param(
57                "B283k Point",
58                "Point coordinates do not satisfy curve equation",
59            ));
60        }
61        Ok(Point {
62            is_identity: Choice::from(0),
63            x: x_fe,
64            y: y_fe,
65        })
66    }
67
68    /// Create the identity point (point at infinity).
69    pub fn identity() -> Self {
70        Point {
71            is_identity: Choice::from(1),
72            x: FieldElement::zero(),
73            y: FieldElement::zero(),
74        }
75    }
76
77    /// Check if this point is the identity element.
78    pub fn is_identity(&self) -> bool {
79        self.is_identity.into()
80    }
81
82    /// Get the x-coordinate of this point as bytes.
83    pub fn x_coordinate_bytes(&self) -> [u8; B283K_FIELD_ELEMENT_SIZE] {
84        self.x.to_bytes()
85    }
86
87    /// Get the y-coordinate of this point as bytes.
88    pub fn y_coordinate_bytes(&self) -> [u8; B283K_FIELD_ELEMENT_SIZE] {
89        self.y.to_bytes()
90    }
91
92    /// Serialize this point in compressed format.
93    ///
94    /// The compressed format uses the trace to disambiguate the y-coordinate.
95    pub fn serialize_compressed(&self) -> [u8; B283K_POINT_COMPRESSED_SIZE] {
96        let mut out = [0u8; B283K_POINT_COMPRESSED_SIZE];
97        if self.is_identity() {
98            return out;
99        }
100
101        let y_tilde = self.x.invert().unwrap().mul(&self.y).trace();
102        out[0] = if y_tilde == 1 { 0x03 } else { 0x02 };
103        out[1..].copy_from_slice(&self.x.to_bytes());
104        out
105    }
106
107    /// Deserialize a point from compressed format.
108    ///
109    /// Recovers the y-coordinate from the x-coordinate and the compression flag.
110    /// Returns an error if the bytes don't represent a valid point.
111    pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
112        validate::length(
113            "B283k Compressed Point",
114            bytes.len(),
115            B283K_POINT_COMPRESSED_SIZE,
116        )?;
117        if bytes.iter().all(|&b| b == 0) {
118            return Ok(Self::identity());
119        }
120        let tag = bytes[0];
121        if tag != 0x02 && tag != 0x03 {
122            return Err(Error::param(
123                "B283k Point",
124                "Invalid compressed point prefix",
125            ));
126        }
127        let mut x_bytes = [0u8; B283K_FIELD_ELEMENT_SIZE];
128        x_bytes.copy_from_slice(&bytes[1..]);
129        let x = FieldElement::from_bytes(&x_bytes)?;
130        if x.is_zero() {
131            return Ok(Point {
132                is_identity: Choice::from(0),
133                x,
134                y: FieldElement::one().sqrt(),
135            });
136        }
137
138        // y^2 + xy = x^3 + 1 => y^2 + xy + (x^3 + 1) = 0
139        // Let z = y/x. z^2*x^2 + x*z*x + x^3 + 1 = 0 => z^2*x^2 + x^2*z + x^3 + 1 = 0
140        // z^2 + z = x + 1/x^2
141        let rhs = x.add(&x.square().invert().unwrap());
142
143        // Step 1: Check existence of a solution
144        if rhs.trace() != 0 {
145            return Err(Error::param("B283k Point", "Cannot decompress point"));
146        }
147
148        // Step 2: Solve z^2 + z = rhs using half-trace
149        let mut z = Self::half_trace(&rhs);
150
151        // Step 3: Choose the root whose LSB matches ~y_P
152        if z.trace() != (tag as u64 - 2) {
153            z = z.add(&FieldElement::one());
154        }
155
156        let y = x.mul(&z);
157        Ok(Point {
158            is_identity: Choice::from(0),
159            x,
160            y,
161        })
162    }
163
164    /// Return the half-trace of `a` in GF(2^283).
165    ///
166    /// For odd m, the half-trace Htr(a) = sum_{i=0}^{(m-1)/2} a^{2^{2i}}
167    /// satisfies Htr(a)^2 + Htr(a) = a when Tr(a) = 0.
168    fn half_trace(a: &FieldElement) -> FieldElement {
169        // m = 283 → (m-1)/2 = 141
170        let mut ht = *a; // a^{2^{0}}
171        let mut t = *a;
172        for _ in 0..141 {
173            t = t.square(); // a^{2^{2k+1}}
174            t = t.square(); // a^{2^{2k+2}} = a^{2^{2(k+1)}}
175            ht = ht.add(&t); // accumulate a^{2^{2(k+1)}}
176        }
177        ht
178    }
179
180    /// Add two points using the group law for binary elliptic curves.
181    pub fn add(&self, other: &Self) -> Self {
182        if self.is_identity() {
183            return other.clone();
184        }
185        if other.is_identity() {
186            return self.clone();
187        }
188
189        if self.x == other.x {
190            if self.y == other.y {
191                return self.double();
192            } else {
193                return Self::identity();
194            }
195        }
196
197        let lambda = (self.y.add(&other.y)).mul(&(self.x.add(&other.x)).invert().unwrap());
198        let x3 = lambda.square().add(&lambda).add(&self.x).add(&other.x);
199        let y3 = lambda.mul(&(self.x.add(&x3))).add(&x3).add(&self.y);
200        Point {
201            is_identity: Choice::from(0),
202            x: x3,
203            y: y3,
204        }
205    }
206
207    /// Double a point (add it to itself).
208    pub fn double(&self) -> Self {
209        if self.is_identity() || self.x.is_zero() {
210            return Self::identity();
211        }
212
213        let lambda = self.x.add(&self.y.mul(&self.x.invert().unwrap()));
214        let x2 = lambda.square().add(&lambda);
215        let y2 = self.x.square().add(&lambda.mul(&x2)).add(&x2);
216        Point {
217            is_identity: Choice::from(0),
218            x: x2,
219            y: y2,
220        }
221    }
222
223    /// Scalar multiplication: compute scalar * self.
224    ///
225    /// Uses constant-time double-and-add algorithm.
226    pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
227        if scalar.is_zero() {
228            return Ok(Self::identity());
229        }
230        let scalar_bytes = scalar.as_secret_buffer().as_ref();
231        let mut res = Self::identity();
232        let mut temp = self.clone();
233
234        for byte in scalar_bytes.iter().rev() {
235            for i in 0..8 {
236                if (byte >> i) & 1 == 1 {
237                    res = res.add(&temp);
238                }
239                temp = temp.double();
240            }
241        }
242        Ok(res)
243    }
244
245    fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
246        // y^2 + xy = x^3 + 1
247        let y_sq = y.square();
248        let xy = x.mul(y);
249        let lhs = y_sq.add(&xy);
250
251        let x_cubed = x.square().mul(x);
252        let rhs = x_cubed.add(&FieldElement::one());
253
254        lhs == rhs
255    }
256}