Add implementation of point addition for projective group elements.

This commit is contained in:
Sean Bowe 2019-08-11 17:19:13 -06:00
parent 4678a67ce6
commit f996747fd6
No known key found for this signature in database
GPG Key ID: 95684257D8F8B031
3 changed files with 535 additions and 2 deletions

View File

@ -35,6 +35,9 @@ fn criterion_benchmark(c: &mut Criterion) {
c.bench_function(&format!("{} doubling", name), move |b| {
b.iter(|| black_box(a).double())
});
c.bench_function(&format!("{} addition", name), move |b| {
b.iter(|| black_box(a).add(&a))
});
}
// G2Affine
@ -65,6 +68,9 @@ fn criterion_benchmark(c: &mut Criterion) {
c.bench_function(&format!("{} doubling", name), move |b| {
b.iter(|| black_box(a).double())
});
c.bench_function(&format!("{} addition", name), move |b| {
b.iter(|| black_box(a).add(&a))
});
}
}

244
src/g1.rs
View File

@ -1,8 +1,11 @@
//! This module provides an implementation of the $\mathbb{G}_1$ group of BLS12-381.
use crate::fp::Fp;
use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use crate::fp::Fp;
/// This is an element of $\mathbb{G}_1$ represented in the affine coordinate space.
/// It is ideal to keep elements in this representation to reduce memory usage and
/// improve performance through the use of mixed curve model arithmetic.
@ -192,6 +195,48 @@ impl PartialEq for G1Projective {
}
}
impl<'a> Neg for &'a G1Projective {
type Output = G1Projective;
#[inline]
fn neg(self) -> G1Projective {
G1Projective {
x: self.x,
y: -self.y,
z: self.z,
}
}
}
impl Neg for G1Projective {
type Output = G1Projective;
#[inline]
fn neg(self) -> G1Projective {
-&self
}
}
impl<'a, 'b> Add<&'b G1Projective> for &'a G1Projective {
type Output = G1Projective;
#[inline]
fn add(self, rhs: &'b G1Projective) -> G1Projective {
self.add(rhs)
}
}
impl<'a, 'b> Sub<&'b G1Projective> for &'a G1Projective {
type Output = G1Projective;
#[inline]
fn sub(self, rhs: &'b G1Projective) -> G1Projective {
self + (-rhs)
}
}
impl_binops_additive!(G1Projective, G1Projective);
impl G1Projective {
/// Returns the identity of the group: the point at infinity.
pub fn identity() -> G1Projective {
@ -258,6 +303,75 @@ impl G1Projective {
G1Projective::conditional_select(&tmp, &G1Projective::identity(), self.is_identity())
}
/// Adds this point to another point.
pub fn add(&self, rhs: &G1Projective) -> G1Projective {
// This Jacobian point addition technique is based on the implementation in libsecp256k1,
// which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally.
// If self is the identity, return rhs. Otherwise, return self. The other cases will be
// predicated on neither self nor rhs being the identity.
let f1 = self.is_identity();
let res = G1Projective::conditional_select(self, rhs, f1);
let f2 = rhs.is_identity();
// If neither are the identity but x1 = x2 and y1 != y2, then return the identity
let z = rhs.z.square();
let u1 = self.x * z;
let z = z * rhs.z;
let s1 = self.y * z;
let z = self.z.square();
let u2 = rhs.x * z;
let z = z * self.z;
let s2 = rhs.y * z;
let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2));
let res =
G1Projective::conditional_select(&res, &G1Projective::identity(), (!f1) & (!f2) & f3);
let t = u1 + u2;
let m = s1 + s2;
let rr = t.square();
let m_alt = -u2;
let tt = u1 * m_alt;
let rr = rr + tt;
// Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3.
// libsecp256k1 does this by substituting in an alternative (defined) expression for lambda.
let degenerate = m.is_zero() & rr.is_zero();
let rr_alt = s1 + s1;
let m_alt = m_alt + u1;
let rr_alt = Fp::conditional_select(&rr_alt, &rr, !degenerate);
let m_alt = Fp::conditional_select(&m_alt, &m, !degenerate);
let n = m_alt.square();
let q = n * t;
let n = n.square();
let n = Fp::conditional_select(&n, &m, degenerate);
let t = rr_alt.square();
let z3 = m_alt * self.z * rhs.z; // We allow rhs.z != 1, so we must account for this.
let z3 = z3 + z3;
let q = -q;
let t = t + q;
let x3 = t;
let t = t + t;
let t = t + q;
let t = t * rr_alt;
let t = t + n;
let y3 = -t;
let x3 = x3 + x3;
let x3 = x3 + x3;
let y3 = y3 + y3;
let y3 = y3 + y3;
let tmp = G1Projective {
x: x3,
y: y3,
z: z3,
};
G1Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
}
/// Returns true if this element is the identity (the point at infinity).
#[inline]
pub fn is_identity(&self) -> Choice {
@ -461,3 +575,131 @@ fn test_doubling() {
);
}
}
#[test]
fn test_projective_addition() {
{
let a = G1Projective::identity();
let b = G1Projective::identity();
let c = a + b;
assert!(bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
}
{
let a = G1Projective::identity();
let mut b = G1Projective::generator();
{
let z = Fp::from_raw_unchecked([
0xba7afa1f9a6fe250,
0xfa0f5b595eafe731,
0x3bdc477694c306e7,
0x2149be4b3949fa24,
0x64aa6e0649b2078c,
0x12b108ac33643c3e,
]);
b = G1Projective {
x: b.x * (z.square()),
y: b.y * (z.square() * z),
z,
};
}
let c = a + b;
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
assert!(c == G1Projective::generator());
}
{
let a = G1Projective::identity();
let mut b = G1Projective::generator();
{
let z = Fp::from_raw_unchecked([
0xba7afa1f9a6fe250,
0xfa0f5b595eafe731,
0x3bdc477694c306e7,
0x2149be4b3949fa24,
0x64aa6e0649b2078c,
0x12b108ac33643c3e,
]);
b = G1Projective {
x: b.x * (z.square()),
y: b.y * (z.square() * z),
z,
};
}
let c = b + a;
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
assert!(c == G1Projective::generator());
}
{
let a = G1Projective::generator().double().double(); // 4P
let b = G1Projective::generator().double(); // 2P
let c = a + b;
let mut d = G1Projective::generator();
for _ in 0..5 {
d = d + G1Projective::generator();
}
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
assert!(!bool::from(d.is_identity()));
assert!(bool::from(d.is_on_curve()));
assert_eq!(c, d);
}
// Degenerate case
{
let beta = Fp::from_raw_unchecked([
0xcd03c9e48671f071,
0x5dab22461fcda5d2,
0x587042afd3851b95,
0x8eb60ebe01bacb9e,
0x3f97d6e83d050d2,
0x18f0206554638741,
]);
let beta = beta.square();
let a = G1Projective::generator().double().double();
let b = G1Projective {
x: a.x * beta,
y: -a.y,
z: a.z,
};
assert!(bool::from(a.is_on_curve()));
assert!(bool::from(b.is_on_curve()));
let c = a + b;
assert_eq!(
G1Affine::from(c),
G1Affine::from(G1Projective {
x: Fp::from_raw_unchecked([
0x29e1e987ef68f2d0,
0xc5f3ec531db03233,
0xacd6c4b6ca19730f,
0x18ad9e827bc2bab7,
0x46e3b2c5785cc7a9,
0x7e571d42d22ddd6
]),
y: Fp::from_raw_unchecked([
0x94d117a7e5a539e7,
0x8e17ef673d4b5d22,
0x9d746aaf508a33ea,
0x8c6d883d2516c9a2,
0xbc3b8d5fb0447f7,
0x7bfa4c7210f4f44
]),
z: Fp::one()
})
);
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
}
}
#[test]
fn test_projective_negation_and_subtraction() {
let a = G1Projective::generator().double();
assert_eq!(a + (-a), G1Projective::identity());
assert_eq!(a + (-a), a - a);
}

287
src/g2.rs
View File

@ -1,8 +1,11 @@
//! This module provides an implementation of the $\mathbb{G}_2$ group of BLS12-381.
use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use crate::fp::Fp;
use crate::fp2::Fp2;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
/// This is an element of $\mathbb{G}_2$ represented in the affine coordinate space.
/// It is ideal to keep elements in this representation to reduce memory usage and
@ -223,6 +226,48 @@ impl PartialEq for G2Projective {
}
}
impl<'a> Neg for &'a G2Projective {
type Output = G2Projective;
#[inline]
fn neg(self) -> G2Projective {
G2Projective {
x: self.x,
y: -self.y,
z: self.z,
}
}
}
impl Neg for G2Projective {
type Output = G2Projective;
#[inline]
fn neg(self) -> G2Projective {
-&self
}
}
impl<'a, 'b> Add<&'b G2Projective> for &'a G2Projective {
type Output = G2Projective;
#[inline]
fn add(self, rhs: &'b G2Projective) -> G2Projective {
self.add(rhs)
}
}
impl<'a, 'b> Sub<&'b G2Projective> for &'a G2Projective {
type Output = G2Projective;
#[inline]
fn sub(self, rhs: &'b G2Projective) -> G2Projective {
self + (-rhs)
}
}
impl_binops_additive!(G2Projective, G2Projective);
impl G2Projective {
/// Returns the identity of the group: the point at infinity.
pub fn identity() -> G2Projective {
@ -309,6 +354,75 @@ impl G2Projective {
G2Projective::conditional_select(&tmp, &G2Projective::identity(), self.is_identity())
}
/// Adds this point to another point.
pub fn add(&self, rhs: &G2Projective) -> G2Projective {
// This Jacobian point addition technique is based on the implementation in libsecp256k1,
// which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally.
// If self is the identity, return rhs. Otherwise, return self. The other cases will be
// predicated on neither self nor rhs being the identity.
let f1 = self.is_identity();
let res = G2Projective::conditional_select(self, rhs, f1);
let f2 = rhs.is_identity();
// If neither are the identity but x1 = x2 and y1 != y2, then return the identity
let z = rhs.z.square();
let u1 = self.x * z;
let z = z * rhs.z;
let s1 = self.y * z;
let z = self.z.square();
let u2 = rhs.x * z;
let z = z * self.z;
let s2 = rhs.y * z;
let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2));
let res =
G2Projective::conditional_select(&res, &G2Projective::identity(), (!f1) & (!f2) & f3);
let t = u1 + u2;
let m = s1 + s2;
let rr = t.square();
let m_alt = -u2;
let tt = u1 * m_alt;
let rr = rr + tt;
// Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3.
// libsecp256k1 does this by substituting in an alternative (defined) expression for lambda.
let degenerate = m.is_zero() & rr.is_zero();
let rr_alt = s1 + s1;
let m_alt = m_alt + u1;
let rr_alt = Fp2::conditional_select(&rr_alt, &rr, !degenerate);
let m_alt = Fp2::conditional_select(&m_alt, &m, !degenerate);
let n = m_alt.square();
let q = n * t;
let n = n.square();
let n = Fp2::conditional_select(&n, &m, degenerate);
let t = rr_alt.square();
let z3 = m_alt * self.z * rhs.z; // We allow rhs.z != 1, so we must account for this.
let z3 = z3 + z3;
let q = -q;
let t = t + q;
let x3 = t;
let t = t + t;
let t = t + q;
let t = t * rr_alt;
let t = t + n;
let y3 = -t;
let x3 = x3 + x3;
let x3 = x3 + x3;
let y3 = y3 + y3;
let y3 = y3 + y3;
let tmp = G2Projective {
x: x3,
y: y3,
z: z3,
};
G2Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
}
/// Returns true if this element is the identity (the point at infinity).
#[inline]
pub fn is_identity(&self) -> Choice {
@ -562,3 +676,174 @@ fn test_doubling() {
);
}
}
#[test]
fn test_projective_addition() {
{
let a = G2Projective::identity();
let b = G2Projective::identity();
let c = a + b;
assert!(bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
}
{
let a = G2Projective::identity();
let mut b = G2Projective::generator();
{
let z = Fp2 {
c0: Fp::from_raw_unchecked([
0xba7afa1f9a6fe250,
0xfa0f5b595eafe731,
0x3bdc477694c306e7,
0x2149be4b3949fa24,
0x64aa6e0649b2078c,
0x12b108ac33643c3e,
]),
c1: Fp::from_raw_unchecked([
0x125325df3d35b5a8,
0xdc469ef5555d7fe3,
0x2d716d2443106a9,
0x5a1db59a6ff37d0,
0x7cf7784e5300bb8f,
0x16a88922c7a5e844,
]),
};
b = G2Projective {
x: b.x * (z.square()),
y: b.y * (z.square() * z),
z,
};
}
let c = a + b;
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
assert!(c == G2Projective::generator());
}
{
let a = G2Projective::identity();
let mut b = G2Projective::generator();
{
let z = Fp2 {
c0: Fp::from_raw_unchecked([
0xba7afa1f9a6fe250,
0xfa0f5b595eafe731,
0x3bdc477694c306e7,
0x2149be4b3949fa24,
0x64aa6e0649b2078c,
0x12b108ac33643c3e,
]),
c1: Fp::from_raw_unchecked([
0x125325df3d35b5a8,
0xdc469ef5555d7fe3,
0x2d716d2443106a9,
0x5a1db59a6ff37d0,
0x7cf7784e5300bb8f,
0x16a88922c7a5e844,
]),
};
b = G2Projective {
x: b.x * (z.square()),
y: b.y * (z.square() * z),
z,
};
}
let c = b + a;
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
assert!(c == G2Projective::generator());
}
{
let a = G2Projective::generator().double().double(); // 4P
let b = G2Projective::generator().double(); // 2P
let c = a + b;
let mut d = G2Projective::generator();
for _ in 0..5 {
d = d + G2Projective::generator();
}
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
assert!(!bool::from(d.is_identity()));
assert!(bool::from(d.is_on_curve()));
assert_eq!(c, d);
}
// Degenerate case
{
let beta = Fp2 {
c0: Fp::from_raw_unchecked([
0xcd03c9e48671f071,
0x5dab22461fcda5d2,
0x587042afd3851b95,
0x8eb60ebe01bacb9e,
0x3f97d6e83d050d2,
0x18f0206554638741,
]),
c1: Fp::zero(),
};
let beta = beta.square();
let a = G2Projective::generator().double().double();
let b = G2Projective {
x: a.x * beta,
y: -a.y,
z: a.z,
};
assert!(bool::from(a.is_on_curve()));
assert!(bool::from(b.is_on_curve()));
let c = a + b;
assert_eq!(
G2Affine::from(c),
G2Affine::from(G2Projective {
x: Fp2 {
c0: Fp::from_raw_unchecked([
0x705abc799ca773d3,
0xfe132292c1d4bf08,
0xf37ece3e07b2b466,
0x887e1c43f447e301,
0x1e0970d033bc77e8,
0x1985c81e20a693f2
]),
c1: Fp::from_raw_unchecked([
0x1d79b25db36ab924,
0x23948e4d529639d3,
0x471ba7fb0d006297,
0x2c36d4b4465dc4c0,
0x82bbc3cfec67f538,
0x51d2728b67bf952
])
},
y: Fp2 {
c0: Fp::from_raw_unchecked([
0x41b1bbf6576c0abf,
0xb6cc93713f7a0f9a,
0x6b65b43e48f3f01f,
0xfb7a4cfcaf81be4f,
0x3e32dadc6ec22cb6,
0xbb0fc49d79807e3
]),
c1: Fp::from_raw_unchecked([
0x7d1397788f5f2ddf,
0xab2907144ff0d8e8,
0x5b7573e0cdb91f92,
0x4cb8932dd31daf28,
0x62bbfac6db052a54,
0x11f95c16d14c3bbe
])
},
z: Fp2::one()
})
);
assert!(!bool::from(c.is_identity()));
assert!(bool::from(c.is_on_curve()));
}
}
#[test]
fn test_projective_negation_and_subtraction() {
let a = G2Projective::generator().double();
assert_eq!(a + (-a), G2Projective::identity());
assert_eq!(a + (-a), a - a);
}