From 0d2142c001c732c2a5e503b41156806b440d764c Mon Sep 17 00:00:00 2001 From: mmagician Date: Sun, 1 Jan 2023 15:53:39 +0100 Subject: [PATCH] Fast cofactor clearing for BLS12-377 (#141) * add faster cofactor clearing and tests for g1 * add faster cofactor clearing and tests for g2 parameters of endomorphisms are wrong for now * add h_eff to g2 tests for correctness test * improve cofactor tests g2 * add a test for psi(psi(P)) == psi2(P) * fix bls12-377 psi & psi2 computation parameters * rename const to DOUBLE_P_POWER_ENDOMORPHISM_COEFF_0 and make private * fix clippy warnings in changed code * remove bls12-381-specific in line comment * update code comments, make methods private * master should be patched with master * update changelog --- CHANGELOG.md | 2 +- Cargo.toml | 12 +-- bls12_377/src/curves/g1.rs | 60 +++++++++++++-- bls12_377/src/curves/g2.rs | 152 ++++++++++++++++++++++++++++++++++++- bls12_381/src/curves/g2.rs | 56 ++++++++++---- 5 files changed, 251 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ecea87..c691770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ - [\#74](https://github.com/arkworks-rs/curves/pull/74) Use Scott's subgroup membership tests for `G1` and `G2` of BLS12-381. - [\#103](https://github.com/arkworks-rs/curves/pull/103) Faster cofactor clearing for BLS12-381. - [\#107](https://github.com/arkworks-rs/curves/pull/107/) Use 2-NAF of `ATE_LOOP_COUNT` to speed up the Miller loop in MNT curves. - +- [\#141](https://github.com/arkworks-rs/curves/pull/103) Faster cofactor clearing for BLS12-377. ### Bug fixes diff --git a/Cargo.toml b/Cargo.toml index 7474eac..a0fccb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,9 +64,9 @@ debug-assertions = true debug = true [patch.crates-io] -ark-ff = { git = "https://github.com/arkworks-rs/algebra/", branch = "release-0.4" } -ark-ec = { git = "https://github.com/arkworks-rs/algebra/", branch = "release-0.4" } -ark-poly = { git = "https://github.com/arkworks-rs/algebra/", branch = "release-0.4" } -ark-serialize = { git = "https://github.com/arkworks-rs/algebra/", branch = "release-0.4" } -ark-algebra-test-templates = { git = "https://github.com/arkworks-rs/algebra/", branch = "release-0.4" } -ark-r1cs-std = { git = "https://github.com/arkworks-rs/r1cs-std/", branch = "release-0.4" } \ No newline at end of file +ark-ff = { git = "https://github.com/arkworks-rs/algebra/" } +ark-ec = { git = "https://github.com/arkworks-rs/algebra/" } +ark-poly = { git = "https://github.com/arkworks-rs/algebra/" } +ark-serialize = { git = "https://github.com/arkworks-rs/algebra/" } +ark-algebra-test-templates = { git = "https://github.com/arkworks-rs/algebra/" } +ark-r1cs-std = { git = "https://github.com/arkworks-rs/r1cs-std/" } \ No newline at end of file diff --git a/bls12_377/src/curves/g1.rs b/bls12_377/src/curves/g1.rs index f7f6103..025afb9 100644 --- a/bls12_377/src/curves/g1.rs +++ b/bls12_377/src/curves/g1.rs @@ -1,12 +1,15 @@ -use ark_ec::models::{ - short_weierstrass::{Affine as SWAffine, SWCurveConfig}, - twisted_edwards::{ - Affine as TEAffine, MontCurveConfig, Projective as TEProjective, TECurveConfig, +use ark_ec::{ + bls12::Bls12Config, + models::{ + short_weierstrass::{Affine as SWAffine, SWCurveConfig}, + twisted_edwards::{ + Affine as TEAffine, MontCurveConfig, Projective as TEProjective, TECurveConfig, + }, }, CurveConfig, }; -use ark_ff::{Field, MontFp, Zero}; -use core::ops::Neg; +use ark_ff::{Field, MontFp, PrimeField, Zero}; +use ark_std::{ops::Neg, One}; use crate::{Fq, Fr}; @@ -39,6 +42,20 @@ impl SWCurveConfig for Config { fn mul_by_a(_: Self::BaseField) -> Self::BaseField { Self::BaseField::zero() } + + #[inline] + fn clear_cofactor(p: &G1SWAffine) -> G1SWAffine { + // Using the effective cofactor. + // + // It is enough to multiply by (x - 1), instead of (x - 1)^2 / 3 + let h_eff = x_minus_one().into_bigint(); + ::mul_affine(p, h_eff.as_ref()).into() + } +} + +fn x_minus_one() -> Fr { + const X: Fr = Fr::from_sign_and_limbs(!crate::Config::X_IS_NEGATIVE, crate::Config::X); + X - Fr::one() } pub type G1SWAffine = SWAffine; @@ -209,3 +226,34 @@ pub const TE_GENERATOR_X: Fq = MontFp!("7122256953170913722937026889632370569028 /// TE_GENERATOR_Y = /// 6177051365529633638563236407038680211609544222665285371549726196884440490905471891908272386851767077598415378235 pub const TE_GENERATOR_Y: Fq = MontFp!("6177051365529633638563236407038680211609544222665285371549726196884440490905471891908272386851767077598415378235"); + +#[cfg(test)] +mod test { + + use super::*; + use crate::g1; + use ark_std::{rand::Rng, UniformRand}; + + fn sample_unchecked() -> SWAffine { + let mut rng = ark_std::test_rng(); + loop { + let x = Fq::rand(&mut rng); + let greatest = rng.gen(); + + if let Some(p) = SWAffine::get_point_from_x_unchecked(x, greatest) { + return p; + } + } + } + + #[test] + fn test_cofactor_clearing() { + const SAMPLES: usize = 100; + for _ in 0..SAMPLES { + let p: SWAffine = sample_unchecked(); + let p = ::clear_cofactor(&p); + assert!(p.is_on_curve()); + assert!(p.is_in_correct_subgroup_assuming_on_curve()); + } + } +} diff --git a/bls12_377/src/curves/g2.rs b/bls12_377/src/curves/g2.rs index d25265b..b8c604a 100644 --- a/bls12_377/src/curves/g2.rs +++ b/bls12_377/src/curves/g2.rs @@ -1,10 +1,13 @@ use ark_ec::{ + bls12::Bls12Config, models::{short_weierstrass::SWCurveConfig, CurveConfig}, - short_weierstrass::Affine, + short_weierstrass::{Affine, Projective}, + AffineRepr, CurveGroup, Group, }; use ark_ff::{Field, MontFp, Zero}; +use ark_std::ops::Neg; -use crate::{g1, Fq, Fq2, Fr}; +use crate::*; pub type G2Affine = Affine; #[derive(Clone, Default, PartialEq, Eq)] @@ -56,6 +59,36 @@ impl SWCurveConfig for Config { fn mul_by_a(_: Self::BaseField) -> Self::BaseField { Self::BaseField::zero() } + + #[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) + + let x: &'static [u64] = crate::Config::X; + let p_projective = p.into_group(); + + // [x]P + let x_p = Config::mul_affine(p, x); + // ψ(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; + tmp += &psi_p; + + // tmp2 = [x^2]P + [x]ψ(P) + let mut tmp2: Projective = tmp; + tmp2 = tmp2.mul_bigint(x); + + // 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); @@ -76,3 +109,118 @@ pub const G2_GENERATOR_Y_C0: Fq = MontFp!("6316029476829207320938136194393519890 /// G2_GENERATOR_Y_C1 = /// 149157405641012693445398062341192467754805999074082136895788947234480009303640899064710353187729182149407503257491 pub const G2_GENERATOR_Y_C1: Fq = MontFp!("149157405641012693445398062341192467754805999074082136895788947234480009303640899064710353187729182149407503257491"); + +// PSI_X = u^((p-1)/3) +const P_POWER_ENDOMORPHISM_COEFF_0 : Fq2 = Fq2::new( + MontFp!( + "80949648264912719408558363140637477264845294720710499478137287262712535938301461879813459410946" + ), + Fq::ZERO, +); + +// PSI_Y = u^((p-1)/2) +const P_POWER_ENDOMORPHISM_COEFF_1: Fq2 = Fq2::new( + MontFp!( + "216465761340224619389371505802605247630151569547285782856803747159100223055385581585702401816380679166954762214499"), + Fq::ZERO, + ); + +// PSI_2_X = u^((p^2 - 1)/3) +const DOUBLE_P_POWER_ENDOMORPHISM_COEFF_0: Fq2 = Fq2::new( + MontFp!("80949648264912719408558363140637477264845294720710499478137287262712535938301461879813459410945"), + Fq::ZERO + ); + +/// psi(x,y) is the untwist-Frobenius-twist endomorhism on E'(Fq2) +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 + 1/u. + // To map a point (x, y) in E' to (s, t) in E, + // one set s = x * (u ^ (1/3)), t = y * (u ^ (1/2)), + // because E: y^2 = x^3 + 1. + // 2. Apply the Frobenius endomorphism (s, t) => (s', t'), + // another point on curve E, where s' = s^p, t' = t^p. + // 3. Map the point from E back to E'; that is, + // one set x' = s' / ((u) ^ (1/3)), y' = t' / ((u) ^ (1/2)). + // + // To sum up, it maps + // (x,y) -> (x^p * (u ^ ((p-1)/3)), y^p * (u ^ ((p-1)/2))) + // as implemented in the code as follows. + + let mut res = *p; + res.x.frobenius_map_in_place(1); + res.y.frobenius_map_in_place(1); + + res.x *= P_POWER_ENDOMORPHISM_COEFF_0; + res.y *= P_POWER_ENDOMORPHISM_COEFF_1; + + res +} + +/// For a p-power endomorphism psi(P), compute psi(psi(P)) +fn double_p_power_endomorphism(p: &Projective) -> Projective { + // p_power_endomorphism(&p_power_endomorphism(&p.into_affine())).into() + let mut res = *p; + + res.x *= DOUBLE_P_POWER_ENDOMORPHISM_COEFF_0; + // u^((p^2 - 1)/2) == -1 + res.y = res.y.neg(); + + 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 x1 = Fq::rand(&mut rng); + let x2 = Fq::rand(&mut rng); + let greatest = rng.gen(); + let x = Fq2::new(x1, x2); + + if let Some(p) = Affine::get_point_from_x_unchecked(x, greatest) { + return p; + } + } + } + + #[test] + fn test_psi_2() { + let p = sample_unchecked(); + let psi_p = p_power_endomorphism(&p); + let psi2_p_composed = p_power_endomorphism(&psi_p); + let psi2_p_optimised = double_p_power_endomorphism(&p.into()); + + assert_eq!(psi2_p_composed, psi2_p_optimised); + } + + #[test] + fn test_cofactor_clearing() { + let h_eff = &[ + 0x1e34800000000000, + 0xcf664765b0000003, + 0x8e8e73ad8a538800, + 0x78ba279637388559, + 0xb85860aaaad29276, + 0xf7ee7c4b03103b45, + 0x8f6ade35a5c7d769, + 0xa951764c46f4edd2, + 0x53648d3d9502abfb, + 0x1f60243677e306, + ]; + const SAMPLES: usize = 10; + for _ in 0..SAMPLES { + let p: Affine = sample_unchecked(); + let optimised = p.clear_cofactor(); + let naive = g2::Config::mul_affine(&p, h_eff); + assert_eq!(optimised.into_group(), naive); + assert!(optimised.is_on_curve()); + assert!(optimised.is_in_correct_subgroup_assuming_on_curve()); + } + } +} diff --git a/bls12_381/src/curves/g2.rs b/bls12_381/src/curves/g2.rs index 7ebfb57..03fe65f 100644 --- a/bls12_381/src/curves/g2.rs +++ b/bls12_381/src/curves/g2.rs @@ -200,11 +200,8 @@ pub const G2_GENERATOR_Y_C0: Fq = MontFp!("1985150602287291935568054521177171638 /// 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582 pub const G2_GENERATOR_Y_C1: Fq = MontFp!("927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582"); -// psi(x,y) = (x**p * PSI_X, y**p * PSI_Y) is the Frobenius composed -// with the quadratic twist and its inverse - // PSI_X = 1/(u+1)^((p-1)/3) -pub const P_POWER_ENDOMORPHISM_COEFF_0 : Fq2 = Fq2::new( +const P_POWER_ENDOMORPHISM_COEFF_0 : Fq2 = Fq2::new( Fq::ZERO, MontFp!( "4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437" @@ -212,28 +209,30 @@ pub const P_POWER_ENDOMORPHISM_COEFF_0 : Fq2 = Fq2::new( ); // PSI_Y = 1/(u+1)^((p-1)/2) -pub const P_POWER_ENDOMORPHISM_COEFF_1: Fq2 = Fq2::new( +const P_POWER_ENDOMORPHISM_COEFF_1: Fq2 = Fq2::new( MontFp!( "2973677408986561043442465346520108879172042883009249989176415018091420807192182638567116318576472649347015917690530"), MontFp!( "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257") ); -pub const DOUBLE_P_POWER_ENDOMORPHISM: Fq2 = Fq2::new( +// PSI_2_X = (u+1)^((1-p^2)/3) +const DOUBLE_P_POWER_ENDOMORPHISM_COEFF_0: Fq2 = Fq2::new( MontFp!("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436"), Fq::ZERO ); -pub fn p_power_endomorphism(p: &Affine) -> Affine { +/// psi(P) is the untwist-Frobenius-twist endomorhism on E'(Fq2) +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). // To map a point (x, y) in E' to (s, t) in E, - // one set s = x / ((u+1) ^ (1/3)), t = y / ((u+1) ^ (1/2)), + // set s = x / ((u+1) ^ (1/3)), t = y / ((u+1) ^ (1/2)), // because E: y^2 = x^3 + 4. // 2. Apply the Frobenius endomorphism (s, t) => (s', t'), // another point on curve E, where s' = s^p, t' = t^p. // 3. Map the point from E back to E'; that is, - // one set x' = s' * ((u+1) ^ (1/3)), y' = t' * ((u+1) ^ (1/2)). + // set x' = s' * ((u+1) ^ (1/3)), y' = t' * ((u+1) ^ (1/2)). // // To sum up, it maps // (x,y) -> (x^p / ((u+1)^((p-1)/3)), y^p / ((u+1)^((p-1)/2))) @@ -252,10 +251,10 @@ pub fn p_power_endomorphism(p: &Affine) -> Affine { } /// For a p-power endomorphism psi(P), compute psi(psi(P)) -pub fn double_p_power_endomorphism(p: &Projective) -> Projective { +fn double_p_power_endomorphism(p: &Projective) -> Projective { let mut res = *p; - res.x *= DOUBLE_P_POWER_ENDOMORPHISM; + res.x *= DOUBLE_P_POWER_ENDOMORPHISM_COEFF_0; res.y = res.y.neg(); res @@ -265,7 +264,31 @@ pub fn double_p_power_endomorphism(p: &Projective) -> Projective mod test { use super::*; - use ark_std::UniformRand; + use ark_std::{rand::Rng, UniformRand}; + + fn sample_unchecked() -> Affine { + let mut rng = ark_std::test_rng(); + loop { + let x1 = Fq::rand(&mut rng); + let x2 = Fq::rand(&mut rng); + let greatest = rng.gen(); + let x = Fq2::new(x1, x2); + + if let Some(p) = Affine::get_point_from_x_unchecked(x, greatest) { + return p; + } + } + } + + #[test] + fn test_psi_2() { + let p = sample_unchecked(); + let psi_p = p_power_endomorphism(&p); + let psi2_p_composed = p_power_endomorphism(&psi_p); + let psi2_p_optimised = double_p_power_endomorphism(&p.into()); + + assert_eq!(psi2_p_composed, psi2_p_optimised); + } #[test] fn test_cofactor_clearing() { @@ -284,13 +307,14 @@ mod test { 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 p: Affine = sample_unchecked(); + let optimised = p.clear_cofactor(); let naive = g2::Config::mul_affine(&p, h_eff); - assert_eq!(optimised, naive); + assert_eq!(optimised.into_group(), naive); + assert!(optimised.is_on_curve()); + assert!(optimised.is_in_correct_subgroup_assuming_on_curve()); } } }