dcrypt_algorithms/ec/k256/
point.rs1use crate::ec::k256::{
4 constants::{
5 K256_FIELD_ELEMENT_SIZE, K256_POINT_COMPRESSED_SIZE, K256_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};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum PointFormat {
19 Identity,
21 Uncompressed,
23 Compressed,
25}
26
27#[derive(Clone, Debug)]
29pub struct Point {
30 pub(crate) is_identity: Choice,
31 pub(crate) x: FieldElement,
32 pub(crate) y: FieldElement,
33}
34
35impl Default for Point {
36 fn default() -> Self {
37 Self::identity()
38 }
39}
40
41impl Zeroize for Point {
42 fn zeroize(&mut self) {
43 self.is_identity.zeroize();
44 self.x.zeroize();
45 self.y.zeroize();
46 }
47}
48
49impl Drop for Point {
50 fn drop(&mut self) {
51 self.zeroize();
52 }
53}
54
55impl ZeroizeOnDrop for Point {}
56
57#[derive(Clone, Copy, Debug)]
58pub(crate) struct ProjectivePoint {
59 is_identity: Choice,
60 x: FieldElement,
61 y: FieldElement,
62 z: FieldElement,
63}
64
65impl Default for ProjectivePoint {
69 fn default() -> Self {
70 Self::identity()
71 }
72}
73
74impl Zeroize for ProjectivePoint {
75 fn zeroize(&mut self) {
76 self.is_identity.zeroize();
77 self.x.zeroize();
78 self.y.zeroize();
79 self.z.zeroize();
80 }
81}
82
83impl PartialEq for Point {
84 fn eq(&self, other: &Self) -> bool {
85 let self_is_identity: bool = self.is_identity.into();
86 let other_is_identity: bool = other.is_identity.into();
87 if self_is_identity || other_is_identity {
88 return self_is_identity == other_is_identity;
89 }
90 self.x == other.x && self.y == other.y
91 }
92}
93
94impl Point {
95 pub fn new_uncompressed(
99 x: &[u8; K256_FIELD_ELEMENT_SIZE],
100 y: &[u8; K256_FIELD_ELEMENT_SIZE],
101 ) -> Result<Self> {
102 let x_fe = Zeroizing::new(FieldElement::from_bytes(x)?);
103 let y_fe = Zeroizing::new(FieldElement::from_bytes(y)?);
104 if !Self::is_on_curve(&x_fe, &y_fe) {
105 return Err(Error::param(
106 "K256 Point",
107 "Point coordinates do not satisfy curve equation",
108 ));
109 }
110 Ok(Point {
111 is_identity: Choice::from(0),
112 x: x_fe.into_inner(),
113 y: y_fe.into_inner(),
114 })
115 }
116
117 pub fn identity() -> Self {
119 Point {
120 is_identity: Choice::from(1),
121 x: FieldElement::zero(),
122 y: FieldElement::zero(),
123 }
124 }
125
126 pub fn is_identity(&self) -> bool {
128 self.is_identity.into()
129 }
130
131 pub fn is_valid(&self) -> bool {
133 if self.is_identity() {
134 return true;
135 }
136 Self::is_on_curve(&self.x, &self.y)
137 }
138
139 pub fn x_coordinate_bytes(&self) -> [u8; K256_FIELD_ELEMENT_SIZE] {
141 self.x.to_bytes()
142 }
143
144 pub fn y_coordinate_bytes(&self) -> [u8; K256_FIELD_ELEMENT_SIZE] {
146 self.y.to_bytes()
147 }
148
149 pub fn serialize_uncompressed(&self) -> [u8; K256_POINT_UNCOMPRESSED_SIZE] {
156 let mut out = Zeroizing::new([0u8; K256_POINT_UNCOMPRESSED_SIZE]);
157 if self.is_identity() {
158 return [0u8; K256_POINT_UNCOMPRESSED_SIZE];
159 }
160 out[0] = 0x04;
161 let x_bytes = Zeroizing::new(self.x.to_bytes());
162 let y_bytes = Zeroizing::new(self.y.to_bytes());
163 out[1..33].copy_from_slice(&x_bytes[..]);
164 out[33..].copy_from_slice(&y_bytes[..]);
165 let mut public_output = [0u8; K256_POINT_UNCOMPRESSED_SIZE];
168 public_output.copy_from_slice(&out[..]);
169 public_output
170 }
171
172 pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
176 validate::length(
177 "K256 Uncompressed Point",
178 bytes.len(),
179 K256_POINT_UNCOMPRESSED_SIZE,
180 )?;
181
182 if bytes[0] != 0x04 {
183 return Err(Error::param(
184 "K256 Point",
185 "Invalid uncompressed point prefix (expected 0x04)",
186 ));
187 }
188
189 let mut x_bytes = Zeroizing::new([0u8; K256_FIELD_ELEMENT_SIZE]);
190 let mut y_bytes = Zeroizing::new([0u8; K256_FIELD_ELEMENT_SIZE]);
191 x_bytes.copy_from_slice(&bytes[1..33]);
192 y_bytes.copy_from_slice(&bytes[33..65]);
193
194 Self::new_uncompressed(&x_bytes, &y_bytes)
195 }
196
197 pub fn serialize_compressed(&self) -> [u8; K256_POINT_COMPRESSED_SIZE] {
203 let mut out = Zeroizing::new([0u8; K256_POINT_COMPRESSED_SIZE]);
204 if self.is_identity() {
205 return [0u8; K256_POINT_COMPRESSED_SIZE];
206 }
207 out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
208 let x_bytes = Zeroizing::new(self.x.to_bytes());
209 out[1..].copy_from_slice(&x_bytes[..]);
210 let mut public_output = [0u8; K256_POINT_COMPRESSED_SIZE];
213 public_output.copy_from_slice(&out[..]);
214 public_output
215 }
216
217 pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
221 validate::length(
222 "K256 Compressed Point",
223 bytes.len(),
224 K256_POINT_COMPRESSED_SIZE,
225 )?;
226 let tag = bytes[0];
227 if tag != 0x02 && tag != 0x03 {
228 return Err(Error::param(
229 "K256 Point",
230 "Invalid compressed point prefix",
231 ));
232 }
233 let mut x_bytes = Zeroizing::new([0u8; K256_FIELD_ELEMENT_SIZE]);
234 x_bytes.copy_from_slice(&bytes[1..]);
235 let x_fe = Zeroizing::new(
236 FieldElement::from_bytes(&x_bytes)
237 .map_err(|_| Error::param("K256 Point", "Invalid x-coordinate"))?,
238 );
239
240 let x_squared = Zeroizing::new(x_fe.square());
241 let x3 = Zeroizing::new(x_squared.mul(&x_fe));
242 let mut seven = Zeroizing::new([0u32; 8]);
243 seven[0] = 7;
244 let b = Zeroizing::new(FieldElement(seven.into_inner()));
245 let rhs = Zeroizing::new(x3.add(&b));
246 let y_fe = Zeroizing::new(
247 rhs.sqrt()
248 .ok_or_else(|| Error::param("K256 Point", "Invalid compressed point: no sqrt"))?,
249 );
250 let y_final = Zeroizing::new(
251 if (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02) {
252 *y_fe
253 } else {
254 y_fe.negate()
255 },
256 );
257 Ok(Point {
258 is_identity: Choice::from(0),
259 x: x_fe.into_inner(),
260 y: y_final.into_inner(),
261 })
262 }
263
264 pub fn add(&self, other: &Self) -> Self {
266 let left = self.to_projective();
267 let right = other.to_projective();
268 let sum = Zeroizing::new(left.add(&right));
269 let affine = Zeroizing::new(sum.to_affine());
270 affine.into_inner()
271 }
272
273 pub fn double(&self) -> Self {
275 let point = self.to_projective();
276 let doubled = Zeroizing::new(point.double());
277 let affine = Zeroizing::new(doubled.to_affine());
278 affine.into_inner()
279 }
280
281 pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
285 let scalar_bytes = scalar.as_secret_buffer().as_ref();
286 let base = self.to_projective();
287 let mut result = Zeroizing::new(ProjectivePoint::identity());
288
289 for byte in scalar_bytes.iter() {
290 for bit_pos in (0..8).rev() {
291 let doubled = Zeroizing::new(result.double());
292 let bit = (byte >> bit_pos) & 1;
293 let choice = Choice::from(bit);
294 let result_added = Zeroizing::new(doubled.add(&base));
295 let selected = Zeroizing::new(ProjectivePoint::conditional_select(
296 &doubled,
297 &result_added,
298 choice,
299 ));
300 result.zeroize();
301 *result = selected.into_inner();
302 }
303 }
304 let affine = Zeroizing::new(result.to_affine());
305 Ok(affine.into_inner())
306 }
307
308 fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
309 let y_squared = Zeroizing::new(y.square());
310 let x_squared = Zeroizing::new(x.square());
311 let x_cubed = Zeroizing::new(x_squared.mul(x));
312 let mut seven_limbs = Zeroizing::new([0u32; 8]);
313 seven_limbs[0] = 7;
314 let seven = Zeroizing::new(FieldElement(seven_limbs.into_inner()));
315 let rhs = Zeroizing::new(x_cubed.add(&seven));
316 *y_squared == *rhs
317 }
318
319 fn to_projective(&self) -> Zeroizing<ProjectivePoint> {
320 if self.is_identity() {
321 return Zeroizing::new(ProjectivePoint::identity());
322 }
323 Zeroizing::new(ProjectivePoint {
324 is_identity: Choice::from(0),
325 x: self.x,
326 y: self.y,
327 z: FieldElement::one(),
328 })
329 }
330}
331
332impl ConditionallySelectable for ProjectivePoint {
333 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
334 let selected = Zeroizing::new(Self {
335 is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
336 x: FieldElement::conditional_select(&a.x, &b.x, choice),
337 y: FieldElement::conditional_select(&a.y, &b.y, choice),
338 z: FieldElement::conditional_select(&a.z, &b.z, choice),
339 });
340 selected.into_inner()
341 }
342}
343
344impl ProjectivePoint {
345 pub fn identity() -> Self {
346 ProjectivePoint {
347 is_identity: Choice::from(1),
348 x: FieldElement::zero(),
349 y: FieldElement::one(),
350 z: FieldElement::zero(),
351 }
352 }
353
354 pub fn add(&self, other: &Self) -> Self {
355 let z1_sq = Zeroizing::new(self.z.square());
357 let z2_sq = Zeroizing::new(other.z.square());
358 let u1 = Zeroizing::new(self.x.mul(&z2_sq));
359 let u2 = Zeroizing::new(other.x.mul(&z1_sq));
360 let self_y_z2 = Zeroizing::new(self.y.mul(&z2_sq));
361 let s1 = Zeroizing::new(self_y_z2.mul(&other.z));
362 let other_y_z1 = Zeroizing::new(other.y.mul(&z1_sq));
363 let s2 = Zeroizing::new(other_y_z1.mul(&self.z));
364
365 let h = Zeroizing::new(u2.sub(&u1));
366 let r = Zeroizing::new(s2.sub(&s1));
367
368 let h_sq = Zeroizing::new(h.square());
370 let h_cu = Zeroizing::new(h_sq.mul(&h));
371 let v = Zeroizing::new(u1.mul(&h_sq));
372
373 let r_sq = Zeroizing::new(r.square());
374 let r_sq_minus_h_cu = Zeroizing::new(r_sq.sub(&h_cu));
375 let two_v = Zeroizing::new(v.add(&v));
376 let x3 = Zeroizing::new(r_sq_minus_h_cu.sub(&two_v));
377
378 let v_minus_x3 = Zeroizing::new(v.sub(&x3));
379 let r_times_difference = Zeroizing::new(r.mul(&v_minus_x3));
380 let s1_h_cu = Zeroizing::new(s1.mul(&h_cu));
381 let y3 = Zeroizing::new(r_times_difference.sub(&s1_h_cu));
382
383 let z_product = Zeroizing::new(self.z.mul(&other.z));
384 let z3 = Zeroizing::new(z_product.mul(&h));
385
386 let generic = Zeroizing::new(ProjectivePoint {
387 is_identity: Choice::from(0),
388 x: x3.into_inner(),
389 y: y3.into_inner(),
390 z: z3.into_inner(),
391 });
392
393 let double = Zeroizing::new(self.double());
395
396 let h_is_zero = Choice::from((h.is_zero() as u8) & 1);
398 let r_is_zero = Choice::from((r.is_zero() as u8) & 1);
399 let p_eq_q = h_is_zero & r_is_zero;
400 let p_eq_neg_q = h_is_zero & !r_is_zero;
401
402 let mut result = Zeroizing::new(Self::conditional_select(&generic, &double, p_eq_q));
403 let identity = Zeroizing::new(Self::identity());
404 let selected = Zeroizing::new(Self::conditional_select(&result, &identity, p_eq_neg_q));
405 result.zeroize();
406 *result = selected.into_inner();
407 let selected = Zeroizing::new(Self::conditional_select(&result, other, self.is_identity));
408 result.zeroize();
409 *result = selected.into_inner();
410 let selected = Zeroizing::new(Self::conditional_select(&result, self, other.is_identity));
411 result.zeroize();
412 *result = selected.into_inner();
413
414 result.into_inner()
415 }
416
417 pub fn double(&self) -> Self {
418 let y_sq = Zeroizing::new(self.y.square());
421 let s_once = Zeroizing::new(self.x.mul(&y_sq));
422 let s_twice = Zeroizing::new(s_once.add(&s_once)); let s = Zeroizing::new(s_twice.add(&s_twice)); let x_sq = Zeroizing::new(self.x.square());
426 let m_twice = Zeroizing::new(x_sq.add(&x_sq));
427 let m = Zeroizing::new(m_twice.add(&x_sq)); let m_squared = Zeroizing::new(m.square());
430 let s_plus_s = Zeroizing::new(s.add(&s));
431 let x3 = Zeroizing::new(m_squared.sub(&s_plus_s)); let s_minus_x3 = Zeroizing::new(s.sub(&x3));
434 let m_term = Zeroizing::new(m.mul(&s_minus_x3));
435
436 let y_sq_sq = Zeroizing::new(y_sq.square());
437 let two_y4 = Zeroizing::new(y_sq_sq.add(&y_sq_sq));
438 let four_y4 = Zeroizing::new(two_y4.add(&two_y4));
439 let eight_y4 = Zeroizing::new(four_y4.add(&four_y4));
440 let y3 = Zeroizing::new(m_term.sub(&eight_y4)); let yz = Zeroizing::new(self.y.mul(&self.z));
443 let z3 = Zeroizing::new(yz.add(&yz)); let result = Zeroizing::new(ProjectivePoint {
446 is_identity: Choice::from(0),
447 x: x3.into_inner(),
448 y: y3.into_inner(),
449 z: z3.into_inner(),
450 });
451
452 let is_y_zero = self.y.is_zero();
454 let return_identity = self.is_identity | Choice::from(is_y_zero as u8);
455
456 let identity = Zeroizing::new(Self::identity());
457 let selected = Zeroizing::new(Self::conditional_select(
458 &result,
459 &identity,
460 return_identity,
461 ));
462 selected.into_inner()
463 }
464
465 pub fn to_affine(&self) -> Point {
466 let safe_z = Zeroizing::new(FieldElement::conditional_select(
467 &self.z,
468 &FieldElement::one(),
469 self.is_identity,
470 ));
471 let z_inv = Zeroizing::new(safe_z.invert().expect("Nonzero Z should be invertible"));
472 let z_inv_sq = Zeroizing::new(z_inv.square());
473 let z_inv_cu = Zeroizing::new(z_inv_sq.mul(&z_inv));
474 let x_aff = Zeroizing::new(self.x.mul(&z_inv_sq));
475 let y_aff = Zeroizing::new(self.y.mul(&z_inv_cu));
476 let x = Zeroizing::new(FieldElement::conditional_select(
477 &x_aff,
478 &FieldElement::zero(),
479 self.is_identity,
480 ));
481 let y = Zeroizing::new(FieldElement::conditional_select(
482 &y_aff,
483 &FieldElement::zero(),
484 self.is_identity,
485 ));
486 let point = Zeroizing::new(Point {
487 is_identity: self.is_identity,
488 x: x.into_inner(),
489 y: y.into_inner(),
490 });
491 point.into_inner()
492 }
493}
494
495#[cfg(test)]
496mod lifecycle_tests {
497 use super::*;
498
499 fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
500
501 #[test]
502 fn affine_drop_and_projective_copy_limit_are_explicit() {
503 assert_zeroize_on_drop::<Point>();
504
505 let mut affine = Point {
506 is_identity: Choice::from(0),
507 x: FieldElement::one(),
508 y: FieldElement::one(),
509 };
510 affine.zeroize();
511 assert!(affine.x.is_zero());
512 assert!(affine.y.is_zero());
513
514 let original = ProjectivePoint {
515 is_identity: Choice::from(0),
516 x: FieldElement::one(),
517 y: FieldElement::one(),
518 z: FieldElement::one(),
519 };
520 let mut owned_copy = original;
521 owned_copy.zeroize();
522 assert!(owned_copy.x.is_zero());
523 assert!(!original.x.is_zero());
524 }
525}