dcrypt_algorithms/ec/p224/
point.rs1use crate::ec::p224::{
4 constants::{
5 P224_FIELD_ELEMENT_SIZE, P224_POINT_COMPRESSED_SIZE, P224_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_P224;
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, Debug)]
74pub(crate) struct ProjectivePoint {
75 is_identity: Choice,
77 x: FieldElement,
79 y: FieldElement,
81 z: FieldElement,
83}
84
85impl Default for ProjectivePoint {
88 fn default() -> Self {
89 Self::identity()
90 }
91}
92
93impl Zeroize for ProjectivePoint {
94 fn zeroize(&mut self) {
95 self.is_identity.zeroize();
96 self.x.zeroize();
97 self.y.zeroize();
98 self.z.zeroize();
99 }
100}
101
102impl PartialEq for Point {
103 fn eq(&self, other: &Self) -> bool {
108 let self_is_identity: bool = self.is_identity.into();
110 let other_is_identity: bool = other.is_identity.into();
111
112 if self_is_identity || other_is_identity {
113 return self_is_identity == other_is_identity;
114 }
115
116 self.x == other.x && self.y == other.y
118 }
119}
120
121impl Point {
122 pub fn new_uncompressed(
129 x: &[u8; P224_FIELD_ELEMENT_SIZE],
130 y: &[u8; P224_FIELD_ELEMENT_SIZE],
131 ) -> Result<Self> {
132 let x_fe = Zeroizing::new(FieldElement::from_bytes(x)?);
133 let y_fe = Zeroizing::new(FieldElement::from_bytes(y)?);
134
135 if !Self::is_on_curve(&x_fe, &y_fe) {
137 return Err(Error::param(
138 "P-224 Point",
139 "Point coordinates do not satisfy curve equation",
140 ));
141 }
142
143 Ok(Point {
144 is_identity: Choice::from(0),
145 x: x_fe.into_inner(),
146 y: y_fe.into_inner(),
147 })
148 }
149
150 pub fn identity() -> Self {
155 Point {
156 is_identity: Choice::from(1),
157 x: FieldElement::zero(),
158 y: FieldElement::zero(),
159 }
160 }
161
162 pub fn is_identity(&self) -> bool {
164 self.is_identity.into()
165 }
166
167 pub fn x_coordinate_bytes(&self) -> [u8; P224_FIELD_ELEMENT_SIZE] {
169 self.x.to_bytes()
170 }
171
172 pub fn y_coordinate_bytes(&self) -> [u8; P224_FIELD_ELEMENT_SIZE] {
174 self.y.to_bytes()
175 }
176
177 pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
186 if bytes.is_empty() {
187 return Err(Error::param("P-224 Point", "Empty point data"));
188 }
189
190 match (bytes[0], bytes.len()) {
191 (0x00, 1) => Ok(PointFormat::Identity),
192 (0x04, P224_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
193 (0x02 | 0x03, P224_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
194 _ => Err(Error::param(
195 "P-224 Point",
196 "Unknown or malformed point format",
197 )),
198 }
199 }
200
201 pub fn serialize_uncompressed(&self) -> [u8; P224_POINT_UNCOMPRESSED_SIZE] {
213 let mut result = Zeroizing::new([0u8; P224_POINT_UNCOMPRESSED_SIZE]);
214
215 if self.is_identity() {
217 return [0u8; P224_POINT_UNCOMPRESSED_SIZE]; }
219
220 result[0] = 0x04;
222 let x_bytes = Zeroizing::new(self.x.to_bytes());
223 let y_bytes = Zeroizing::new(self.y.to_bytes());
224 result[1..29].copy_from_slice(&x_bytes[..]);
225 result[29..57].copy_from_slice(&y_bytes[..]);
226
227 let mut public_output = [0u8; P224_POINT_UNCOMPRESSED_SIZE];
230 public_output.copy_from_slice(&result[..]);
231 public_output
232 }
233
234 pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
239 validate::length("P-224 Point", bytes.len(), P224_POINT_UNCOMPRESSED_SIZE)?;
240
241 if bytes[0] != 0x04 {
243 return Err(Error::param(
244 "P-224 Point",
245 "Invalid uncompressed point format (expected 0x04 prefix)",
246 ));
247 }
248
249 let mut x_bytes = Zeroizing::new([0u8; P224_FIELD_ELEMENT_SIZE]);
251 let mut y_bytes = Zeroizing::new([0u8; P224_FIELD_ELEMENT_SIZE]);
252
253 x_bytes.copy_from_slice(&bytes[1..29]);
254 y_bytes.copy_from_slice(&bytes[29..57]);
255
256 Self::new_uncompressed(&x_bytes, &y_bytes)
257 }
258
259 pub fn serialize_compressed(&self) -> [u8; P224_POINT_COMPRESSED_SIZE] {
273 let mut out = Zeroizing::new([0u8; P224_POINT_COMPRESSED_SIZE]);
274
275 if self.is_identity() {
277 return out.into_inner();
278 }
279
280 out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
282 let x_bytes = Zeroizing::new(self.x.to_bytes());
283 out[1..].copy_from_slice(&x_bytes[..]);
284 out.into_inner()
285 }
286
287 pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
301 validate::length(
302 "P-224 Compressed Point",
303 bytes.len(),
304 P224_POINT_COMPRESSED_SIZE,
305 )?;
306
307 let tag = bytes[0];
308 if tag != 0x02 && tag != 0x03 {
309 return Err(Error::param(
310 "P-224 Point",
311 "Invalid compressed point prefix (expected 0x02 or 0x03)",
312 ));
313 }
314
315 let mut x_bytes = Zeroizing::new([0u8; P224_FIELD_ELEMENT_SIZE]);
317 x_bytes.copy_from_slice(&bytes[1..]);
318
319 let x_fe = Zeroizing::new(FieldElement::from_bytes(&x_bytes).map_err(|_| {
320 Error::param(
321 "P-224 Point",
322 "Invalid compressed point: x-coordinate yields quadratic non-residue",
323 )
324 })?);
325
326 let x2 = Zeroizing::new(x_fe.square());
328 let x3 = Zeroizing::new(x2.mul(&x_fe));
329 let a = Zeroizing::new(FieldElement(FieldElement::A_M3)); let ax = Zeroizing::new(a.mul(&x_fe));
331 let b = Zeroizing::new(FieldElement::from_bytes(&NIST_P224.b).unwrap());
332 let x3_plus_ax = Zeroizing::new(x3.add(&ax));
333 let rhs = Zeroizing::new(x3_plus_ax.add(&b));
334
335 let y_fe = Zeroizing::new(rhs.sqrt().ok_or_else(|| {
337 Error::param(
338 "P-224 Point",
339 "Invalid compressed point: x-coordinate yields quadratic non-residue",
340 )
341 })?);
342
343 let y_final = Zeroizing::new(
345 if (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02) {
346 (*y_fe).clone()
347 } else {
348 FieldElement::get_modulus().sub(&y_fe)
350 },
351 );
352
353 Ok(Point {
354 is_identity: Choice::from(0),
355 x: x_fe.into_inner(),
356 y: y_final.into_inner(),
357 })
358 }
359
360 pub fn add(&self, other: &Self) -> Self {
366 let p1 = self.to_projective();
367 let p2 = other.to_projective();
368 let result = Zeroizing::new(p1.add(&p2));
369 let affine = Zeroizing::new(result.to_affine());
370 affine.into_inner()
371 }
372
373 pub fn double(&self) -> Self {
378 let p = self.to_projective();
379 let result = Zeroizing::new(p.double());
380 let affine = Zeroizing::new(result.to_affine());
381 affine.into_inner()
382 }
383
384 pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
392 let scalar_bytes = scalar.as_secret_buffer().as_ref();
393
394 let base = self.to_projective();
396 let mut result = Zeroizing::new(ProjectivePoint {
397 is_identity: Choice::from(1), x: FieldElement::zero(),
399 y: FieldElement::one(),
400 z: FieldElement::zero(),
401 });
402
403 for byte in scalar_bytes.iter() {
404 for bit_pos in (0..8).rev() {
405 let doubled = Zeroizing::new(result.double());
406 let added = Zeroizing::new(doubled.add(&base));
407 let choice = Choice::from((byte >> bit_pos) & 1);
408 let selected = Zeroizing::new(ProjectivePoint::conditional_select(
409 &doubled, &added, choice,
410 ));
411 result.zeroize();
412 *result = selected.into_inner();
413 }
414 }
415
416 let affine_result = Zeroizing::new(result.to_affine());
417 Ok(affine_result.into_inner())
418 }
419
420 fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
429 let y_squared = Zeroizing::new(y.square());
431
432 let x_squared = Zeroizing::new(x.square());
434 let x_cubed = Zeroizing::new(x_squared.mul(x));
435 let a_coeff = Zeroizing::new(FieldElement(FieldElement::A_M3)); let ax = Zeroizing::new(a_coeff.mul(x));
437 let b_coeff = Zeroizing::new(FieldElement::from_bytes(&NIST_P224.b).unwrap());
438
439 let x_cubed_plus_ax = Zeroizing::new(x_cubed.add(&ax));
441 let rhs = Zeroizing::new(x_cubed_plus_ax.add(&b_coeff));
442
443 y_squared == rhs
444 }
445
446 fn to_projective(&self) -> Zeroizing<ProjectivePoint> {
451 if self.is_identity() {
452 return Zeroizing::new(ProjectivePoint {
453 is_identity: Choice::from(1),
454 x: FieldElement::zero(),
455 y: FieldElement::one(),
456 z: FieldElement::zero(),
457 });
458 }
459
460 Zeroizing::new(ProjectivePoint {
461 is_identity: Choice::from(0),
462 x: self.x.clone(),
463 y: self.y.clone(),
464 z: FieldElement::one(),
465 })
466 }
467}
468
469impl ProjectivePoint {
470 pub fn identity() -> Self {
471 Self {
472 is_identity: Choice::from(1),
473 x: FieldElement::zero(),
474 y: FieldElement::one(),
475 z: FieldElement::zero(),
476 }
477 }
478
479 pub fn add(&self, other: &Self) -> Self {
487 let z1_squared = Zeroizing::new(self.z.square());
492 let z2_squared = Zeroizing::new(other.z.square());
493 let z1_cubed = Zeroizing::new(z1_squared.mul(&self.z));
494 let z2_cubed = Zeroizing::new(z2_squared.mul(&other.z));
495
496 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());
508 let h_cubed = Zeroizing::new(h_squared.mul(&h));
509 let v = Zeroizing::new(u1.mul(&h_squared));
510
511 let r_squared = Zeroizing::new(r.square());
513 let r_squared_minus_h_cubed = Zeroizing::new(r_squared.sub(&h_cubed));
514 let two_v = Zeroizing::new(v.add(&v));
515 let x3 = Zeroizing::new(r_squared_minus_h_cubed.sub(&two_v));
516
517 let v_minus_x3 = Zeroizing::new(v.sub(&x3));
519 let r_times_diff = Zeroizing::new(r.mul(&v_minus_x3));
520 let s1_times_h_cubed = Zeroizing::new(s1.mul(&h_cubed));
521 let y3 = Zeroizing::new(r_times_diff.sub(&s1_times_h_cubed));
522
523 let z1_times_z2 = Zeroizing::new(self.z.mul(&other.z));
525 let z3 = Zeroizing::new(z1_times_z2.mul(&h));
526
527 let generic = Zeroizing::new(Self {
528 is_identity: Choice::from(0),
529 x: x3.into_inner(),
530 y: y3.into_inner(),
531 z: z3.into_inner(),
532 });
533
534 let double_point = Zeroizing::new(self.double());
535 let h_is_zero = Choice::from(h.is_zero() as u8);
536 let r_is_zero = Choice::from(r.is_zero() as u8);
537 let p_eq_q = h_is_zero & r_is_zero;
538 let p_eq_neg_q = h_is_zero & !r_is_zero;
539
540 let mut result = Zeroizing::new(Self::conditional_select(&generic, &double_point, p_eq_q));
541 let identity = Zeroizing::new(Self::identity());
542 let selected = Zeroizing::new(Self::conditional_select(&result, &identity, p_eq_neg_q));
543 result.zeroize();
544 *result = selected.into_inner();
545 let selected = Zeroizing::new(Self::conditional_select(&result, other, self.is_identity));
546 result.zeroize();
547 *result = selected.into_inner();
548 let selected = Zeroizing::new(Self::conditional_select(&result, self, other.is_identity));
549 result.zeroize();
550 *result = selected.into_inner();
551 result.into_inner()
552 }
553
554 #[inline]
561 pub fn double(&self) -> Self {
562 let delta = Zeroizing::new(self.z.square());
565
566 let gamma = Zeroizing::new(self.y.square());
568
569 let beta = Zeroizing::new(self.x.mul(&gamma));
571
572 let x_plus_delta = Zeroizing::new(self.x.add(&delta));
574 let x_minus_delta = Zeroizing::new(self.x.sub(&delta));
575 let alpha_once = Zeroizing::new(x_plus_delta.mul(&x_minus_delta));
576 let alpha_twice = Zeroizing::new(alpha_once.add(&alpha_once));
577 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());
585 let x3 = Zeroizing::new(alpha_squared.sub(&eight_beta));
586
587 let y_plus_z = Zeroizing::new(self.y.add(&self.z));
589 let y_plus_z_squared = Zeroizing::new(y_plus_z.square());
590 let z3_minus_delta = Zeroizing::new(y_plus_z_squared.sub(&gamma));
591 let z3 = Zeroizing::new(z3_minus_delta.sub(&delta));
592
593 let four_beta_minus_x3 = Zeroizing::new(four_beta.sub(&x3));
595 let alpha_term = Zeroizing::new(alpha.mul(&four_beta_minus_x3));
596
597 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));
602
603 let result = Zeroizing::new(Self {
604 is_identity: Choice::from(0),
605 x: x3.into_inner(),
606 y: y3.into_inner(),
607 z: z3.into_inner(),
608 });
609
610 let return_identity = self.is_identity | Choice::from(self.y.is_zero() as u8);
611 let identity = Zeroizing::new(Self::identity());
612 let selected = Zeroizing::new(Self::conditional_select(
613 &result,
614 &identity,
615 return_identity,
616 ));
617 selected.into_inner()
618 }
619
620 pub fn to_affine(&self) -> Point {
625 let safe_z = Zeroizing::new(Self::select_field(
628 &self.z,
629 &FieldElement::one(),
630 self.is_identity,
631 ));
632 let z_inv = Zeroizing::new(
633 safe_z
634 .invert()
635 .expect("Non-zero Z coordinate should be invertible"),
636 );
637 let z_inv_squared = Zeroizing::new(z_inv.square());
638 let z_inv_cubed = Zeroizing::new(z_inv_squared.mul(&z_inv));
639
640 let x_affine = Zeroizing::new(self.x.mul(&z_inv_squared));
642 let y_affine = Zeroizing::new(self.y.mul(&z_inv_cubed));
643
644 let x = Zeroizing::new(Self::select_field(
645 &x_affine,
646 &FieldElement::zero(),
647 self.is_identity,
648 ));
649 let y = Zeroizing::new(Self::select_field(
650 &y_affine,
651 &FieldElement::zero(),
652 self.is_identity,
653 ));
654 let point = Zeroizing::new(Point {
655 is_identity: self.is_identity,
656 x: x.into_inner(),
657 y: y.into_inner(),
658 });
659 point.into_inner()
660 }
661
662 fn select_field(a: &FieldElement, b: &FieldElement, choice: Choice) -> FieldElement {
663 let mut out = Zeroizing::new([0u32; 7]);
664 for (i, limb) in out.iter_mut().enumerate() {
665 *limb = u32::conditional_select(&a.0[i], &b.0[i], choice);
666 }
667 FieldElement(out.into_inner())
668 }
669
670 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
671 let selected = Zeroizing::new(Self {
672 is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
673 x: Self::select_field(&a.x, &b.x, choice),
674 y: Self::select_field(&a.y, &b.y, choice),
675 z: Self::select_field(&a.z, &b.z, choice),
676 });
677 selected.into_inner()
678 }
679}
680
681#[cfg(test)]
682mod lifecycle_tests {
683 use super::*;
684
685 fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
686
687 #[test]
688 fn affine_and_projective_owners_zeroize() {
689 assert_zeroize_on_drop::<Point>();
690
691 let mut affine = Point {
692 is_identity: Choice::from(0),
693 x: FieldElement::one(),
694 y: FieldElement::one(),
695 };
696 affine.zeroize();
697 assert!(affine.x.is_zero());
698 assert!(affine.y.is_zero());
699
700 let mut projective = ProjectivePoint {
701 is_identity: Choice::from(0),
702 x: FieldElement::one(),
703 y: FieldElement::one(),
704 z: FieldElement::one(),
705 };
706 projective.zeroize();
707 assert!(projective.x.is_zero());
708 assert!(projective.y.is_zero());
709 assert!(projective.z.is_zero());
710 }
711}