//! Affine curve points. #![allow(clippy::op_ref)] use crate::{PrimeCurveParams, ProjectivePoint}; use core::{ borrow::Borrow, ops::{Mul, Neg}, }; use elliptic_curve::{ ff::{Field, PrimeField}, generic_array::ArrayLength, group::{prime::PrimeCurveAffine, GroupEncoding}, point::{AffineCoordinates, DecompactPoint, DecompressPoint, Double}, sec1::{ self, CompressedPoint, EncodedPoint, FromEncodedPoint, ModulusSize, ToCompactEncodedPoint, ToEncodedPoint, UncompressedPointSize, }, subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}, zeroize::DefaultIsZeroes, Error, FieldBytes, FieldBytesEncoding, FieldBytesSize, PublicKey, Result, Scalar, }; #[cfg(feature = "serde")] use serdect::serde::{de, ser, Deserialize, Serialize}; /// Point on a Weierstrass curve in affine coordinates. #[derive(Clone, Copy, Debug)] pub struct AffinePoint { /// x-coordinate pub(crate) x: C::FieldElement, /// y-coordinate pub(crate) y: C::FieldElement, /// Is this point the point at infinity? 0 = no, 1 = yes /// /// This is a proxy for [`Choice`], but uses `u8` instead to permit `const` /// constructors for `IDENTITY` and `GENERATOR`. pub(crate) infinity: u8, } impl AffinePoint where C: PrimeCurveParams, { /// Additive identity of the group a.k.a. the point at infinity. pub const IDENTITY: Self = Self { x: C::FieldElement::ZERO, y: C::FieldElement::ZERO, infinity: 1, }; /// Base point of the curve. pub const GENERATOR: Self = Self { x: C::GENERATOR.0, y: C::GENERATOR.1, infinity: 0, }; /// Is this point the point at infinity? pub fn is_identity(&self) -> Choice { Choice::from(self.infinity) } /// Conditionally negate [`AffinePoint`] for use with point compaction. fn to_compact(self) -> Self { let neg_self = -self; let choice = C::Uint::decode_field_bytes(&self.y.to_repr()) .ct_gt(&C::Uint::decode_field_bytes(&neg_self.y.to_repr())); Self { x: self.x, y: C::FieldElement::conditional_select(&self.y, &neg_self.y, choice), infinity: self.infinity, } } } impl AffineCoordinates for AffinePoint where C: PrimeCurveParams, { type FieldRepr = FieldBytes; fn x(&self) -> FieldBytes { self.x.to_repr() } fn y_is_odd(&self) -> Choice { self.y.is_odd() } } impl ConditionallySelectable for AffinePoint where C: PrimeCurveParams, { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { Self { x: C::FieldElement::conditional_select(&a.x, &b.x, choice), y: C::FieldElement::conditional_select(&a.y, &b.y, choice), infinity: u8::conditional_select(&a.infinity, &b.infinity, choice), } } } impl ConstantTimeEq for AffinePoint where C: PrimeCurveParams, { fn ct_eq(&self, other: &Self) -> Choice { self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) & self.infinity.ct_eq(&other.infinity) } } impl Default for AffinePoint where C: PrimeCurveParams, { fn default() -> Self { Self::IDENTITY } } impl DefaultIsZeroes for AffinePoint where C: PrimeCurveParams {} impl DecompressPoint for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, { fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { C::FieldElement::from_repr(*x_bytes).and_then(|x| { let alpha = x * &x * &x + &(C::EQUATION_A * &x) + &C::EQUATION_B; let beta = alpha.sqrt(); beta.map(|beta| { let y = C::FieldElement::conditional_select( &-beta, &beta, beta.is_odd().ct_eq(&y_is_odd), ); Self { x, y, infinity: 0 } }) }) } } impl DecompactPoint for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, { fn decompact(x_bytes: &FieldBytes) -> CtOption { Self::decompress(x_bytes, Choice::from(0)).map(|point| point.to_compact()) } } impl Eq for AffinePoint where C: PrimeCurveParams {} impl FromEncodedPoint for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, FieldBytesSize: ModulusSize, CompressedPoint: Copy, { /// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded /// [`AffinePoint`]. /// /// # Returns /// /// `None` value if `encoded_point` is not on the secp384r1 curve. fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { match encoded_point.coordinates() { sec1::Coordinates::Identity => CtOption::new(Self::IDENTITY, 1.into()), sec1::Coordinates::Compact { x } => Self::decompact(x), sec1::Coordinates::Compressed { x, y_is_odd } => { Self::decompress(x, Choice::from(y_is_odd as u8)) } sec1::Coordinates::Uncompressed { x, y } => { C::FieldElement::from_repr(*y).and_then(|y| { Self::decompress(x, y.is_odd()) .and_then(|point| CtOption::new(point, point.y.ct_eq(&y))) }) } } } } impl From> for AffinePoint where C: PrimeCurveParams, { fn from(p: ProjectivePoint) -> AffinePoint { p.to_affine() } } impl From<&ProjectivePoint> for AffinePoint where C: PrimeCurveParams, { fn from(p: &ProjectivePoint) -> AffinePoint { p.to_affine() } } impl From> for AffinePoint where C: PrimeCurveParams, { fn from(public_key: PublicKey) -> AffinePoint { *public_key.as_affine() } } impl From<&PublicKey> for AffinePoint where C: PrimeCurveParams, { fn from(public_key: &PublicKey) -> AffinePoint { AffinePoint::from(*public_key) } } impl From> for EncodedPoint where C: PrimeCurveParams, FieldBytesSize: ModulusSize, CompressedPoint: Copy, as ArrayLength>::ArrayType: Copy, { fn from(affine: AffinePoint) -> EncodedPoint { affine.to_encoded_point(false) } } impl GroupEncoding for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, FieldBytesSize: ModulusSize, CompressedPoint: Copy, as ArrayLength>::ArrayType: Copy, { type Repr = CompressedPoint; /// NOTE: not constant-time with respect to identity point fn from_bytes(bytes: &Self::Repr) -> CtOption { EncodedPoint::::from_bytes(bytes) .map(|point| CtOption::new(point, Choice::from(1))) .unwrap_or_else(|_| { // SEC1 identity encoding is technically 1-byte 0x00, but the // `GroupEncoding` API requires a fixed-width `Repr` let is_identity = bytes.ct_eq(&Self::Repr::default()); CtOption::new(EncodedPoint::::identity(), is_identity) }) .and_then(|point| Self::from_encoded_point(&point)) } fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { // No unchecked conversion possible for compressed points Self::from_bytes(bytes) } fn to_bytes(&self) -> Self::Repr { let encoded = self.to_encoded_point(true); let mut result = CompressedPoint::::default(); result[..encoded.len()].copy_from_slice(encoded.as_bytes()); result } } impl PartialEq for AffinePoint where C: PrimeCurveParams, { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).into() } } impl PrimeCurveAffine for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, FieldBytesSize: ModulusSize, ProjectivePoint: Double, CompressedPoint: Copy, as ArrayLength>::ArrayType: Copy, { type Curve = ProjectivePoint; type Scalar = Scalar; fn identity() -> AffinePoint { Self::IDENTITY } fn generator() -> AffinePoint { Self::GENERATOR } fn is_identity(&self) -> Choice { self.is_identity() } fn to_curve(&self) -> ProjectivePoint { ProjectivePoint::from(*self) } } impl ToCompactEncodedPoint for AffinePoint where C: PrimeCurveParams, FieldBytesSize: ModulusSize, CompressedPoint: Copy, as ArrayLength>::ArrayType: Copy, { /// Serialize this value as a SEC1 compact [`EncodedPoint`] fn to_compact_encoded_point(&self) -> CtOption> { let point = self.to_compact(); let mut bytes = CompressedPoint::::default(); bytes[0] = sec1::Tag::Compact.into(); bytes[1..].copy_from_slice(&point.x.to_repr()); let encoded = EncodedPoint::::from_bytes(bytes); let is_some = point.y.ct_eq(&self.y); CtOption::new(encoded.unwrap_or_default(), is_some) } } impl ToEncodedPoint for AffinePoint where C: PrimeCurveParams, FieldBytesSize: ModulusSize, CompressedPoint: Copy, as ArrayLength>::ArrayType: Copy, { fn to_encoded_point(&self, compress: bool) -> EncodedPoint { EncodedPoint::::conditional_select( &EncodedPoint::::from_affine_coordinates( &self.x.to_repr(), &self.y.to_repr(), compress, ), &EncodedPoint::::identity(), self.is_identity(), ) } } impl TryFrom> for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, FieldBytesSize: ModulusSize, CompressedPoint: Copy, { type Error = Error; fn try_from(point: EncodedPoint) -> Result> { AffinePoint::try_from(&point) } } impl TryFrom<&EncodedPoint> for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, FieldBytesSize: ModulusSize, CompressedPoint: Copy, { type Error = Error; fn try_from(point: &EncodedPoint) -> Result> { Option::from(AffinePoint::::from_encoded_point(point)).ok_or(Error) } } impl TryFrom> for PublicKey where C: PrimeCurveParams, { type Error = Error; fn try_from(affine_point: AffinePoint) -> Result> { PublicKey::from_affine(affine_point) } } impl TryFrom<&AffinePoint> for PublicKey where C: PrimeCurveParams, { type Error = Error; fn try_from(affine_point: &AffinePoint) -> Result> { PublicKey::::try_from(*affine_point) } } // // Arithmetic trait impls // impl Mul for AffinePoint where C: PrimeCurveParams, S: Borrow>, ProjectivePoint: Double, { type Output = ProjectivePoint; fn mul(self, scalar: S) -> ProjectivePoint { ProjectivePoint::::from(self) * scalar } } impl Neg for AffinePoint where C: PrimeCurveParams, { type Output = Self; fn neg(self) -> Self { AffinePoint { x: self.x, y: -self.y, infinity: self.infinity, } } } impl Neg for &AffinePoint where C: PrimeCurveParams, { type Output = AffinePoint; fn neg(self) -> AffinePoint { -(*self) } } // // serde support // #[cfg(feature = "serde")] impl Serialize for AffinePoint where C: PrimeCurveParams, FieldBytesSize: ModulusSize, CompressedPoint: Copy, as ArrayLength>::ArrayType: Copy, { fn serialize(&self, serializer: S) -> core::result::Result where S: ser::Serializer, { self.to_encoded_point(true).serialize(serializer) } } #[cfg(feature = "serde")] impl<'de, C> Deserialize<'de> for AffinePoint where C: PrimeCurveParams, FieldBytes: Copy, FieldBytesSize: ModulusSize, CompressedPoint: Copy, { fn deserialize(deserializer: D) -> core::result::Result where D: de::Deserializer<'de>, { EncodedPoint::::deserialize(deserializer)? .try_into() .map_err(de::Error::custom) } }