Skip to main content

k256/arithmetic/
affine.rs

1//! Affine points
2
3#![allow(clippy::op_ref)]
4
5use super::{CURVE_EQUATION_B, FieldElement, ProjectivePoint};
6use crate::{CompressedPoint, FieldBytes, PublicKey, Scalar, Sec1Point, Secp256k1};
7use elliptic_curve::{
8    Error, Generate, Result, ctutils,
9    ff::PrimeField,
10    group::{CurveAffine, GroupEncoding},
11    ops::{Mul, MulVartime, Neg},
12    point::{AffineCoordinates, DecompactPoint, DecompressPoint, NonIdentity},
13    rand_core::{TryCryptoRng, TryRng},
14    sec1::{self, FromSec1Point, ToSec1Point},
15    subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
16    zeroize::DefaultIsZeroes,
17};
18
19#[cfg(feature = "serde")]
20use serdect::serde::{Deserialize, Serialize, de, ser};
21
22/// secp256k1 curve point expressed in affine coordinates.
23///
24/// # `serde` support
25///
26/// When the `serde` feature of this crate is enabled, the `Serialize` and
27/// `Deserialize` traits are impl'd for this type.
28///
29/// The serialization uses the [SEC1] `Elliptic-Curve-Point-to-Octet-String`
30/// encoding, serialized as binary.
31///
32/// When serialized with a text-based format, the SEC1 representation is
33/// subsequently hex encoded.
34///
35/// [SEC1]: https://www.secg.org/sec1-v2.pdf
36#[derive(Clone, Copy, Debug)]
37pub struct AffinePoint {
38    /// x-coordinate
39    pub(crate) x: FieldElement,
40
41    /// y-coordinate
42    pub(crate) y: FieldElement,
43
44    /// Is this point the point at infinity? 0 = no, 1 = yes
45    ///
46    /// This is a proxy for [`Choice`], but uses `u8` instead to permit `const`
47    /// constructors for `IDENTITY` and `GENERATOR`.
48    pub(super) infinity: u8,
49}
50
51impl AffinePoint {
52    /// Additive identity of the group: the point at infinity.
53    pub const IDENTITY: Self = Self {
54        x: FieldElement::ZERO,
55        y: FieldElement::ZERO,
56        infinity: 1,
57    };
58
59    /// Base point of secp256k1.
60    ///
61    /// ```text
62    /// Gₓ = 79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 59f2815b 16f81798
63    /// Gᵧ = 483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 9c47d08f fb10d4b8
64    /// ```
65    pub const GENERATOR: Self = Self {
66        x: FieldElement::from_bytes_unchecked(&[
67            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
68            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
69            0x16, 0xf8, 0x17, 0x98,
70        ]),
71        y: FieldElement::from_bytes_unchecked(&[
72            0x48, 0x3a, 0xda, 0x77, 0x26, 0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, 0xfc, 0x0e, 0x11,
73            0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19, 0x9c, 0x47, 0xd0, 0x8f,
74            0xfb, 0x10, 0xd4, 0xb8,
75        ]),
76        infinity: 0,
77    };
78
79    /// Generate a random [`AffinePoint`].
80    ///
81    /// This internal method avoids the `TryCryptoRng` bounds so it can be used in `group` impls for
82    /// `ProjectivePoint`.
83    pub(crate) fn try_random<R: TryRng + ?Sized>(
84        rng: &mut R,
85    ) -> core::result::Result<Self, R::Error> {
86        let mut bytes = FieldBytes::default();
87        let mut sign = 0;
88
89        loop {
90            rng.try_fill_bytes(&mut bytes)?;
91            rng.try_fill_bytes(core::array::from_mut(&mut sign))?;
92            if let Some(point) = Self::decompress(&bytes, Choice::from(sign & 1)).into_option() {
93                return Ok(point);
94            }
95        }
96    }
97}
98
99impl AffinePoint {
100    /// Create a new [`AffinePoint`] with the given coordinates.
101    pub(crate) const fn new(x: FieldElement, y: FieldElement) -> Self {
102        Self { x, y, infinity: 0 }
103    }
104}
105
106impl CurveAffine for AffinePoint {
107    type Scalar = Scalar;
108    type Curve = ProjectivePoint;
109
110    /// Returns the identity of the group: the point at infinity.
111    fn identity() -> Self {
112        Self::IDENTITY
113    }
114
115    /// Returns the base point of secp256k1.
116    fn generator() -> Self {
117        Self::GENERATOR
118    }
119
120    /// Is this point the identity point?
121    fn is_identity(&self) -> Choice {
122        Choice::from(self.infinity)
123    }
124
125    /// Convert to curve representation.
126    fn to_curve(&self) -> ProjectivePoint {
127        ProjectivePoint::from(*self)
128    }
129}
130
131impl AffineCoordinates for AffinePoint {
132    type FieldRepr = FieldBytes;
133
134    fn from_coordinates(x: &Self::FieldRepr, y: &Self::FieldRepr) -> CtOption<Self> {
135        let x = FieldElement::from_bytes(x);
136        let y = FieldElement::from_bytes(y);
137
138        x.and_then(|x| {
139            y.and_then(|y| {
140                // Check that the point is on the curve
141                let lhs = (y * &y).negate(1);
142                let rhs = x * &x * &x + &CURVE_EQUATION_B;
143                let point = Self::new(x, y);
144                CtOption::new(point, (lhs + &rhs).normalizes_to_zero())
145            })
146        })
147    }
148
149    fn x(&self) -> FieldBytes {
150        self.x.to_bytes()
151    }
152
153    fn y(&self) -> FieldBytes {
154        self.y.to_bytes()
155    }
156
157    fn x_is_odd(&self) -> Choice {
158        self.x.normalize().is_odd()
159    }
160
161    fn y_is_odd(&self) -> Choice {
162        self.y.normalize().is_odd()
163    }
164}
165
166impl ConditionallySelectable for AffinePoint {
167    fn conditional_select(a: &AffinePoint, b: &AffinePoint, choice: Choice) -> AffinePoint {
168        AffinePoint {
169            x: FieldElement::conditional_select(&a.x, &b.x, choice),
170            y: FieldElement::conditional_select(&a.y, &b.y, choice),
171            infinity: u8::conditional_select(&a.infinity, &b.infinity, choice),
172        }
173    }
174}
175
176impl ConstantTimeEq for AffinePoint {
177    fn ct_eq(&self, other: &AffinePoint) -> Choice {
178        (self.x.negate(1) + &other.x).normalizes_to_zero()
179            & (self.y.negate(1) + &other.y).normalizes_to_zero()
180            & self.infinity.ct_eq(&other.infinity)
181    }
182}
183
184impl ctutils::CtEq for AffinePoint {
185    fn ct_eq(&self, other: &Self) -> ctutils::Choice {
186        ConstantTimeEq::ct_eq(self, other).into()
187    }
188}
189
190impl ctutils::CtSelect for AffinePoint {
191    fn ct_select(&self, other: &Self, choice: ctutils::Choice) -> Self {
192        ConditionallySelectable::conditional_select(self, other, choice.into())
193    }
194}
195
196impl Default for AffinePoint {
197    fn default() -> Self {
198        Self::IDENTITY
199    }
200}
201
202impl DefaultIsZeroes for AffinePoint {}
203
204impl PartialEq for AffinePoint {
205    fn eq(&self, other: &AffinePoint) -> bool {
206        self.ct_eq(other).into()
207    }
208}
209
210impl Eq for AffinePoint {}
211
212impl Generate for AffinePoint {
213    fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(
214        rng: &mut R,
215    ) -> core::result::Result<Self, R::Error> {
216        Self::try_random(rng)
217    }
218}
219
220impl Mul<Scalar> for AffinePoint {
221    type Output = ProjectivePoint;
222
223    fn mul(self, scalar: Scalar) -> ProjectivePoint {
224        ProjectivePoint::from(self) * scalar
225    }
226}
227
228impl Mul<&Scalar> for AffinePoint {
229    type Output = ProjectivePoint;
230
231    fn mul(self, scalar: &Scalar) -> ProjectivePoint {
232        ProjectivePoint::from(self) * scalar
233    }
234}
235
236impl MulVartime<Scalar> for AffinePoint {
237    fn mul_vartime(self, scalar: Scalar) -> ProjectivePoint {
238        ProjectivePoint::from(self).mul_vartime(scalar)
239    }
240}
241
242impl MulVartime<&Scalar> for AffinePoint {
243    fn mul_vartime(self, scalar: &Scalar) -> ProjectivePoint {
244        ProjectivePoint::from(self).mul_vartime(scalar)
245    }
246}
247
248impl Neg for AffinePoint {
249    type Output = AffinePoint;
250
251    fn neg(self) -> Self::Output {
252        AffinePoint {
253            x: self.x,
254            y: self.y.negate(1).normalize_weak(),
255            infinity: self.infinity,
256        }
257    }
258}
259
260impl DecompressPoint<Secp256k1> for AffinePoint {
261    fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption<Self> {
262        FieldElement::from_bytes(x_bytes).and_then(|x| {
263            let alpha = (x * &x * &x) + &CURVE_EQUATION_B;
264            let beta = alpha.sqrt();
265
266            beta.map(|beta| {
267                let beta = beta.normalize(); // Need to normalize for is_odd() to be consistent
268                let y = FieldElement::conditional_select(
269                    &beta.negate(1),
270                    &beta,
271                    beta.is_odd().ct_eq(&y_is_odd),
272                );
273
274                Self::new(x, y.normalize())
275            })
276        })
277    }
278}
279
280/// Decompaction using Taproot conventions as described in [BIP 340].
281///
282/// [BIP 340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
283impl DecompactPoint<Secp256k1> for AffinePoint {
284    fn decompact(x_bytes: &FieldBytes) -> CtOption<Self> {
285        Self::decompress(x_bytes, Choice::from(0))
286    }
287}
288
289impl GroupEncoding for AffinePoint {
290    type Repr = CompressedPoint;
291
292    fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
293        Sec1Point::from_bytes(bytes)
294            .map_or_else(
295                |_| {
296                    // SEC1 identity encoding is technically 1-byte 0x00, but the
297                    // `GroupEncoding` API requires a fixed-width `Repr`
298                    let is_identity =
299                        ctutils::CtEq::ct_eq(bytes.as_slice(), Self::Repr::default().as_slice());
300
301                    ctutils::CtOption::new(Sec1Point::identity(), is_identity)
302                },
303                ctutils::CtOption::some,
304            )
305            .and_then(|point| Self::from_sec1_point(&point))
306            .into()
307    }
308
309    fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
310        // No unchecked conversion possible for compressed points
311        Self::from_bytes(bytes)
312    }
313
314    fn to_bytes(&self) -> Self::Repr {
315        let encoded = self.to_sec1_point(true);
316        let mut result = CompressedPoint::default();
317        result[..encoded.len()].copy_from_slice(encoded.as_bytes());
318        result
319    }
320}
321
322impl FromSec1Point<Secp256k1> for AffinePoint {
323    /// Attempts to parse the given [`Sec1Point`] as an SEC1-encoded [`AffinePoint`].
324    ///
325    /// # Returns
326    ///
327    /// `None` value if `encoded_point` is not on the secp256k1 curve.
328    fn from_sec1_point(encoded_point: &Sec1Point) -> ctutils::CtOption<Self> {
329        match encoded_point.coordinates() {
330            sec1::Coordinates::Identity => ctutils::CtOption::some(Self::IDENTITY),
331            sec1::Coordinates::Compact { x } => Self::decompact(x).into(),
332            sec1::Coordinates::Compressed { x, y_is_odd } => {
333                AffinePoint::decompress(x, Choice::from(u8::from(y_is_odd))).into()
334            }
335            sec1::Coordinates::Uncompressed { x, y } => Self::from_coordinates(x, y).into(),
336        }
337    }
338}
339
340impl ToSec1Point<Secp256k1> for AffinePoint {
341    fn to_sec1_point(&self, compress: bool) -> Sec1Point {
342        ctutils::CtSelect::ct_select(
343            &Sec1Point::from_affine_coordinates(&self.x.to_repr(), &self.y.to_repr(), compress),
344            &Sec1Point::identity(),
345            self.is_identity().into(),
346        )
347    }
348}
349
350impl TryFrom<Sec1Point> for AffinePoint {
351    type Error = Error;
352
353    fn try_from(point: Sec1Point) -> Result<AffinePoint> {
354        AffinePoint::try_from(&point)
355    }
356}
357
358impl TryFrom<&Sec1Point> for AffinePoint {
359    type Error = Error;
360
361    fn try_from(point: &Sec1Point) -> Result<AffinePoint> {
362        Option::from(AffinePoint::from_sec1_point(point)).ok_or(Error)
363    }
364}
365
366impl From<AffinePoint> for Sec1Point {
367    fn from(affine_point: AffinePoint) -> Sec1Point {
368        Sec1Point::from(&affine_point)
369    }
370}
371
372impl From<&AffinePoint> for Sec1Point {
373    fn from(affine_point: &AffinePoint) -> Sec1Point {
374        affine_point.to_sec1_point(true)
375    }
376}
377
378impl From<NonIdentity<AffinePoint>> for AffinePoint {
379    fn from(affine_point: NonIdentity<AffinePoint>) -> Self {
380        affine_point.to_point()
381    }
382}
383
384impl From<PublicKey> for AffinePoint {
385    fn from(public_key: PublicKey) -> AffinePoint {
386        *public_key.as_affine()
387    }
388}
389
390impl From<&PublicKey> for AffinePoint {
391    fn from(public_key: &PublicKey) -> AffinePoint {
392        AffinePoint::from(*public_key)
393    }
394}
395
396/// The constant-time alternative is available at [`NonIdentity::new()`].
397impl TryFrom<AffinePoint> for NonIdentity<AffinePoint> {
398    type Error = Error;
399
400    fn try_from(affine_point: AffinePoint) -> Result<Self> {
401        NonIdentity::new(affine_point).into_option().ok_or(Error)
402    }
403}
404
405impl TryFrom<AffinePoint> for PublicKey {
406    type Error = Error;
407
408    fn try_from(affine_point: AffinePoint) -> Result<PublicKey> {
409        PublicKey::from_affine(affine_point)
410    }
411}
412
413impl TryFrom<&AffinePoint> for PublicKey {
414    type Error = Error;
415
416    fn try_from(affine_point: &AffinePoint) -> Result<PublicKey> {
417        PublicKey::try_from(*affine_point)
418    }
419}
420
421#[cfg(feature = "serde")]
422impl Serialize for AffinePoint {
423    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
424    where
425        S: ser::Serializer,
426    {
427        self.to_sec1_point(true).serialize(serializer)
428    }
429}
430
431#[cfg(feature = "serde")]
432impl<'de> Deserialize<'de> for AffinePoint {
433    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
434    where
435        D: de::Deserializer<'de>,
436    {
437        Sec1Point::deserialize(deserializer)?
438            .try_into()
439            .map_err(de::Error::custom)
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::AffinePoint;
446    use crate::Sec1Point;
447    use elliptic_curve::{
448        group::{CurveAffine, GroupEncoding},
449        sec1::{FromSec1Point, ToSec1Point},
450    };
451    use hex_literal::hex;
452
453    const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!(
454        "0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
455         483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"
456    );
457    const COMPRESSED_BASEPOINT: &[u8] =
458        &hex!("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798");
459
460    #[test]
461    fn uncompressed_round_trip() {
462        let pubkey = Sec1Point::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap();
463        let res: Sec1Point = AffinePoint::from_sec1_point(&pubkey)
464            .unwrap()
465            .to_sec1_point(false);
466
467        assert_eq!(res, pubkey);
468    }
469
470    #[test]
471    fn compressed_round_trip() {
472        let pubkey = Sec1Point::from_bytes(COMPRESSED_BASEPOINT).unwrap();
473        let res: Sec1Point = AffinePoint::from_sec1_point(&pubkey)
474            .unwrap()
475            .to_sec1_point(true);
476
477        assert_eq!(res, pubkey);
478    }
479
480    #[test]
481    fn uncompressed_to_compressed() {
482        let encoded = Sec1Point::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap();
483
484        let res = AffinePoint::from_sec1_point(&encoded)
485            .unwrap()
486            .to_sec1_point(true);
487
488        assert_eq!(res.as_bytes(), COMPRESSED_BASEPOINT);
489    }
490
491    #[test]
492    fn compressed_to_uncompressed() {
493        let encoded = Sec1Point::from_bytes(COMPRESSED_BASEPOINT).unwrap();
494
495        let res = AffinePoint::from_sec1_point(&encoded)
496            .unwrap()
497            .to_sec1_point(false);
498
499        assert_eq!(res.as_bytes(), UNCOMPRESSED_BASEPOINT);
500    }
501
502    #[test]
503    fn affine_negation() {
504        let basepoint = AffinePoint::GENERATOR;
505        assert_eq!((-(-basepoint)), basepoint);
506    }
507
508    #[test]
509    fn identity_encoding() {
510        // This is technically an invalid SEC1 encoding, but is preferable to panicking.
511        assert_eq!([0; 33], AffinePoint::IDENTITY.to_bytes().as_slice());
512        assert!(bool::from(
513            AffinePoint::from_bytes(&AffinePoint::IDENTITY.to_bytes())
514                .unwrap()
515                .is_identity()
516        ));
517    }
518}