diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ca94a6..0930e352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Improvements +- [\#74](https://github.com/arkworks-rs/curves/pull/74) Use Scott's subgroup membership tests for `G1` and `G2` of BLS12-381. + ### Bug fixes ## v0.3.0 diff --git a/Cargo.toml b/Cargo.toml index ba7ae624..25ae9434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,3 +56,9 @@ lto = "thin" incremental = true debug-assertions = true debug = true + +# To be removed in the new release. +[patch.crates-io] +ark-ec = { git = "https://github.com/arkworks-rs/algebra" } +ark-ff = { git = "https://github.com/arkworks-rs/algebra" } +ark-serialize = { git = "https://github.com/arkworks-rs/algebra" } diff --git a/bls12_381/src/curves/g1.rs b/bls12_381/src/curves/g1.rs index 2cc63acf..bcd22bf4 100644 --- a/bls12_381/src/curves/g1.rs +++ b/bls12_381/src/curves/g1.rs @@ -1,9 +1,13 @@ use crate::*; use ark_ec::{ bls12, + bls12::Bls12Parameters, models::{ModelParameters, SWModelParameters}, + short_weierstrass_jacobian::GroupAffine, + AffineCurve, ProjectiveCurve, }; -use ark_ff::{field_new, Zero}; +use ark_ff::{biginteger::BigInteger256, field_new, Zero}; +use ark_std::ops::Neg; pub type G1Affine = bls12::G1Affine; pub type G1Projective = bls12::G1Projective; @@ -40,6 +44,25 @@ impl SWModelParameters for Parameters { fn mul_by_a(_: &Self::BaseField) -> Self::BaseField { Self::BaseField::zero() } + + fn is_in_correct_subgroup_assuming_on_curve(p: &GroupAffine) -> bool { + // Algorithm from Section 6 of https://eprint.iacr.org/2021/1130. + // + // Check that endomorphism_p(P) == -[X^2]P + + let x = BigInteger256::new([crate::Parameters::X[0], 0, 0, 0]); + + // An early-out optimization described in Section 6. + // If uP == P but P != point of infinity, then the point is not in the right subgroup. + let x_times_p = p.mul(x); + if x_times_p.eq(p) && !p.infinity { + return false; + } + + let minus_x_squared_times_p = x_times_p.mul(x).neg(); + let endomorphism_p = endomorphism(p); + minus_x_squared_times_p.eq(&endomorphism_p) + } } /// G1_GENERATOR_X = @@ -51,3 +74,14 @@ pub const G1_GENERATOR_X: Fq = field_new!(Fq, "368541675371338701678108831518307 /// 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569 #[rustfmt::skip] pub const G1_GENERATOR_Y: Fq = field_new!(Fq, "1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569"); + +/// BETA is a non-trivial cubic root of unity in Fq. +pub const BETA: Fq = field_new!(Fq, "793479390729215512621379701633421447060886740281060493010456487427281649075476305620758731620350"); + +pub fn endomorphism(p: &GroupAffine) -> GroupAffine { + // Endomorphism of the points on the curve. + // endomorphism_p(x,y) = (BETA * x, y) where BETA is a non-trivial cubic root of unity in Fq. + let mut res = (*p).clone(); + res.x *= BETA; + res +} diff --git a/bls12_381/src/curves/g2.rs b/bls12_381/src/curves/g2.rs index f1147e69..15b4ed6d 100644 --- a/bls12_381/src/curves/g2.rs +++ b/bls12_381/src/curves/g2.rs @@ -1,9 +1,12 @@ use crate::*; +use ark_ec::bls12::Bls12Parameters; use ark_ec::{ bls12, models::{ModelParameters, SWModelParameters}, + short_weierstrass_jacobian::GroupAffine, + AffineCurve, }; -use ark_ff::{field_new, Zero}; +use ark_ff::{biginteger::BigInteger256, field_new, Field, Zero}; pub type G2Affine = bls12::G2Affine; pub type G2Projective = bls12::G2Projective; @@ -51,6 +54,21 @@ impl SWModelParameters for Parameters { fn mul_by_a(_: &Self::BaseField) -> Self::BaseField { Self::BaseField::zero() } + + fn is_in_correct_subgroup_assuming_on_curve(point: &GroupAffine) -> bool { + // Algorithm from Section 4 of https://eprint.iacr.org/2021/1130. + // + // Checks that [p]P = [X]P + + let mut x_times_point = point.mul(BigInteger256([crate::Parameters::X[0], 0, 0, 0])); + if crate::Parameters::X_IS_NEGATIVE { + x_times_point = -x_times_point; + } + + let p_times_point = p_power_endomorphism(point); + + x_times_point.eq(&p_times_point) + } } pub const G2_GENERATOR_X: Fq2 = field_new!(Fq2, G2_GENERATOR_X_C0, G2_GENERATOR_X_C1); @@ -75,3 +93,52 @@ pub const G2_GENERATOR_Y_C0: Fq = field_new!(Fq, "198515060228729193556805452117 /// 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582 #[rustfmt::skip] pub const G2_GENERATOR_Y_C1: Fq = field_new!(Fq, "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 = field_new!( + Fq2, + FQ_ZERO, + field_new!( + Fq, + "4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437" + ) +); + +// PSI_Y = 1/(u+1)^((p-1)/2) +pub const P_POWER_ENDOMORPHISM_COEFF_1: Fq2 = field_new!( + Fq2, + field_new!( + Fq, + "2973677408986561043442465346520108879172042883009249989176415018091420807192182638567116318576472649347015917690530"), + field_new!( + Fq, + "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257") +); + +pub fn p_power_endomorphism(p: &GroupAffine) -> GroupAffine { + // 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)), 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)). + // + // To sum up, it maps + // (x,y) -> (x^p / ((u+1)^((p-1)/3)), y^p / ((u+1)^((p-1)/2))) + // as implemented in the code as follows. + + let mut res = *p; + res.x.frobenius_map(1); + res.y.frobenius_map(1); + + let tmp_x = res.x.clone(); + res.x.c0 = -P_POWER_ENDOMORPHISM_COEFF_0.c1 * &tmp_x.c1; + res.x.c1 = P_POWER_ENDOMORPHISM_COEFF_0.c1 * &tmp_x.c0; + res.y *= P_POWER_ENDOMORPHISM_COEFF_1; + + res +} diff --git a/bls12_381/src/curves/tests.rs b/bls12_381/src/curves/tests.rs index 5ea217da..1ea49a78 100644 --- a/bls12_381/src/curves/tests.rs +++ b/bls12_381/src/curves/tests.rs @@ -1,8 +1,12 @@ #![allow(unused_imports)] -use ark_ec::{models::SWModelParameters, AffineCurve, PairingEngine, ProjectiveCurve}; +use ark_ec::{ + models::SWModelParameters, + short_weierstrass_jacobian::{GroupAffine, GroupProjective}, + AffineCurve, PairingEngine, ProjectiveCurve, +}; use ark_ff::{ fields::{Field, FpParameters, PrimeField, SquareRootField}, - One, Zero, + BitIteratorBE, One, UniformRand, Zero, }; use ark_serialize::CanonicalSerialize; use ark_std::rand::Rng; @@ -11,6 +15,7 @@ use core::ops::{AddAssign, MulAssign}; use crate::{g1, g2, Bls12_381, Fq, Fq12, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective}; use ark_algebra_test_templates::{curves::*, groups::*}; +use ark_ec::group::Group; #[test] fn test_g1_projective_curve() { @@ -115,3 +120,54 @@ fn test_g1_generator_raw() { x.add_assign(&Fq::one()); } } + +#[test] +fn test_g1_endomorphism_beta() { + assert!(g1::BETA.pow(&[3u64]).is_one()); +} + +#[test] +fn test_g1_subgroup_membership_via_endomorphism() { + let mut rng = test_rng(); + let generator = G1Projective::rand(&mut rng).into_affine(); + assert!(generator.is_in_correct_subgroup_assuming_on_curve()); +} + +#[test] +fn test_g1_subgroup_non_membership_via_endomorphism() { + let mut rng = test_rng(); + loop { + let x = Fq::rand(&mut rng); + let greatest = rng.gen(); + + if let Some(p) = G1Affine::get_point_from_x(x, greatest) { + if !p.into_projective().mul(Fr::characteristic()).is_zero() { + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); + return; + } + } + } +} + +#[test] +fn test_g2_subgroup_membership_via_endomorphism() { + let mut rng = test_rng(); + let generator = G2Projective::rand(&mut rng).into_affine(); + assert!(generator.is_in_correct_subgroup_assuming_on_curve()); +} + +#[test] +fn test_g2_subgroup_non_membership_via_endomorphism() { + let mut rng = test_rng(); + loop { + let x = Fq2::rand(&mut rng); + let greatest = rng.gen(); + + if let Some(p) = G2Affine::get_point_from_x(x, greatest) { + if !p.into_projective().mul(Fr::characteristic()).is_zero() { + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); + return; + } + } + } +} diff --git a/curve-benches/src/macros/ec.rs b/curve-benches/src/macros/ec.rs index 3b402111..2820c210 100644 --- a/curve-benches/src/macros/ec.rs +++ b/curve-benches/src/macros/ec.rs @@ -196,6 +196,32 @@ macro_rules! ec_bench { }); } + fn deser_uncompressed(b: &mut $crate::bencher::Bencher) { + use ark_ec::ProjectiveCurve; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + const SAMPLES: usize = 1000; + + let mut rng = ark_std::test_rng(); + + let mut num_bytes = 0; + let tmp = <$projective>::rand(&mut rng).into_affine(); + let v: Vec<_> = (0..SAMPLES) + .flat_map(|_| { + let mut bytes = Vec::with_capacity(1000); + tmp.serialize_uncompressed(&mut bytes).unwrap(); + num_bytes = bytes.len(); + bytes + }) + .collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + let index = count * num_bytes; + <$affine>::deserialize_uncompressed(&v[index..(index + num_bytes)]).unwrap() + }); + } + fn msm_131072(b: &mut $crate::bencher::Bencher) { use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; const SAMPLES: usize = 131072; @@ -224,6 +250,7 @@ macro_rules! ec_bench { deser, ser_unchecked, deser_unchecked, + deser_uncompressed, msm_131072, ); };