Skip to content

Commit

Permalink
k256+primeorder: add BatchInvert and BatchNormalize impls (#971)
Browse files Browse the repository at this point in the history
  • Loading branch information
ycscaly authored Nov 14, 2023
1 parent 46d8e97 commit 8e1fff4
Show file tree
Hide file tree
Showing 18 changed files with 426 additions and 28 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ members = [

[profile.dev]
opt-level = 2

[patch.crates-io]
elliptic-curve = { git = "https://github.com/RustCrypto/traits" }
9 changes: 9 additions & 0 deletions bign256/src/arithmetic/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use core::{
ops::{AddAssign, MulAssign, Neg, SubAssign},
};
use elliptic_curve::bigint::Limb;
use elliptic_curve::ops::Invert;
use elliptic_curve::{
ff::PrimeField,
subtle::{Choice, ConstantTimeEq, CtOption},
Expand Down Expand Up @@ -140,6 +141,14 @@ impl PrimeField for FieldElement {
const DELTA: Self = Self::from_u64(4);
}

impl Invert for FieldElement {
type Output = CtOption<Self>;

fn invert(&self) -> CtOption<Self> {
self.invert()
}
}

#[cfg(test)]
mod tests {
use super::FieldElement;
Expand Down
9 changes: 9 additions & 0 deletions bp256/src/arithmetic/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use core::{
iter::{Product, Sum},
ops::{AddAssign, MulAssign, Neg, SubAssign},
};
use elliptic_curve::ops::Invert;
use elliptic_curve::{
bigint::{ArrayEncoding, Integer, Limb},
ff::PrimeField,
Expand Down Expand Up @@ -288,6 +289,14 @@ impl PrimeField for FieldElement {
}
}

impl Invert for FieldElement {
type Output = CtOption<Self>;

fn invert(&self) -> CtOption<Self> {
self.invert()
}
}

#[cfg(test)]
mod tests {
use super::FieldElement;
Expand Down
9 changes: 9 additions & 0 deletions bp384/src/arithmetic/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use core::{
iter::{Product, Sum},
ops::{AddAssign, MulAssign, Neg, SubAssign},
};
use elliptic_curve::ops::Invert;
use elliptic_curve::{
bigint::{ArrayEncoding, Integer, Limb},
ff::PrimeField,
Expand Down Expand Up @@ -289,6 +290,14 @@ impl PrimeField for FieldElement {
}
}

impl Invert for FieldElement {
type Output = CtOption<Self>;

fn invert(&self) -> CtOption<Self> {
self.invert()
}
}

#[cfg(test)]
mod tests {
use super::FieldElement;
Expand Down
39 changes: 39 additions & 0 deletions k256/src/arithmetic/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use core::{
};
use elliptic_curve::{
ff::{self, Field, PrimeField},
ops::Invert,
rand_core::RngCore,
subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
zeroize::DefaultIsZeroes,
Expand Down Expand Up @@ -245,6 +246,14 @@ impl FieldElement {
}
}

impl Invert for FieldElement {
type Output = CtOption<Self>;

fn invert(&self) -> CtOption<Self> {
self.invert()
}
}

impl Field for FieldElement {
const ZERO: Self = Self::ZERO;
const ONE: Self = Self::ONE;
Expand Down Expand Up @@ -500,8 +509,10 @@ impl<'a> Product<&'a FieldElement> for FieldElement {
#[cfg(test)]
mod tests {
use elliptic_curve::ff::{Field, PrimeField};
use elliptic_curve::ops::BatchInvert;
use num_bigint::{BigUint, ToBigUint};
use proptest::prelude::*;
use rand_core::OsRng;

use super::FieldElement;
use crate::{
Expand All @@ -510,6 +521,9 @@ mod tests {
FieldBytes,
};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

impl From<&BigUint> for FieldElement {
fn from(x: &BigUint) -> Self {
let bytes = biguint_to_bytes(x);
Expand Down Expand Up @@ -672,6 +686,31 @@ mod tests {
assert_eq!((two * &inv_two).normalize(), one);
}

#[test]
fn batch_invert_array() {
let k: FieldElement = FieldElement::random(&mut OsRng);
let l: FieldElement = FieldElement::random(&mut OsRng);

let expected = [k.invert().unwrap(), l.invert().unwrap()];
assert_eq!(
<FieldElement as BatchInvert<_>>::batch_invert(&[k, l]).unwrap(),
expected
);
}

#[test]
#[cfg(feature = "alloc")]
fn batch_invert() {
let k: FieldElement = FieldElement::random(&mut OsRng);
let l: FieldElement = FieldElement::random(&mut OsRng);

let expected = vec![k.invert().unwrap(), l.invert().unwrap()];
let field_elements = vec![k, l];
let res: Vec<_> =
<FieldElement as BatchInvert<_>>::batch_invert(field_elements.as_slice()).unwrap();
assert_eq!(res, expected);
}

#[test]
fn sqrt() {
let one = FieldElement::ONE;
Expand Down
166 changes: 158 additions & 8 deletions k256/src/arithmetic/projective.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use core::{
iter::Sum,
ops::{Add, AddAssign, Neg, Sub, SubAssign},
};
use elliptic_curve::ops::BatchInvert;
use elliptic_curve::{
group::{
ff::Field,
Expand All @@ -18,9 +19,12 @@ use elliptic_curve::{
sec1::{FromEncodedPoint, ToEncodedPoint},
subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
zeroize::DefaultIsZeroes,
Error, Result,
BatchNormalize, Error, Result,
};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

#[rustfmt::skip]
const ENDOMORPHISM_BETA: FieldElement = FieldElement::from_bytes_unchecked(&[
0x7a, 0xe9, 0x6a, 0x2b, 0x65, 0x7c, 0x07, 0x10,
Expand All @@ -34,7 +38,7 @@ const ENDOMORPHISM_BETA: FieldElement = FieldElement::from_bytes_unchecked(&[
pub struct ProjectivePoint {
x: FieldElement,
y: FieldElement,
z: FieldElement,
pub(super) z: FieldElement,
}

impl ProjectivePoint {
Expand Down Expand Up @@ -65,18 +69,20 @@ impl ProjectivePoint {
Self::GENERATOR
}

/// Returns the affine representation of this point, or `None` if it is the identity.
/// Returns the affine representation of this point.
pub fn to_affine(&self) -> AffinePoint {
self.z
.invert()
.map(|zinv| {
let x = self.x * &zinv;
let y = self.y * &zinv;
AffinePoint::new(x.normalize(), y.normalize())
})
.map(|zinv| self.to_affine_internal(zinv))
.unwrap_or_else(|| AffinePoint::IDENTITY)
}

pub(super) fn to_affine_internal(self, zinv: FieldElement) -> AffinePoint {
let x = self.x * &zinv;
let y = self.y * &zinv;
AffinePoint::new(x.normalize(), y.normalize())
}

/// Returns `-self`.
fn neg(&self) -> ProjectivePoint {
ProjectivePoint {
Expand Down Expand Up @@ -250,6 +256,72 @@ impl From<AffinePoint> for ProjectivePoint {
}
}

impl<const N: usize> BatchNormalize<&[ProjectivePoint; N]> for ProjectivePoint {
type Output = [Self::AffineRepr; N];

fn batch_normalize(
points: &[Self; N],
) -> <Self as BatchNormalize<&[ProjectivePoint; N]>>::Output {
let mut zs = [FieldElement::ONE; N];

for i in 0..N {
if points[i].z != FieldElement::ZERO {
// Even a single zero value will fail inversion for the entire batch.
// Put a dummy value (above `FieldElement::ONE`) so inversion succeeds
// and treat that case specially later-on.
zs[i] = points[i].z;
}
}

// This is safe to unwrap since we assured that all elements are non-zero
let zs_inverses = <FieldElement as BatchInvert<_>>::batch_invert(&zs).unwrap();

let mut affine_points = [AffinePoint::IDENTITY; N];
for i in 0..N {
if points[i].z != FieldElement::ZERO {
// If the `z` coordinate is non-zero, we can use it to invert;
// otherwise it defaults to the `IDENTITY` value in initialization.
affine_points[i] = points[i].to_affine_internal(zs_inverses[i])
}
}

affine_points
}
}

#[cfg(feature = "alloc")]
impl BatchNormalize<&[ProjectivePoint]> for ProjectivePoint {
type Output = Vec<Self::AffineRepr>;

fn batch_normalize(points: &[Self]) -> <Self as BatchNormalize<&[ProjectivePoint]>>::Output {
let mut zs: Vec<_> = vec![FieldElement::ONE; points.len()];

for i in 0..points.len() {
if points[i].z != FieldElement::ZERO {
// Even a single zero value will fail inversion for the entire batch.
// Put a dummy value (above `FieldElement::ONE`) so inversion succeeds
// and treat that case specially later-on.
zs[i] = points[i].z;
}
}

// This is safe to unwrap since we assured that all elements are non-zero
let zs_inverses: Vec<_> =
<FieldElement as BatchInvert<_>>::batch_invert(zs.as_slice()).unwrap();

let mut affine_points: Vec<_> = vec![AffinePoint::IDENTITY; points.len()];
for i in 0..points.len() {
if points[i].z != FieldElement::ZERO {
// If the `z` coordinate is non-zero, we can use it to invert;
// otherwise it defaults to the `IDENTITY` value in initialization.
affine_points[i] = points[i].to_affine_internal(zs_inverses[i])
}
}

affine_points.into_iter().collect()
}
}

impl From<&AffinePoint> for ProjectivePoint {
fn from(p: &AffinePoint) -> Self {
Self::from(*p)
Expand Down Expand Up @@ -387,6 +459,14 @@ impl Curve for ProjectivePoint {
fn to_affine(&self) -> AffinePoint {
ProjectivePoint::to_affine(self)
}

#[cfg(feature = "alloc")]
fn batch_normalize(p: &[Self], q: &mut [Self::AffineRepr]) {
assert_eq!(p.len(), q.len());

let affine_points: Vec<_> = <Self as BatchNormalize<_>>::batch_normalize(p);
q.copy_from_slice(&affine_points);
}
}

impl PrimeCurve for ProjectivePoint {
Expand Down Expand Up @@ -609,6 +689,13 @@ mod tests {
Scalar,
};
use elliptic_curve::group::{ff::PrimeField, prime::PrimeCurveAffine};
use elliptic_curve::ops::MulByGenerator;
use elliptic_curve::Field;
use elliptic_curve::{group, BatchNormalize};
use rand_core::OsRng;

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

#[test]
fn affine_to_projective() {
Expand All @@ -627,6 +714,69 @@ mod tests {
));
}

#[test]
fn batch_normalize_array() {
let k: Scalar = Scalar::random(&mut OsRng);
let l: Scalar = Scalar::random(&mut OsRng);
let g = ProjectivePoint::mul_by_generator(&k);
let h = ProjectivePoint::mul_by_generator(&l);

let mut res = [AffinePoint::IDENTITY; 2];
let expected = [g.to_affine(), h.to_affine()];
assert_eq!(
<ProjectivePoint as BatchNormalize<_>>::batch_normalize(&[g, h]),
expected
);

<ProjectivePoint as group::Curve>::batch_normalize(&[g, h], &mut res);
assert_eq!(res, expected);

let expected = [g.to_affine(), AffinePoint::IDENTITY];
assert_eq!(
<ProjectivePoint as BatchNormalize<_>>::batch_normalize(&[
g,
ProjectivePoint::IDENTITY
]),
expected
);

<ProjectivePoint as group::Curve>::batch_normalize(
&[g, ProjectivePoint::IDENTITY],
&mut res,
);
assert_eq!(res, expected);
}

#[test]
#[cfg(feature = "alloc")]
fn batch_normalize_slice() {
let k: Scalar = Scalar::random(&mut OsRng);
let l: Scalar = Scalar::random(&mut OsRng);
let g = ProjectivePoint::mul_by_generator(&k);
let h = ProjectivePoint::mul_by_generator(&l);

let expected = vec![g.to_affine(), h.to_affine()];
let scalars = vec![g, h];
let mut res: Vec<_> =
<ProjectivePoint as BatchNormalize<_>>::batch_normalize(scalars.as_slice());
assert_eq!(res, expected);

<ProjectivePoint as group::Curve>::batch_normalize(&[g, h], res.as_mut());
assert_eq!(res.to_vec(), expected);

let expected = vec![g.to_affine(), AffinePoint::IDENTITY];
let scalars = vec![g, ProjectivePoint::IDENTITY];
res = <ProjectivePoint as BatchNormalize<_>>::batch_normalize(scalars.as_slice());

assert_eq!(res, expected);

<ProjectivePoint as group::Curve>::batch_normalize(
&[g, ProjectivePoint::IDENTITY],
res.as_mut(),
);
assert_eq!(res.to_vec(), expected);
}

#[test]
fn projective_identity_addition() {
let identity = ProjectivePoint::IDENTITY;
Expand Down
Loading

0 comments on commit 8e1fff4

Please sign in to comment.