p256/arithmetic/
affine.rs

1//! Affine points
2
3#![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/// NIST P-256 (secp256r1) curve point expressed in affine coordinates.
25///
26/// # `serde` support
27///
28/// When the `serde` feature of this crate is enabled, the `Serialize` and
29/// `Deserialize` traits are impl'd for this type.
30///
31/// The serialization uses the [SEC1] `Elliptic-Curve-Point-to-Octet-String`
32/// encoding, serialized as binary.
33///
34/// When serialized with a text-based format, the SEC1 representation is
35/// subsequently hex encoded.
36///
37/// [SEC1]: https://www.secg.org/sec1-v2.pdf
38#[derive(Clone, Copy, Debug)]
39#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
40pub struct AffinePoint {
41    /// x-coordinate
42    pub(crate) x: FieldElement,
43
44    /// y-coordinate
45    pub(crate) y: FieldElement,
46
47    /// Is this point the point at infinity? 0 = no, 1 = yes
48    ///
49    /// This is a proxy for [`Choice`], but uses `u8` instead to permit `const`
50    /// constructors for `IDENTITY` and `GENERATOR`.
51    pub(super) infinity: u8,
52}
53
54impl AffinePoint {
55    /// Additive identity of the group: the point at infinity.
56    pub const IDENTITY: Self = Self {
57        x: FieldElement::ZERO,
58        y: FieldElement::ZERO,
59        infinity: 1,
60    };
61
62    /// Base point of P-256.
63    ///
64    /// Defined in FIPS 186-4 § D.1.2.3:
65    ///
66    /// ```text
67    /// Gₓ = 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296
68    /// Gᵧ = 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5
69    /// ```
70    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    /// NOTE: not constant-time with respect to identity point
199    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                // SEC1 identity encoding is technically 1-byte 0x00, but the
204                // `GroupEncoding` API requires a fixed-width `Repr`
205                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        // No unchecked conversion possible for compressed points
213        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                // Convert to canonical form for comparisons
230                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    /// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded [`AffinePoint`].
246    ///
247    /// # Returns
248    ///
249    /// `None` value if `encoded_point` is not on the secp256r1 curve.
250    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                        // Check that the point is on the curve
264                        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    /// Serialize this value as a  SEC1 compact [`EncodedPoint`]
291    fn to_compact_encoded_point(&self) -> CtOption<EncodedPoint> {
292        // Convert to canonical form for comparisons
293        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        // Reuse the CompressedPoint type since it's the same size as a compact point
299        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    // Tag compact with 05 as the first byte, to trigger tag based compaction
402    const COMPACT_BASEPOINT: &[u8] =
403        &hex!("058e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c01");
404
405    // Tag uncompact basepoint with 04 as the first byte as it is uncompressed
406    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        // Do not do compact encoding as we want to keep uncompressed point
486        let res = point.to_encoded_point(false);
487        assert_eq!(res.as_bytes(), UNCOMPACT_BASEPOINT);
488    }
489
490    #[test]
491    fn identity_encoding() {
492        // This is technically an invalid SEC1 encoding, but is preferable to panicking.
493        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}