Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pairing): BLS12-381 Subgroup Check #559

Closed

Conversation

PatStiles
Copy link
Collaborator

@PatStiles PatStiles commented Sep 19, 2023

Add Naive Subgroup Check to pairing

Description

Description of the pull request changes and motivation.

Addresses part of #543 by adding a naive implementation of a pairing subgroup check leveraging the check_point_is_in_subgroup() from compression. To accomodate this check the following was added:

@MauroToscano @diegokingston I wasn't sure if using a result was the best practice but it seemed simplest at the time. Let me know if you have any suggestions?

  • New feature

Checklist

  • Linked to Github Issue
  • Unit tests added

@PatStiles PatStiles requested review from schouhy, ajgara and a team as code owners September 19, 2023 03:33
@codecov-commenter
Copy link

codecov-commenter commented Sep 19, 2023

Codecov Report

Merging #559 (04c48cc) into main (314fafc) will decrease coverage by 0.01%.
The diff coverage is 95.04%.

@@            Coverage Diff             @@
##             main     #559      +/-   ##
==========================================
- Coverage   95.50%   95.49%   -0.01%     
==========================================
  Files         110      110              
  Lines       19002    19088      +86     
==========================================
+ Hits        18147    18229      +82     
- Misses        855      859       +4     
Files Coverage Δ
crypto/src/commitments/kzg.rs 99.67% <100.00%> (ø)
.../short_weierstrass/curves/bls12_381/compression.rs 95.68% <100.00%> (-0.15%) ⬇️
...urve/short_weierstrass/curves/bls12_381/pairing.rs 99.66% <100.00%> (+0.13%) ⬆️
math/src/errors.rs 20.00% <0.00%> (-2.23%) ⬇️
math/src/elliptic_curve/traits.rs 53.84% <0.00%> (-16.16%) ⬇️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@MauroToscano
Copy link
Collaborator

An error is how it's done in Rust, it's good

Copy link
Collaborator

@MauroToscano MauroToscano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look right. KZG is defined for any G1, G2. But the check is working over the BLS12 381. It also doesn't look right that chek_point_is_in_subgroup works over any shortweierstrass point, but actually, it's only working over the bls 381. The compiler is being tricked into thinking that the function is more generic than what it is

Comment on lines 17 to 24
pub const SUBGROUP_ORDER: U256 =
U256::from_hex_unchecked("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");

