dcrypt_algorithms/ec/p384/
point.rs1use crate::ec::p384::{
4 constants::{
5 P384_FIELD_ELEMENT_SIZE, P384_POINT_COMPRESSED_SIZE, P384_POINT_UNCOMPRESSED_SIZE,
6 },
7 field::FieldElement,
8 scalar::Scalar,
9};
10use crate::error::{validate, Error, Result};
11use dcrypt_params::traditional::ecdsa::NIST_P384;
12use subtle::{Choice, ConditionallySelectable};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum PointFormat {
17 Identity,
19 Uncompressed,
21 Compressed,
23}
24
25#[derive(Clone, Debug)]
30pub struct Point {
31 pub(crate) is_identity: Choice,
33 pub(crate) x: FieldElement,
35 pub(crate) y: FieldElement,
37}
38
39#[derive(Clone, Copy, Debug)]
46pub(crate) struct ProjectivePoint {
47 is_identity: Choice,
49 x: FieldElement,
51 y: FieldElement,
53 z: FieldElement,
55}
56
57impl PartialEq for Point {
58 fn eq(&self, other: &Self) -> bool {
60 let self_is_identity: bool = self.is_identity.into();
61 let other_is_identity: bool = other.is_identity.into();
62
63 if self_is_identity || other_is_identity {
64 return self_is_identity == other_is_identity;
65 }
66 self.x == other.x && self.y == other.y
67 }
68}
69
70impl Point {
71 pub fn new_uncompressed(
78 x: &[u8; P384_FIELD_ELEMENT_SIZE],
79 y: &[u8; P384_FIELD_ELEMENT_SIZE],
80 ) -> Result<Self> {
81 let x_fe = FieldElement::from_bytes(x)?;
82 let y_fe = FieldElement::from_bytes(y)?;
83
84 if !Self::is_on_curve(&x_fe, &y_fe) {
86 return Err(Error::param(
87 "P-384 Point",
88 "Point coordinates do not satisfy curve equation",
89 ));
90 }
91
92 Ok(Point {
93 is_identity: Choice::from(0),
94 x: x_fe,
95 y: y_fe,
96 })
97 }
98
99 pub fn identity() -> Self {
104 Point {
105 is_identity: Choice::from(1),
106 x: FieldElement::zero(),
107 y: FieldElement::zero(),
108 }
109 }
110
111 pub fn is_identity(&self) -> bool {
113 self.is_identity.into()
114 }
115
116 pub fn x_coordinate_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
118 self.x.to_bytes()
119 }
120
121 pub fn y_coordinate_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
123 self.y.to_bytes()
124 }
125
126 pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
130 if bytes.is_empty() {
131 return Err(Error::param("P-384 Point", "Empty point data"));
132 }
133
134 match (bytes[0], bytes.len()) {
135 (0x00, P384_POINT_UNCOMPRESSED_SIZE) => {
136 if bytes.iter().all(|&b| b == 0) {
138 Ok(PointFormat::Identity)
139 } else {
140 Err(Error::param(
141 "P-384 Point",
142 "Invalid identity point encoding",
143 ))
144 }
145 }
146 (0x04, P384_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
147 (0x02 | 0x03, P384_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
148 _ => Err(Error::param(
149 "P-384 Point",
150 "Unknown or malformed point format",
151 )),
152 }
153 }
154
155 pub fn serialize_uncompressed(&self) -> [u8; P384_POINT_UNCOMPRESSED_SIZE] {
159 let mut result = [0u8; P384_POINT_UNCOMPRESSED_SIZE];
160
161 if self.is_identity() {
163 return result; }
165
166 result[0] = 0x04;
168 result[1..49].copy_from_slice(&self.x.to_bytes());
169 result[49..97].copy_from_slice(&self.y.to_bytes());
170
171 result
172 }
173
174 pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
179 validate::length("P-384 Point", bytes.len(), P384_POINT_UNCOMPRESSED_SIZE)?;
180
181 if bytes.iter().all(|&b| b == 0) {
183 return Ok(Self::identity());
184 }
185
186 if bytes[0] != 0x04 {
188 return Err(Error::param(
189 "P-384 Point",
190 "Invalid uncompressed point format (expected 0x04 prefix)",
191 ));
192 }
193
194 let mut x_bytes = [0u8; P384_FIELD_ELEMENT_SIZE];
196 let mut y_bytes = [0u8; P384_FIELD_ELEMENT_SIZE];
197
198 x_bytes.copy_from_slice(&bytes[1..49]);
199 y_bytes.copy_from_slice(&bytes[49..97]);
200
201 Self::new_uncompressed(&x_bytes, &y_bytes)
202 }
203
204 pub fn serialize_compressed(&self) -> [u8; P384_POINT_COMPRESSED_SIZE] {
211 let mut out = [0u8; P384_POINT_COMPRESSED_SIZE];
212
213 if self.is_identity() {
215 return out;
216 }
217
218 out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
220 out[1..].copy_from_slice(&self.x.to_bytes());
221 out
222 }
223
224 pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
229 validate::length(
230 "P-384 Compressed Point",
231 bytes.len(),
232 P384_POINT_COMPRESSED_SIZE,
233 )?;
234
235 if bytes.iter().all(|&b| b == 0) {
237 return Ok(Self::identity());
238 }
239
240 let tag = bytes[0];
241 if tag != 0x02 && tag != 0x03 {
242 return Err(Error::param(
243 "P-384 Point",
244 "Invalid compressed point prefix (expected 0x02 or 0x03)",
245 ));
246 }
247
248 let mut x_bytes = [0u8; P384_FIELD_ELEMENT_SIZE];
250 x_bytes.copy_from_slice(&bytes[1..]);
251
252 let x_fe = FieldElement::from_bytes(&x_bytes).map_err(|_| {
253 Error::param(
254 "P-384 Point",
255 "Invalid compressed point: x-coordinate yields quadratic non-residue",
256 )
257 })?;
258
259 let rhs = {
261 let x2 = x_fe.square();
262 let x3 = x2.mul(&x_fe);
263 let a = FieldElement(FieldElement::A_M3); let b = FieldElement::from_bytes(&NIST_P384.b).unwrap();
265 x3.add(&a.mul(&x_fe)).add(&b)
266 };
267
268 let y_fe = rhs.sqrt().ok_or_else(|| {
270 Error::param(
271 "P-384 Point",
272 "Invalid compressed point: x-coordinate yields quadratic non-residue",
273 )
274 })?;
275
276 let y_final = if (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02) {
278 y_fe
279 } else {
280 FieldElement::get_modulus().sub(&y_fe)
282 };
283
284 Ok(Point {
285 is_identity: Choice::from(0),
286 x: x_fe,
287 y: y_final,
288 })
289 }
290
291 pub fn add(&self, other: &Self) -> Self {
295 let p1 = self.to_projective();
296 let p2 = other.to_projective();
297 let result = p1.add(&p2);
298 result.to_affine()
299 }
300
301 pub fn double(&self) -> Self {
303 let p = self.to_projective();
304 let result = p.double();
305 result.to_affine()
306 }
307
308 pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
312 if scalar.is_zero() {
313 return Ok(Self::identity());
314 }
315
316 let scalar_bytes = scalar.as_secret_buffer().as_ref();
317
318 let base = self.to_projective();
320 let mut result = ProjectivePoint::identity();
321
322 for byte in scalar_bytes.iter() {
323 for bit_pos in (0..8).rev() {
324 result = result.double();
325
326 let bit = (byte >> bit_pos) & 1;
327 let choice = Choice::from(bit);
328
329 let result_added = result.add(&base);
331
332 result = ProjectivePoint::conditional_select(&result, &result_added, choice);
334 }
335 }
336
337 Ok(result.to_affine())
338 }
339
340 fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
344 let y_squared = y.square();
346
347 let x_cubed = x.square().mul(x);
349 let a_coeff = FieldElement(FieldElement::A_M3); let ax = a_coeff.mul(x);
351 let b_coeff = FieldElement::from_bytes(&NIST_P384.b).unwrap();
352
353 let x_cubed_plus_ax = x_cubed.add(&ax);
355 let rhs = x_cubed_plus_ax.add(&b_coeff);
356
357 y_squared == rhs
358 }
359
360 fn to_projective(&self) -> ProjectivePoint {
362 if self.is_identity() {
363 return ProjectivePoint::identity();
364 }
365
366 ProjectivePoint {
367 is_identity: Choice::from(0),
368 x: self.x.clone(),
369 y: self.y.clone(),
370 z: FieldElement::one(),
371 }
372 }
373}
374
375impl ConditionallySelectable for ProjectivePoint {
376 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
377 Self {
378 is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
379 x: FieldElement::conditional_select(&a.x, &b.x, choice),
380 y: FieldElement::conditional_select(&a.y, &b.y, choice),
381 z: FieldElement::conditional_select(&a.z, &b.z, choice),
382 }
383 }
384}
385
386impl ProjectivePoint {
387 pub fn identity() -> Self {
389 ProjectivePoint {
390 is_identity: Choice::from(1),
391 x: FieldElement::zero(),
392 y: FieldElement::one(),
393 z: FieldElement::zero(),
394 }
395 }
396
397 pub fn add(&self, other: &Self) -> Self {
399 let z1_squared = self.z.square();
402 let z2_squared = other.z.square();
403 let z1_cubed = z1_squared.mul(&self.z);
404 let z2_cubed = z2_squared.mul(&other.z);
405
406 let u1 = self.x.mul(&z2_squared); let u2 = other.x.mul(&z1_squared); let s1 = self.y.mul(&z2_cubed); let s2 = other.y.mul(&z1_cubed); let h = u2.sub(&u1); let r = s2.sub(&s1); let h_squared = h.square();
417 let h_cubed = h_squared.mul(&h);
418 let v = u1.mul(&h_squared);
419
420 let r_squared = r.square();
422 let two_v = v.add(&v);
423 let mut x3 = r_squared.sub(&h_cubed);
424 x3 = x3.sub(&two_v);
425
426 let v_minus_x3 = v.sub(&x3);
428 let r_times_diff = r.mul(&v_minus_x3);
429 let s1_times_h_cubed = s1.mul(&h_cubed);
430 let y3 = r_times_diff.sub(&s1_times_h_cubed);
431
432 let z1_times_z2 = self.z.mul(&other.z);
434 let z3 = z1_times_z2.mul(&h);
435
436 let generic_point = Self {
437 is_identity: Choice::from(0),
438 x: x3,
439 y: y3,
440 z: z3,
441 };
442
443 let double_point = self.double();
445
446 let h_is_zero = Choice::from((h.is_zero() as u8) & 1);
448 let r_is_zero = Choice::from((r.is_zero() as u8) & 1);
449
450 let p_eq_q = h_is_zero & r_is_zero;
452 let p_eq_neg_q = h_is_zero & !r_is_zero;
454
455 let mut result = generic_point;
457
458 result = Self::conditional_select(&result, &double_point, p_eq_q);
460
461 result = Self::conditional_select(&result, &Self::identity(), p_eq_neg_q);
463
464 result = Self::conditional_select(&result, other, self.is_identity);
466 result = Self::conditional_select(&result, self, other.is_identity);
467
468 result
469 }
470
471 #[inline]
473 pub fn double(&self) -> Self {
474 let delta = self.z.square();
477
478 let gamma = self.y.square();
480
481 let beta = self.x.mul(&gamma);
483
484 let x_plus_delta = self.x.add(&delta);
486 let x_minus_delta = self.x.sub(&delta);
487 let mut alpha = x_plus_delta.mul(&x_minus_delta);
488 alpha = alpha.add(&alpha).add(&alpha); let mut eight_beta = beta.add(&beta); eight_beta = eight_beta.add(&eight_beta); eight_beta = eight_beta.add(&eight_beta); let x3 = alpha.square().sub(&eight_beta);
496
497 let y_plus_z = self.y.add(&self.z);
499 let z3 = y_plus_z.square().sub(&gamma).sub(&delta);
500
501 let mut four_beta = beta.add(&beta); four_beta = four_beta.add(&four_beta); let mut y3 = four_beta.sub(&x3);
505 y3 = alpha.mul(&y3);
506
507 let gamma_sq = gamma.square(); let mut eight_gamma_sq = gamma_sq.add(&gamma_sq); eight_gamma_sq = eight_gamma_sq.add(&eight_gamma_sq); eight_gamma_sq = eight_gamma_sq.add(&eight_gamma_sq); y3 = y3.sub(&eight_gamma_sq);
512
513 let result = Self {
514 is_identity: Choice::from(0),
515 x: x3,
516 y: y3,
517 z: z3,
518 };
519
520 let is_y_zero = self.y.is_zero();
522 let return_identity = self.is_identity | Choice::from(is_y_zero as u8);
523
524 Self::conditional_select(&result, &Self::identity(), return_identity)
525 }
526
527 pub fn to_affine(&self) -> Point {
529 if self.is_identity.into() {
530 return Point::identity();
531 }
532
533 let z_inv = self
535 .z
536 .invert()
537 .expect("Non-zero Z coordinate should be invertible");
538 let z_inv_squared = z_inv.square();
539 let z_inv_cubed = z_inv_squared.mul(&z_inv);
540
541 let x_affine = self.x.mul(&z_inv_squared);
543 let y_affine = self.y.mul(&z_inv_cubed);
544
545 Point {
546 is_identity: Choice::from(0),
547 x: x_affine,
548 y: y_affine,
549 }
550 }
551}