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_internal::constant_time::{Choice, ConditionallySelectable};
12use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
13use dcrypt_params::traditional::ecdsa::NIST_P384;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum PointFormat {
18 Identity,
20 Uncompressed,
22 Compressed,
24}
25
26#[derive(Clone, Debug)]
31pub struct Point {
32 pub(crate) is_identity: Choice,
34 pub(crate) x: FieldElement,
36 pub(crate) y: FieldElement,
38}
39
40impl Zeroize for Point {
41 fn zeroize(&mut self) {
42 self.is_identity.zeroize();
43 self.x.zeroize();
44 self.y.zeroize();
45 }
46}
47
48impl Drop for Point {
49 fn drop(&mut self) {
50 self.zeroize();
51 }
52}
53
54impl ZeroizeOnDrop for Point {}
55
56#[derive(Clone, Copy, Debug)]
66pub(crate) struct ProjectivePoint {
67 is_identity: Choice,
69 x: FieldElement,
71 y: FieldElement,
73 z: FieldElement,
75}
76
77impl Default for ProjectivePoint {
78 fn default() -> Self {
79 Self::identity()
80 }
81}
82
83impl Zeroize for ProjectivePoint {
84 fn zeroize(&mut self) {
85 self.is_identity.zeroize();
86 self.x.zeroize();
87 self.y.zeroize();
88 self.z.zeroize();
89 }
90}
91
92impl PartialEq for Point {
93 fn eq(&self, other: &Self) -> bool {
95 let self_is_identity: bool = self.is_identity.into();
96 let other_is_identity: bool = other.is_identity.into();
97
98 if self_is_identity || other_is_identity {
99 return self_is_identity == other_is_identity;
100 }
101 self.x == other.x && self.y == other.y
102 }
103}
104
105impl Point {
106 pub fn new_uncompressed(
113 x: &[u8; P384_FIELD_ELEMENT_SIZE],
114 y: &[u8; P384_FIELD_ELEMENT_SIZE],
115 ) -> Result<Self> {
116 let x_fe = Zeroizing::new(FieldElement::from_bytes(x)?);
117 let y_fe = Zeroizing::new(FieldElement::from_bytes(y)?);
118
119 if !Self::is_on_curve(&x_fe, &y_fe) {
121 return Err(Error::param(
122 "P-384 Point",
123 "Point coordinates do not satisfy curve equation",
124 ));
125 }
126
127 Ok(Point {
128 is_identity: Choice::from(0),
129 x: x_fe.into_inner(),
130 y: y_fe.into_inner(),
131 })
132 }
133
134 pub fn identity() -> Self {
139 Point {
140 is_identity: Choice::from(1),
141 x: FieldElement::zero(),
142 y: FieldElement::zero(),
143 }
144 }
145
146 pub fn is_identity(&self) -> bool {
148 self.is_identity.into()
149 }
150
151 pub fn x_coordinate_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
153 self.x.to_bytes()
154 }
155
156 pub fn y_coordinate_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
158 self.y.to_bytes()
159 }
160
161 pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
165 if bytes.is_empty() {
166 return Err(Error::param("P-384 Point", "Empty point data"));
167 }
168
169 match (bytes[0], bytes.len()) {
170 (0x00, 1) => Ok(PointFormat::Identity),
171 (0x04, P384_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
172 (0x02 | 0x03, P384_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
173 _ => Err(Error::param(
174 "P-384 Point",
175 "Unknown or malformed point format",
176 )),
177 }
178 }
179
180 pub fn serialize_uncompressed(&self) -> [u8; P384_POINT_UNCOMPRESSED_SIZE] {
187 let mut result = [0u8; P384_POINT_UNCOMPRESSED_SIZE];
188
189 if self.is_identity() {
191 return result; }
193
194 result[0] = 0x04;
196 self.x.write_bytes(&mut result[1..49]);
197 self.y.write_bytes(&mut result[49..97]);
198
199 result
200 }
201
202 pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
207 validate::length("P-384 Point", bytes.len(), P384_POINT_UNCOMPRESSED_SIZE)?;
208
209 if bytes[0] != 0x04 {
211 return Err(Error::param(
212 "P-384 Point",
213 "Invalid uncompressed point format (expected 0x04 prefix)",
214 ));
215 }
216
217 let mut x_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
219 let mut y_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
220
221 x_bytes.copy_from_slice(&bytes[1..49]);
222 y_bytes.copy_from_slice(&bytes[49..97]);
223
224 Self::new_uncompressed(&x_bytes, &y_bytes)
225 }
226
227 pub fn serialize_compressed(&self) -> [u8; P384_POINT_COMPRESSED_SIZE] {
238 let mut out = [0u8; P384_POINT_COMPRESSED_SIZE];
239
240 if self.is_identity() {
242 return out;
243 }
244
245 out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
247 self.x.write_bytes(&mut out[1..]);
248 out
249 }
250
251 pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
256 validate::length(
257 "P-384 Compressed Point",
258 bytes.len(),
259 P384_POINT_COMPRESSED_SIZE,
260 )?;
261
262 let tag = bytes[0];
263 if tag != 0x02 && tag != 0x03 {
264 return Err(Error::param(
265 "P-384 Point",
266 "Invalid compressed point prefix (expected 0x02 or 0x03)",
267 ));
268 }
269
270 let mut x_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
272 x_bytes.copy_from_slice(&bytes[1..]);
273
274 let x_fe = Zeroizing::new(FieldElement::from_bytes(&x_bytes).map_err(|_| {
275 Error::param(
276 "P-384 Point",
277 "Invalid compressed point: x-coordinate yields quadratic non-residue",
278 )
279 })?);
280
281 let x2 = Zeroizing::new(x_fe.square());
283 let x3 = Zeroizing::new(x2.mul(&x_fe));
284 let a = Zeroizing::new(FieldElement(FieldElement::A_M3)); let b = Zeroizing::new(FieldElement::from_bytes(&NIST_P384.b).unwrap());
286 let ax = Zeroizing::new(a.mul(&x_fe));
287 let x3_plus_ax = Zeroizing::new(x3.add(&ax));
288 let rhs = Zeroizing::new(x3_plus_ax.add(&b));
289
290 let y_fe = Zeroizing::new(rhs.sqrt().ok_or_else(|| {
292 Error::param(
293 "P-384 Point",
294 "Invalid compressed point: x-coordinate yields quadratic non-residue",
295 )
296 })?);
297
298 let y_matches = (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02);
300 let y_final = if y_matches {
301 Zeroizing::new(y_fe.into_inner())
302 } else {
303 let modulus = Zeroizing::new(FieldElement::get_modulus());
305 Zeroizing::new(modulus.sub(&y_fe))
306 };
307
308 Ok(Point {
309 is_identity: Choice::from(0),
310 x: x_fe.into_inner(),
311 y: y_final.into_inner(),
312 })
313 }
314
315 pub fn add(&self, other: &Self) -> Self {
319 let p1 = Zeroizing::new(self.to_projective());
320 let p2 = Zeroizing::new(other.to_projective());
321 let result = Zeroizing::new(p1.add(&p2));
322 result.to_affine()
323 }
324
325 pub fn double(&self) -> Self {
327 let p = Zeroizing::new(self.to_projective());
328 let result = Zeroizing::new(p.double());
329 result.to_affine()
330 }
331
332 pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
336 let scalar_bytes = scalar.as_secret_buffer().as_ref();
337
338 let base = Zeroizing::new(self.to_projective());
340 let mut result = Zeroizing::new(ProjectivePoint::identity());
341
342 for byte in scalar_bytes.iter() {
343 for bit_pos in (0..8).rev() {
344 let mut bit = (byte >> bit_pos) & 1;
345 let mut choice = Choice::from(bit);
346 bit.zeroize();
347
348 let doubled = Zeroizing::new(result.double());
349
350 let result_added = Zeroizing::new(doubled.add(&base));
352
353 let selected = Zeroizing::new(ProjectivePoint::conditional_select(
355 &doubled,
356 &result_added,
357 choice,
358 ));
359 result.zeroize();
360 *result = selected.into_inner();
361 choice.zeroize();
362 }
363 }
364
365 Ok(result.to_affine())
366 }
367
368 fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
372 let y_squared = Zeroizing::new(y.square());
374
375 let x_squared = Zeroizing::new(x.square());
377 let x_cubed = Zeroizing::new(x_squared.mul(x));
378 let a_coeff = Zeroizing::new(FieldElement(FieldElement::A_M3)); let ax = Zeroizing::new(a_coeff.mul(x));
380 let b_coeff = Zeroizing::new(FieldElement::from_bytes(&NIST_P384.b).unwrap());
381
382 let x_cubed_plus_ax = Zeroizing::new(x_cubed.add(&ax));
384 let rhs = Zeroizing::new(x_cubed_plus_ax.add(&b_coeff));
385
386 *y_squared == *rhs
387 }
388
389 fn to_projective(&self) -> ProjectivePoint {
391 let regular = Zeroizing::new(ProjectivePoint {
392 is_identity: Choice::from(0),
393 x: self.x,
394 y: self.y,
395 z: FieldElement::one(),
396 });
397 let identity = Zeroizing::new(ProjectivePoint::identity());
398 let selected = Zeroizing::new(ProjectivePoint::conditional_select(
399 ®ular,
400 &identity,
401 self.is_identity,
402 ));
403 selected.into_inner()
404 }
405}
406
407impl ConditionallySelectable for ProjectivePoint {
408 #[inline(never)]
409 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
410 Self {
411 is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
412 x: FieldElement::conditional_select(&a.x, &b.x, choice),
413 y: FieldElement::conditional_select(&a.y, &b.y, choice),
414 z: FieldElement::conditional_select(&a.z, &b.z, choice),
415 }
416 }
417}
418
419impl ProjectivePoint {
420 pub fn identity() -> Self {
422 ProjectivePoint {
423 is_identity: Choice::from(1),
424 x: FieldElement::zero(),
425 y: FieldElement::one(),
426 z: FieldElement::zero(),
427 }
428 }
429
430 pub fn add(&self, other: &Self) -> Self {
432 let z1_squared = Zeroizing::new(self.z.square());
435 let z2_squared = Zeroizing::new(other.z.square());
436 let z1_cubed = Zeroizing::new(z1_squared.mul(&self.z));
437 let z2_cubed = Zeroizing::new(z2_squared.mul(&other.z));
438
439 let u1 = Zeroizing::new(self.x.mul(&z2_squared)); let u2 = Zeroizing::new(other.x.mul(&z1_squared)); let s1 = Zeroizing::new(self.y.mul(&z2_cubed)); let s2 = Zeroizing::new(other.y.mul(&z1_cubed)); let h = Zeroizing::new(u2.sub(&u1)); let r = Zeroizing::new(s2.sub(&s1)); let h_squared = Zeroizing::new(h.square());
450 let h_cubed = Zeroizing::new(h_squared.mul(&h));
451 let v = Zeroizing::new(u1.mul(&h_squared));
452
453 let r_squared = Zeroizing::new(r.square());
455 let two_v = Zeroizing::new(v.add(&v));
456 let x3_minus_h_cubed = Zeroizing::new(r_squared.sub(&h_cubed));
457 let x3 = Zeroizing::new(x3_minus_h_cubed.sub(&two_v));
458
459 let v_minus_x3 = Zeroizing::new(v.sub(&x3));
461 let r_times_diff = Zeroizing::new(r.mul(&v_minus_x3));
462 let s1_times_h_cubed = Zeroizing::new(s1.mul(&h_cubed));
463 let y3 = Zeroizing::new(r_times_diff.sub(&s1_times_h_cubed));
464
465 let z1_times_z2 = Zeroizing::new(self.z.mul(&other.z));
467 let z3 = Zeroizing::new(z1_times_z2.mul(&h));
468
469 let generic_point = Zeroizing::new(Self {
470 is_identity: Choice::from(0),
471 x: x3.into_inner(),
472 y: y3.into_inner(),
473 z: z3.into_inner(),
474 });
475
476 let double_point = Zeroizing::new(self.double());
478
479 let mut h_is_zero = Choice::from((h.is_zero() as u8) & 1);
481 let mut r_is_zero = Choice::from((r.is_zero() as u8) & 1);
482
483 let mut p_eq_q = h_is_zero & r_is_zero;
485 let mut p_eq_neg_q = h_is_zero & !r_is_zero;
487
488 let mut result = Zeroizing::new(Self::conditional_select(
490 &generic_point,
491 &double_point,
492 p_eq_q,
493 ));
494
495 let identity = Zeroizing::new(Self::identity());
497 let selected = Zeroizing::new(Self::conditional_select(&result, &identity, p_eq_neg_q));
498 result.zeroize();
499 *result = selected.into_inner();
500
501 let selected = Zeroizing::new(Self::conditional_select(&result, other, self.is_identity));
503 result.zeroize();
504 *result = selected.into_inner();
505 let selected = Zeroizing::new(Self::conditional_select(&result, self, other.is_identity));
506 result.zeroize();
507 *result = selected.into_inner();
508
509 h_is_zero.zeroize();
510 r_is_zero.zeroize();
511 p_eq_q.zeroize();
512 p_eq_neg_q.zeroize();
513 result.into_inner()
514 }
515
516 #[inline]
518 pub fn double(&self) -> Self {
519 let delta = Zeroizing::new(self.z.square());
522
523 let gamma = Zeroizing::new(self.y.square());
525
526 let beta = Zeroizing::new(self.x.mul(&gamma));
528
529 let x_plus_delta = Zeroizing::new(self.x.add(&delta));
531 let x_minus_delta = Zeroizing::new(self.x.sub(&delta));
532 let alpha_base = Zeroizing::new(x_plus_delta.mul(&x_minus_delta));
533 let two_alpha = Zeroizing::new(alpha_base.add(&alpha_base));
534 let alpha = Zeroizing::new(two_alpha.add(&alpha_base)); let two_beta = Zeroizing::new(beta.add(&beta));
539 let four_beta = Zeroizing::new(two_beta.add(&two_beta));
540 let eight_beta = Zeroizing::new(four_beta.add(&four_beta));
541 let alpha_squared = Zeroizing::new(alpha.square());
542 let x3 = Zeroizing::new(alpha_squared.sub(&eight_beta));
543
544 let y_plus_z = Zeroizing::new(self.y.add(&self.z));
546 let y_plus_z_squared = Zeroizing::new(y_plus_z.square());
547 let z3_minus_gamma = Zeroizing::new(y_plus_z_squared.sub(&gamma));
548 let z3 = Zeroizing::new(z3_minus_gamma.sub(&delta));
549
550 let four_beta_minus_x3 = Zeroizing::new(four_beta.sub(&x3));
552 let alpha_times_difference = Zeroizing::new(alpha.mul(&four_beta_minus_x3));
553
554 let gamma_sq = Zeroizing::new(gamma.square());
555 let two_gamma_sq = Zeroizing::new(gamma_sq.add(&gamma_sq));
556 let four_gamma_sq = Zeroizing::new(two_gamma_sq.add(&two_gamma_sq));
557 let eight_gamma_sq = Zeroizing::new(four_gamma_sq.add(&four_gamma_sq));
558 let y3 = Zeroizing::new(alpha_times_difference.sub(&eight_gamma_sq));
559
560 let result = Zeroizing::new(Self {
561 is_identity: Choice::from(0),
562 x: x3.into_inner(),
563 y: y3.into_inner(),
564 z: z3.into_inner(),
565 });
566
567 let mut return_identity = self.is_identity | Choice::from(self.y.is_zero() as u8);
569
570 let identity = Zeroizing::new(Self::identity());
571 let selected = Zeroizing::new(Self::conditional_select(
572 &result,
573 &identity,
574 return_identity,
575 ));
576 return_identity.zeroize();
577 selected.into_inner()
578 }
579
580 pub fn to_affine(&self) -> Point {
582 let one = Zeroizing::new(FieldElement::one());
585 let safe_z = Zeroizing::new(FieldElement::conditional_select(
586 &self.z,
587 &one,
588 self.is_identity,
589 ));
590 let z_inv = Zeroizing::new(
591 safe_z
592 .invert()
593 .expect("Non-zero Z coordinate should be invertible"),
594 );
595 let z_inv_squared = Zeroizing::new(z_inv.square());
596 let z_inv_cubed = Zeroizing::new(z_inv_squared.mul(&z_inv));
597
598 let x_affine = Zeroizing::new(self.x.mul(&z_inv_squared));
600 let y_affine = Zeroizing::new(self.y.mul(&z_inv_cubed));
601 let zero = Zeroizing::new(FieldElement::zero());
602 let x = Zeroizing::new(FieldElement::conditional_select(
603 &x_affine,
604 &zero,
605 self.is_identity,
606 ));
607 let y = Zeroizing::new(FieldElement::conditional_select(
608 &y_affine,
609 &zero,
610 self.is_identity,
611 ));
612
613 Point {
614 is_identity: self.is_identity,
615 x: x.into_inner(),
616 y: y.into_inner(),
617 }
618 }
619}