generic_ec/point/
mod.rs

1use core::fmt;
2use core::hash::{self, Hash};
3use core::iter::Sum;
4
5use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
6
7use crate::as_raw::FromRaw;
8use crate::{
9    as_raw::{AsRaw, TryFromRaw},
10    core::*,
11    errors::InvalidPoint,
12    EncodedPoint, Generator,
13};
14
15use self::definition::Point;
16
17pub mod coords;
18pub mod definition;
19
20impl<E: Curve> Point<E> {
21    /// Curve generator
22    ///
23    /// Curve generator is a regular point defined in curve specs. See [`Generator<E>`](Generator).
24    pub fn generator() -> Generator<E> {
25        Generator::default()
26    }
27
28    /// Returns identity point $\O$ (sometimes called as _point at infinity_)
29    ///
30    /// Identity point has special properties:
31    ///
32    /// $$\forall P \in \G: P + \O = P$$
33    /// $$\forall s \in \Zq: s \cdot \O = \O$$
34    ///
35    /// When you validate input from user or message received on wire, you should bear in mind that
36    /// any `Point<E>` may be zero. If your algorithm does not accept identity points, you may check
37    /// whether point is zero by calling [`.is_zero()`](Point::is_zero). Alternatively, you may accept
38    /// [`NonZero<Point<E>>`](crate::NonZero) instead, which is guaranteed to be non zero.
39    pub fn zero() -> Self {
40        // Correctness:
41        // 1. Zero point belongs to curve by definition
42        // 2. Zero point is free of any component (including torsion component)
43        Self::from_raw_unchecked(E::Point::zero())
44    }
45
46    /// Indicates whether it's [identity point](Self::zero)
47    ///
48    /// ```rust
49    /// use generic_ec::{Point, curves::Secp256k1};
50    ///
51    /// assert!(Point::<Secp256k1>::zero().is_zero());
52    /// assert!(!Point::<Secp256k1>::generator().to_point().is_zero());
53    /// ```
54    pub fn is_zero(&self) -> bool {
55        self.ct_is_zero().into()
56    }
57
58    /// Indicates whether it's [identity point](Self::zero) (in constant time)
59    ///
60    /// Same as [`.is_zero()`](Self::is_zero) but performs constant-time comparison.
61    pub fn ct_is_zero(&self) -> Choice {
62        Zero::is_zero(self.as_raw())
63    }
64
65    /// Encodes a point as bytes
66    ///
67    /// Function can return both compressed and uncompressed bytes representation of a point.
68    /// Compressed bytes representation is more compact, but parsing takes a little bit more
69    /// time. On other hand, uncompressed representation takes ~twice more space, but parsing
70    /// is instant.
71    ///
72    /// For some curves, `compressed` parameter may be ignored, and same bytes representation
73    /// is returned.
74    ///
75    /// ```rust
76    /// use generic_ec::{Point, Scalar, curves::Secp256k1};
77    /// use rand::rngs::OsRng;
78    ///
79    /// let random_point = Point::<Secp256k1>::generator() * Scalar::random(&mut OsRng);
80    /// let point_bytes = random_point.to_bytes(false);
81    /// let point_decoded = Point::from_bytes(&point_bytes)?;
82    /// assert_eq!(random_point, point_decoded);
83    /// # Ok::<(), Box<dyn std::error::Error>>(())
84    /// ```
85    pub fn to_bytes(&self, compressed: bool) -> EncodedPoint<E> {
86        if compressed {
87            let bytes = self.as_raw().to_bytes_compressed();
88            EncodedPoint::new_compressed(bytes)
89        } else {
90            let bytes = self.as_raw().to_bytes_uncompressed();
91            EncodedPoint::new_uncompressed(bytes)
92        }
93    }
94
95    /// Decodes a point from bytes
96    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, InvalidPoint> {
97        E::Point::decode(bytes.as_ref())
98            .and_then(Self::try_from_raw)
99            .ok_or(InvalidPoint)
100    }
101
102    /// Returns size of bytes buffer that can fit a serialized point
103    ///
104    /// `compressed` parameter has the same meaning as for [`Point::to_bytes`]; a
105    /// buffer created with length of `Point::serialized_len(compress)` would fit
106    /// exactly the serialization `p.to_bytes(compress)`.
107    pub fn serialized_len(compressed: bool) -> usize {
108        if compressed {
109            E::CompressedPointArray::zeroes().as_ref().len()
110        } else {
111            E::UncompressedPointArray::zeroes().as_ref().len()
112        }
113    }
114}
115
116impl<E: Curve + NoInvalidPoints> FromRaw for Point<E> {
117    fn from_raw(raw: Self::Raw) -> Self {
118        Point::from_raw_unchecked(raw)
119    }
120}
121
122impl<E: Curve> TryFromRaw for Point<E> {
123    fn ct_try_from_raw(point: E::Point) -> CtOption<Self> {
124        let is_on_curve = point.is_on_curve();
125        let is_torsion_free = point.is_torsion_free();
126        let is_valid = is_on_curve & is_torsion_free;
127
128        // Correctness: we checked validity of the point. Although invalid point
129        // is still given to `from_raw_unchecked`, it's never exposed by CtOption,
130        // so no one can obtain "invalid" instance of `Point`.
131        CtOption::new(Point::from_raw_unchecked(point), is_valid)
132    }
133}
134
135impl<E: Curve> ConditionallySelectable for Point<E> {
136    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
137        // Correctness: both `a` and `b` have to be valid points by construction
138        Point::from_raw_unchecked(<E::Point as ConditionallySelectable>::conditional_select(
139            a.as_raw(),
140            b.as_raw(),
141            choice,
142        ))
143    }
144}
145
146impl<E: Curve> ConstantTimeEq for Point<E> {
147    fn ct_eq(&self, other: &Self) -> Choice {
148        self.as_raw().ct_eq(other.as_raw())
149    }
150}
151
152impl<E: Curve> AsRef<Point<E>> for Point<E> {
153    fn as_ref(&self) -> &Point<E> {
154        self
155    }
156}
157
158impl<E: Curve> Sum for Point<E> {
159    fn sum<I: Iterator<Item = Self>>(mut iter: I) -> Self {
160        let Some(first_point) = iter.next() else {
161            return Point::zero();
162        };
163        iter.fold(first_point, |acc, p| acc + p)
164    }
165}
166
167impl<'a, E: Curve> Sum<&'a Point<E>> for Point<E> {
168    fn sum<I: Iterator<Item = &'a Point<E>>>(mut iter: I) -> Self {
169        let Some(first_point) = iter.next() else {
170            return Point::zero();
171        };
172        iter.fold(*first_point, |acc, p| acc + p)
173    }
174}
175
176impl<E: Curve> fmt::Debug for Point<E> {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        let mut s = f.debug_struct("Point");
179        s.field("curve", &E::CURVE_NAME);
180
181        #[cfg(feature = "std")]
182        {
183            s.field("value", &hex::encode(self.to_bytes(true)));
184        }
185        #[cfg(not(feature = "std"))]
186        {
187            s.field("value", &"...");
188        }
189
190        s.finish()
191    }
192}
193#[allow(clippy::derived_hash_with_manual_eq)]
194impl<E: Curve> Hash for Point<E> {
195    fn hash<H: hash::Hasher>(&self, state: &mut H) {
196        state.write(self.to_bytes(true).as_bytes())
197    }
198}
199
200impl<E: Curve> PartialOrd for Point<E> {
201    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
202        Some(self.cmp(other))
203    }
204}
205
206impl<E: Curve> Ord for Point<E> {
207    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
208        self.to_bytes(true)
209            .as_bytes()
210            .cmp(other.to_bytes(true).as_bytes())
211    }
212}
213
214impl<E: Curve> crate::traits::IsZero for Point<E> {
215    fn is_zero(&self) -> bool {
216        *self == Point::zero()
217    }
218}
219
220impl<E: Curve> crate::traits::Zero for Point<E> {
221    fn zero() -> Self {
222        Point::zero()
223    }
224
225    fn is_zero(x: &Self) -> Choice {
226        x.ct_eq(&Self::zero())
227    }
228}
229
230#[cfg(feature = "udigest")]
231impl<E: Curve> udigest::Digestable for Point<E> {
232    fn unambiguously_encode<B>(&self, encoder: udigest::encoding::EncodeValue<B>)
233    where
234        B: udigest::Buffer,
235    {
236        let mut s = encoder.encode_struct();
237        s.add_field("curve").encode_leaf_value(E::CURVE_NAME);
238        s.add_field("point").encode_leaf_value(self.to_bytes(true));
239        s.finish();
240    }
241}