1#![allow(clippy::op_ref)]
4
5use super::{FieldElement, ProjectivePoint, CURVE_EQUATION_A, CURVE_EQUATION_B, MODULUS};
6use crate::{CompressedPoint, EncodedPoint, FieldBytes, NistP256, PublicKey, Scalar};
7use core::ops::{Mul, Neg};
8use elliptic_curve::{
9 bigint::Encoding,
10 group::{prime::PrimeCurveAffine, GroupEncoding},
11 sec1::{self, FromEncodedPoint, ToCompactEncodedPoint, ToEncodedPoint},
12 subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
13 zeroize::DefaultIsZeroes,
14 AffineArithmetic, AffineXCoordinate, Curve, DecompactPoint, DecompressPoint, Error, Result,
15};
16
17#[cfg(feature = "serde")]
18use serdect::serde::{de, ser, Deserialize, Serialize};
19
20impl AffineArithmetic for NistP256 {
21 type AffinePoint = AffinePoint;
22}
23
24#[derive(Clone, Copy, Debug)]
39#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
40pub struct AffinePoint {
41 pub(crate) x: FieldElement,
43
44 pub(crate) y: FieldElement,
46
47 pub(super) infinity: u8,
52}
53
54impl AffinePoint {
55 pub const IDENTITY: Self = Self {
57 x: FieldElement::ZERO,
58 y: FieldElement::ZERO,
59 infinity: 1,
60 };
61
62 pub const GENERATOR: Self = Self {
71 x: FieldElement([
72 0xf4a1_3945_d898_c296,
73 0x7703_7d81_2deb_33a0,
74 0xf8bc_e6e5_63a4_40f2,
75 0x6b17_d1f2_e12c_4247,
76 ])
77 .to_montgomery(),
78 y: FieldElement([
79 0xcbb6_4068_37bf_51f5,
80 0x2bce_3357_6b31_5ece,
81 0x8ee7_eb4a_7c0f_9e16,
82 0x4fe3_42e2_fe1a_7f9b,
83 ])
84 .to_montgomery(),
85 infinity: 0,
86 };
87}
88
89impl PrimeCurveAffine for AffinePoint {
90 type Scalar = Scalar;
91 type Curve = ProjectivePoint;
92
93 fn identity() -> AffinePoint {
94 Self::IDENTITY
95 }
96
97 fn generator() -> AffinePoint {
98 Self::GENERATOR
99 }
100
101 fn is_identity(&self) -> Choice {
102 Choice::from(self.infinity)
103 }
104
105 fn to_curve(&self) -> ProjectivePoint {
106 ProjectivePoint::from(*self)
107 }
108}
109
110impl AffineXCoordinate<NistP256> for AffinePoint {
111 fn x(&self) -> FieldBytes {
112 self.x.to_bytes()
113 }
114}
115
116impl ConditionallySelectable for AffinePoint {
117 fn conditional_select(a: &AffinePoint, b: &AffinePoint, choice: Choice) -> AffinePoint {
118 AffinePoint {
119 x: FieldElement::conditional_select(&a.x, &b.x, choice),
120 y: FieldElement::conditional_select(&a.y, &b.y, choice),
121 infinity: u8::conditional_select(&a.infinity, &b.infinity, choice),
122 }
123 }
124}
125
126impl ConstantTimeEq for AffinePoint {
127 fn ct_eq(&self, other: &AffinePoint) -> Choice {
128 self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) & self.infinity.ct_eq(&other.infinity)
129 }
130}
131
132impl Default for AffinePoint {
133 fn default() -> Self {
134 Self::IDENTITY
135 }
136}
137
138impl DefaultIsZeroes for AffinePoint {}
139
140impl Eq for AffinePoint {}
141
142impl PartialEq for AffinePoint {
143 fn eq(&self, other: &AffinePoint) -> bool {
144 self.ct_eq(other).into()
145 }
146}
147
148impl Mul<Scalar> for AffinePoint {
149 type Output = ProjectivePoint;
150
151 fn mul(self, scalar: Scalar) -> ProjectivePoint {
152 ProjectivePoint::from(self) * scalar
153 }
154}
155
156impl Mul<&Scalar> for AffinePoint {
157 type Output = ProjectivePoint;
158
159 fn mul(self, scalar: &Scalar) -> ProjectivePoint {
160 ProjectivePoint::from(self) * scalar
161 }
162}
163
164impl Neg for AffinePoint {
165 type Output = AffinePoint;
166
167 fn neg(self) -> Self::Output {
168 AffinePoint {
169 x: self.x,
170 y: -self.y,
171 infinity: self.infinity,
172 }
173 }
174}
175
176impl DecompressPoint<NistP256> for AffinePoint {
177 fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption<Self> {
178 FieldElement::from_bytes(x_bytes).and_then(|x| {
179 let alpha = x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B;
180 let beta = alpha.sqrt();
181
182 beta.map(|beta| {
183 let y = FieldElement::conditional_select(
184 &(MODULUS - &beta),
185 &beta,
186 beta.is_odd().ct_eq(&y_is_odd),
187 );
188
189 Self { x, y, infinity: 0 }
190 })
191 })
192 }
193}
194
195impl GroupEncoding for AffinePoint {
196 type Repr = CompressedPoint;
197
198 fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
200 EncodedPoint::from_bytes(bytes)
201 .map(|point| CtOption::new(point, Choice::from(1)))
202 .unwrap_or_else(|_| {
203 let is_identity = bytes.ct_eq(&Self::Repr::default());
206 CtOption::new(EncodedPoint::identity(), is_identity)
207 })
208 .and_then(|point| Self::from_encoded_point(&point))
209 }
210
211 fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
212 Self::from_bytes(bytes)
214 }
215
216 fn to_bytes(&self) -> Self::Repr {
217 let encoded = self.to_encoded_point(true);
218 let mut result = CompressedPoint::default();
219 result[..encoded.len()].copy_from_slice(encoded.as_bytes());
220 result
221 }
222}
223
224impl DecompactPoint<NistP256> for AffinePoint {
225 fn decompact(x_bytes: &FieldBytes) -> CtOption<Self> {
226 FieldElement::from_bytes(x_bytes).and_then(|x| {
227 let montgomery_y = (x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B).sqrt();
228 montgomery_y.map(|montgomery_y| {
229 let y = montgomery_y.to_canonical();
231 let p_y = MODULUS.subtract(&y);
232 let (_, borrow) = p_y.informed_subtract(&y);
233 let recovered_y = if borrow == 0 { y } else { p_y };
234 AffinePoint {
235 x,
236 y: recovered_y.to_montgomery(),
237 infinity: 0,
238 }
239 })
240 })
241 }
242}
243
244impl FromEncodedPoint<NistP256> for AffinePoint {
245 fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption<Self> {
251 match encoded_point.coordinates() {
252 sec1::Coordinates::Identity => CtOption::new(Self::identity(), 1.into()),
253 sec1::Coordinates::Compact { x } => AffinePoint::decompact(x),
254 sec1::Coordinates::Compressed { x, y_is_odd } => {
255 AffinePoint::decompress(x, Choice::from(y_is_odd as u8))
256 }
257 sec1::Coordinates::Uncompressed { x, y } => {
258 let x = FieldElement::from_bytes(x);
259 let y = FieldElement::from_bytes(y);
260
261 x.and_then(|x| {
262 y.and_then(|y| {
263 let lhs = y * &y;
265 let rhs = x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B;
266 let point = AffinePoint { x, y, infinity: 0 };
267 CtOption::new(point, lhs.ct_eq(&rhs))
268 })
269 })
270 }
271 }
272 }
273}
274
275impl ToEncodedPoint<NistP256> for AffinePoint {
276 fn to_encoded_point(&self, compress: bool) -> EncodedPoint {
277 EncodedPoint::conditional_select(
278 &EncodedPoint::from_affine_coordinates(
279 &self.x.to_bytes(),
280 &self.y.to_bytes(),
281 compress,
282 ),
283 &EncodedPoint::identity(),
284 self.is_identity(),
285 )
286 }
287}
288
289impl ToCompactEncodedPoint<NistP256> for AffinePoint {
290 fn to_compact_encoded_point(&self) -> CtOption<EncodedPoint> {
292 let y = self.y.to_canonical();
294 let (p_y, borrow) = MODULUS.informed_subtract(&y);
295 assert_eq!(borrow, 0);
296 let (_, borrow) = p_y.informed_subtract(&y);
297
298 let mut bytes = CompressedPoint::default();
300 bytes[0] = sec1::Tag::Compact.into();
301 bytes[1..(<NistP256 as Curve>::UInt::BYTE_SIZE + 1)].copy_from_slice(&self.x.to_bytes());
302 CtOption::new(
303 EncodedPoint::from_bytes(bytes).expect("compact key"),
304 borrow.ct_eq(&0),
305 )
306 }
307}
308
309impl TryFrom<EncodedPoint> for AffinePoint {
310 type Error = Error;
311
312 fn try_from(point: EncodedPoint) -> Result<AffinePoint> {
313 AffinePoint::try_from(&point)
314 }
315}
316
317impl TryFrom<&EncodedPoint> for AffinePoint {
318 type Error = Error;
319
320 fn try_from(point: &EncodedPoint) -> Result<AffinePoint> {
321 Option::from(AffinePoint::from_encoded_point(point)).ok_or(Error)
322 }
323}
324
325impl From<AffinePoint> for EncodedPoint {
326 fn from(affine_point: AffinePoint) -> EncodedPoint {
327 affine_point.to_encoded_point(false)
328 }
329}
330
331impl From<PublicKey> for AffinePoint {
332 fn from(public_key: PublicKey) -> AffinePoint {
333 *public_key.as_affine()
334 }
335}
336
337impl From<&PublicKey> for AffinePoint {
338 fn from(public_key: &PublicKey) -> AffinePoint {
339 AffinePoint::from(*public_key)
340 }
341}
342
343impl TryFrom<AffinePoint> for PublicKey {
344 type Error = Error;
345
346 fn try_from(affine_point: AffinePoint) -> Result<PublicKey> {
347 PublicKey::from_affine(affine_point)
348 }
349}
350
351impl TryFrom<&AffinePoint> for PublicKey {
352 type Error = Error;
353
354 fn try_from(affine_point: &AffinePoint) -> Result<PublicKey> {
355 PublicKey::try_from(*affine_point)
356 }
357}
358
359#[cfg(feature = "serde")]
360#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
361impl Serialize for AffinePoint {
362 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
363 where
364 S: ser::Serializer,
365 {
366 self.to_encoded_point(true).serialize(serializer)
367 }
368}
369
370#[cfg(feature = "serde")]
371#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
372impl<'de> Deserialize<'de> for AffinePoint {
373 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
374 where
375 D: de::Deserializer<'de>,
376 {
377 EncodedPoint::deserialize(deserializer)?
378 .try_into()
379 .map_err(de::Error::custom)
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::AffinePoint;
386 use crate::EncodedPoint;
387 use elliptic_curve::{
388 group::{prime::PrimeCurveAffine, GroupEncoding},
389 sec1::{FromEncodedPoint, ToCompactEncodedPoint, ToEncodedPoint},
390 };
391 use hex_literal::hex;
392
393 const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!(
394 "046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296
395 4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5"
396 );
397
398 const COMPRESSED_BASEPOINT: &[u8] =
399 &hex!("036B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
400
401 const COMPACT_BASEPOINT: &[u8] =
403 &hex!("058e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c01");
404
405 const UNCOMPACT_BASEPOINT: &[u8] = &hex!(
407 "048e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c0
408 13ca9d8667de0c07aa71d98b3c8065d2e97ab7bb9cb8776bcc0577a7ac58acd4e"
409 );
410
411 #[test]
412 fn uncompressed_round_trip() {
413 let pubkey = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap();
414 let point = AffinePoint::from_encoded_point(&pubkey).unwrap();
415 assert_eq!(point, AffinePoint::generator());
416
417 let res: EncodedPoint = point.into();
418 assert_eq!(res, pubkey);
419 }
420
421 #[test]
422 fn compressed_round_trip() {
423 let pubkey = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap();
424 let point = AffinePoint::from_encoded_point(&pubkey).unwrap();
425 assert_eq!(point, AffinePoint::generator());
426
427 let res: EncodedPoint = point.to_encoded_point(true);
428 assert_eq!(res, pubkey);
429 }
430
431 #[test]
432 fn uncompressed_to_compressed() {
433 let encoded = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap();
434
435 let res = AffinePoint::from_encoded_point(&encoded)
436 .unwrap()
437 .to_encoded_point(true);
438
439 assert_eq!(res.as_bytes(), COMPRESSED_BASEPOINT);
440 }
441
442 #[test]
443 fn compressed_to_uncompressed() {
444 let encoded = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap();
445
446 let res = AffinePoint::from_encoded_point(&encoded)
447 .unwrap()
448 .to_encoded_point(false);
449
450 assert_eq!(res.as_bytes(), UNCOMPRESSED_BASEPOINT);
451 }
452
453 #[test]
454 fn affine_negation() {
455 let basepoint = AffinePoint::generator();
456 assert_eq!(-(-basepoint), basepoint);
457 }
458
459 #[test]
460 fn compact_round_trip() {
461 let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap();
462 assert!(pubkey.is_compact());
463
464 let point = AffinePoint::from_encoded_point(&pubkey).unwrap();
465 let res = point.to_compact_encoded_point().unwrap();
466 assert_eq!(res, pubkey)
467 }
468
469 #[test]
470 fn uncompact_to_compact() {
471 let pubkey = EncodedPoint::from_bytes(UNCOMPACT_BASEPOINT).unwrap();
472 assert_eq!(false, pubkey.is_compact());
473
474 let point = AffinePoint::from_encoded_point(&pubkey).unwrap();
475 let res = point.to_compact_encoded_point().unwrap();
476 assert_eq!(res.as_bytes(), COMPACT_BASEPOINT)
477 }
478
479 #[test]
480 fn compact_to_uncompact() {
481 let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap();
482 assert!(pubkey.is_compact());
483
484 let point = AffinePoint::from_encoded_point(&pubkey).unwrap();
485 let res = point.to_encoded_point(false);
487 assert_eq!(res.as_bytes(), UNCOMPACT_BASEPOINT);
488 }
489
490 #[test]
491 fn identity_encoding() {
492 assert_eq!([0; 33], AffinePoint::IDENTITY.to_bytes().as_slice());
494 assert!(bool::from(
495 AffinePoint::from_bytes(&AffinePoint::IDENTITY.to_bytes())
496 .unwrap()
497 .is_identity()
498 ))
499 }
500}