Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

k256+primeorder: add BatchInvert and BatchNormalize impls #971

Merged
merged 21 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
}
tarcieri marked this conversation as resolved.
Show resolved Hide resolved

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
Loading