use crate::ec::p384::{
constants::{
P384_FIELD_ELEMENT_SIZE, P384_POINT_COMPRESSED_SIZE, P384_POINT_UNCOMPRESSED_SIZE,
},
field::FieldElement,
scalar::Scalar,
};
use crate::error::{validate, Error, Result};
use dcrypt_internal::constant_time::{Choice, ConditionallySelectable};
use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
use dcrypt_params::traditional::ecdsa::NIST_P384;
#[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,
}
impl Zeroize for Point {
fn zeroize(&mut self) {
self.is_identity.zeroize();
self.x.zeroize();
self.y.zeroize();
}
}
impl Drop for Point {
fn drop(&mut self) {
self.zeroize();
}
}
impl ZeroizeOnDrop for Point {}
#[derive(Clone, Copy, Debug)]
pub(crate) struct ProjectivePoint {
is_identity: Choice,
x: FieldElement,
y: FieldElement,
z: FieldElement,
}
impl Default for ProjectivePoint {
fn default() -> Self {
Self::identity()
}
}
impl Zeroize for ProjectivePoint {
fn zeroize(&mut self) {
self.is_identity.zeroize();
self.x.zeroize();
self.y.zeroize();
self.z.zeroize();
}
}
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 Point {
pub fn new_uncompressed(
x: &[u8; P384_FIELD_ELEMENT_SIZE],
y: &[u8; P384_FIELD_ELEMENT_SIZE],
) -> Result<Self> {
let x_fe = Zeroizing::new(FieldElement::from_bytes(x)?);
let y_fe = Zeroizing::new(FieldElement::from_bytes(y)?);
if !Self::is_on_curve(&x_fe, &y_fe) {
return Err(Error::param(
"P-384 Point",
"Point coordinates do not satisfy curve equation",
));
}
Ok(Point {
is_identity: Choice::from(0),
x: x_fe.into_inner(),
y: y_fe.into_inner(),
})
}
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; P384_FIELD_ELEMENT_SIZE] {
self.x.to_bytes()
}
pub fn y_coordinate_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
self.y.to_bytes()
}
pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
if bytes.is_empty() {
return Err(Error::param("P-384 Point", "Empty point data"));
}
match (bytes[0], bytes.len()) {
(0x00, 1) => Ok(PointFormat::Identity),
(0x04, P384_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
(0x02 | 0x03, P384_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
_ => Err(Error::param(
"P-384 Point",
"Unknown or malformed point format",
)),
}
}
pub fn serialize_uncompressed(&self) -> [u8; P384_POINT_UNCOMPRESSED_SIZE] {
let mut result = [0u8; P384_POINT_UNCOMPRESSED_SIZE];
if self.is_identity() {
return result; }
result[0] = 0x04;
self.x.write_bytes(&mut result[1..49]);
self.y.write_bytes(&mut result[49..97]);
result
}
pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
validate::length("P-384 Point", bytes.len(), P384_POINT_UNCOMPRESSED_SIZE)?;
if bytes[0] != 0x04 {
return Err(Error::param(
"P-384 Point",
"Invalid uncompressed point format (expected 0x04 prefix)",
));
}
let mut x_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
let mut y_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
x_bytes.copy_from_slice(&bytes[1..49]);
y_bytes.copy_from_slice(&bytes[49..97]);
Self::new_uncompressed(&x_bytes, &y_bytes)
}
pub fn serialize_compressed(&self) -> [u8; P384_POINT_COMPRESSED_SIZE] {
let mut out = [0u8; P384_POINT_COMPRESSED_SIZE];
if self.is_identity() {
return out;
}
out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
self.x.write_bytes(&mut out[1..]);
out
}
pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
validate::length(
"P-384 Compressed Point",
bytes.len(),
P384_POINT_COMPRESSED_SIZE,
)?;
let tag = bytes[0];
if tag != 0x02 && tag != 0x03 {
return Err(Error::param(
"P-384 Point",
"Invalid compressed point prefix (expected 0x02 or 0x03)",
));
}
let mut x_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
x_bytes.copy_from_slice(&bytes[1..]);
let x_fe = Zeroizing::new(FieldElement::from_bytes(&x_bytes).map_err(|_| {
Error::param(
"P-384 Point",
"Invalid compressed point: x-coordinate yields quadratic non-residue",
)
})?);
let x2 = Zeroizing::new(x_fe.square());
let x3 = Zeroizing::new(x2.mul(&x_fe));
let a = Zeroizing::new(FieldElement(FieldElement::A_M3)); let b = Zeroizing::new(FieldElement::from_bytes(&NIST_P384.b).unwrap());
let ax = Zeroizing::new(a.mul(&x_fe));
let x3_plus_ax = Zeroizing::new(x3.add(&ax));
let rhs = Zeroizing::new(x3_plus_ax.add(&b));
let y_fe = Zeroizing::new(rhs.sqrt().ok_or_else(|| {
Error::param(
"P-384 Point",
"Invalid compressed point: x-coordinate yields quadratic non-residue",
)
})?);
let y_matches = (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02);
let y_final = if y_matches {
Zeroizing::new(y_fe.into_inner())
} else {
let modulus = Zeroizing::new(FieldElement::get_modulus());
Zeroizing::new(modulus.sub(&y_fe))
};
Ok(Point {
is_identity: Choice::from(0),
x: x_fe.into_inner(),
y: y_final.into_inner(),
})
}
pub fn add(&self, other: &Self) -> Self {
let p1 = Zeroizing::new(self.to_projective());
let p2 = Zeroizing::new(other.to_projective());
let result = Zeroizing::new(p1.add(&p2));
result.to_affine()
}
pub fn double(&self) -> Self {
let p = Zeroizing::new(self.to_projective());
let result = Zeroizing::new(p.double());
result.to_affine()
}
pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
let scalar_bytes = scalar.as_secret_buffer().as_ref();
let base = Zeroizing::new(self.to_projective());
let mut result = Zeroizing::new(ProjectivePoint::identity());
for byte in scalar_bytes.iter() {
for bit_pos in (0..8).rev() {
let mut bit = (byte >> bit_pos) & 1;
let mut choice = Choice::from(bit);
bit.zeroize();
let doubled = Zeroizing::new(result.double());
let result_added = Zeroizing::new(doubled.add(&base));
let selected = Zeroizing::new(ProjectivePoint::conditional_select(
&doubled,
&result_added,
choice,
));
result.zeroize();
*result = selected.into_inner();
choice.zeroize();
}
}
Ok(result.to_affine())
}
fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
let y_squared = Zeroizing::new(y.square());
let x_squared = Zeroizing::new(x.square());
let x_cubed = Zeroizing::new(x_squared.mul(x));
let a_coeff = Zeroizing::new(FieldElement(FieldElement::A_M3)); let ax = Zeroizing::new(a_coeff.mul(x));
let b_coeff = Zeroizing::new(FieldElement::from_bytes(&NIST_P384.b).unwrap());
let x_cubed_plus_ax = Zeroizing::new(x_cubed.add(&ax));
let rhs = Zeroizing::new(x_cubed_plus_ax.add(&b_coeff));
*y_squared == *rhs
}
fn to_projective(&self) -> ProjectivePoint {
let regular = Zeroizing::new(ProjectivePoint {
is_identity: Choice::from(0),
x: self.x,
y: self.y,
z: FieldElement::one(),
});
let identity = Zeroizing::new(ProjectivePoint::identity());
let selected = Zeroizing::new(ProjectivePoint::conditional_select(
®ular,
&identity,
self.is_identity,
));
selected.into_inner()
}
}
impl ConditionallySelectable for ProjectivePoint {
#[inline(never)]
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),
z: FieldElement::conditional_select(&a.z, &b.z, choice),
}
}
}
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_squared = Zeroizing::new(self.z.square());
let z2_squared = Zeroizing::new(other.z.square());
let z1_cubed = Zeroizing::new(z1_squared.mul(&self.z));
let z2_cubed = Zeroizing::new(z2_squared.mul(&other.z));
let u1 = Zeroizing::new(self.x.mul(&z2_squared)); let u2 = Zeroizing::new(other.x.mul(&z1_squared)); let s1 = Zeroizing::new(self.y.mul(&z2_cubed)); let s2 = Zeroizing::new(other.y.mul(&z1_cubed));
let h = Zeroizing::new(u2.sub(&u1)); let r = Zeroizing::new(s2.sub(&s1));
let h_squared = Zeroizing::new(h.square());
let h_cubed = Zeroizing::new(h_squared.mul(&h));
let v = Zeroizing::new(u1.mul(&h_squared));
let r_squared = Zeroizing::new(r.square());
let two_v = Zeroizing::new(v.add(&v));
let x3_minus_h_cubed = Zeroizing::new(r_squared.sub(&h_cubed));
let x3 = Zeroizing::new(x3_minus_h_cubed.sub(&two_v));
let v_minus_x3 = Zeroizing::new(v.sub(&x3));
let r_times_diff = Zeroizing::new(r.mul(&v_minus_x3));
let s1_times_h_cubed = Zeroizing::new(s1.mul(&h_cubed));
let y3 = Zeroizing::new(r_times_diff.sub(&s1_times_h_cubed));
let z1_times_z2 = Zeroizing::new(self.z.mul(&other.z));
let z3 = Zeroizing::new(z1_times_z2.mul(&h));
let generic_point = Zeroizing::new(Self {
is_identity: Choice::from(0),
x: x3.into_inner(),
y: y3.into_inner(),
z: z3.into_inner(),
});
let double_point = Zeroizing::new(self.double());
let mut h_is_zero = Choice::from((h.is_zero() as u8) & 1);
let mut r_is_zero = Choice::from((r.is_zero() as u8) & 1);
let mut p_eq_q = h_is_zero & r_is_zero;
let mut p_eq_neg_q = h_is_zero & !r_is_zero;
let mut result = Zeroizing::new(Self::conditional_select(
&generic_point,
&double_point,
p_eq_q,
));
let identity = Zeroizing::new(Self::identity());
let selected = Zeroizing::new(Self::conditional_select(&result, &identity, p_eq_neg_q));
result.zeroize();
*result = selected.into_inner();
let selected = Zeroizing::new(Self::conditional_select(&result, other, self.is_identity));
result.zeroize();
*result = selected.into_inner();
let selected = Zeroizing::new(Self::conditional_select(&result, self, other.is_identity));
result.zeroize();
*result = selected.into_inner();
h_is_zero.zeroize();
r_is_zero.zeroize();
p_eq_q.zeroize();
p_eq_neg_q.zeroize();
result.into_inner()
}
#[inline]
pub fn double(&self) -> Self {
let delta = Zeroizing::new(self.z.square());
let gamma = Zeroizing::new(self.y.square());
let beta = Zeroizing::new(self.x.mul(&gamma));
let x_plus_delta = Zeroizing::new(self.x.add(&delta));
let x_minus_delta = Zeroizing::new(self.x.sub(&delta));
let alpha_base = Zeroizing::new(x_plus_delta.mul(&x_minus_delta));
let two_alpha = Zeroizing::new(alpha_base.add(&alpha_base));
let alpha = Zeroizing::new(two_alpha.add(&alpha_base));
let two_beta = Zeroizing::new(beta.add(&beta));
let four_beta = Zeroizing::new(two_beta.add(&two_beta));
let eight_beta = Zeroizing::new(four_beta.add(&four_beta));
let alpha_squared = Zeroizing::new(alpha.square());
let x3 = Zeroizing::new(alpha_squared.sub(&eight_beta));
let y_plus_z = Zeroizing::new(self.y.add(&self.z));
let y_plus_z_squared = Zeroizing::new(y_plus_z.square());
let z3_minus_gamma = Zeroizing::new(y_plus_z_squared.sub(&gamma));
let z3 = Zeroizing::new(z3_minus_gamma.sub(&delta));
let four_beta_minus_x3 = Zeroizing::new(four_beta.sub(&x3));
let alpha_times_difference = Zeroizing::new(alpha.mul(&four_beta_minus_x3));
let gamma_sq = Zeroizing::new(gamma.square());
let two_gamma_sq = Zeroizing::new(gamma_sq.add(&gamma_sq));
let four_gamma_sq = Zeroizing::new(two_gamma_sq.add(&two_gamma_sq));
let eight_gamma_sq = Zeroizing::new(four_gamma_sq.add(&four_gamma_sq));
let y3 = Zeroizing::new(alpha_times_difference.sub(&eight_gamma_sq));
let result = Zeroizing::new(Self {
is_identity: Choice::from(0),
x: x3.into_inner(),
y: y3.into_inner(),
z: z3.into_inner(),
});
let mut return_identity = self.is_identity | Choice::from(self.y.is_zero() as u8);
let identity = Zeroizing::new(Self::identity());
let selected = Zeroizing::new(Self::conditional_select(
&result,
&identity,
return_identity,
));
return_identity.zeroize();
selected.into_inner()
}
pub fn to_affine(&self) -> Point {
let one = Zeroizing::new(FieldElement::one());
let safe_z = Zeroizing::new(FieldElement::conditional_select(
&self.z,
&one,
self.is_identity,
));
let z_inv = Zeroizing::new(
safe_z
.invert()
.expect("Non-zero Z coordinate should be invertible"),
);
let z_inv_squared = Zeroizing::new(z_inv.square());
let z_inv_cubed = Zeroizing::new(z_inv_squared.mul(&z_inv));
let x_affine = Zeroizing::new(self.x.mul(&z_inv_squared));
let y_affine = Zeroizing::new(self.y.mul(&z_inv_cubed));
let zero = Zeroizing::new(FieldElement::zero());
let x = Zeroizing::new(FieldElement::conditional_select(
&x_affine,
&zero,
self.is_identity,
));
let y = Zeroizing::new(FieldElement::conditional_select(
&y_affine,
&zero,
self.is_identity,
));
Point {
is_identity: self.is_identity,
x: x.into_inner(),
y: y.into_inner(),
}
}
}