Skip to main content

primitives/algebra/elliptic_curve/
point.rs

1use std::{
2    hash::Hash,
3    iter::Sum,
4    mem::MaybeUninit,
5    ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
6};
7
8use elliptic_curve::group::{Group, GroupEncoding};
9use rand::{
10    distributions::{Distribution, Standard},
11    RngCore,
12};
13use serde::{Deserialize, Serialize};
14use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
15
16use crate::{
17    algebra::elliptic_curve::{
18        curve::{FromCoordinates, PointAtInfinityError, PointCoordinates, ToCoordinates},
19        Curve,
20        Scalar,
21        ScalarAsExtension,
22    },
23    errors::PrimitiveError,
24    random::{CryptoRngCore, Random},
25    sharing::unauthenticated::AdditiveShares,
26    utils::codec::InPlaceCodec,
27};
28
29/// A point on a given curve.
30#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
31#[repr(transparent)]
32pub struct Point<C: Curve>(pub(crate) C::Point);
33
34// SAFETY: Point<C> is #[repr(transparent)] over C::Point.
35unsafe impl<C: Curve> bytemuck::TransparentWrapper<C::Point> for Point<C> {}
36
37// SAFETY: `write_le_bytes`/`read_le_bytes` use the curve's canonical `GroupEncoding`
38// (`to_bytes`/`from_bytes`), which is a fixed `ENCODED_SIZE`-byte, architecture-independent
39// encoding. `write_le_bytes` initializes every byte, and `read_le_bytes` validates the encoding via
40// `from_bytes`, so the round-trip is unbiased.
41unsafe impl<C: Curve> InPlaceCodec for Point<C> {
42    const ENCODED_SIZE: usize = size_of::<<C::Point as GroupEncoding>::Repr>();
43
44    fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
45        let mut bytes = self.0.to_bytes();
46        if C::POINT_BIG_ENDIAN {
47            bytes.as_mut().reverse();
48        }
49        for (slot, &b) in out.iter_mut().zip(bytes.as_ref()) {
50            slot.write(b);
51        }
52    }
53
54    fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
55        let mut repr = <C::Point as GroupEncoding>::Repr::default();
56        if bytes.len() != repr.as_ref().len() {
57            return Err(PrimitiveError::InvalidSize(
58                repr.as_ref().len(),
59                bytes.len(),
60            ));
61        }
62        repr.as_mut().copy_from_slice(bytes);
63        if C::POINT_BIG_ENDIAN {
64            repr.as_mut().reverse();
65        }
66        Option::from(C::Point::from_bytes(&repr))
67            .map(Point)
68            .ok_or_else(|| {
69                PrimitiveError::DeserializationFailed("invalid curve point encoding".into())
70            })
71    }
72}
73
74// Encoded as a fixed-arity tuple (`serialize_tuple`/`deserialize_tuple`), not a `[u8]`/`Vec<u8>`:
75// arity is fixed and known to both ends, so formats like `bincode` write no length prefix. Also
76// keeps `Point<C>: Serialize` unconditional on `C: Curve` (no bound on
77// `<C::Point as GroupEncoding>::Repr`), which downstream code (e.g. `CompressedCircuit<C>`) relies
78// on.
79impl<C: Curve> Serialize for Point<C> {
80    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
81        use serde::ser::SerializeTuple;
82        let bytes = self.to_inplace_bytes();
83        let mut tup = serializer.serialize_tuple(bytes.len())?;
84        for b in &bytes {
85            tup.serialize_element(b)?;
86        }
87        tup.end()
88    }
89}
90
91impl<'de, C: Curve> Deserialize<'de> for Point<C> {
92    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
93        struct PointVisitor<C>(std::marker::PhantomData<C>);
94
95        impl<'de, C: Curve> serde::de::Visitor<'de> for PointVisitor<C> {
96            type Value = Point<C>;
97
98            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99                write!(f, "{} bytes", Point::<C>::ENCODED_SIZE)
100            }
101
102            fn visit_seq<A: serde::de::SeqAccess<'de>>(
103                self,
104                mut seq: A,
105            ) -> Result<Self::Value, A::Error> {
106                let mut bytes = Vec::with_capacity(Point::<C>::ENCODED_SIZE);
107                while let Some(b) = seq.next_element::<u8>()? {
108                    bytes.push(b);
109                }
110                Point::<C>::from_inplace_bytes(&bytes).map_err(serde::de::Error::custom)
111            }
112        }
113
114        deserializer.deserialize_tuple(
115            Point::<C>::ENCODED_SIZE,
116            PointVisitor(std::marker::PhantomData),
117        )
118    }
119}
120
121// ------------------------
122// | Misc Implementations |
123// ------------------------
124
125impl<C: Curve> Point<C> {
126    /// The additive identity in the curve group
127    pub fn identity() -> Point<C> {
128        Point(C::Point::identity())
129    }
130
131    pub fn new(point: C::Point) -> Point<C> {
132        Point(point)
133    }
134
135    /// Check whether the given point is the identity point in the group
136    pub fn is_identity(&self) -> Choice {
137        self.ct_eq(&Point::identity())
138    }
139
140    /// Return the wrapped type
141    pub fn inner(&self) -> C::Point {
142        self.0
143    }
144
145    /// The group generator
146    pub fn generator() -> Point<C> {
147        Point(<C::Point as Group>::generator())
148    }
149
150    pub fn from_coordinates(coordinates: PointCoordinates<C>) -> Option<Point<C>> {
151        C::Point::from_coordinates(coordinates).map(Point)
152    }
153
154    pub fn to_coordinates(self) -> Result<PointCoordinates<C>, PointAtInfinityError> {
155        self.0.to_coordinates()
156    }
157}
158
159impl<C: Curve> Random for Point<C> {
160    #[inline]
161    fn random(rng: impl CryptoRngCore) -> Self {
162        Point(C::Point::random(rng))
163    }
164}
165
166impl<C: Curve> Distribution<Point<C>> for Standard {
167    #[inline]
168    fn sample<R: RngCore + ?Sized>(&self, rng: &mut R) -> Point<C> {
169        Point(C::Point::random(rng))
170    }
171}
172
173// ------------------------------------
174// | Curve Arithmetic Implementations |
175// ------------------------------------
176
177// === Addition === //
178
179#[macros::op_variants(owned, borrowed, flipped_commutative)]
180impl<C: Curve> Add<&Point<C>> for Point<C> {
181    type Output = Point<C>;
182
183    #[inline]
184    fn add(mut self, rhs: &Point<C>) -> Self::Output {
185        self.0 += rhs.0;
186        self
187    }
188}
189
190#[macros::op_variants(owned)]
191impl<C: Curve> AddAssign<&Point<C>> for Point<C> {
192    #[inline]
193    fn add_assign(&mut self, rhs: &Point<C>) {
194        self.0 += rhs.0;
195    }
196}
197
198// === Subtraction === //
199
200#[macros::op_variants(owned, borrowed, flipped)]
201impl<C: Curve> Sub<&Point<C>> for Point<C> {
202    type Output = Point<C>;
203
204    #[inline]
205    fn sub(mut self, rhs: &Point<C>) -> Self::Output {
206        self.0 -= rhs.0;
207        self
208    }
209}
210
211#[macros::op_variants(owned)]
212impl<C: Curve> SubAssign<&Point<C>> for Point<C> {
213    #[inline]
214    fn sub_assign(&mut self, rhs: &Point<C>) {
215        self.0 -= rhs.0;
216    }
217}
218
219// === Negation === //
220
221#[macros::op_variants(borrowed)]
222impl<C: Curve> Neg for Point<C> {
223    type Output = Point<C>;
224
225    #[inline]
226    fn neg(self) -> Self::Output {
227        Point(-self.0)
228    }
229}
230
231// === Scalar Multiplication === //
232
233#[macros::op_variants(owned, borrowed, flipped)]
234impl<C: Curve> Mul<&ScalarAsExtension<C>> for Point<C> {
235    type Output = Point<C>;
236
237    #[inline]
238    fn mul(mut self, rhs: &ScalarAsExtension<C>) -> Self::Output {
239        self.0 *= rhs.0;
240        self
241    }
242}
243
244#[macros::op_variants(owned, borrowed, flipped_commutative)]
245impl<C: Curve> Mul<&Point<C>> for ScalarAsExtension<C> {
246    type Output = Point<C>;
247
248    #[inline]
249    fn mul(self, rhs: &Point<C>) -> Self::Output {
250        Point(rhs.0 * self.0)
251    }
252}
253
254#[macros::op_variants(owned, borrowed, flipped)]
255impl<C: Curve> Mul<&Scalar<C>> for Point<C> {
256    type Output = Point<C>;
257
258    #[inline]
259    fn mul(self, rhs: &Scalar<C>) -> Self::Output {
260        Point(self.0 * rhs.0)
261    }
262}
263
264#[macros::op_variants(owned, borrowed, flipped_commutative)]
265impl<C: Curve> Mul<&Point<C>> for Scalar<C> {
266    type Output = Point<C>;
267
268    #[inline]
269    fn mul(self, rhs: &Point<C>) -> Self::Output {
270        Point(rhs.0 * self.0)
271    }
272}
273
274// === MulAssign === //
275
276#[macros::op_variants(owned)]
277impl<C: Curve> MulAssign<&ScalarAsExtension<C>> for Point<C> {
278    #[inline]
279    fn mul_assign(&mut self, rhs: &ScalarAsExtension<C>) {
280        self.0 *= rhs.0;
281    }
282}
283
284#[macros::op_variants(owned)]
285impl<C: Curve> MulAssign<&Scalar<C>> for Point<C> {
286    #[inline]
287    fn mul_assign(&mut self, rhs: &Scalar<C>) {
288        self.0 *= rhs.0;
289    }
290}
291
292// === Equality === //
293
294impl<C: Curve> ConstantTimeEq for Point<C> {
295    #[inline]
296    fn ct_eq(&self, other: &Self) -> Choice {
297        self.0.ct_eq(&other.0)
298    }
299}
300
301impl<C: Curve> ConditionallySelectable for Point<C> {
302    #[inline]
303    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
304        let selected = C::Point::conditional_select(&a.0, &b.0, choice);
305        Point(selected)
306    }
307}
308
309// === Other === //
310
311impl<C: Curve> AdditiveShares for Point<C> {}
312
313// === Iterator traits === //
314
315impl<C: Curve> Sum for Point<C> {
316    #[inline]
317    fn sum<I: Iterator<Item = Point<C>>>(iter: I) -> Self {
318        iter.fold(Point::identity(), |acc, x| acc + x)
319    }
320}
321
322impl<'a, C: Curve> Sum<&'a Point<C>> for Point<C> {
323    #[inline]
324    fn sum<I: Iterator<Item = &'a Point<C>>>(iter: I) -> Self {
325        iter.fold(Point::identity(), |acc, x| acc + x)
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332    use crate::{algebra::elliptic_curve::Curve25519Ristretto, utils::bincode_io};
333
334    #[test]
335    fn test_point_serialization() {
336        let point = Point::<Curve25519Ristretto>::generator();
337        let bytes = point.to_inplace_bytes();
338        let deserialized_point = Point::<Curve25519Ristretto>::from_inplace_bytes(&bytes).unwrap();
339        assert_eq!(point, deserialized_point);
340
341        let bytes = bytes[1..].to_vec(); // Invalid length
342        let result = Point::<Curve25519Ristretto>::from_inplace_bytes(&bytes);
343        assert!(result.is_err());
344    }
345
346    #[test]
347    fn test_point_serde_roundtrip() {
348        let point = Point::<Curve25519Ristretto>::generator();
349        let serialized = bincode_io::serialize(&point).unwrap();
350        assert_eq!(serialized, point.to_inplace_bytes(), "no length prefix");
351        let deserialized: Point<Curve25519Ristretto> =
352            bincode_io::deserialize(&serialized).unwrap();
353        assert_eq!(point, deserialized);
354    }
355}