k256/arithmetic/
affine.rs

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