use crate::ec::b283k::{
constants::{B283K_FIELD_ELEMENT_SIZE, B283K_POINT_COMPRESSED_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, Copy, Debug)]
pub struct Point {
pub(crate) is_identity: Choice,
pub(crate) x: FieldElement,
pub(crate) y: FieldElement,
}
impl PartialEq for Point {
fn eq(&self, other: &Self) -> bool {
let self_is_identity: bool = self.is_identity.into();
let other_is_identity: bool = other.is_identity.into();
if self_is_identity || other_is_identity {
return self_is_identity == other_is_identity;
}
self.x == other.x && self.y == other.y
}
}
impl Eq for Point {}
impl ConditionallySelectable for Point {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Self {
is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
x: FieldElement::conditional_select(&a.x, &b.x, choice),
y: FieldElement::conditional_select(&a.y, &b.y, choice),
}
}
}
impl Point {
pub fn new_uncompressed(
x: &[u8; B283K_FIELD_ELEMENT_SIZE],
y: &[u8; B283K_FIELD_ELEMENT_SIZE],
) -> Result<Self> {
let x_fe = FieldElement::from_bytes(x)?;
let y_fe = FieldElement::from_bytes(y)?;
if !Self::is_on_curve(&x_fe, &y_fe) {
return Err(Error::param(
"B283k Point",
"Point coordinates do not satisfy curve equation",
));
}
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; B283K_FIELD_ELEMENT_SIZE] {
self.x.to_bytes()
}
pub fn y_coordinate_bytes(&self) -> [u8; B283K_FIELD_ELEMENT_SIZE] {
self.y.to_bytes()
}
pub fn serialize_compressed(&self) -> [u8; B283K_POINT_COMPRESSED_SIZE] {
let mut out = [0u8; B283K_POINT_COMPRESSED_SIZE];
if self.is_identity() {
return out;
}
let y_tilde = self.x.invert().unwrap().mul(&self.y).trace();
out[0] = if y_tilde == 1 { 0x03 } else { 0x02 };
out[1..].copy_from_slice(&self.x.to_bytes());
out
}
pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
validate::length(
"B283k Compressed Point",
bytes.len(),
B283K_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(
"B283k Point",
"Invalid compressed point prefix",
));
}
let mut x_bytes = [0u8; B283K_FIELD_ELEMENT_SIZE];
x_bytes.copy_from_slice(&bytes[1..]);
let x = FieldElement::from_bytes(&x_bytes)?;
if x.is_zero() {
return Ok(Point {
is_identity: Choice::from(0),
x,
y: FieldElement::one().sqrt(),
});
}
let rhs = x.add(&x.square().invert().unwrap());
if rhs.trace() != 0 {
return Err(Error::param("B283k Point", "Cannot decompress point"));
}
let mut z = Self::half_trace(&rhs);
if z.trace() != (tag as u64 - 2) {
z = z.add(&FieldElement::one());
}
let y = x.mul(&z);
Ok(Point {
is_identity: Choice::from(0),
x,
y,
})
}
fn half_trace(a: &FieldElement) -> FieldElement {
let mut ht = *a;
let mut t = *a;
for _ in 0..141 {
t = t.square();
t = t.square();
ht = ht.add(&t);
}
ht
}
pub fn add(&self, other: &Self) -> Self {
let x_eq = self.x == other.x;
let y_eq = self.y == other.y;
let p_eq_q = Choice::from((x_eq && y_eq) as u8);
let p_eq_neg_q = Choice::from((x_eq && !y_eq) as u8);
let sum_y = self.y.add(&other.y);
let sum_x = self.x.add(&other.x);
let sum_x_is_zero = Choice::from(sum_x.is_zero() as u8);
let denom = FieldElement::conditional_select(&sum_x, &FieldElement::one(), sum_x_is_zero);
let inv_denom = denom.invert().unwrap_or(FieldElement::zero());
let lambda = sum_y.mul(&inv_denom);
let x3 = lambda.square().add(&lambda).add(&self.x).add(&other.x);
let y3 = lambda.mul(&(self.x.add(&x3))).add(&x3).add(&self.y);
let generic = Point {
is_identity: Choice::from(0),
x: x3,
y: y3,
};
let double = self.double();
let mut result = Self::conditional_select(&generic, &double, 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 x_is_zero = Choice::from(self.x.is_zero() as u8);
let denom = FieldElement::conditional_select(&self.x, &FieldElement::one(), x_is_zero);
let inv_x = denom.invert().unwrap_or(FieldElement::zero());
let term = self.y.mul(&inv_x);
let lambda = self.x.add(&term);
let x2 = lambda.square().add(&lambda);
let y2 = self.x.square().add(&lambda.mul(&x2)).add(&x2);
let result = Point {
is_identity: Choice::from(0),
x: x2,
y: y2,
};
Self::conditional_select(&result, &Self::identity(), self.is_identity)
}
pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
if scalar.is_zero() {
return Ok(Self::identity());
}
let scalar_bytes = scalar.as_secret_buffer().as_ref();
let mut res = Self::identity();
let mut temp = self.clone();
for byte in scalar_bytes.iter().rev() {
for i in 0..8 {
let bit = (byte >> i) & 1;
let choice = Choice::from(bit);
let res_added = res.add(&temp);
res = Point::conditional_select(&res, &res_added, choice);
temp = temp.double();
}
}
Ok(res)
}
fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
let y_sq = y.square();
let xy = x.mul(y);
let lhs = y_sq.add(&xy);
let x_cubed = x.square().mul(x);
let rhs = x_cubed.add(&FieldElement::one());
lhs == rhs
}
}