use super::fp256::{sbb, FieldElement};
const BASE_X: [u8; 32] = [
0x91, 0xF5, 0xD0, 0xE7, 0xE2, 0xD4, 0x17, 0xE3, 0x10, 0x8B, 0x13, 0xB0, 0x75, 0xCD, 0xC7, 0x75,
0x60, 0x45, 0xF8, 0x42, 0x44, 0x79, 0xFC, 0xFE, 0x8F, 0x23, 0xD2, 0x72, 0x50, 0xA0, 0x88, 0x3F,
];
const BASE_Y: [u8; 32] = [
0x74, 0x2F, 0x27, 0xA2, 0x68, 0x64, 0x1C, 0x9D, 0x7D, 0xDF, 0x69, 0x89, 0x2B, 0xE3, 0xDF, 0x3D,
0x8F, 0x9C, 0xC5, 0x22, 0x60, 0xB8, 0x9A, 0x49, 0x53, 0xC8, 0x37, 0x9C, 0x7C, 0x0A, 0x21, 0x2B,
];
const ORDER_N: [u8; 32] = [
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x29, 0xE2, 0x60, 0x87, 0x78, 0x9B, 0xC2, 0x81, 0x5B, 0xDF, 0xF9, 0x70, 0x93, 0x54, 0x3C, 0xCF,
];
pub(crate) fn curve_a() -> FieldElement {
let mut bytes = [0u8; 32];
bytes[31] = 2;
FieldElement::from_be_bytes(&bytes)
}
pub(crate) fn curve_d() -> FieldElement {
let mut bytes = [0u8; 32];
bytes[31] = 0x18;
FieldElement::from_be_bytes(&bytes)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Point {
pub x: FieldElement,
pub y: FieldElement,
}
impl Point {
pub const NEUTRAL: Self = Self {
x: FieldElement::ONE,
y: FieldElement::ZERO,
};
#[must_use]
pub fn is_on_curve(self) -> bool {
let x2 = self.x.square();
let y2 = self.y.square();
let lhs = x2.add(curve_a().multiply(y2));
let rhs = curve_d().multiply(x2).multiply(y2).add(FieldElement::ONE);
lhs == rhs
}
#[must_use]
#[allow(clippy::should_implement_trait)] pub fn add(self, other: Self) -> Self {
ProjectivePoint::from_affine(self)
.add(ProjectivePoint::from_affine(other))
.to_affine()
}
#[must_use]
pub fn scalar_multiply(self, scalar: &[u8; 32]) -> Self {
let base = ProjectivePoint::from_affine(self);
let mut acc = ProjectivePoint::from_affine(Self::NEUTRAL);
for &byte in scalar {
for bit_idx in (0..8).rev() {
acc = acc.add(acc);
let bit = u64::from((byte >> bit_idx) & 1);
let candidate = acc.add(base);
acc = ProjectivePoint::select(bit, candidate, acc);
}
}
acc.to_affine()
}
}
#[derive(Clone, Copy, Debug)]
struct ProjectivePoint {
x: FieldElement,
y: FieldElement,
z: FieldElement,
}
impl ProjectivePoint {
fn from_affine(p: Point) -> Self {
Self {
x: p.x,
y: p.y,
z: FieldElement::ONE,
}
}
fn to_affine(self) -> Point {
let z_inv = self.z.invert();
Point {
x: self.x.multiply(z_inv),
y: self.y.multiply(z_inv),
}
}
#[allow(clippy::many_single_char_names)] fn add(self, other: Self) -> Self {
let a = curve_a();
let d = curve_d();
let zz = self.z.multiply(other.z);
let b = zz.square();
let c = self.x.multiply(other.x);
let dd = self.y.multiply(other.y);
let e = d.multiply(c).multiply(dd);
let f = b.sub(e);
let g = b.add(e);
let x_sum = self.x.add(self.y);
let y_sum = other.x.add(other.y);
let cross = x_sum.multiply(y_sum);
let x_r = zz.multiply(g).multiply(c.sub(a.multiply(dd)));
let y_r = zz.multiply(f).multiply(cross.sub(c).sub(dd));
let z_r = f.multiply(g);
Self {
x: x_r,
y: y_r,
z: z_r,
}
}
fn select(bit: u64, a: Self, b: Self) -> Self {
Self {
x: FieldElement::select(bit, a.x, b.x),
y: FieldElement::select(bit, a.y, b.y),
z: FieldElement::select(bit, a.z, b.z),
}
}
}
#[must_use]
pub fn point_from_x(x: FieldElement) -> Option<Point> {
let a = curve_a();
let d = curve_d();
let p_minus_1 = FieldElement::ZERO.sub(FieldElement::ONE);
let x_squared = x.square();
if x == FieldElement::ZERO
|| x == FieldElement::ONE
|| x == p_minus_1
|| x_squared == a.multiply(d.invert())
{
return None;
}
let numerator = FieldElement::ONE.sub(x_squared);
let denominator = a.sub(d.multiply(x_squared));
let v = numerator.multiply(denominator.invert());
if !v.euler_criterion() {
return None;
}
let candidate = Point { x, y: v.sqrt() };
if candidate.scalar_multiply(&order()) != Point::NEUTRAL {
return None;
}
Some(candidate)
}
#[must_use]
pub fn base_point() -> Point {
Point {
x: FieldElement::from_be_bytes(&BASE_X),
y: FieldElement::from_be_bytes(&BASE_Y),
}
}
#[must_use]
pub fn order() -> [u8; 32] {
ORDER_N
}
#[allow(clippy::needless_range_loop)]
fn bytes_be_to_limbs(bytes: &[u8; 32]) -> [u64; 4] {
let mut limbs = [0u64; 4];
let mut limb_bytes = [0u8; 8];
for i in 0..4 {
limb_bytes.copy_from_slice(&bytes[i * 8..i * 8 + 8]);
limbs[3 - i] = u64::from_be_bytes(limb_bytes);
}
limbs
}
#[allow(clippy::needless_range_loop)]
fn is_less_than(a: &[u8; 32], b: &[u8; 32]) -> bool {
let al = bytes_be_to_limbs(a);
let bl = bytes_be_to_limbs(b);
let mut borrow = 0u64;
for i in 0..4 {
let (_, bw) = sbb(al[i], bl[i], borrow);
borrow = bw;
}
borrow == 1
}
#[must_use]
pub fn is_valid_scalar(scalar: &[u8; 32]) -> bool {
let mut one = [0u8; 32];
one[31] = 1;
let mut n_minus_1 = ORDER_N;
n_minus_1[31] -= 1;
let scalar_gt_one = is_less_than(&one, scalar);
let scalar_lt_n_minus_1 = is_less_than(scalar, &n_minus_1);
scalar_gt_one & scalar_lt_n_minus_1
}