pub fn check_point_is_in_subgroup(point: &G1Point) -> bool {
let inf = G1Point::neutral_element();
let aux_point = point.operate_with_self(MODULUS);
pub fn check_point_is_in_subgroup<SW: IsShortWeierstrass>(
point: &ShortWeierstrassProjectivePoint<SW>,
) -> bool {
let inf = ShortWeierstrassProjectivePoint::<SW>::neutral_element();
let aux_point = point.operate_with_self(SUBGROUP_ORDER);
inf == aux_point
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better for this to be in the pairing.rs file

@MauroToscano
Copy link
Collaborator

MauroToscano commented Sep 21, 2023

It doesn't look right. KZG is defined for any G1, G2. But the check is working over the BLS12 381. It also doesn't look right that chek_point_is_in_subgroup works over any shortweierstrass point, but actually, it's only working over the bls 381. The compiler is being tricked into thinking that the function is more generic than what it is

There's also an issue with the orders. G1 and G2 may not have the same order,or at least it's not obvious in the code*. Use the neutral_element of each for the checks as to avoid problems.

point: &ShortWeierstrassProjectivePoint<SW>,
) -> bool {
let inf = ShortWeierstrassProjectivePoint::<SW>::neutral_element();
let aux_point = point.operate_with_self(SUBGROUP_ORDER);
inf == aux_point
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you can use a is a specific optimized method to check whether a point is the neutral element (here). In the case of Short Weierstrass, a point on the curve is the neutral element if and only if its Z coordinate is zero. So there's no need to compare all three coordinates.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@PatStiles
Copy link
Collaborator Author

@MauroToscano @schouhy Given your feedback I elected to add the subgroup check as a trait function for IsGroup. This may lead to deduplication if we have multiple coordinates system. Let me know your thoughts!

Copy link
Contributor

@schouhy schouhy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! Looks good. I left a suggestion and a request to remove a comment.

Comment on lines 14 to 18
fn is_in_subgroup<T: IsUnsignedInteger>(&self, order: T) -> bool {
let aux_point = self.operate_with_self(order);
aux_point.is_neutral_element()
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn is_in_subgroup<T: IsUnsignedInteger>(&self, order: T) -> bool {
let aux_point = self.operate_with_self(order);
aux_point.is_neutral_element()
}
/// This method checks whether the element belongs to the `order`-torsion subgroup.
/// That is, the subgroup formed by all elements `P` such that (in additive notation) `order * P` is the neutral element.
fn is_in_subgroup<T: IsUnsignedInteger>(&self, order: T) -> bool {
self.operate_with_self(order).is_neutral_element()
}

Comment on lines 32 to 33
//NOTE: Should we switch this to an result?
//Should we continue and skip elements if it doesn't work out
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Result is good. Let's remove this comment

@PatStiles
Copy link
Collaborator Author

@schouhy @MauroToscano should be good to go.

@RajeshRk18
Copy link

RajeshRk18 commented Sep 25, 2023

@schouhy @MauroToscano I have already implemented subgroup checks using endomorphism #543 (comment)

Should I make a separate PR titled optimized subgroup checks?

@PatStiles
Copy link
Collaborator Author

@MauroToscano @schouhy I've started to work on adding Jacobian Coordinates so we can add the endomorphism used in gnark Should I continue with this pr or open a new one?

@schouhy
Copy link
Contributor

schouhy commented Sep 27, 2023

@PatStiles is gnark's algorithm is strictly dependant on jacobian coordinates? That seems odd. I would expect it to be independent of the inner representation of the point. I'll check it out if I have time. But it would be useful if you could explain why we need jacobian coordinates for that.

@MauroToscano
Copy link
Collaborator

New PR, keep this one naive and small

@PatStiles
Copy link
Collaborator Author

PatStiles commented Sep 27, 2023

@schouhy I haven't dived very deep into the implementation yet but will do so this afternoon. I noticed first when going over the gnark implementation the addition and mul operations differ. I investigating the alg reference https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl are found they references these slides (pg 17) here which used jacobian coordinates.

One thing to note is this could be a semantic change only as gnark's implementation has both jacobian and extended jacobian coordinates which appear to be whats normally referenced as Jacobian.

Looking at there FromAffine() I think it is projective

Let me know your thoughts!

@RajeshRk18 RajeshRk18 mentioned this pull request Sep 27, 2023
9 tasks
@PatStiles PatStiles changed the title feat(pairing): add naive subgroup check to pairing feat(pairing): BLS12-381 Subgroup Check Oct 9, 2023
Copy link
Contributor

@schouhy schouhy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! Thanks for your work!
I left a few requests. Let me know what you think.

Also, I would move all the code relevant to subgrup membership to the curve.rs file instead of pairing.rs.

"5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe",
);

pub const ENDO_U: BLS12381TwistCurveFieldElement =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add docs about what these constant represent and add references to their sources. Also, prefer more declarative variable names for code readability: e.g. TWIST_ENDOMORPHISM_U

]);

