dcrypt_algorithms/ec/p256/
point.rs1use crate::ec::p256::{
4 constants::{
5 P256_FIELD_ELEMENT_SIZE, P256_POINT_COMPRESSED_SIZE, P256_POINT_UNCOMPRESSED_SIZE,
6 },
7 field::FieldElement,
8 scalar::Scalar,
9};
10use crate::error::{validate, Error, Result};
11use dcrypt_internal::{
12 constant_time::{Choice, ConditionallySelectable},
13 Zeroize, ZeroizeOnDrop, Zeroizing,
14};
15use dcrypt_params::traditional::ecdsa::NIST_P256;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PointFormat {
20 Identity,
22 Uncompressed,
24 Compressed,
26}
27
28#[derive(Clone, Debug)]
33pub struct Point {
34 pub(crate) is_identity: Choice,
36 pub(crate) x: FieldElement,
38 pub(crate) y: FieldElement,
40}
41
42impl Default for Point {
43 fn default() -> Self {
44 Self::identity()
45 }
46}
47
48impl Zeroize for Point {
49 fn zeroize(&mut self) {
50 self.is_identity.zeroize();
51 self.x.zeroize();
52 self.y.zeroize();
53 }
54}
55
56impl Drop for Point {
57 fn drop(&mut self) {
58 self.zeroize();
59 }
60}
61
62impl ZeroizeOnDrop for Point {}
63
64#[derive(Clone, Copy, Debug)]
74pub(crate) struct ProjectivePoint {
75 is_identity: Choice,
77 x: FieldElement,
79 y: FieldElement,
81 z: FieldElement,
83}
84
85impl Default for ProjectivePoint {
89 fn default() -> Self {
90 Self::identity()
91 }
92}
93
94impl Zeroize for ProjectivePoint {
95 fn zeroize(&mut self) {
96 self.is_identity.zeroize();
97 self.x.zeroize();
98 self.y.zeroize();
99 self.z.zeroize();
100 }
101}
102
103impl PartialEq for Point {
104 fn eq(&self, other: &Self) -> bool {
109 let self_is_identity: bool = self.is_identity.into();
111 let other_is_identity: bool = other.is_identity.into();
112
113 if self_is_identity || other_is_identity {
114 return self_is_identity == other_is_identity;
115 }
116
117 self.x == other.x && self.y == other.y
119 }
120}
121
122impl Point {
123 pub fn new_uncompressed(
130 x: &[u8; P256_FIELD_ELEMENT_SIZE],
131 y: &[u8; P256_FIELD_ELEMENT_SIZE],
132 ) -> Result<Self> {
133 let x_fe = Zeroizing::new(FieldElement::from_bytes(x)?);
134 let y_fe = Zeroizing::new(FieldElement::from_bytes(y)?);
135
136 if !Self::is_on_curve(&x_fe, &y_fe) {
138 return Err(Error::param(
139 "P-256 Point",
140 "Point coordinates do not satisfy curve equation",
141 ));
142 }
143
144 Ok(Point {
145 is_identity: Choice::from(0),
146 x: x_fe.into_inner(),
147 y: y_fe.into_inner(),
148 })
149 }
150
151 pub fn identity() -> Self {
156 Point {
157 is_identity: Choice::from(1),
158 x: FieldElement::zero(),
159 y: FieldElement::zero(),
160 }
161 }
162
163 pub fn is_identity(&self) -> bool {
165 self.is_identity.into()
166 }
167
168 pub fn x_coordinate_bytes(&self) -> [u8; P256_FIELD_ELEMENT_SIZE] {
170 self.x.to_bytes()
171 }
172
173 pub fn y_coordinate_bytes(&self) -> [u8; P256_FIELD_ELEMENT_SIZE] {
175 self.y.to_bytes()
176 }
177
178 pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
187 if bytes.is_empty() {
188 return Err(Error::param("P-256 Point", "Empty point data"));
189 }
190
191 match (bytes[0], bytes.len()) {
192 (0x00, 1) => Ok(PointFormat::Identity),
193 (0x04, P256_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
194 (0x02 | 0x03, P256_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
195 _ => Err(Error::param(
196 "P-256 Point",
197 "Unknown or malformed point format",
198 )),
199 }
200 }
201
202 pub fn serialize_uncompressed(&self) -> [u8; P256_POINT_UNCOMPRESSED_SIZE] {
214 let mut result = Zeroizing::new([0u8; P256_POINT_UNCOMPRESSED_SIZE]);
215
216 if self.is_identity() {
218 return [0u8; P256_POINT_UNCOMPRESSED_SIZE]; }
220
221 result[0] = 0x04;
223 let x_bytes = Zeroizing::new(self.x.to_bytes());
224 let y_bytes = Zeroizing::new(self.y.to_bytes());
225 result[1..33].copy_from_slice(&x_bytes[..]);
226 result[33..65].copy_from_slice(&y_bytes[..]);
227
228 let mut public_output = [0u8; P256_POINT_UNCOMPRESSED_SIZE];
232 public_output.copy_from_slice(&result[..]);
233 public_output
234 }
235
236 pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
241 validate::length("P-256 Point", bytes.len(), P256_POINT_UNCOMPRESSED_SIZE)?;
242
243 if bytes[0] != 0x04 {
245 return Err(Error::param(
246 "P-256 Point",
247 "Invalid uncompressed point format (expected 0x04 prefix)",
248 ));
249 }
250
251 let mut x_bytes = Zeroizing::new([0u8; P256_FIELD_ELEMENT_SIZE]);
253 let mut y_bytes = Zeroizing::new([0u8; P256_FIELD_ELEMENT_SIZE]);
254
255 x_bytes.copy_from_slice(&bytes[1..33]);
256 y_bytes.copy_from_slice(&bytes[33..65]);
257
258 Self::new_uncompressed(&x_bytes, &y_bytes)
259 }
260
261 pub fn serialize_compressed(&self) -> [u8; P256_POINT_COMPRESSED_SIZE] {
275 let mut out = Zeroizing::new([0u8; P256_POINT_COMPRESSED_SIZE]);
276
277 if self.is_identity() {
279 return [0u8; P256_POINT_COMPRESSED_SIZE];
280 }
281
282 out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
284 let x_bytes = Zeroizing::new(self.x.to_bytes());
285 out[1..].copy_from_slice(&x_bytes[..]);
286 let mut public_output = [0u8; P256_POINT_COMPRESSED_SIZE];
289 public_output.copy_from_slice(&out[..]);
290 public_output
291 }
292
293 pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
307 validate::length(
308 "P-256 Compressed Point",
309 bytes.len(),
310 P256_POINT_COMPRESSED_SIZE,
311 )?;
312
313 let tag = bytes[0];
314 if tag != 0x02 && tag != 0x03 {
315 return Err(Error::param(
316 "P-256 Point",
317 "Invalid compressed point prefix (expected 0x02 or 0x03)",
318 ));
319 }
320
321 let mut x_bytes = Zeroizing::new([0u8; P256_FIELD_ELEMENT_SIZE]);
323 x_bytes.copy_from_slice(&bytes[1..]);
324
325 let x_fe = Zeroizing::new(FieldElement::from_bytes(&x_bytes).map_err(|_| {
326 Error::param(
327 "P-256 Point",
328 "Invalid compressed point: x-coordinate yields quadratic non-residue",
329 )
330 })?);
331
332 let x2 = Zeroizing::new(x_fe.square());
334 let x3 = Zeroizing::new(x2.mul(&x_fe));
335 let a = Zeroizing::new(FieldElement(FieldElement::A_M3)); let ax = Zeroizing::new(a.mul(&x_fe));
337 let b = Zeroizing::new(FieldElement::from_bytes(&NIST_P256.b).unwrap());
338 let x3_plus_ax = Zeroizing::new(x3.add(&ax));
339 let rhs = Zeroizing::new(x3_plus_ax.add(&b));
340
341 let y_fe = Zeroizing::new(rhs.sqrt().ok_or_else(|| {
343 Error::param(
344 "P-256 Point",
345 "Invalid compressed point: x-coordinate yields quadratic non-residue",
346 )
347 })?);
348
349 let y_final = Zeroizing::new(
351 if (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02) {
352 *y_fe
353 } else {
354 FieldElement::get_modulus().sub(&y_fe)
356 },
357 );
358
359 Ok(Point {
360 is_identity: Choice::from(0),
361 x: x_fe.into_inner(),
362 y: y_final.into_inner(),
363 })
364 }
365
366 pub fn add(&self, other: &Self) -> Self {
372 let p1 = self.to_projective();
373 let p2 = other.to_projective();
374 let result = Zeroizing::new(p1.add(&p2));
375 let affine = Zeroizing::new(result.to_affine());
376 affine.into_inner()
377 }
378
379 pub fn double(&self) -> Self {
384 let p = self.to_projective();
385 let result = Zeroizing::new(p.double());
386 let affine = Zeroizing::new(result.to_affine());
387 affine.into_inner()
388 }
389
390 pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
394 let scalar_bytes = scalar.as_secret_buffer().as_ref();
395
396 let base = self.to_projective();
398 let mut result = Zeroizing::new(ProjectivePoint::identity());
399
400 for byte in scalar_bytes.iter() {
401 for bit_pos in (0..8).rev() {
402 let doubled = Zeroizing::new(result.double());
403
404 let bit = (byte >> bit_pos) & 1;
405 let choice = Choice::from(bit);
406
407 let result_added = Zeroizing::new(doubled.add(&base));
409
410 let selected = Zeroizing::new(ProjectivePoint::conditional_select(
412 &doubled,
413 &result_added,
414 choice,
415 ));
416 result.zeroize();
417 *result = selected.into_inner();
418 }
419 }
420
421 let affine = Zeroizing::new(result.to_affine());
422 Ok(affine.into_inner())
423 }
424
425 fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
434 let y_squared = Zeroizing::new(y.square());
436
437 let x_squared = Zeroizing::new(x.square());
439 let x_cubed = Zeroizing::new(x_squared.mul(x));
440 let a_coeff = Zeroizing::new(FieldElement(FieldElement::A_M3)); let ax = Zeroizing::new(a_coeff.mul(x));
442 let b_coeff = Zeroizing::new(FieldElement::from_bytes(&NIST_P256.b).unwrap());
443
444 let x_cubed_plus_ax = Zeroizing::new(x_cubed.add(&ax));
446 let rhs = Zeroizing::new(x_cubed_plus_ax.add(&b_coeff));
447
448 y_squared == rhs
449 }
450
451 fn to_projective(&self) -> Zeroizing<ProjectivePoint> {
456 if self.is_identity() {
457 return Zeroizing::new(ProjectivePoint {
458 is_identity: Choice::from(1),
459 x: FieldElement::zero(),
460 y: FieldElement::one(),
461 z: FieldElement::zero(),
462 });
463 }
464
465 Zeroizing::new(ProjectivePoint {
466 is_identity: Choice::from(0),
467 x: self.x,
468 y: self.y,
469 z: FieldElement::one(),
470 })
471 }
472}
473
474impl ConditionallySelectable for ProjectivePoint {
475 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
476 let selected = Zeroizing::new(Self {
477 is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
478 x: FieldElement::conditional_select(&a.x, &b.x, choice),
479 y: FieldElement::conditional_select(&a.y, &b.y, choice),
480 z: FieldElement::conditional_select(&a.z, &b.z, choice),
481 });
482 selected.into_inner()
483 }
484}
485
486impl ProjectivePoint {
487 pub fn identity() -> Self {
489 ProjectivePoint {
490 is_identity: Choice::from(1),
491 x: FieldElement::zero(),
492 y: FieldElement::one(),
493 z: FieldElement::zero(),
494 }
495 }
496
497 pub fn add(&self, other: &Self) -> Self {
502 let z1_squared = Zeroizing::new(self.z.square());
505 let z2_squared = Zeroizing::new(other.z.square());
506 let z1_cubed = Zeroizing::new(z1_squared.mul(&self.z));
507 let z2_cubed = Zeroizing::new(z2_squared.mul(&other.z));
508
509 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());
521 let h_cubed = Zeroizing::new(h_squared.mul(&h));
522 let v = Zeroizing::new(u1.mul(&h_squared));
523
524 let r_squared = Zeroizing::new(r.square());
526 let x3_minus_h_cubed = Zeroizing::new(r_squared.sub(&h_cubed));
527 let two_v = Zeroizing::new(v.add(&v));
528 let x3 = Zeroizing::new(x3_minus_h_cubed.sub(&two_v));
529
530 let v_minus_x3 = Zeroizing::new(v.sub(&x3));
532 let r_times_diff = Zeroizing::new(r.mul(&v_minus_x3));
533 let s1_times_h_cubed = Zeroizing::new(s1.mul(&h_cubed));
534 let y3 = Zeroizing::new(r_times_diff.sub(&s1_times_h_cubed));
535
536 let z1_times_z2 = Zeroizing::new(self.z.mul(&other.z));
538 let z3 = Zeroizing::new(z1_times_z2.mul(&h));
539
540 let generic_point = Zeroizing::new(Self {
541 is_identity: Choice::from(0),
542 x: x3.into_inner(),
543 y: y3.into_inner(),
544 z: z3.into_inner(),
545 });
546
547 let double_point = Zeroizing::new(self.double());
549
550 let h_is_zero = Choice::from((h.is_zero() as u8) & 1);
552 let r_is_zero = Choice::from((r.is_zero() as u8) & 1);
553
554 let p_eq_q = h_is_zero & r_is_zero;
556 let p_eq_neg_q = h_is_zero & !r_is_zero;
558
559 let mut result = Zeroizing::new(Self::conditional_select(
561 &generic_point,
562 &double_point,
563 p_eq_q,
564 ));
565
566 let identity = Zeroizing::new(Self::identity());
568 let selected = Zeroizing::new(Self::conditional_select(&result, &identity, p_eq_neg_q));
569 result.zeroize();
570 *result = selected.into_inner();
571
572 let selected = Zeroizing::new(Self::conditional_select(&result, other, self.is_identity));
575 result.zeroize();
576 *result = selected.into_inner();
577 let selected = Zeroizing::new(Self::conditional_select(&result, self, other.is_identity));
578 result.zeroize();
579 *result = selected.into_inner();
580
581 result.into_inner()
582 }
583
584 #[inline]
590 pub fn double(&self) -> Self {
591 let delta = Zeroizing::new(self.z.square());
594
595 let gamma = Zeroizing::new(self.y.square());
597
598 let beta = Zeroizing::new(self.x.mul(&gamma));
600
601 let x_plus_delta = Zeroizing::new(self.x.add(&delta));
603 let x_minus_delta = Zeroizing::new(self.x.sub(&delta));
604 let alpha_once = Zeroizing::new(x_plus_delta.mul(&x_minus_delta));
605 let alpha_twice = Zeroizing::new(alpha_once.add(&alpha_once));
606 let alpha = Zeroizing::new(alpha_twice.add(&alpha_once)); let two_beta = Zeroizing::new(beta.add(&beta)); let four_beta = Zeroizing::new(two_beta.add(&two_beta)); let eight_beta = Zeroizing::new(four_beta.add(&four_beta)); let alpha_squared = Zeroizing::new(alpha.square());
614 let x3 = Zeroizing::new(alpha_squared.sub(&eight_beta));
615
616 let y_plus_z = Zeroizing::new(self.y.add(&self.z));
618 let y_plus_z_squared = Zeroizing::new(y_plus_z.square());
619 let z3_minus_delta = Zeroizing::new(y_plus_z_squared.sub(&gamma));
620 let z3 = Zeroizing::new(z3_minus_delta.sub(&delta));
621
622 let four_beta_minus_x3 = Zeroizing::new(four_beta.sub(&x3));
624 let alpha_term = Zeroizing::new(alpha.mul(&four_beta_minus_x3));
625
626 let gamma_sq = Zeroizing::new(gamma.square()); let two_gamma_sq = Zeroizing::new(gamma_sq.add(&gamma_sq)); let four_gamma_sq = Zeroizing::new(two_gamma_sq.add(&two_gamma_sq)); let eight_gamma_sq = Zeroizing::new(four_gamma_sq.add(&four_gamma_sq)); let y3 = Zeroizing::new(alpha_term.sub(&eight_gamma_sq));
631
632 let result = Zeroizing::new(Self {
633 is_identity: Choice::from(0),
634 x: x3.into_inner(),
635 y: y3.into_inner(),
636 z: z3.into_inner(),
637 });
638
639 let is_y_zero = self.y.is_zero();
646 let return_identity = self.is_identity | Choice::from(is_y_zero as u8);
647
648 let identity = Zeroizing::new(Self::identity());
649 let selected = Zeroizing::new(Self::conditional_select(
650 &result,
651 &identity,
652 return_identity,
653 ));
654 selected.into_inner()
655 }
656
657 pub fn to_affine(&self) -> Point {
662 let safe_z = Zeroizing::new(FieldElement::conditional_select(
665 &self.z,
666 &FieldElement::one(),
667 self.is_identity,
668 ));
669 let z_inv = Zeroizing::new(
670 safe_z
671 .invert()
672 .expect("Non-zero Z coordinate should be invertible"),
673 );
674 let z_inv_squared = Zeroizing::new(z_inv.square());
675 let z_inv_cubed = Zeroizing::new(z_inv_squared.mul(&z_inv));
676
677 let x_affine = Zeroizing::new(self.x.mul(&z_inv_squared));
679 let y_affine = Zeroizing::new(self.y.mul(&z_inv_cubed));
680
681 let x = Zeroizing::new(FieldElement::conditional_select(
682 &x_affine,
683 &FieldElement::zero(),
684 self.is_identity,
685 ));
686 let y = Zeroizing::new(FieldElement::conditional_select(
687 &y_affine,
688 &FieldElement::zero(),
689 self.is_identity,
690 ));
691 let point = Zeroizing::new(Point {
692 is_identity: self.is_identity,
693 x: x.into_inner(),
694 y: y.into_inner(),
695 });
696 point.into_inner()
697 }
698}
699
700#[cfg(test)]
701mod lifecycle_tests {
702 use super::*;
703
704 fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
705
706 #[test]
707 fn affine_drop_and_projective_copy_limit_are_explicit() {
708 assert_zeroize_on_drop::<Point>();
709
710 let mut affine = Point {
711 is_identity: Choice::from(0),
712 x: FieldElement::one(),
713 y: FieldElement::one(),
714 };
715 affine.zeroize();
716 assert!(affine.x.is_zero());
717 assert!(affine.y.is_zero());
718
719 let original = ProjectivePoint {
720 is_identity: Choice::from(0),
721 x: FieldElement::one(),
722 y: FieldElement::one(),
723 z: FieldElement::one(),
724 };
725 let mut owned_copy = original;
726 owned_copy.zeroize();
727 assert!(owned_copy.x.is_zero());
728 assert!(!original.x.is_zero());
729 }
730}