From 95b119639af3d7d638e83af8bc9408d0e9a5ce67 Mon Sep 17 00:00:00 2001 From: PayneJoe Date: Tue, 2 Jul 2024 14:47:15 +0800 Subject: [PATCH 1/5] affine versioned pairing supporting for BitVM --- ec/src/models/bn/g2.rs | 106 +++++++++++++++++++++++++++++++--- ec/src/models/bn/mod.rs | 103 ++++++++++++++++++++++++++++++++- ec/src/pairing.rs | 24 ++++++++ test-templates/src/pairing.rs | 67 +++++++++++++++++++-- 4 files changed, 288 insertions(+), 12 deletions(-) diff --git a/ec/src/models/bn/g2.rs b/ec/src/models/bn/g2.rs index b8444b2a2..db0d74e68 100644 --- a/ec/src/models/bn/g2.rs +++ b/ec/src/models/bn/g2.rs @@ -40,6 +40,88 @@ pub struct G2HomProjective { z: Fp2, } +impl G2Prepared

{ + fn affine_double_in_place(t: &mut G2Affine

, three_div_two: &P::Fp) -> EllCoeff

{ + // for affine coordinates + // slope: alpha = 3 * x^2 / 2 * y + let mut alpha = t.x.square(); + alpha /= t.y; + alpha.mul_assign_by_fp(&three_div_two); + let bias = t.y - alpha * t.x; + + // update T + // T.x = alpha^2 - 2 * t.x + // T.y = -bias - alpha * T.x + let tx = alpha.square() - t.x.double(); + t.y = -bias - alpha * tx; + t.x = tx; + + (Fp2::::ONE, alpha, -bias) + } + + fn affine_add_in_place(t: &mut G2Affine

, q: &G2Affine

) -> EllCoeff

{ + // alpha = (t.y - q.y) / (t.x - q.x) + // bias = t.y - alpha * t.x + let alpha = (t.y - q.y) / (t.x - q.x); + let bias = t.y - alpha * t.x; + + // update T + // T.x = alpha^2 - t.x - q.x + // T.y = -bias - alpha * T.x + let tx = alpha.square() - t.x - q.x; + t.y = -bias - alpha * tx; + t.x = tx; + + (Fp2::::ONE, alpha, -bias) + } + + /// !!! this method cannot be used directly for users, so we need reuse the `from` trait already exists + fn from_affine(q: G2Affine

) -> Self { + if q.infinity { + G2Prepared { + ell_coeffs: vec![], + infinity: true, + } + } else { + // let two_inv = P::Fp::one().double().inverse().unwrap(); + let two_inv = P::Fp::one().double().inverse().unwrap(); + let three_div_two = (P::Fp::one().double() + P::Fp::one()) * two_inv; + + let mut ell_coeffs = vec![]; + let mut r = q.clone(); + + let neg_q = -q; + + for bit in P::ATE_LOOP_COUNT.iter().rev().skip(1) { + ell_coeffs.push(Self::affine_double_in_place(&mut r, &three_div_two)); + + match bit { + 1 => ell_coeffs.push(Self::affine_add_in_place(&mut r, &q)), + -1 => ell_coeffs.push(Self::affine_add_in_place(&mut r, &neg_q)), + _ => continue, + } + } + + let q1 = mul_by_char::

(q); + let mut q2 = mul_by_char::

(q1); + + if P::X_IS_NEGATIVE { + r.y = -r.y; + } + + q2.y = -q2.y; + + ell_coeffs.push(Self::affine_add_in_place(&mut r, &q1)); + ell_coeffs.push(Self::affine_add_in_place(&mut r, &q2)); + + Self { + ell_coeffs, + infinity: false, + } + } + } +} + impl G2HomProjective

