use crate::ec::p192::{
constants::{
P192_FIELD_ELEMENT_SIZE, P192_POINT_COMPRESSED_SIZE, P192_POINT_UNCOMPRESSED_SIZE,
},
field::FieldElement,
scalar::Scalar,
};
use crate::error::{validate, Error, Result};
use subtle::{Choice, ConditionallySelectable};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointFormat {
Identity,
Uncompressed,
Compressed,
}
#[derive(Clone, Debug)]
pub struct Point {
pub(crate) is_identity: Choice,
pub(crate) x: FieldElement,
pub(crate) y: FieldElement,
}
#[derive(Clone, Debug)]
pub(crate) struct ProjectivePoint {
pub(crate) is_identity: Choice,
pub(crate) x: FieldElement,
pub(crate) y: FieldElement,
pub(crate) z: FieldElement,
}
impl PartialEq for Point {
fn eq(&self, other: &Self) -> bool {
let a_id: bool = self.is_identity.into();
let b_id: bool = other.is_identity.into();
if a_id || b_id {
return a_id == b_id;
}
self.x == other.x && self.y == other.y
}
}
impl Point {
pub fn new_uncompressed(
x_bytes: &[u8; P192_FIELD_ELEMENT_SIZE],
y_bytes: &[u8; P192_FIELD_ELEMENT_SIZE],
) -> Result<Self> {
let x_fe = FieldElement::from_bytes(x_bytes)?;
let y_fe = FieldElement::from_bytes(y_bytes)?;
if !Self::is_on_curve(&x_fe, &y_fe) {
return Err(Error::param("P-192 Point", "Point not on curve"));
}
Ok(Point {
is_identity: Choice::from(0),
x: x_fe,
y: y_fe,
})
}
pub fn identity() -> Self {
Point {
is_identity: Choice::from(1),
x: FieldElement::zero(),
y: FieldElement::zero(),
}
}
pub fn is_identity(&self) -> bool {
self.is_identity.into()
}
pub fn x_coordinate_bytes(&self) -> [u8; P192_FIELD_ELEMENT_SIZE] {
self.x.to_bytes()
}
pub fn y_coordinate_bytes(&self) -> [u8; P192_FIELD_ELEMENT_SIZE] {
self.y.to_bytes()
}
pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
if bytes.is_empty() {
return Err(Error::param("P-192 Point", "Empty encoding"));
}
match (bytes[0], bytes.len()) {
(0x00, P192_POINT_UNCOMPRESSED_SIZE) => {
if bytes.iter().all(|&b| b == 0) {
Ok(PointFormat::Identity)
} else {
Err(Error::param("P-192 Point", "Invalid identity encoding"))
}
}
(0x04, P192_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
(0x02 | 0x03, P192_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
_ => Err(Error::param("P-192 Point", "Unknown or malformed format")),
}
}
pub fn serialize_uncompressed(&self) -> [u8; P192_POINT_UNCOMPRESSED_SIZE] {
let mut out = [0u8; P192_POINT_UNCOMPRESSED_SIZE];
if self.is_identity() {
return out; }
out[0] = 0x04;
out[1..1 + P192_FIELD_ELEMENT_SIZE].copy_from_slice(&self.x.to_bytes());
out[1 + P192_FIELD_ELEMENT_SIZE..].copy_from_slice(&self.y.to_bytes());
out
}
pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
validate::length("P-192 Point", bytes.len(), P192_POINT_UNCOMPRESSED_SIZE)?;
if bytes.iter().all(|&b| b == 0) {
return Ok(Self::identity());
}
if bytes[0] != 0x04 {
return Err(Error::param(
"P-192 Point",
"Invalid prefix for uncompressed",
));
}
let mut xb = [0u8; P192_FIELD_ELEMENT_SIZE];
let mut yb = [0u8; P192_FIELD_ELEMENT_SIZE];
xb.copy_from_slice(&bytes[1..1 + P192_FIELD_ELEMENT_SIZE]);
yb.copy_from_slice(&bytes[1 + P192_FIELD_ELEMENT_SIZE..]);
Self::new_uncompressed(&xb, &yb)
}
pub fn serialize_compressed(&self) -> [u8; P192_POINT_COMPRESSED_SIZE] {
let mut out = [0u8; P192_POINT_COMPRESSED_SIZE];
if self.is_identity() {
return out; }
out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
out[1..].copy_from_slice(&self.x.to_bytes());
out
}
pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
validate::length(
"P-192 Compressed Point",
bytes.len(),
P192_POINT_COMPRESSED_SIZE,
)?;
if bytes.iter().all(|&b| b == 0) {
return Ok(Self::identity());
}
let tag = bytes[0];
if tag != 0x02 && tag != 0x03 {
return Err(Error::param("P-192 Point", "Invalid compressed prefix"));
}
let mut xb = [0u8; P192_FIELD_ELEMENT_SIZE];
xb.copy_from_slice(&bytes[1..]);
let x_fe = FieldElement::from_bytes(&xb)
.map_err(|_| Error::param("P-192 Point", "Invalid compressed point: x not in field"))?;
let rhs = {
let x2 = x_fe.square();
let x3 = x2.mul(&x_fe);
let a = FieldElement(FieldElement::A_M3);
let b_coeff = FieldElement::from_bytes(&crate::ec::p192::field::B).unwrap();
x3.add(&a.mul(&x_fe)).add(&b_coeff)
};
let y_candidate = rhs
.sqrt()
.ok_or_else(|| Error::param("P-192 Point", "Invalid compressed point: no sqrt"))?;
let y_final =
if (y_candidate.is_odd() && tag == 0x03) || (!y_candidate.is_odd() && tag == 0x02) {
y_candidate
} else {
y_candidate.negate() };
Ok(Point {
is_identity: Choice::from(0),
x: x_fe,
y: y_final,
})
}
pub fn add(&self, other: &Self) -> Self {
let p1 = self.to_projective();
let p2 = other.to_projective();
let sum = p1.add(&p2);
sum.to_affine()
}
pub fn double(&self) -> Self {
let p = self.to_projective();
let d = p.double();
d.to_affine()
}
pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
if scalar.is_zero() {
return Ok(Self::identity());
}
let base = self.to_projective();
let mut acc = ProjectivePoint::identity();
let bytes = scalar.as_secret_buffer().as_ref();
for &byte in bytes.iter() {
for i in (0..8).rev() {
acc = acc.double();
let acc_added = acc.add(&base);
let choice = Choice::from((byte >> i) & 1);
acc = ProjectivePoint::conditional_select(&acc, &acc_added, choice);
}
}
Ok(acc.to_affine())
}
fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
let y2 = y.square();
let x2 = x.square();
let x3 = x2.mul(x);
let a = FieldElement(FieldElement::A_M3);
let b_coeff = FieldElement::from_bytes(&crate::ec::p192::field::B).unwrap();
let rhs = x3.add(&a.mul(x)).add(&b_coeff);
y2 == rhs
}
fn to_projective(&self) -> ProjectivePoint {
if self.is_identity() {
ProjectivePoint::identity()
} else {
ProjectivePoint {
is_identity: Choice::from(0),
x: self.x.clone(),
y: self.y.clone(),
z: FieldElement::one(),
}
}
}
}
impl ProjectivePoint {
pub fn identity() -> Self {
ProjectivePoint {
is_identity: Choice::from(1),
x: FieldElement::zero(),
y: FieldElement::one(),
z: FieldElement::zero(),
}
}
pub fn add(&self, other: &Self) -> Self {
let z1_sq = self.z.square();
let z2_sq = other.z.square();
let z1_cu = z1_sq.mul(&self.z);
let z2_cu = z2_sq.mul(&other.z);
let u1 = self.x.mul(&z2_sq); let u2 = other.x.mul(&z1_sq); let s1 = self.y.mul(&z2_cu); let s2 = other.y.mul(&z1_cu);
let h = u2.sub(&u1);
let r = s2.sub(&s1);
let h2 = h.square();
let h3 = h2.mul(&h);
let v = u1.mul(&h2);
let r2 = r.square();
let two_v = v.add(&v);
let mut x3 = r2.sub(&h3);
x3 = x3.sub(&two_v);
let v_minus_x3 = v.sub(&x3);
let r_times = r.mul(&v_minus_x3);
let s1_h3 = s1.mul(&h3);
let y3 = r_times.sub(&s1_h3);
let z1z2 = self.z.mul(&other.z);
let z3 = z1z2.mul(&h);
let generic = ProjectivePoint {
is_identity: Choice::from(0),
x: x3,
y: y3,
z: z3,
};
let double_point = self.double();
let h_is_zero = Choice::from(h.is_zero() as u8);
let r_is_zero = Choice::from(r.is_zero() as u8);
let p_eq_q = h_is_zero & r_is_zero;
let p_eq_neg_q = h_is_zero & !r_is_zero;
let mut result = Self::conditional_select(&generic, &double_point, p_eq_q);
result = Self::conditional_select(&result, &Self::identity(), p_eq_neg_q);
result = Self::conditional_select(&result, other, self.is_identity);
result = Self::conditional_select(&result, self, other.is_identity);
result
}
pub fn double(&self) -> Self {
let delta = self.z.square();
let gamma = self.y.square();
let beta = self.x.mul(&gamma);
let t1 = self.x.add(&delta); let t2 = self.x.sub(&delta); let mut alpha = t1.mul(&t2); let three = FieldElement::from_u32(3);
alpha = alpha.mul(&three);
let eight_beta = {
let two_beta = beta.add(&beta);
let four_beta = two_beta.add(&two_beta);
four_beta.add(&four_beta) };
let x3 = alpha.square().sub(&eight_beta);
let z3 = self.y.add(&self.z).square().sub(&gamma).sub(&delta);
let four_beta = {
let two_beta = beta.add(&beta);
two_beta.add(&two_beta)
};
let mut y3 = four_beta.sub(&x3);
y3 = alpha.mul(&y3);
let eight_gamma_sq = {
let gamma_sq = gamma.square();
let two = gamma_sq.add(&gamma_sq);
let four = two.add(&two);
four.add(&four) };
let y3 = y3.sub(&eight_gamma_sq);
let result = ProjectivePoint {
is_identity: Choice::from(0),
x: x3,
y: y3,
z: z3,
};
let return_identity = self.is_identity | Choice::from(self.y.is_zero() as u8);
Self::conditional_select(&result, &Self::identity(), return_identity)
}
pub fn to_affine(&self) -> Point {
if self.is_identity.into() {
return Point::identity();
}
let z_inv = self.z.invert().expect("Nonzero Z ⇒ invertible");
let z_inv_sq = z_inv.square();
let z_inv_cu = z_inv_sq.mul(&z_inv);
let x_aff = self.x.mul(&z_inv_sq);
let y_aff = self.y.mul(&z_inv_cu);
Point {
is_identity: Choice::from(0),
x: x_aff,
y: y_aff,
}
}
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
let select_field = |lhs: &FieldElement, rhs: &FieldElement| {
let mut out = [0u32; 6];
for (i, limb) in out.iter_mut().enumerate() {
*limb = u32::conditional_select(&lhs.0[i], &rhs.0[i], choice);
}
FieldElement(out)
};
Self {
is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
x: select_field(&a.x, &b.x),
y: select_field(&a.y, &b.y),
z: select_field(&a.z, &b.z),
}
}
}