dcrypt_algorithms/ec/p521/
point.rs1use crate::ec::p521::{
4 constants::{
5 P521_FIELD_ELEMENT_SIZE, P521_POINT_COMPRESSED_SIZE, P521_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_P521;
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, 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 {
98 let self_is_identity: bool = self.is_identity.into();
100 let other_is_identity: bool = other.is_identity.into();
101
102 if self_is_identity || other_is_identity {
103 return self_is_identity == other_is_identity;
104 }
105
106 self.x == other.x && self.y == other.y
108 }
109}
110
111impl Point {
112 pub fn new_uncompressed(
119 x: &[u8; P521_FIELD_ELEMENT_SIZE],
120 y: &[u8; P521_FIELD_ELEMENT_SIZE],
121 ) -> Result<Self> {
122 let x_fe = Zeroizing::new(FieldElement::from_bytes(x)?);
123 let y_fe = Zeroizing::new(FieldElement::from_bytes(y)?);
124
125 if !Self::is_on_curve(&x_fe, &y_fe) {
127 return Err(Error::param(
128 "P-521 Point",
129 "Point coordinates do not satisfy curve equation",
130 ));
131 }
132
133 Ok(Point {
134 is_identity: Choice::from(0),
135 x: x_fe.into_inner(),
136 y: y_fe.into_inner(),
137 })
138 }
139
140 pub fn identity() -> Self {
145 Point {
146 is_identity: Choice::from(1),
147 x: FieldElement::zero(),
148 y: FieldElement::zero(),
149 }
150 }
151
152 pub fn is_identity(&self) -> bool {
154 self.is_identity.into()
155 }
156
157 pub fn x_coordinate_bytes(&self) -> [u8; P521_FIELD_ELEMENT_SIZE] {
159 self.x.to_bytes()
160 }
161
162 pub fn y_coordinate_bytes(&self) -> [u8; P521_FIELD_ELEMENT_SIZE] {
164 self.y.to_bytes()
165 }
166
167 pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
176 if bytes.is_empty() {
177 return Err(Error::param("P-521 Point", "Empty point data"));
178 }
179
180 match (bytes[0], bytes.len()) {
181 (0x00, 1) => Ok(PointFormat::Identity),
182 (0x04, P521_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
183 (0x02 | 0x03, P521_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
184 _ => Err(Error::param(
185 "P-521 Point",
186 "Unknown or malformed point format",
187 )),
188 }
189 }
190
191 pub fn serialize_uncompressed(&self) -> [u8; P521_POINT_UNCOMPRESSED_SIZE] {
203 let mut result = [0u8; P521_POINT_UNCOMPRESSED_SIZE];
204
205 if self.is_identity() {
207 return result; }
209
210 result[0] = 0x04;
212 self.x.write_bytes(&mut result[1..67]);
213 self.y.write_bytes(&mut result[67..133]);
214
215 result
216 }
217
218 pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
223 validate::length("P-521 Point", bytes.len(), P521_POINT_UNCOMPRESSED_SIZE)?;
224
225 if bytes[0] != 0x04 {
227 return Err(Error::param(
228 "P-521 Point",
229 "Invalid uncompressed point format (expected 0x04 prefix)",
230 ));
231 }
232
233 let mut x_bytes = Zeroizing::new([0u8; P521_FIELD_ELEMENT_SIZE]);
235 let mut y_bytes = Zeroizing::new([0u8; P521_FIELD_ELEMENT_SIZE]);
236
237 x_bytes.copy_from_slice(&bytes[1..67]);
238 y_bytes.copy_from_slice(&bytes[67..133]);
239
240 Self::new_uncompressed(&x_bytes, &y_bytes)
241 }
242
243 pub fn serialize_compressed(&self) -> [u8; P521_POINT_COMPRESSED_SIZE] {
257 let mut out = [0u8; P521_POINT_COMPRESSED_SIZE];
258
259 if self.is_identity() {
261 return out;
262 }
263
264 out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
266 self.x.write_bytes(&mut out[1..]);
267 out
268 }
269
270 pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
284 validate::length(
285 "P-521 Compressed Point",
286 bytes.len(),
287 P521_POINT_COMPRESSED_SIZE,
288 )?;
289
290 let tag = bytes[0];
291 if tag != 0x02 && tag != 0x03 {
292 return Err(Error::param(
293 "P-521 Point",
294 "Invalid compressed point prefix (expected 0x02 or 0x03)",
295 ));
296 }
297
298 let mut x_bytes = Zeroizing::new([0u8; P521_FIELD_ELEMENT_SIZE]);
300 x_bytes.copy_from_slice(&bytes[1..]);
301
302 let x_fe = Zeroizing::new(FieldElement::from_bytes(&x_bytes).map_err(|_| {
303 Error::param(
304 "P-521 Point",
305 "Invalid compressed point: x-coordinate yields quadratic non-residue",
306 )
307 })?);
308
309 let x2 = Zeroizing::new(x_fe.square());
311 let x3 = Zeroizing::new(x2.mul(&x_fe));
312 let a = Zeroizing::new(FieldElement(FieldElement::A_M3)); let b = Zeroizing::new(FieldElement::from_bytes(&NIST_P521.b).unwrap());
314 let ax = Zeroizing::new(a.mul(&x_fe));
315 let x3_plus_ax = Zeroizing::new(x3.add(&ax));
316 let rhs = Zeroizing::new(x3_plus_ax.add(&b));
317
318 let y_fe = Zeroizing::new(rhs.sqrt().ok_or_else(|| {
320 Error::param(
321 "P-521 Point",
322 "Invalid compressed point: x-coordinate yields quadratic non-residue",
323 )
324 })?);
325
326 let y_matches = (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02);
328 let y_final = if y_matches {
329 Zeroizing::new(y_fe.into_inner())
330 } else {
331 let modulus = Zeroizing::new(FieldElement::get_modulus());
333 Zeroizing::new(modulus.sub(&y_fe))
334 };
335
336 Ok(Point {
337 is_identity: Choice::from(0),
338 x: x_fe.into_inner(),
339 y: y_final.into_inner(),
340 })
341 }
342
343 pub fn add(&self, other: &Self) -> Self {
349 let p1 = Zeroizing::new(self.to_projective());
350 let p2 = Zeroizing::new(other.to_projective());
351 let result = Zeroizing::new(p1.add(&p2));
352 result.to_affine()
353 }
354
355 pub fn double(&self) -> Self {
360 let p = Zeroizing::new(self.to_projective());
361 let result = Zeroizing::new(p.double());
362 result.to_affine()
363 }
364
365 pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
372 let mut r0 = Zeroizing::new(ProjectivePoint::identity());
373 let mut r1 = Zeroizing::new(self.to_projective()); for byte in scalar.as_secret_buffer().as_ref() {
377 for bit in (0..8).rev() {
378 let mut choice = Choice::from((byte >> bit) & 1);
379
380 ProjectivePoint::conditional_swap(&mut r0, &mut r1, choice);
381 let t0 = Zeroizing::new(r0.add(&r1)); let t1 = Zeroizing::new(r0.double()); r1.zeroize();
385 *r1 = t0.into_inner();
386 r0.zeroize();
387 *r0 = t1.into_inner();
388 ProjectivePoint::conditional_swap(&mut r0, &mut r1, choice);
389 choice.zeroize();
390 }
391 }
392
393 Ok(r0.to_affine())
394 }
395
396 fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
405 let y_squared = Zeroizing::new(y.square());
407
408 let x_squared = Zeroizing::new(x.square());
410 let x_cubed = Zeroizing::new(x_squared.mul(x));
411 let a_coeff = Zeroizing::new(FieldElement(FieldElement::A_M3)); let ax = Zeroizing::new(a_coeff.mul(x));
413 let b_coeff = Zeroizing::new(FieldElement::from_bytes(&NIST_P521.b).unwrap());
414
415 let x_cubed_plus_ax = Zeroizing::new(x_cubed.add(&ax));
417 let rhs = Zeroizing::new(x_cubed_plus_ax.add(&b_coeff));
418
419 *y_squared == *rhs
420 }
421
422 fn to_projective(&self) -> ProjectivePoint {
427 let regular = Zeroizing::new(ProjectivePoint {
428 is_identity: Choice::from(0),
429 x: self.x.clone(),
430 y: self.y.clone(),
431 z: FieldElement::one(),
432 });
433 let identity = Zeroizing::new(ProjectivePoint::identity());
434 let selected = Zeroizing::new(ProjectivePoint::conditional_select(
435 ®ular,
436 &identity,
437 self.is_identity,
438 ));
439 selected.into_inner()
440 }
441}
442
443impl ProjectivePoint {
444 fn identity() -> Self {
445 Self {
446 is_identity: Choice::from(1),
447 x: FieldElement::zero(),
448 y: FieldElement::one(),
449 z: FieldElement::zero(),
450 }
451 }
452
453 #[inline(always)]
458 fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
459 FieldElement::conditional_swap(&mut a.x, &mut b.x, choice);
460 FieldElement::conditional_swap(&mut a.y, &mut b.y, choice);
461 FieldElement::conditional_swap(&mut a.z, &mut b.z, choice);
462
463 let mut ai = a.is_identity;
465 let mut bi = b.is_identity;
466 a.is_identity = Choice::conditional_select(&ai, &bi, choice);
467 b.is_identity = Choice::conditional_select(&bi, &ai, choice);
468 ai.zeroize();
469 bi.zeroize();
470 }
471
472 pub fn add(&self, other: &Self) -> Self {
480 let z1_squared = Zeroizing::new(self.z.square());
485 let z2_squared = Zeroizing::new(other.z.square());
486 let z1_cubed = Zeroizing::new(z1_squared.mul(&self.z));
487 let z2_cubed = Zeroizing::new(z2_squared.mul(&other.z));
488
489 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());
501 let h_cubed = Zeroizing::new(h_squared.mul(&h));
502 let v = Zeroizing::new(u1.mul(&h_squared));
503
504 let r_squared = Zeroizing::new(r.square());
506 let two_v = Zeroizing::new(v.add(&v));
507 let x3_minus_h_cubed = Zeroizing::new(r_squared.sub(&h_cubed));
508 let x3 = Zeroizing::new(x3_minus_h_cubed.sub(&two_v));
509
510 let v_minus_x3 = Zeroizing::new(v.sub(&x3));
512 let r_times_diff = Zeroizing::new(r.mul(&v_minus_x3));
513 let s1_times_h_cubed = Zeroizing::new(s1.mul(&h_cubed));
514 let y3 = Zeroizing::new(r_times_diff.sub(&s1_times_h_cubed));
515
516 let z1_times_z2 = Zeroizing::new(self.z.mul(&other.z));
518 let z3 = Zeroizing::new(z1_times_z2.mul(&h));
519
520 let generic = Zeroizing::new(Self {
521 is_identity: Choice::from(0),
522 x: x3.into_inner(),
523 y: y3.into_inner(),
524 z: z3.into_inner(),
525 });
526
527 let double_point = Zeroizing::new(self.double());
528 let mut h_is_zero = Choice::from(h.is_zero() as u8);
529 let mut r_is_zero = Choice::from(r.is_zero() as u8);
530 let mut p_eq_q = h_is_zero & r_is_zero;
531 let mut p_eq_neg_q = h_is_zero & !r_is_zero;
532
533 let mut result = Zeroizing::new(Self::conditional_select(&generic, &double_point, p_eq_q));
534 let identity = Zeroizing::new(Self::identity());
535 let selected = Zeroizing::new(Self::conditional_select(&result, &identity, p_eq_neg_q));
536 result.zeroize();
537 *result = selected.into_inner();
538
539 let selected = Zeroizing::new(Self::conditional_select(&result, other, self.is_identity));
540 result.zeroize();
541 *result = selected.into_inner();
542 let selected = Zeroizing::new(Self::conditional_select(&result, self, other.is_identity));
543 result.zeroize();
544 *result = selected.into_inner();
545
546 h_is_zero.zeroize();
547 r_is_zero.zeroize();
548 p_eq_q.zeroize();
549 p_eq_neg_q.zeroize();
550 result.into_inner()
551 }
552
553 #[inline]
560 pub fn double(&self) -> Self {
561 let delta = Zeroizing::new(self.z.square());
564
565 let gamma = Zeroizing::new(self.y.square());
567
568 let beta = Zeroizing::new(self.x.mul(&gamma));
570
571 let x_plus_delta = Zeroizing::new(self.x.add(&delta));
573 let x_minus_delta = Zeroizing::new(self.x.sub(&delta));
574 let alpha_base = Zeroizing::new(x_plus_delta.mul(&x_minus_delta));
575 let two_alpha = Zeroizing::new(alpha_base.add(&alpha_base));
576 let alpha = Zeroizing::new(two_alpha.add(&alpha_base)); let two_beta = Zeroizing::new(beta.add(&beta));
581 let four_beta = Zeroizing::new(two_beta.add(&two_beta));
582 let eight_beta = Zeroizing::new(four_beta.add(&four_beta));
583 let alpha_squared = Zeroizing::new(alpha.square());
584 let x3 = Zeroizing::new(alpha_squared.sub(&eight_beta));
585
586 let y_plus_z = Zeroizing::new(self.y.add(&self.z));
588 let y_plus_z_squared = Zeroizing::new(y_plus_z.square());
589 let z3_minus_gamma = Zeroizing::new(y_plus_z_squared.sub(&gamma));
590 let z3 = Zeroizing::new(z3_minus_gamma.sub(&delta));
591
592 let four_beta_minus_x3 = Zeroizing::new(four_beta.sub(&x3));
594 let alpha_times_difference = Zeroizing::new(alpha.mul(&four_beta_minus_x3));
595
596 let gamma_sq = Zeroizing::new(gamma.square());
597 let two_gamma_sq = Zeroizing::new(gamma_sq.add(&gamma_sq));
598 let four_gamma_sq = Zeroizing::new(two_gamma_sq.add(&two_gamma_sq));
599 let eight_gamma_sq = Zeroizing::new(four_gamma_sq.add(&four_gamma_sq));
600 let y3 = Zeroizing::new(alpha_times_difference.sub(&eight_gamma_sq));
601
602 let result = Zeroizing::new(Self {
603 is_identity: Choice::from(0),
604 x: x3.into_inner(),
605 y: y3.into_inner(),
606 z: z3.into_inner(),
607 });
608
609 let mut return_identity = self.is_identity | Choice::from(self.y.is_zero() as u8);
610 let identity = Zeroizing::new(Self::identity());
611 let selected = Zeroizing::new(Self::conditional_select(
612 &result,
613 &identity,
614 return_identity,
615 ));
616 return_identity.zeroize();
617 selected.into_inner()
618 }
619
620 pub fn to_affine(&self) -> Point {
625 let one = Zeroizing::new(FieldElement::one());
628 let safe_z = Zeroizing::new(FieldElement::conditional_select(
629 &self.z,
630 &one,
631 self.is_identity,
632 ));
633 let z_inv = Zeroizing::new(
634 safe_z
635 .invert()
636 .expect("Non-zero Z coordinate should be invertible"),
637 );
638 let z_inv_squared = Zeroizing::new(z_inv.square());
639 let z_inv_cubed = Zeroizing::new(z_inv_squared.mul(&z_inv));
640
641 let x_affine = Zeroizing::new(self.x.mul(&z_inv_squared));
643 let y_affine = Zeroizing::new(self.y.mul(&z_inv_cubed));
644 let zero = Zeroizing::new(FieldElement::zero());
645 let x = Zeroizing::new(FieldElement::conditional_select(
646 &x_affine,
647 &zero,
648 self.is_identity,
649 ));
650 let y = Zeroizing::new(FieldElement::conditional_select(
651 &y_affine,
652 &zero,
653 self.is_identity,
654 ));
655
656 Point {
657 is_identity: self.is_identity,
658 x: x.into_inner(),
659 y: y.into_inner(),
660 }
661 }
662
663 #[inline(never)]
664 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
665 Self {
666 is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
667 x: FieldElement::conditional_select(&a.x, &b.x, choice),
668 y: FieldElement::conditional_select(&a.y, &b.y, choice),
669 z: FieldElement::conditional_select(&a.z, &b.z, choice),
670 }
671 }
672}