impl ShortWeierstrassProjectivePoint<BLS12381Curve> {
/// Returns phi(p) where `phi: (x,y)->(ux,y)` and `u` is the Cube Root of Unity in `G_1`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

roots of unity live in the prime field, not in the group.

Suggested change
/// Returns phi(p) where `phi: (x,y)->(ux,y)` and `u` is the Cube Root of Unity in `G_1`
/// Returns phi(p) where `phi: (x,y)->(ux,y)` and `u` is the Cube Root of Unity in the base prime field

Comment on lines 93 to 96
fn is_on_curve(&self) -> bool {
let [x, y, z] = self.coordinates();
y.square() * z == (x.square() * x) + (z.square() * z * BLS12381TwistCurve::b())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An instance of ShortWeierstrassProjectivePoint<BLS12381TwistCurve> should always satisfy the curve equation. If not, the constructor is failing. No need to check this.
And in any case, the curve has already a definning_equation method that implements this

y.square() * z == (x.square() * x) + (z.square() * z * BLS12381TwistCurve::b())
}

// psi(p) = u o frob o u**-1 where u:E'->E iso from the twist to E
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// psi(p) = u o frob o u**-1 where u:E'->E iso from the twist to E
// psi(p) = u o frob o u**-1, where u is the isomorphism u:E'(𝔽ₚ₆) −> E(𝔽ₚ₁₂) from the twist to E

Comment on lines 99 to 106
fn psi(&self) -> Self {
let [x, y, z] = self.coordinates();
Self::new([
x.conjugate() * ENDO_U,
y.conjugate() * ENDO_V,
z.conjugate(),
])
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This morphism should have minimal polynomial X^2 - tX + q (https://eprint.iacr.org/2022/352.pdf 4.2 equation (7)). Please add a test that checks this in a few points.
Also, please add a reference to the source of the formula.

Comment on lines 108 to 37
// https://eprint.iacr.org/2019/814.pdf, 3.1
// z*psi^3 - psi^2 + 1 = 0 <==> -z*(psi o phi) + phi + 1 = 0
pub fn is_in_subgroup(&self) -> bool {
let res = self
.psi()
.operate_with(&self.operate_with_self(MILLER_LOOP_CONSTANT));
res.is_on_curve() && res.is_neutral_element()
}
}
Copy link
Contributor

@schouhy schouhy Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code doesn't match the description and reference of the comment. You prolly mean https://eprint.iacr.org/2022/352.pdf 4.2. Also, please make it a doc string and explain what z is.

Comment on lines 84 to 88
impl FieldElement<Degree2ExtensionField> {
//TODO: Expose this function in extensions/qaudratic.rs
pub fn conjugate(&self) -> Self {
let [a, b] = self.value();
Self::new([a.clone(), -b])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Degree2ExtensionField is a custom implementation that doesn't use the generic one in extensions/quadratic.rs.
Please move this implementation to field_extension.rs where Degree2ExtensionField is defined

Comment on lines 393 to 402
fn generator_g2_is_on_curve() {
let g = BLS12381TwistCurve::generator();
assert!(g.is_on_curve())
}

#[test]
fn arbitrary_g2_point_is_on_curve() {
let p = BLS12381TwistCurve::generator().operate_with_self(20_u64);
assert!(p.is_on_curve())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These shouldn't be unit tests of pairing. Please move them to curve.rs and use the defining_equation method instead of is_on_curve

Comment on lines 369 to 390
fn generator_g1_is_in_subgroup() {
let g = BLS12381Curve::generator();
assert!(g.is_in_subgroup())
}

#[test]
fn arbitrary_g1_point_is_in_subgroup() {
let p = BLS12381Curve::generator().operate_with_self(20_u64);
assert!(p.is_in_subgroup())
}

#[test]
fn generator_g2_is_in_subgroup() {
let g = BLS12381TwistCurve::generator();
assert!(g.is_in_subgroup())
}

#[test]
fn arbitrary_g2_point_is_in_subgroup() {
let p = BLS12381TwistCurve::generator().operate_with_self(20_u64);
assert!(p.is_in_subgroup())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests of points that are in the curve but not in the subgroup, where the p.is_in_subgroup returns false.

@PatStiles PatStiles closed this Oct 31, 2023
@PatStiles PatStiles force-pushed the feat/pairing_subgroup_check branch from 47f60be to dff5f2d Compare October 31, 2023 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants