diff --git a/benches/groups.rs b/benches/groups.rs index 873eb4485..6fac70a21 100644 --- a/benches/groups.rs +++ b/benches/groups.rs @@ -42,6 +42,11 @@ fn criterion_benchmark(c: &mut Criterion) { let a = G1Projective::generator(); let a_affine = G1Affine::generator(); let s = Scalar::from_raw([1, 2, 3, 4]); + + const N: usize = 10000; + let v = vec![G1Projective::generator(); N]; + let mut q = vec![G1Affine::identity(); N]; + c.bench_function(&format!("{} check on curve", name), move |b| { b.iter(|| black_box(a).is_on_curve()) }); @@ -63,6 +68,12 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function(&format!("{} scalar multiplication", name), move |b| { b.iter(|| black_box(a) * black_box(s)) }); + c.bench_function(&format!("{} batch to affine n={}", name, N), move |b| { + b.iter(|| { + G1Projective::batch_normalize(black_box(&v), black_box(&mut q)); + black_box(&q)[0] + }) + }); } // G2Affine @@ -100,6 +111,11 @@ fn criterion_benchmark(c: &mut Criterion) { let a = G2Projective::generator(); let a_affine = G2Affine::generator(); let s = Scalar::from_raw([1, 2, 3, 4]); + + const N: usize = 10000; + let v = vec![G2Projective::generator(); N]; + let mut q = vec![G2Affine::identity(); N]; + c.bench_function(&format!("{} check on curve", name), move |b| { b.iter(|| black_box(a).is_on_curve()) }); @@ -121,6 +137,12 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function(&format!("{} scalar multiplication", name), move |b| { b.iter(|| black_box(a) * black_box(s)) }); + c.bench_function(&format!("{} batch to affine n={}", name, N), move |b| { + b.iter(|| { + G2Projective::batch_normalize(black_box(&v), black_box(&mut q)); + black_box(&q)[0] + }) + }); } } diff --git a/src/g1.rs b/src/g1.rs index e49e564df..73c6a04c0 100644 --- a/src/g1.rs +++ b/src/g1.rs @@ -737,6 +737,46 @@ impl G1Projective { acc } + /// Converts a batch of `G1Projective` elements into `G1Affine` elements. This + /// function will panic if `p.len() != q.len()`. + pub fn batch_normalize(p: &[Self], q: &mut [G1Affine]) { + assert_eq!(p.len(), q.len()); + + let mut acc = Fp::one(); + for (p, q) in p.iter().zip(q.iter_mut()) { + // We use the `x` field of `G1Affine` to store the product + // of previous z-coordinates seen. + q.x = acc; + + // We will end up skipping all identities in p + acc = Fp::conditional_select(&(acc * p.z), &acc, p.is_identity()); + } + + // This is the inverse, as all z-coordinates are nonzero and the ones + // that are not are skipped. + acc = acc.invert().unwrap(); + + for (p, q) in p.iter().rev().zip(q.iter_mut().rev()) { + let skip = p.is_identity(); + + // Compute tmp = 1/z + let tmp = q.x * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc = Fp::conditional_select(&(acc * p.z), &acc, skip); + + // Set the coordinates to the correct value + let tmp2 = tmp.square(); + let tmp3 = tmp2 * tmp; + + q.x = p.x * tmp2; + q.y = p.y * tmp3; + q.infinity = Choice::from(0u8); + + *q = G1Affine::conditional_select(&q, &G1Affine::identity(), skip); + } + } + /// Returns true if this element is the identity (the point at infinity). #[inline] pub fn is_identity(&self) -> Choice { @@ -1264,3 +1304,42 @@ fn test_is_torsion_free() { assert!(bool::from(G1Affine::identity().is_torsion_free())); assert!(bool::from(G1Affine::generator().is_torsion_free())); } + +#[test] +fn test_batch_normalize() { + let a = G1Projective::generator().double(); + let b = a.double(); + let c = b.double(); + + for a_identity in (0..1).map(|n| n == 1) { + for b_identity in (0..1).map(|n| n == 1) { + for c_identity in (0..1).map(|n| n == 1) { + let mut v = [a, b, c]; + if a_identity { + v[0] = G1Projective::identity() + } + if b_identity { + v[1] = G1Projective::identity() + } + if c_identity { + v[2] = G1Projective::identity() + } + + let mut t = [ + G1Affine::identity(), + G1Affine::identity(), + G1Affine::identity(), + ]; + let expected = [ + G1Affine::from(v[0]), + G1Affine::from(v[1]), + G1Affine::from(v[2]), + ]; + + G1Projective::batch_normalize(&v[..], &mut t[..]); + + assert_eq!(&t[..], &expected[..]); + } + } + } +} diff --git a/src/g2.rs b/src/g2.rs index d3d2b67ba..e21b69832 100644 --- a/src/g2.rs +++ b/src/g2.rs @@ -829,6 +829,46 @@ impl G2Projective { acc } + /// Converts a batch of `G2Projective` elements into `G2Affine` elements. This + /// function will panic if `p.len() != q.len()`. + pub fn batch_normalize(p: &[Self], q: &mut [G2Affine]) { + assert_eq!(p.len(), q.len()); + + let mut acc = Fp2::one(); + for (p, q) in p.iter().zip(q.iter_mut()) { + // We use the `x` field of `G2Affine` to store the product + // of previous z-coordinates seen. + q.x = acc; + + // We will end up skipping all identities in p + acc = Fp2::conditional_select(&(acc * p.z), &acc, p.is_identity()); + } + + // This is the inverse, as all z-coordinates are nonzero and the ones + // that are not are skipped. + acc = acc.invert().unwrap(); + + for (p, q) in p.iter().rev().zip(q.iter_mut().rev()) { + let skip = p.is_identity(); + + // Compute tmp = 1/z + let tmp = q.x * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc = Fp2::conditional_select(&(acc * p.z), &acc, skip); + + // Set the coordinates to the correct value + let tmp2 = tmp.square(); + let tmp3 = tmp2 * tmp; + + q.x = p.x * tmp2; + q.y = p.y * tmp3; + q.infinity = Choice::from(0u8); + + *q = G2Affine::conditional_select(&q, &G2Affine::identity(), skip); + } + } + /// Returns true if this element is the identity (the point at infinity). #[inline] pub fn is_identity(&self) -> Choice { @@ -1512,3 +1552,42 @@ fn test_is_torsion_free() { assert!(bool::from(G2Affine::identity().is_torsion_free())); assert!(bool::from(G2Affine::generator().is_torsion_free())); } + +#[test] +fn test_batch_normalize() { + let a = G2Projective::generator().double(); + let b = a.double(); + let c = b.double(); + + for a_identity in (0..1).map(|n| n == 1) { + for b_identity in (0..1).map(|n| n == 1) { + for c_identity in (0..1).map(|n| n == 1) { + let mut v = [a, b, c]; + if a_identity { + v[0] = G2Projective::identity() + } + if b_identity { + v[1] = G2Projective::identity() + } + if c_identity { + v[2] = G2Projective::identity() + } + + let mut t = [ + G2Affine::identity(), + G2Affine::identity(), + G2Affine::identity(), + ]; + let expected = [ + G2Affine::from(v[0]), + G2Affine::from(v[1]), + G2Affine::from(v[2]), + ]; + + G2Projective::batch_normalize(&v[..], &mut t[..]); + + assert_eq!(&t[..], &expected[..]); + } + } + } +}