mirror of
https://github.com/saymrwulf/anza-cryptography-source.git
synced 2026-09-04 20:24:04 +00:00
747 lines
20 KiB
Rust
747 lines
20 KiB
Rust
|
|
//! P-256 elliptic curve group operations.
|
||
|
|
//!
|
||
|
|
//! Points are represented in two forms:
|
||
|
|
//!
|
||
|
|
//! - [`AffinePoint`] — standard `(x, y)` coordinates, used for storage and
|
||
|
|
//! table entries. Includes an `infinity` flag for the identity element.
|
||
|
|
//! - [`ProjectivePoint`] — Jacobian `(X : Y : Z)` coordinates, used during
|
||
|
|
//! multi-step scalar multiplication to avoid per-step field inversions.
|
||
|
|
//!
|
||
|
|
//! Use [`ProjectivePoint::to_affine`] to convert back and pay the single
|
||
|
|
//! field inversion, or [`batch_normalize`][`ProjectivePoint`] implicitly via
|
||
|
|
//! the precomputed table builders.
|
||
|
|
|
||
|
|
use core::ops::{Add, Neg, Sub};
|
||
|
|
use std::sync::OnceLock;
|
||
|
|
|
||
|
|
use crate::field::FieldElement;
|
||
|
|
|
||
|
|
const BASE_WINDOWS: usize = 32;
|
||
|
|
const BASE_WINDOW_POINTS: usize = 256;
|
||
|
|
const SHAMIR_WINDOW_POINTS: usize = 16;
|
||
|
|
|
||
|
|
const CURVE_B: FieldElement = FieldElement::from_montgomery_limbs([
|
||
|
|
0xd89c_df62_29c4_bddf,
|
||
|
|
0xacf0_05cd_7884_3090,
|
||
|
|
0xe5a2_20ab_f721_2ed6,
|
||
|
|
0xdc30_061d_0487_4834,
|
||
|
|
]);
|
||
|
|
|
||
|
|
const GENERATOR_X: FieldElement = FieldElement::from_montgomery_limbs([
|
||
|
|
0x79e7_30d4_18a9_143c,
|
||
|
|
0x75ba_95fc_5fed_b601,
|
||
|
|
0x79fb_732b_7762_2510,
|
||
|
|
0x1890_5f76_a537_55c6,
|
||
|
|
]);
|
||
|
|
|
||
|
|
const GENERATOR_Y: FieldElement = FieldElement::from_montgomery_limbs([
|
||
|
|
0xddf2_5357_ce95_560a,
|
||
|
|
0x8b4a_b8e4_ba19_e45c,
|
||
|
|
0xd2e8_8688_dd21_f325,
|
||
|
|
0x8571_ff18_2588_5d85,
|
||
|
|
]);
|
||
|
|
|
||
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||
|
|
pub struct AffinePoint {
|
||
|
|
x: FieldElement,
|
||
|
|
y: FieldElement,
|
||
|
|
infinity: bool,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl AffinePoint {
|
||
|
|
pub const IDENTITY: Self = Self {
|
||
|
|
x: FieldElement::ZERO,
|
||
|
|
y: FieldElement::ZERO,
|
||
|
|
infinity: true,
|
||
|
|
};
|
||
|
|
|
||
|
|
pub const GENERATOR: Self = Self {
|
||
|
|
x: GENERATOR_X,
|
||
|
|
y: GENERATOR_Y,
|
||
|
|
infinity: false,
|
||
|
|
};
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn identity() -> Self {
|
||
|
|
Self::IDENTITY
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn generator() -> Self {
|
||
|
|
Self::GENERATOR
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn new(x: FieldElement, y: FieldElement) -> Option<Self> {
|
||
|
|
let point = Self {
|
||
|
|
x,
|
||
|
|
y,
|
||
|
|
infinity: false,
|
||
|
|
};
|
||
|
|
|
||
|
|
point.is_on_curve().then_some(point)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn from_uncompressed(bytes: [u8; 65]) -> Option<Self> {
|
||
|
|
if bytes[0] != 0x04 {
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut x = [0u8; 32];
|
||
|
|
let mut y = [0u8; 32];
|
||
|
|
x.copy_from_slice(&bytes[1..33]);
|
||
|
|
y.copy_from_slice(&bytes[33..65]);
|
||
|
|
|
||
|
|
Self::new(
|
||
|
|
FieldElement::from_be_bytes(x)?,
|
||
|
|
FieldElement::from_be_bytes(y)?,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn from_compressed(bytes: [u8; 33]) -> Option<Self> {
|
||
|
|
if bytes[0] != 0x02 && bytes[0] != 0x03 {
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut x_bytes = [0u8; 32];
|
||
|
|
x_bytes.copy_from_slice(&bytes[1..33]);
|
||
|
|
let x = FieldElement::from_be_bytes(x_bytes)?;
|
||
|
|
let rhs = x.square() * x - triple(x) + CURVE_B;
|
||
|
|
let mut y = rhs.sqrt()?;
|
||
|
|
|
||
|
|
if (y.to_be_bytes()[31] & 1) != (bytes[0] & 1) {
|
||
|
|
y = -y;
|
||
|
|
}
|
||
|
|
|
||
|
|
((y.to_be_bytes()[31] & 1) == (bytes[0] & 1)).then_some(Self {
|
||
|
|
x,
|
||
|
|
y,
|
||
|
|
infinity: false,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_projective(self) -> ProjectivePoint {
|
||
|
|
if self.infinity {
|
||
|
|
ProjectivePoint::IDENTITY
|
||
|
|
} else {
|
||
|
|
ProjectivePoint {
|
||
|
|
x: self.x,
|
||
|
|
y: self.y,
|
||
|
|
z: FieldElement::ONE,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_uncompressed(self) -> Option<[u8; 65]> {
|
||
|
|
if self.infinity {
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut out = [0u8; 65];
|
||
|
|
out[0] = 0x04;
|
||
|
|
out[1..33].copy_from_slice(&self.x.to_be_bytes());
|
||
|
|
out[33..65].copy_from_slice(&self.y.to_be_bytes());
|
||
|
|
Some(out)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn is_identity(self) -> bool {
|
||
|
|
self.infinity
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn x(self) -> Option<FieldElement> {
|
||
|
|
(!self.infinity).then_some(self.x)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn y(self) -> Option<FieldElement> {
|
||
|
|
(!self.infinity).then_some(self.y)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn is_on_curve(self) -> bool {
|
||
|
|
if self.infinity {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
let y2 = self.y.square();
|
||
|
|
let x2 = self.x.square();
|
||
|
|
let x3 = x2 * self.x;
|
||
|
|
let three_x = triple(self.x);
|
||
|
|
y2 == x3 - three_x + CURVE_B
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Neg for AffinePoint {
|
||
|
|
type Output = Self;
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn neg(self) -> Self::Output {
|
||
|
|
if self.infinity {
|
||
|
|
self
|
||
|
|
} else {
|
||
|
|
Self {
|
||
|
|
x: self.x,
|
||
|
|
y: -self.y,
|
||
|
|
infinity: false,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Clone, Copy, Debug)]
|
||
|
|
pub struct ProjectivePoint {
|
||
|
|
x: FieldElement,
|
||
|
|
y: FieldElement,
|
||
|
|
z: FieldElement,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl PartialEq for ProjectivePoint {
|
||
|
|
#[inline]
|
||
|
|
fn eq(&self, other: &Self) -> bool {
|
||
|
|
let self_is_identity = self.z.is_zero();
|
||
|
|
let other_is_identity = other.z.is_zero();
|
||
|
|
|
||
|
|
if self_is_identity || other_is_identity {
|
||
|
|
return self_is_identity && other_is_identity;
|
||
|
|
}
|
||
|
|
|
||
|
|
let self_z2 = self.z.square();
|
||
|
|
let other_z2 = other.z.square();
|
||
|
|
let self_z3 = self_z2 * self.z;
|
||
|
|
let other_z3 = other_z2 * other.z;
|
||
|
|
|
||
|
|
// x1 * z2^2 == x2 * z1^2
|
||
|
|
// y1 * z2^3 == y2 * z1^3
|
||
|
|
|
||
|
|
self.x * other_z2 == other.x * self_z2 && self.y * other_z3 == other.y * self_z3
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Eq for ProjectivePoint {}
|
||
|
|
|
||
|
|
impl ProjectivePoint {
|
||
|
|
pub const IDENTITY: Self = Self {
|
||
|
|
x: FieldElement::ZERO,
|
||
|
|
y: FieldElement::ONE,
|
||
|
|
z: FieldElement::ZERO,
|
||
|
|
};
|
||
|
|
|
||
|
|
pub const GENERATOR: Self = Self {
|
||
|
|
x: GENERATOR_X,
|
||
|
|
y: GENERATOR_Y,
|
||
|
|
z: FieldElement::ONE,
|
||
|
|
};
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn identity() -> Self {
|
||
|
|
Self::IDENTITY
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn generator() -> Self {
|
||
|
|
Self::GENERATOR
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn from_affine(point: AffinePoint) -> Self {
|
||
|
|
point.to_projective()
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_affine(self) -> AffinePoint {
|
||
|
|
match self.z.invert() {
|
||
|
|
Some(zinv) => {
|
||
|
|
let zinv2 = zinv.square();
|
||
|
|
AffinePoint {
|
||
|
|
x: self.x * zinv2,
|
||
|
|
y: self.y * zinv2 * zinv,
|
||
|
|
infinity: false,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
None => AffinePoint::IDENTITY,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn to_uncompressed(self) -> Option<[u8; 65]> {
|
||
|
|
self.to_affine().to_uncompressed()
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn is_identity(self) -> bool {
|
||
|
|
self.z.is_zero()
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn has_affine_x(self, x: FieldElement) -> bool {
|
||
|
|
!self.is_identity() && self.x == x * self.z.square()
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn double(self) -> Self {
|
||
|
|
if self.is_identity() || self.y.is_zero() {
|
||
|
|
return Self::IDENTITY;
|
||
|
|
}
|
||
|
|
|
||
|
|
let xx = self.x.square();
|
||
|
|
let yy = self.y.square();
|
||
|
|
let yyyy = yy.square();
|
||
|
|
let zz = self.z.square();
|
||
|
|
let s = double((self.x + yy).square() - xx - yyyy);
|
||
|
|
let m = triple(xx - zz.square());
|
||
|
|
let x = m.square() - double(s);
|
||
|
|
let y = m * (s - x) - double(double(double(yyyy)));
|
||
|
|
let z = (self.y + self.z).square() - yy - zz;
|
||
|
|
|
||
|
|
Self { x, y, z }
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn add_mixed(self, rhs: AffinePoint) -> Self {
|
||
|
|
if rhs.infinity {
|
||
|
|
return self;
|
||
|
|
}
|
||
|
|
if self.is_identity() {
|
||
|
|
return rhs.to_projective();
|
||
|
|
}
|
||
|
|
|
||
|
|
let z1z1 = self.z.square();
|
||
|
|
let u2 = rhs.x * z1z1;
|
||
|
|
let s2 = rhs.y * self.z * z1z1;
|
||
|
|
let h = u2 - self.x;
|
||
|
|
let slope_num = double(s2 - self.y);
|
||
|
|
|
||
|
|
if h.is_zero() {
|
||
|
|
return if slope_num.is_zero() {
|
||
|
|
self.double()
|
||
|
|
} else {
|
||
|
|
Self::IDENTITY
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
let hh = h.square();
|
||
|
|
let i = double(double(hh));
|
||
|
|
let j = h * i;
|
||
|
|
let v = self.x * i;
|
||
|
|
let x = slope_num.square() - j - double(v);
|
||
|
|
let y = slope_num * (v - x) - double(self.y * j);
|
||
|
|
let z = (self.z + h).square() - z1z1 - hh;
|
||
|
|
|
||
|
|
Self { x, y, z }
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn mul_scalar_vartime(self, scalar: [u8; 32]) -> Self {
|
||
|
|
let mut table = [Self::IDENTITY; 16];
|
||
|
|
table[1] = self;
|
||
|
|
|
||
|
|
for i in 2..16 {
|
||
|
|
table[i] = table[i - 1] + self;
|
||
|
|
}
|
||
|
|
let affine_table = batch_normalize(table);
|
||
|
|
|
||
|
|
let mut out = Self::IDENTITY;
|
||
|
|
|
||
|
|
for &byte in scalar.iter() {
|
||
|
|
out = out.double().double().double().double();
|
||
|
|
out = out.add_mixed(affine_table[(byte >> 4) as usize]);
|
||
|
|
out = out.double().double().double().double();
|
||
|
|
out = out.add_mixed(affine_table[(byte & 0x0f) as usize]);
|
||
|
|
}
|
||
|
|
|
||
|
|
out
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn fixed_base_scalar_mul_vartime(scalar: [u8; 32]) -> Self {
|
||
|
|
mul_window8_vartime(generator_window8_table(), &scalar)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
pub fn double_scalar_mul_vartime(
|
||
|
|
generator_scalar: [u8; 32],
|
||
|
|
point: AffinePoint,
|
||
|
|
point_scalar: [u8; 32],
|
||
|
|
) -> Self {
|
||
|
|
let generator_table = generator_window4_table();
|
||
|
|
let point_table = window4_table(point);
|
||
|
|
let mut out = Self::IDENTITY;
|
||
|
|
|
||
|
|
for (&generator_byte, &point_byte) in generator_scalar.iter().zip(point_scalar.iter()) {
|
||
|
|
out = double_n(out, 4)
|
||
|
|
.add_mixed(generator_table[(generator_byte >> 4) as usize])
|
||
|
|
.add_mixed(point_table[(point_byte >> 4) as usize]);
|
||
|
|
out = double_n(out, 4)
|
||
|
|
.add_mixed(generator_table[(generator_byte & 0x0f) as usize])
|
||
|
|
.add_mixed(point_table[(point_byte & 0x0f) as usize]);
|
||
|
|
}
|
||
|
|
|
||
|
|
out
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Computes `sum(scalars[i] * points[i])` using variable-time table
|
||
|
|
/// lookups.
|
||
|
|
///
|
||
|
|
/// Returns `None` when `points` and `scalars` have different lengths.
|
||
|
|
/// This routine is intended for public inputs, such as syscall MSM
|
||
|
|
/// plumbing; do not use it with secret scalars.
|
||
|
|
#[inline]
|
||
|
|
pub fn multi_scalar_mul_vartime(points: &[AffinePoint], scalars: &[[u8; 32]]) -> Option<Self> {
|
||
|
|
if points.len() != scalars.len() {
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
|
||
|
|
let tables: Vec<_> = points.iter().copied().map(window4_table).collect();
|
||
|
|
let mut out = Self::IDENTITY;
|
||
|
|
|
||
|
|
for byte_index in 0..32 {
|
||
|
|
out = double_n(out, 4);
|
||
|
|
for (table, scalar) in tables.iter().zip(scalars) {
|
||
|
|
out = out.add_mixed(table[(scalar[byte_index] >> 4) as usize]);
|
||
|
|
}
|
||
|
|
|
||
|
|
out = double_n(out, 4);
|
||
|
|
for (table, scalar) in tables.iter().zip(scalars) {
|
||
|
|
out = out.add_mixed(table[(scalar[byte_index] & 0x0f) as usize]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Some(out)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Add for ProjectivePoint {
|
||
|
|
type Output = Self;
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn add(self, rhs: Self) -> Self::Output {
|
||
|
|
if self.is_identity() {
|
||
|
|
return rhs;
|
||
|
|
}
|
||
|
|
if rhs.is_identity() {
|
||
|
|
return self;
|
||
|
|
}
|
||
|
|
|
||
|
|
let z1z1 = self.z.square();
|
||
|
|
let z2z2 = rhs.z.square();
|
||
|
|
let u1 = self.x * z2z2;
|
||
|
|
let u2 = rhs.x * z1z1;
|
||
|
|
let s1 = self.y * rhs.z * z2z2;
|
||
|
|
let s2 = rhs.y * self.z * z1z1;
|
||
|
|
|
||
|
|
if u1 == u2 {
|
||
|
|
return if s1 == s2 {
|
||
|
|
self.double()
|
||
|
|
} else {
|
||
|
|
Self::IDENTITY
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
let h = u2 - u1;
|
||
|
|
let i = double(h).square();
|
||
|
|
let j = h * i;
|
||
|
|
let slope_num = double(s2 - s1);
|
||
|
|
let v = u1 * i;
|
||
|
|
let x = slope_num.square() - j - double(v);
|
||
|
|
let y = slope_num * (v - x) - double(s1 * j);
|
||
|
|
let z = ((self.z + rhs.z).square() - z1z1 - z2z2) * h;
|
||
|
|
|
||
|
|
Self { x, y, z }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Sub for ProjectivePoint {
|
||
|
|
type Output = Self;
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn sub(self, rhs: Self) -> Self::Output {
|
||
|
|
self + (-rhs)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Neg for ProjectivePoint {
|
||
|
|
type Output = Self;
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn neg(self) -> Self::Output {
|
||
|
|
Self {
|
||
|
|
x: self.x,
|
||
|
|
y: -self.y,
|
||
|
|
z: self.z,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn double(x: FieldElement) -> FieldElement {
|
||
|
|
x + x
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn triple(x: FieldElement) -> FieldElement {
|
||
|
|
x + x + x
|
||
|
|
}
|
||
|
|
|
||
|
|
fn generator_window8_table() -> &'static [[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS] {
|
||
|
|
static TABLE: OnceLock<Box<[[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS]>> =
|
||
|
|
OnceLock::new();
|
||
|
|
|
||
|
|
TABLE
|
||
|
|
.get_or_init(|| build_window8_table(ProjectivePoint::GENERATOR))
|
||
|
|
.as_ref()
|
||
|
|
}
|
||
|
|
|
||
|
|
fn generator_window4_table() -> &'static [AffinePoint; SHAMIR_WINDOW_POINTS] {
|
||
|
|
static TABLE: OnceLock<[AffinePoint; SHAMIR_WINDOW_POINTS]> = OnceLock::new();
|
||
|
|
|
||
|
|
TABLE.get_or_init(|| window4_table(AffinePoint::GENERATOR))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn window4_table(base: AffinePoint) -> [AffinePoint; SHAMIR_WINDOW_POINTS] {
|
||
|
|
let mut projective = [ProjectivePoint::IDENTITY; SHAMIR_WINDOW_POINTS];
|
||
|
|
|
||
|
|
for i in 1..SHAMIR_WINDOW_POINTS {
|
||
|
|
projective[i] = projective[i - 1].add_mixed(base);
|
||
|
|
}
|
||
|
|
|
||
|
|
batch_normalize(projective)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn batch_normalize<const N: usize>(points: [ProjectivePoint; N]) -> [AffinePoint; N] {
|
||
|
|
let mut products = [FieldElement::ONE; N];
|
||
|
|
let mut acc = FieldElement::ONE;
|
||
|
|
|
||
|
|
for (i, point) in points.iter().enumerate() {
|
||
|
|
products[i] = acc;
|
||
|
|
if !point.is_identity() {
|
||
|
|
acc = acc * point.z;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let Some(mut acc_inverse) = acc.invert() else {
|
||
|
|
return [AffinePoint::IDENTITY; N];
|
||
|
|
};
|
||
|
|
let mut out = [AffinePoint::IDENTITY; N];
|
||
|
|
|
||
|
|
for i in (0..N).rev() {
|
||
|
|
let point = points[i];
|
||
|
|
if point.is_identity() {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
let z_inverse = acc_inverse * products[i];
|
||
|
|
acc_inverse = acc_inverse * point.z;
|
||
|
|
let z_inverse2 = z_inverse.square();
|
||
|
|
out[i] = AffinePoint {
|
||
|
|
x: point.x * z_inverse2,
|
||
|
|
y: point.y * z_inverse2 * z_inverse,
|
||
|
|
infinity: false,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
out
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn double_n(mut point: ProjectivePoint, count: usize) -> ProjectivePoint {
|
||
|
|
for _ in 0..count {
|
||
|
|
point = point.double();
|
||
|
|
}
|
||
|
|
|
||
|
|
point
|
||
|
|
}
|
||
|
|
|
||
|
|
fn build_window8_table(
|
||
|
|
mut base: ProjectivePoint,
|
||
|
|
) -> Box<[[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS]> {
|
||
|
|
let mut rows = Vec::with_capacity(BASE_WINDOWS);
|
||
|
|
|
||
|
|
for _ in 0..BASE_WINDOWS {
|
||
|
|
rows.push(projective_window8_table(base));
|
||
|
|
|
||
|
|
for _ in 0..8 {
|
||
|
|
base = base.double();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
rows.into_boxed_slice()
|
||
|
|
.try_into()
|
||
|
|
.expect("fixed-point table has the expected number of rows")
|
||
|
|
}
|
||
|
|
|
||
|
|
fn projective_window8_table(base: ProjectivePoint) -> [AffinePoint; BASE_WINDOW_POINTS] {
|
||
|
|
let mut projective = [ProjectivePoint::IDENTITY; BASE_WINDOW_POINTS];
|
||
|
|
let mut multiple = ProjectivePoint::IDENTITY;
|
||
|
|
|
||
|
|
for entry in projective.iter_mut().skip(1) {
|
||
|
|
multiple = multiple + base;
|
||
|
|
*entry = multiple;
|
||
|
|
}
|
||
|
|
|
||
|
|
batch_normalize(projective)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline]
|
||
|
|
fn mul_window8_vartime(
|
||
|
|
table: &[[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS],
|
||
|
|
scalar: &[u8; 32],
|
||
|
|
) -> ProjectivePoint {
|
||
|
|
let mut out = ProjectivePoint::IDENTITY;
|
||
|
|
|
||
|
|
for (window, byte) in scalar.iter().rev().enumerate() {
|
||
|
|
out = out.add_mixed(table[window][*byte as usize]);
|
||
|
|
}
|
||
|
|
|
||
|
|
out
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::{AffinePoint, ProjectivePoint};
|
||
|
|
use crate::field::FieldElement;
|
||
|
|
use p256::{
|
||
|
|
ProjectivePoint as P256ProjectivePoint, Scalar,
|
||
|
|
elliptic_curve::{ff::PrimeField, group::Group, sec1::ToEncodedPoint},
|
||
|
|
};
|
||
|
|
|
||
|
|
const SCALAR: [u8; 32] = [
|
||
|
|
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||
|
|
0x88, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
|
||
|
|
0x77, 0x88,
|
||
|
|
];
|
||
|
|
const SMALL_SCALAR: [u8; 32] = [
|
||
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
|
0, 5,
|
||
|
|
];
|
||
|
|
|
||
|
|
fn assert_matches_p256(rust: ProjectivePoint, p256: P256ProjectivePoint) {
|
||
|
|
let rust_bytes = rust.to_uncompressed().unwrap();
|
||
|
|
let p256_bytes = p256.to_affine().to_encoded_point(false);
|
||
|
|
assert_eq!(rust_bytes.as_slice(), p256_bytes.as_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn generator_matches_p256() {
|
||
|
|
assert_matches_p256(
|
||
|
|
ProjectivePoint::generator(),
|
||
|
|
P256ProjectivePoint::generator(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn parses_and_serializes_generator() {
|
||
|
|
let bytes = ProjectivePoint::generator().to_uncompressed().unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
AffinePoint::from_uncompressed(bytes).unwrap(),
|
||
|
|
AffinePoint::generator()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn projective_equality_ignores_jacobian_scale() {
|
||
|
|
let point = ProjectivePoint::generator().double();
|
||
|
|
let normalized = ProjectivePoint::from_affine(point.to_affine());
|
||
|
|
assert_eq!(point, normalized);
|
||
|
|
|
||
|
|
let z = FieldElement::from_u64(7);
|
||
|
|
let z2 = z.square();
|
||
|
|
let scaled = ProjectivePoint {
|
||
|
|
x: point.x * z2,
|
||
|
|
y: point.y * z2 * z,
|
||
|
|
z: point.z * z,
|
||
|
|
};
|
||
|
|
|
||
|
|
assert_eq!(point, scaled);
|
||
|
|
assert_ne!(point, -point);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn double_matches_p256() {
|
||
|
|
let p256 = P256ProjectivePoint::generator().double();
|
||
|
|
assert_matches_p256(ProjectivePoint::generator().double(), p256);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn add_matches_p256() {
|
||
|
|
let rust_g = ProjectivePoint::generator();
|
||
|
|
let rust_2g = rust_g.double();
|
||
|
|
let p256_g = P256ProjectivePoint::generator();
|
||
|
|
let p256_2g = p256_g.double();
|
||
|
|
|
||
|
|
assert_matches_p256(rust_2g + rust_g, p256_2g + p256_g);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn mixed_add_matches_p256() {
|
||
|
|
let rust_g = ProjectivePoint::generator();
|
||
|
|
let rust_2g = rust_g.double();
|
||
|
|
let p256_g = P256ProjectivePoint::generator();
|
||
|
|
let p256_2g = p256_g.double();
|
||
|
|
|
||
|
|
assert_matches_p256(
|
||
|
|
rust_2g.add_mixed(AffinePoint::generator()),
|
||
|
|
p256_2g + p256_g,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn scalar_mul_matches_p256() {
|
||
|
|
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||
|
|
assert_matches_p256(
|
||
|
|
ProjectivePoint::generator().mul_scalar_vartime(SCALAR),
|
||
|
|
P256ProjectivePoint::generator() * scalar,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn fixed_base_scalar_mul_matches_p256() {
|
||
|
|
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||
|
|
assert_matches_p256(
|
||
|
|
ProjectivePoint::fixed_base_scalar_mul_vartime(SCALAR),
|
||
|
|
P256ProjectivePoint::generator() * scalar,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn double_scalar_mul_matches_p256() {
|
||
|
|
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||
|
|
let point = ProjectivePoint::generator().double().to_affine();
|
||
|
|
let p256_point = P256ProjectivePoint::generator().double();
|
||
|
|
|
||
|
|
assert_matches_p256(
|
||
|
|
ProjectivePoint::double_scalar_mul_vartime(SCALAR, point, SCALAR),
|
||
|
|
(P256ProjectivePoint::generator() * scalar) + (p256_point * scalar),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn multi_scalar_mul_matches_p256() {
|
||
|
|
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||
|
|
let small_scalar = Option::<Scalar>::from(Scalar::from_repr(SMALL_SCALAR.into())).unwrap();
|
||
|
|
let point = ProjectivePoint::generator().double().to_affine();
|
||
|
|
let p256_point = P256ProjectivePoint::generator().double();
|
||
|
|
|
||
|
|
assert_matches_p256(
|
||
|
|
ProjectivePoint::multi_scalar_mul_vartime(
|
||
|
|
&[AffinePoint::generator(), point],
|
||
|
|
&[SCALAR, SMALL_SCALAR],
|
||
|
|
)
|
||
|
|
.unwrap(),
|
||
|
|
(P256ProjectivePoint::generator() * scalar) + (p256_point * small_scalar),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn multi_scalar_mul_rejects_length_mismatch() {
|
||
|
|
assert!(
|
||
|
|
ProjectivePoint::multi_scalar_mul_vartime(&[AffinePoint::generator()], &[]).is_none()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|