From 4bcf87de229f22f041aba447433d9c64763b4cdb Mon Sep 17 00:00:00 2001 From: mmagician Date: Sun, 16 Oct 2022 19:13:57 +0200 Subject: [PATCH] Faster cofactor clearing for G1 & G2 of bls12-381 + benchmarking (#103) --- bls12_381/src/curves/g1.rs | 53 +++++++++-- bls12_381/src/curves/g2.rs | 89 ++++++++++++++++++- cp6_782/src/curves/mod.rs | 6 +- curve-constraint-tests/src/lib.rs | 6 +- .../src/curves/mod.rs | 4 +- 5 files changed, 145 insertions(+), 13 deletions(-) diff --git a/bls12_381/src/curves/g1.rs b/bls12_381/src/curves/g1.rs index 8f2e1b4..6f15420 100644 --- a/bls12_381/src/curves/g1.rs +++ b/bls12_381/src/curves/g1.rs @@ -1,3 +1,4 @@ +use crate::*; use ark_ec::{ bls12, bls12::Bls12Parameters, @@ -5,11 +6,8 @@ use ark_ec::{ short_weierstrass::{Affine, SWCurveConfig}, AffineRepr, Group, }; -use ark_ff::{Field, MontFp, Zero}; -use ark_std::ops::Neg; - -use crate::*; - +use ark_ff::{Field, MontFp, PrimeField, Zero}; +use ark_std::{ops::Neg, One}; pub type G1Affine = bls12::G1Affine; pub type G1Projective = bls12::G1Projective; @@ -62,6 +60,21 @@ impl SWCurveConfig for Parameters { let endomorphism_p = endomorphism(p); minus_x_squared_times_p.eq(&endomorphism_p) } + + #[inline] + fn clear_cofactor(p: &G1Affine) -> G1Affine { + // Using the effective cofactor, as explained in + // Section 5 of https://eprint.iacr.org/2019/403.pdf. + // + // It is enough to multiply by (1 - x), instead of (x - 1)^2 / 3 + let h_eff = one_minus_x().into_bigint(); + Parameters::mul_affine(&p, h_eff.as_ref()).into() + } +} + +fn one_minus_x() -> Fr { + const X: Fr = Fr::from_sign_and_limbs(!crate::Parameters::X_IS_NEGATIVE, crate::Parameters::X); + Fr::one() - X } /// G1_GENERATOR_X = @@ -83,3 +96,33 @@ pub fn endomorphism(p: &Affine) -> Affine { res.x *= BETA; res } + +#[cfg(test)] +mod test { + + use super::*; + use ark_std::{rand::Rng, UniformRand}; + + fn sample_unchecked() -> Affine { + let mut rng = ark_std::test_rng(); + loop { + let x = Fq::rand(&mut rng); + let greatest = rng.gen(); + + if let Some(p) = Affine::get_point_from_x_unchecked(x, greatest) { + return p; + } + } + } + + #[test] + fn test_cofactor_clearing() { + const SAMPLES: usize = 100; + for _ in 0..SAMPLES { + let p: Affine = sample_unchecked(); + let p = p.clear_cofactor(); + assert!(p.is_on_curve()); + assert!(p.is_in_correct_subgroup_assuming_on_curve()); + } + } +} diff --git a/bls12_381/src/curves/g2.rs b/bls12_381/src/curves/g2.rs index a3c7db7..35291a0 100644 --- a/bls12_381/src/curves/g2.rs +++ b/bls12_381/src/curves/g2.rs @@ -1,9 +1,11 @@ +use ark_std::ops::Neg; + use ark_ec::{ bls12, bls12::Bls12Parameters, models::CurveConfig, - short_weierstrass::{Affine, SWCurveConfig}, - AffineRepr, + short_weierstrass::{Affine, Projective, SWCurveConfig}, + AffineRepr, CurveGroup, Group, }; use ark_ff::{Field, MontFp, Zero}; @@ -69,6 +71,40 @@ impl SWCurveConfig for Parameters { x_times_point.eq(&p_times_point) } + + #[inline] + fn clear_cofactor(p: &G2Affine) -> G2Affine { + // Based on Section 4.1 of https://eprint.iacr.org/2017/419.pdf + // [h(ψ)]P = [x^2 − x − 1]P + [x − 1]ψ(P) + (ψ^2)(2P) + + // x = -15132376222941642752 + // When multiplying, use -c1 instead, and then negate the result. That's much + // more efficient, since the scalar -c1 has less limbs and a much lower Hamming + // weight. + let x: &'static [u64] = crate::Parameters::X; + let p_projective = p.into_group(); + + // [x]P + let x_p = Parameters::mul_affine(p, &x).neg(); + // ψ(P) + let psi_p = p_power_endomorphism(&p); + // (ψ^2)(2P) + let mut psi2_p2 = double_p_power_endomorphism(&p_projective.double()); + + // tmp = [x]P + ψ(P) + let mut tmp = x_p.clone(); + tmp += &psi_p; + + // tmp2 = [x^2]P + [x]ψ(P) + let mut tmp2: Projective = tmp; + tmp2 = tmp2.mul_bigint(x).neg(); + + // add up all the terms + psi2_p2 += tmp2; + psi2_p2 -= x_p; + psi2_p2 += &-psi_p; + (psi2_p2 - p_projective).into_affine() + } } pub const G2_GENERATOR_X: Fq2 = Fq2::new(G2_GENERATOR_X_C0, G2_GENERATOR_X_C1); @@ -109,6 +145,11 @@ pub const P_POWER_ENDOMORPHISM_COEFF_1: Fq2 = Fq2::new( "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257") ); +pub const DOUBLE_P_POWER_ENDOMORPHISM: Fq2 = Fq2::new( + MontFp!("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436"), + Fq::ZERO +); + pub fn p_power_endomorphism(p: &Affine) -> Affine { // The p-power endomorphism for G2 is defined as follows: // 1. Note that G2 is defined on curve E': y^2 = x^3 + 4(u+1). @@ -135,3 +176,47 @@ pub fn p_power_endomorphism(p: &Affine) -> Affine { res } + +/// For a p-power endomorphism psi(P), compute psi(psi(P)) +pub fn double_p_power_endomorphism(p: &Projective) -> Projective { + let mut res = *p; + + res.x *= DOUBLE_P_POWER_ENDOMORPHISM; + res.y = res.y.neg(); + + res +} + +#[cfg(test)] +mod test { + + use super::*; + use ark_std::UniformRand; + + #[test] + fn test_cofactor_clearing() { + // multiplying by h_eff and clearing the cofactor by the efficient + // endomorphism-based method should yield the same result. + let h_eff: &'static [u64] = &[ + 0xe8020005aaa95551, + 0x59894c0adebbf6b4, + 0xe954cbc06689f6a3, + 0x2ec0ec69d7477c1a, + 0x6d82bf015d1212b0, + 0x329c2f178731db95, + 0x9986ff031508ffe1, + 0x88e2a8e9145ad768, + 0x584c6a0ea91b3528, + 0xbc69f08f2ee75b3, + ]; + + let mut rng = ark_std::test_rng(); + const SAMPLES: usize = 10; + for _ in 0..SAMPLES { + let p = Affine::::rand(&mut rng); + let optimised = p.clear_cofactor().into_group(); + let naive = g2::Parameters::mul_affine(&p, h_eff); + assert_eq!(optimised, naive); + } + } +} diff --git a/cp6_782/src/curves/mod.rs b/cp6_782/src/curves/mod.rs index 8bf111e..2b735ef 100644 --- a/cp6_782/src/curves/mod.rs +++ b/cp6_782/src/curves/mod.rs @@ -1,5 +1,7 @@ -use ark_ec::pairing::{MillerLoopOutput, PairingOutput}; -use ark_ec::{models::short_weierstrass::SWCurveConfig, pairing::Pairing}; +use ark_ec::{ + models::short_weierstrass::SWCurveConfig, + pairing::{MillerLoopOutput, Pairing, PairingOutput}, +}; use ark_ff::{ biginteger::BigInteger832, fields::{BitIteratorBE, Field}, diff --git a/curve-constraint-tests/src/lib.rs b/curve-constraint-tests/src/lib.rs index 02bd89b..304cdba 100755 --- a/curve-constraint-tests/src/lib.rs +++ b/curve-constraint-tests/src/lib.rs @@ -512,8 +512,10 @@ pub mod curves { } pub mod pairing { - use ark_ec::pairing::PairingOutput; - use ark_ec::{pairing::Pairing, CurveGroup}; + use ark_ec::{ + pairing::{Pairing, PairingOutput}, + CurveGroup, + }; use ark_ff::{BitIteratorLE, Field, PrimeField}; use ark_r1cs_std::prelude::*; use ark_relations::r1cs::{ConstraintSystem, SynthesisError}; diff --git a/ed_on_bls12_381_bandersnatch/src/curves/mod.rs b/ed_on_bls12_381_bandersnatch/src/curves/mod.rs index 14d3af8..d96e259 100644 --- a/ed_on_bls12_381_bandersnatch/src/curves/mod.rs +++ b/ed_on_bls12_381_bandersnatch/src/curves/mod.rs @@ -16,8 +16,8 @@ pub type EdwardsProjective = Projective; pub type SWAffine = short_weierstrass::Affine; pub type SWProjective = short_weierstrass::Projective; -/// `bandersnatch` is an incomplete twisted Edwards curve. These curves have equations of -/// the form: ax² + y² = 1 + dx²y². +/// `bandersnatch` is an incomplete twisted Edwards curve. These curves have +/// equations of the form: ax² + y² = 1 + dx²y². /// over some base finite field Fq. /// /// bandersnatch's curve equation: -5x² + y² = 1 + dx²y²