{ pub fn double_in_place(&mut self, two_inv: &P::Fp) -> EllCoeff

{ // Formula for line function when working with @@ -96,8 +178,24 @@ impl Default for G2Prepared

{ } } +/// !!! affine mode is for the purpose of verifying pairings impl From> for G2Prepared

{ fn from(q: G2Affine

) -> Self { + if q.infinity { + G2Prepared { + ell_coeffs: vec![], + infinity: true, + } + } else { + Self::from_affine(q) + } + } +} + +/// !!! projective mode is for the purpose of computing pairings +impl From> for G2Prepared

{ + fn from(q: G2Projective

) -> Self { + let q = q.into_affine(); if q.infinity { G2Prepared { ell_coeffs: vec![], @@ -144,12 +242,6 @@ impl From> for G2Prepared

{ } } -impl From> for G2Prepared

{ - fn from(q: G2Projective

) -> Self { - q.into_affine().into() - } -} - impl<'a, P: BnConfig> From<&'a G2Affine

> for G2Prepared

{ fn from(other: &'a G2Affine

) -> Self { (*other).into() @@ -158,7 +250,7 @@ impl<'a, P: BnConfig> From<&'a G2Affine

> for G2Prepared

{ impl<'a, P: BnConfig> From<&'a G2Projective

> for G2Prepared

{ fn from(q: &'a G2Projective

) -> Self { - q.into_affine().into() + (*q).into() } } diff --git a/ec/src/models/bn/mod.rs b/ec/src/models/bn/mod.rs index b09e61b04..ca46c1f3c 100644 --- a/ec/src/models/bn/mod.rs +++ b/ec/src/models/bn/mod.rs @@ -102,6 +102,71 @@ pub trait BnConfig: 'static + Sized { MillerLoopOutput(f) } + fn multi_miller_loop_affine( + a: impl IntoIterator>>, + b: impl IntoIterator>>, + ) -> MillerLoopOutput> { + let mut pairs = a + .into_iter() + .zip_eq(b) + .filter_map(|(p, q)| { + // if input q is projective coordinates, then we will enter `into`` computing pairing mode + // otherwise if input q is affine coordinates, then we will enter `into` verifying pairing mode + let (p, q) = (p.into(), q.into()); + match !p.is_zero() && !q.is_zero() { + true => Some(( + -p.0.x / p.0.y, + p.0.y.inverse().unwrap(), + q.ell_coeffs.into_iter(), + )), + false => None, + } + }) + .collect::>(); + + let mut f = cfg_chunks_mut!(pairs, 4) + .map(|pairs| { + let mut f = as Pairing>::TargetField::one(); + for i in (1..Self::ATE_LOOP_COUNT.len()).rev() { + if i != Self::ATE_LOOP_COUNT.len() - 1 { + f.square_in_place(); + } + + for (coeff_1, coeff_2, coeffs) in pairs.iter_mut() { + Bn::::ell_affine(&mut f, &coeffs.next().unwrap(), &coeff_1, &coeff_2); + } + + let bit = Self::ATE_LOOP_COUNT[i - 1]; + if bit == 1 || bit == -1 { + for (coeff_1, coeff_2, coeffs) in pairs.iter_mut() { + Bn::::ell_affine( + &mut f, + &coeffs.next().unwrap(), + &coeff_1, + &coeff_2, + ); + } + } + } + f + }) + .product::< as Pairing>::TargetField>(); + + if Self::X_IS_NEGATIVE { + f.cyclotomic_inverse_in_place(); + } + + for (coeff_1, coeff_2, coeffs) in &mut pairs { + Bn::::ell_affine(&mut f, &coeffs.next().unwrap(), &coeff_1, &coeff_2); + } + + for (coeff_1, coeff_2, coeffs) in &mut pairs { + Bn::::ell_affine(&mut f, &coeffs.next().unwrap(), &coeff_1, &coeff_2); + } + + MillerLoopOutput(f) + } + #[allow(clippy::let_and_return)] fn final_exponentiation(f: MillerLoopOutput>) -> Option>> { // Easy part: result = elt^((q^6-1)*(q^2+1)). @@ -180,7 +245,7 @@ pub use self::{ pub struct Bn(PhantomData P>); impl Bn

{ - /// Evaluates the line function at point p. + /// Evaluates the line function at point p, where the line function is in projective mode fn ell(f: &mut Fp12, coeffs: &g2::EllCoeff

, p: &G1Affine

) { let mut c0 = coeffs.0; let mut c1 = coeffs.1; @@ -200,6 +265,35 @@ impl Bn

{ } } + /// Evaluates the line function at point p, where the line function is in affine mode + /// input: + /// f, Fq12 + /// coeffs, (1, alpha, bias) + /// x' = -p.x / p.y + /// y' = 1 / p.y + /// output: + /// f = f * f_Q(P)', where f_Q(P)' is a vairant of f_Q(P), f_Q(P) = y' * f_Q(P) + fn ell_affine(f: &mut Fp12, coeffs: &g2::EllCoeff

, xx: &P::Fp, yy: &P::Fp) { + // c0 is a trival value 1 + let c0 = coeffs.0; + let mut c1 = coeffs.1; + let mut c2 = coeffs.2; + + match P::TWIST_TYPE { + TwistType::M => { + c1.mul_assign_by_fp(&xx); + c2.mul_assign_by_fp(&yy); + f.mul_by_014(&c0, &c1, &c2); + }, + // line evaluation is y' * f_Q(P), coefficients are (1, x' * lambda, -y' * bias) + TwistType::D => { + c1.mul_assign_by_fp(&xx); + c2.mul_assign_by_fp(&yy); + f.mul_by_034(&c0, &c1, &(c2)); + }, + } + } + fn exp_by_neg_x(mut f: Fp12) -> Fp12 { f = f.cyclotomic_exp(P::X); if !P::X_IS_NEGATIVE { @@ -227,6 +321,13 @@ impl Pairing for Bn

{ P::multi_miller_loop(a, b) } + fn multi_miller_loop_affine( + a: impl IntoIterator>, + b: impl IntoIterator>, + ) -> MillerLoopOutput { + P::multi_miller_loop_affine(a, b) + } + fn final_exponentiation(f: MillerLoopOutput) -> Option> { P::final_exponentiation(f) } diff --git a/ec/src/pairing.rs b/ec/src/pairing.rs index d92bad976..fd2286489 100644 --- a/ec/src/pairing.rs +++ b/ec/src/pairing.rs @@ -88,6 +88,14 @@ pub trait Pairing: Sized + 'static + Copy + Debug + Sync + Send + Eq { b: impl IntoIterator>, ) -> MillerLoopOutput; + /// Computes the product of Miller loops for some number of (G1, G2) pairs, where the line functions are in affine mode + fn multi_miller_loop_affine( + a: impl IntoIterator>, + b: impl IntoIterator>, + ) -> MillerLoopOutput { + unimplemented!() + } + /// Computes the Miller loop over `a` and `b`. fn miller_loop( a: impl Into, @@ -108,6 +116,14 @@ pub trait Pairing: Sized + 'static + Copy + Debug + Sync + Send + Eq { Self::final_exponentiation(Self::multi_miller_loop(a, b)).unwrap() } + /// Computes a "product" of pairings, where the line functions are in affine mode + fn multi_pairing_affine( + a: impl IntoIterator>, + b: impl IntoIterator>, + ) -> PairingOutput { + Self::final_exponentiation(Self::multi_miller_loop_affine(a, b)).unwrap() + } + /// Performs multiple pairing operations fn pairing( p: impl Into, @@ -115,6 +131,14 @@ pub trait Pairing: Sized + 'static + Copy + Debug + Sync + Send + Eq { ) -> PairingOutput { Self::multi_pairing([p], [q]) } + + /// Performs multiple pairing operations, where the line functions are in affine mode + fn pairing_affine( + p: impl Into, + q: impl Into, + ) -> PairingOutput { + Self::multi_pairing_affine([p], [q]) + } } /// Represents the target group of a pairing. This struct is a diff --git a/test-templates/src/pairing.rs b/test-templates/src/pairing.rs index 755ee22f7..893045fe3 100644 --- a/test-templates/src/pairing.rs +++ b/test-templates/src/pairing.rs @@ -6,8 +6,9 @@ macro_rules! test_pairing { use ark_ec::{pairing::*, CurveGroup, PrimeGroup}; use ark_ff::{CyclotomicMultSubgroup, Field, PrimeField}; use ark_std::{test_rng, One, UniformRand, Zero}; + #[test] - fn test_bilinearity() { + fn test_bilinearity_projective() { for _ in 0..100 { let mut rng = test_rng(); let a: <$Pairing as Pairing>::G1 = UniformRand::rand(&mut rng); @@ -36,7 +37,51 @@ macro_rules! test_pairing { } #[test] - fn test_multi_pairing() { + fn test_bilinearity_affine() { + for _ in 0..100 { + let mut rng = test_rng(); + let a: <$Pairing as Pairing>::G1 = UniformRand::rand(&mut rng); + let b: <$Pairing as Pairing>::G2 = UniformRand::rand(&mut rng); + let s: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); + + let sa = a * s; + let sb = b * s; + + let ans1 = <$Pairing>::pairing_affine(sa, b.into_affine()); + let ans2 = <$Pairing>::pairing_affine(a, sb.into_affine()); + let ans3 = <$Pairing>::pairing_affine(a, b.into_affine()) * s; + + assert_eq!(ans1, ans2); + assert_eq!(ans2, ans3); + + assert_ne!(ans1, PairingOutput::zero()); + assert_ne!(ans2, PairingOutput::zero()); + assert_ne!(ans3, PairingOutput::zero()); + let group_order = <<$Pairing as Pairing>::ScalarField>::characteristic(); + + assert_eq!(ans1.mul_bigint(group_order), PairingOutput::zero()); + assert_eq!(ans2.mul_bigint(group_order), PairingOutput::zero()); + assert_eq!(ans3.mul_bigint(group_order), PairingOutput::zero()); + } + } + + #[test] + fn test_multi_pairing_projective() { + for _ in 0..ITERATIONS { + let rng = &mut test_rng(); + + let a = <$Pairing as Pairing>::G1::rand(rng); + let b = <$Pairing as Pairing>::G2::rand(rng); + let c = <$Pairing as Pairing>::G1::rand(rng); + let d = <$Pairing as Pairing>::G2::rand(rng); + let ans1 = <$Pairing>::pairing(a, b) + &<$Pairing>::pairing(c, d); + let ans2 = <$Pairing>::multi_pairing(&[a, c], &[b, d]); + assert_eq!(ans1, ans2); + } + } + + #[test] + fn test_multi_pairing_affine() { for _ in 0..ITERATIONS { let rng = &mut test_rng(); @@ -44,12 +89,26 @@ macro_rules! test_pairing { let b = <$Pairing as Pairing>::G2::rand(rng).into_affine(); let c = <$Pairing as Pairing>::G1::rand(rng).into_affine(); let d = <$Pairing as Pairing>::G2::rand(rng).into_affine(); - let ans1 = <$Pairing>::pairing(a, b) + &<$Pairing>::pairing(c, d); - let ans2 = <$Pairing>::multi_pairing(&[a, c], &[b, d]); + let ans1 = <$Pairing>::pairing_affine(a, b) + &<$Pairing>::pairing_affine(c, d); + let ans2 = <$Pairing>::multi_pairing_affine(&[a, c], &[b, d]); assert_eq!(ans1, ans2); } } + #[test] + fn test_pairing_affine_vs_projective() { + let rng = &mut test_rng(); + + let a_proj = <$Pairing as Pairing>::G1::rand(rng); + let b_proj = <$Pairing as Pairing>::G2::rand(rng); + let a_affine = a_proj.into_affine(); + let b_affine = b_proj.into_affine(); + + let ans1 = <$Pairing>::multi_pairing(&[a_proj], &[b_proj]); + let ans2 = <$Pairing>::multi_pairing_affine(&[a_affine], &[b_affine]); + assert_eq!(ans1, ans2); + } + #[test] fn test_final_exp() { for _ in 0..ITERATIONS { From 35ff9388940b14edb7d0b8631cbe33b4afa2ff6b Mon Sep 17 00:00:00 2001 From: PayneJoe Date: Thu, 18 Jul 2024 13:16:08 +0800 Subject: [PATCH 2/5] add bilinearity test for multi_pairing --- test-templates/src/pairing.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test-templates/src/pairing.rs b/test-templates/src/pairing.rs index 893045fe3..4fe4af593 100644 --- a/test-templates/src/pairing.rs +++ b/test-templates/src/pairing.rs @@ -7,6 +7,30 @@ macro_rules! test_pairing { use ark_ff::{CyclotomicMultSubgroup, Field, PrimeField}; use ark_std::{test_rng, One, UniformRand, Zero}; + #[test] + fn test_multi_pairing_bilinearity() { + let mut rng = test_rng(); + let g1: <$Pairing as Pairing>::G1 = UniformRand::rand(&mut rng); + let g2: <$Pairing as Pairing>::G2 = UniformRand::rand(&mut rng); + let s1: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); + let s2: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); + let s3 = s1 * s2; + // let ans = <$Pairing>::multi_pairing(&[-g1, g1 * s2, g1], &[g2 * s1, g2, g2 * s3]); + // assert_eq!(ans, PairingOutput::zero()); + let (p1, p2, p3) = (g1.into_affine(), (g1 * s2).into_affine(), g1.into_affine()); + let (q1, q2, q3) = ( + (g2 * s1).into_affine(), + g2.into_affine(), + (g2).into_affine(), + ); + let e1 = <$Pairing>::pairing(p1, q1); + let e2 = <$Pairing>::pairing(p2, q2); + let e3 = <$Pairing>::pairing(p3, q3); + let e33 = <$Pairing>::multi_pairing(&[p1, p2], &[q1, q2]); + assert_eq!(e1 + e2, e3 * s3); + // assert_eq!(e1 + e2, e33); + } + #[test] fn test_bilinearity_projective() { for _ in 0..100 { From e2bf2147b40f62443f258b03d681fe4aa688e403 Mon Sep 17 00:00:00 2001 From: PayneJoe Date: Thu, 18 Jul 2024 18:56:05 +0800 Subject: [PATCH 3/5] bilinearity test --- test-templates/src/pairing.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test-templates/src/pairing.rs b/test-templates/src/pairing.rs index 4fe4af593..7bb1896ae 100644 --- a/test-templates/src/pairing.rs +++ b/test-templates/src/pairing.rs @@ -14,21 +14,25 @@ macro_rules! test_pairing { let g2: <$Pairing as Pairing>::G2 = UniformRand::rand(&mut rng); let s1: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); let s2: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); - let s3 = s1 * s2; - // let ans = <$Pairing>::multi_pairing(&[-g1, g1 * s2, g1], &[g2 * s1, g2, g2 * s3]); - // assert_eq!(ans, PairingOutput::zero()); + let s3 = s1 + s2; + let (p1, p2, p3) = (g1.into_affine(), (g1 * s2).into_affine(), g1.into_affine()); let (q1, q2, q3) = ( (g2 * s1).into_affine(), g2.into_affine(), - (g2).into_affine(), + (g2 * s3).into_affine(), ); let e1 = <$Pairing>::pairing(p1, q1); let e2 = <$Pairing>::pairing(p2, q2); let e3 = <$Pairing>::pairing(p3, q3); let e33 = <$Pairing>::multi_pairing(&[p1, p2], &[q1, q2]); - assert_eq!(e1 + e2, e3 * s3); - // assert_eq!(e1 + e2, e33); + assert_eq!(e1 + e2, e3); + assert_eq!(e3, e33); + + let e4 = <$Pairing>::multi_pairing(&[-p1, p2, p3], &[q1, q2, q3]); + let e5 = <$Pairing>::multi_pairing(&[p1, p2, -p3], &[q1, q2, q3]); + assert_eq!(e4, e5); + assert_eq!(e4, PairingOutput::zero()); } #[test] From 9b032a8762ecca42288d6df02ae99e2445d46d9a Mon Sep 17 00:00:00 2001 From: PayneJoe Date: Sat, 20 Jul 2024 20:45:15 +0800 Subject: [PATCH 4/5] code clean --- test-templates/src/pairing.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/test-templates/src/pairing.rs b/test-templates/src/pairing.rs index 7bb1896ae..893045fe3 100644 --- a/test-templates/src/pairing.rs +++ b/test-templates/src/pairing.rs @@ -7,34 +7,6 @@ macro_rules! test_pairing { use ark_ff::{CyclotomicMultSubgroup, Field, PrimeField}; use ark_std::{test_rng, One, UniformRand, Zero}; - #[test] - fn test_multi_pairing_bilinearity() { - let mut rng = test_rng(); - let g1: <$Pairing as Pairing>::G1 = UniformRand::rand(&mut rng); - let g2: <$Pairing as Pairing>::G2 = UniformRand::rand(&mut rng); - let s1: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); - let s2: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); - let s3 = s1 + s2; - - let (p1, p2, p3) = (g1.into_affine(), (g1 * s2).into_affine(), g1.into_affine()); - let (q1, q2, q3) = ( - (g2 * s1).into_affine(), - g2.into_affine(), - (g2 * s3).into_affine(), - ); - let e1 = <$Pairing>::pairing(p1, q1); - let e2 = <$Pairing>::pairing(p2, q2); - let e3 = <$Pairing>::pairing(p3, q3); - let e33 = <$Pairing>::multi_pairing(&[p1, p2], &[q1, q2]); - assert_eq!(e1 + e2, e3); - assert_eq!(e3, e33); - - let e4 = <$Pairing>::multi_pairing(&[-p1, p2, p3], &[q1, q2, q3]); - let e5 = <$Pairing>::multi_pairing(&[p1, p2, -p3], &[q1, q2, q3]); - assert_eq!(e4, e5); - assert_eq!(e4, PairingOutput::zero()); - } - #[test] fn test_bilinearity_projective() { for _ in 0..100 { From 5086bdb618b5021ddc7676fdeefa934ad75e7904 Mon Sep 17 00:00:00 2001 From: PayneJoe Date: Sat, 20 Jul 2024 21:10:08 +0800 Subject: [PATCH 5/5] add test for multi pairing bilinearity --- test-templates/src/pairing.rs | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test-templates/src/pairing.rs b/test-templates/src/pairing.rs index 893045fe3..6bdf72eb3 100644 --- a/test-templates/src/pairing.rs +++ b/test-templates/src/pairing.rs @@ -7,6 +7,50 @@ macro_rules! test_pairing { use ark_ff::{CyclotomicMultSubgroup, Field, PrimeField}; use ark_std::{test_rng, One, UniformRand, Zero}; + #[test] + fn test_multi_pairing_bilinearity_affine() { + let mut rng = test_rng(); + let g1: <$Pairing as Pairing>::G1 = UniformRand::rand(&mut rng); + let g2: <$Pairing as Pairing>::G2 = UniformRand::rand(&mut rng); + let s1: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); + let s2: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); + let s3 = s1 + s2; + + let (p1, p2, p3) = (g1.into_affine(), (g1 * s2).into_affine(), g1.into_affine()); + // affine mode + let (q1, q2, q3) = ( + (g2 * s1).into_affine(), + g2.into_affine(), + (g2 * s3).into_affine(), + ); + let e1 = <$Pairing>::pairing_affine(p1, q1); + let e2 = <$Pairing>::pairing_affine(p2, q2); + let e3 = <$Pairing>::pairing_affine(p3, q3); + let e33 = <$Pairing>::multi_pairing_affine(&[p1, p2], &[q1, q2]); + assert_eq!(e1 + e2, e3); + assert_eq!(e3, e33); + } + + #[test] + fn test_multi_pairing_bilinearity_projective() { + let mut rng = test_rng(); + let g1: <$Pairing as Pairing>::G1 = UniformRand::rand(&mut rng); + let g2: <$Pairing as Pairing>::G2 = UniformRand::rand(&mut rng); + let s1: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); + let s2: <$Pairing as Pairing>::ScalarField = UniformRand::rand(&mut rng); + let s3 = s1 + s2; + + let (p1, p2, p3) = (g1.into_affine(), (g1 * s2).into_affine(), g1.into_affine()); + // projective mode + let (q1, q2, q3) = (g2 * s1, g2, g2 * s3); + let e1 = <$Pairing>::pairing(p1, q1); + let e2 = <$Pairing>::pairing(p2, q2); + let e3 = <$Pairing>::pairing(p3, q3); + let e33 = <$Pairing>::multi_pairing(&[p1, p2], &[q1, q2]); + assert_eq!(e1 + e2, e3); + assert_eq!(e3, e33); + } + #[test] fn test_bilinearity_projective() { for _ in 0..100 {