Skip to content

Commit 1530d4b

Browse files
feat(ecc): Add subgroup check BLS12-381 using untwist frobenius endomorphism (#649)
* add naive subgroup check to pairing * add naive test * fmt * move subgroup check to IsGroup trait * nits * add jacobian point file * refactor to use endomorphism * nit * Add untwist tests * nit * add out of subgroup checks * add doc comments * fix fmt * add unwraps to groth16 * make imports compatible with std * fmt * nits * ci --------- Co-authored-by: Mauro Toscano <[email protected]>
1 parent 5233228 commit 1530d4b

File tree

9 files changed

+250
-47
lines changed

9 files changed

+250
-47
lines changed

crypto/src/commitments/kzg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ impl<const N: usize, F: IsPrimeField<RepresentativeType = UnsignedInteger<N>>, P
200200
&(alpha_g2.operate_with(&(g2.operate_with_self(x.representative())).neg())),
201201
),
202202
]);
203-
e == FieldElement::one()
203+
e == Ok(FieldElement::one())
204204
}
205205

206206
fn open_batch(

math/src/elliptic_curve/short_weierstrass/curves/bls12_381/compression.rs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
11
use super::field_extension::BLS12381PrimeField;
2-
use crate::cyclic_group::IsGroup;
3-
use crate::elliptic_curve::short_weierstrass::curves::bls12_381::curve::BLS12381Curve;
4-
use crate::elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint;
5-
use crate::field::element::FieldElement;
6-
use crate::unsigned_integer::element::U256;
7-
8-
#[cfg(feature = "std")]
92
use crate::{
10-
elliptic_curve::traits::FromAffine, errors::ByteConversionError, traits::ByteConversion,
3+
elliptic_curve::short_weierstrass::{
4+
curves::bls12_381::curve::BLS12381Curve, point::ShortWeierstrassProjectivePoint,
5+
},
6+
field::element::FieldElement,
117
};
128
#[cfg(feature = "std")]
139
use std::{cmp::Ordering, ops::Neg};
1410

11+
#[cfg(feature = "std")]
12+
use crate::{
13+
cyclic_group::IsGroup, elliptic_curve::traits::FromAffine, errors::ByteConversionError,
14+
traits::ByteConversion,
15+
};
16+
1517
pub type G1Point = ShortWeierstrassProjectivePoint<BLS12381Curve>;
1618
pub type BLS12381FieldElement = FieldElement<BLS12381PrimeField>;
17-
const MODULUS: U256 =
18-
U256::from_hex_unchecked("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
19-
20-
pub fn check_point_is_in_subgroup(point: &G1Point) -> bool {
21-
let inf = G1Point::neutral_element();
22-
let aux_point = point.operate_with_self(MODULUS);
23-
inf == aux_point
24-
}
2519

2620
#[cfg(feature = "std")]
2721
pub fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result<G1Point, ByteConversionError> {
@@ -68,7 +62,8 @@ pub fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result<G1Point, ByteCo
6862
let point =
6963
G1Point::from_affine(x, y.clone()).map_err(|_| ByteConversionError::InvalidValue)?;
7064

71-
check_point_is_in_subgroup(&point)
65+
point
66+
.is_in_subgroup()
7267
.then_some(point)
7368
.ok_or(ByteConversionError::PointNotInSubgroup)
7469
}
@@ -117,13 +112,13 @@ mod tests {
117112
fn test_zero_point() {
118113
let g1 = BLS12381Curve::generator();
119114

120-
assert!(super::check_point_is_in_subgroup(&g1));
115+
assert!(g1.is_in_subgroup());
121116
let new_x = BLS12381FieldElement::zero();
122117
let new_y = BLS12381FieldElement::one() + BLS12381FieldElement::one();
123118

124119
let false_point2 = G1Point::from_affine(new_x, new_y).unwrap();
125120

126-
assert!(!super::check_point_is_in_subgroup(&false_point2));
121+
assert!(!false_point2.is_in_subgroup());
127122
}
128123

129124
#[cfg(feature = "std")]

math/src/elliptic_curve/short_weierstrass/curves/bls12_381/curve.rs

Lines changed: 174 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
use super::field_extension::{BLS12381PrimeField, Degree2ExtensionField};
1+
use super::{
2+
field_extension::{BLS12381PrimeField, Degree2ExtensionField},
3+
twist::BLS12381TwistCurve,
4+
};
5+
use crate::cyclic_group::IsGroup;
26
use crate::elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint;
37
use crate::elliptic_curve::traits::IsEllipticCurve;
8+
use crate::unsigned_integer::element::U256;
49
use crate::{
510
elliptic_curve::short_weierstrass::traits::IsShortWeierstrass, field::element::FieldElement,
611
};
712

13+
pub const SUBGROUP_ORDER: U256 =
14+
U256::from_hex_unchecked("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
15+
816
pub type BLS12381FieldElement = FieldElement<BLS12381PrimeField>;
917
pub type BLS12381TwistCurveFieldElement = FieldElement<Degree2ExtensionField>;
1018

@@ -35,18 +43,111 @@ impl IsShortWeierstrass for BLS12381Curve {
3543
}
3644
}
3745

46+
/// This is equal to the frobenius trace of the BLS12 381 curve minus one or seed value z.
47+
pub const MILLER_LOOP_CONSTANT: u64 = 0xd201000000010000;
48+
49+
/// 𝛽 : primitive cube root of unity of 𝐹ₚ that §satisfies the minimal equation
50+
/// 𝛽² + 𝛽 + 1 = 0 mod 𝑝
51+
pub const CUBE_ROOT_OF_UNITY_G1: BLS12381FieldElement = FieldElement::from_hex_unchecked(
52+
"5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe",
53+
);
54+
55+
/// x-coordinate of 𝜁 ∘ 𝜋_q ∘ 𝜁⁻¹, where 𝜁 is the isomorphism u:E'(𝔽ₚ₆) −> E(𝔽ₚ₁₂) from the twist to E
56+
pub const ENDO_U: BLS12381TwistCurveFieldElement =
57+
BLS12381TwistCurveFieldElement::const_from_raw([
58+
FieldElement::from_hex_unchecked("0"),
59+
FieldElement::from_hex_unchecked("1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaad")
60+
]);
61+
62+
/// y-coordinate of 𝜁 ∘ 𝜋_q ∘ 𝜁⁻¹, where 𝜁 is the isomorphism u:E'(𝔽ₚ₆) −> E(𝔽ₚ₁₂) from the twist to E
63+
pub const ENDO_V: BLS12381TwistCurveFieldElement =
64+
BLS12381TwistCurveFieldElement::const_from_raw([
65+
FieldElement::from_hex_unchecked("135203e60180a68ee2e9c448d77a2cd91c3dedd930b1cf60ef396489f61eb45e304466cf3e67fa0af1ee7b04121bdea2"),
66+
FieldElement::from_hex_unchecked("6af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09")
67+
]);
68+
69+
impl ShortWeierstrassProjectivePoint<BLS12381Curve> {
70+
/// Returns 𝜙(P) = (𝑥, 𝑦) ⇒ (𝛽𝑥, 𝑦), where 𝛽 is the Cube Root of Unity in the base prime field
71+
/// https://eprint.iacr.org/2022/352.pdf 2 Preliminaries
72+
fn phi(&self) -> Self {
73+
// This clone is unsightly
74+
let mut a = self.clone();
75+
a.0.value[0] = a.x() * CUBE_ROOT_OF_UNITY_G1;
76+
a
77+
}
78+
79+
/// 𝜙(P) = −𝑢²P
80+
/// https://eprint.iacr.org/2022/352.pdf 4.3 Prop. 4
81+
pub fn is_in_subgroup(&self) -> bool {
82+
self.operate_with_self(MILLER_LOOP_CONSTANT)
83+
.operate_with_self(MILLER_LOOP_CONSTANT)
84+
.neg()
85+
== self.phi()
86+
}
87+
}
88+
89+
impl ShortWeierstrassProjectivePoint<BLS12381TwistCurve> {
90+
/// 𝜓(P) = 𝜁 ∘ 𝜋ₚ ∘ 𝜁⁻¹, where 𝜁 is the isomorphism u:E'(𝔽ₚ₆) −> E(𝔽ₚ₁₂) from the twist to E,, 𝜋ₚ is the p-power frobenius endomorphism
91+
/// and 𝜓 satisifies minmal equation 𝑋² + 𝑡𝑋 + 𝑞 = 𝑂
92+
/// https://eprint.iacr.org/2022/352.pdf 4.2 (7)
93+
fn psi(&self) -> Self {
94+
let [x, y, z] = self.coordinates();
95+
Self::new([
96+
x.conjugate() * ENDO_U,
97+
y.conjugate() * ENDO_V,
98+
z.conjugate(),
99+
])
100+
}
101+
102+
/// 𝜓(P) = 𝑢P, where 𝑢 = SEED of the curve
103+
/// https://eprint.iacr.org/2022/352.pdf 4.2
104+
pub fn is_in_subgroup(&self) -> bool {
105+
self.psi() == self.operate_with_self(MILLER_LOOP_CONSTANT).neg()
106+
}
107+
}
108+
38109
#[cfg(test)]
39110
mod tests {
40111
use super::*;
41112
use crate::{
42-
cyclic_group::IsGroup, elliptic_curve::traits::EllipticCurveError,
113+
cyclic_group::IsGroup,
114+
elliptic_curve::{
115+
short_weierstrass::curves::bls12_381::field_extension::BLS12381_PRIME_FIELD_ORDER,
116+
traits::EllipticCurveError,
117+
},
43118
field::element::FieldElement,
119+
unsigned_integer::element::U384,
44120
};
45121

46-
use super::BLS12381Curve;
122+
// -15132376222941642751 = MILLER_LOOP_CONSTANT + 1 = -d20100000000ffff
123+
// we want the positive of this coordinate based on x^2 - tx + q
124+
pub const TRACE_OF_FROBENIUS: U256 = U256::from_u64(15132376222941642751);
125+
126+
const ENDO_U_2: BLS12381TwistCurveFieldElement =
127+
BLS12381TwistCurveFieldElement::const_from_raw([
128+
FieldElement::from_hex_unchecked("1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac"),
129+
FieldElement::from_hex_unchecked("0")
130+
]);
131+
132+
const ENDO_V_2: BLS12381TwistCurveFieldElement =
133+
BLS12381TwistCurveFieldElement::const_from_raw([
134+
FieldElement::from_hex_unchecked("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa"),
135+
FieldElement::from_hex_unchecked("0")
136+
]);
137+
138+
// Cmoputes the psi^2() 'Untwist Frobenius Endomorphism'
139+
fn psi_square(
140+
p: &ShortWeierstrassProjectivePoint<BLS12381TwistCurve>,
141+
) -> ShortWeierstrassProjectivePoint<BLS12381TwistCurve> {
142+
let [x, y, z] = p.coordinates();
143+
// Since power of frobenius map is 2 we apply once as applying twice is inverse
144+
ShortWeierstrassProjectivePoint::new([x * ENDO_U_2, y * ENDO_V_2, z.clone()])
145+
}
47146

48147
#[allow(clippy::upper_case_acronyms)]
49148
type FEE = FieldElement<BLS12381PrimeField>;
149+
#[allow(clippy::upper_case_acronyms)]
150+
type FTE = FieldElement<Degree2ExtensionField>;
50151

51152
fn point_1() -> ShortWeierstrassProjectivePoint<BLS12381Curve> {
52153
let x = FEE::new_base("36bb494facde72d0da5c770c4b16d9b2d45cfdc27604a25a1a80b020798e5b0dbd4c6d939a8f8820f042a29ce552ee5");
@@ -117,4 +218,74 @@ mod tests {
117218
g.operate_with_self(3_u16)
118219
);
119220
}
221+
222+
#[test]
223+
fn generator_g1_is_in_subgroup() {
224+
let g = BLS12381Curve::generator();
225+
assert!(g.is_in_subgroup())
226+
}
227+
228+
#[test]
229+
fn arbitrary_g1_point_is_in_subgroup() {
230+
let g = BLS12381Curve::generator().operate_with_self(32u64);
231+
assert!(g.is_in_subgroup())
232+
}
233+
234+
//TODO
235+
#[test]
236+
fn arbitrary_g1_point_not_in_subgroup() {
237+
let x = FEE::new_base("178212cbe4a3026c051d4f867364b3ea84af623f93233b347ffcd3d6b16f16e0a7aedbe1c78d33c6beca76b2b75c8486");
238+
let y = FEE::new_base("13a8b1347e5b43bc4051754b2a29928b5df78cf03ca3b1f73d0424b09fccdef116c9f0ecbec7420a99b2dd785209e9d");
239+
let p = BLS12381Curve::create_point_from_affine(x, y).unwrap();
240+
assert!(!p.is_in_subgroup())
241+
}
242+
243+
#[test]
244+
fn generator_g2_is_in_subgroup() {
245+
let g = BLS12381TwistCurve::generator();
246+
assert!(g.is_in_subgroup())
247+
}
248+
249+
#[test]
250+
fn arbitrary_g2_point_is_in_subgroup() {
251+
let g = BLS12381TwistCurve::generator().operate_with_self(32u64);
252+
assert!(g.is_in_subgroup())
253+
}
254+
255+
//`TODO`
256+
#[test]
257+
fn arbitrary_g2_point_not_in_subgroup() {
258+
let x = FTE::new([
259+
FEE::new(U384::from_hex_unchecked("97798b4a61ac301bbee71e36b5174e2f4adfe3e1729bdae1fcc9965ae84181be373aa80414823eed694f1270014012d")),
260+
FEE::new(U384::from_hex_unchecked("c9852cc6e61868966249aec153b50b29b3c22409f4c7880fd13121981c103c8ef84d9ea29b552431360e82cf69219fa"))
261+
]);
262+
let y = FTE::new([
263+
FEE::new(U384::from_hex_unchecked("16cb3a60f3fa52c8273aceeb94c4c7303e8074aa9eedec7355bbb1e8cceedd4ec1497f573f62822140377b8e339619ed")),
264+
FEE::new(U384::from_hex_unchecked("1cd919b08afe06bebe9adf6223a55868a6fd8b77efc5c67b60fff39be36e9b44b7f10db16827c83b43ad2dad1947778"))
265+
]);
266+
267+
let p = BLS12381TwistCurve::create_point_from_affine(x, y).unwrap();
268+
assert!(!p.is_in_subgroup())
269+
}
270+
271+
#[test]
272+
fn g2_conjugate_works() {
273+
let a = FTE::zero();
274+
let mut expected = a.conjugate();
275+
expected = expected.conjugate();
276+
277+
assert_eq!(a, expected);
278+
}
279+
280+
#[test]
281+
fn untwist_morphism_has_minimal_poly() {
282+
// generator
283+
let p = BLS12381TwistCurve::generator();
284+
let psi_square = psi_square(&p);
285+
let tx = p.psi().operate_with_self(TRACE_OF_FROBENIUS).neg();
286+
let q = p.operate_with_self(BLS12381_PRIME_FIELD_ORDER);
287+
// Minimal Polynomial of Untwist Frobenius Endomorphism: X^2 + tX + q, where X = psh(P) -> psi(p)^2 - t * psi(p) + q * p = 0
288+
let min_poly = psi_square.operate_with(&tx.neg()).operate_with(&q);
289+
assert!(min_poly.is_neutral_element())
290+
}
120291
}

math/src/elliptic_curve/short_weierstrass/curves/bls12_381/field_extension.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ impl FieldElement<Degree2ExtensionField> {
181181
pub fn new_base(a_hex: &str) -> Self {
182182
Self::new([FieldElement::new(U384::from(a_hex)), FieldElement::zero()])
183183
}
184+
185+
pub fn conjugate(&self) -> Self {
186+
let [a, b] = self.value();
187+
Self::new([a.clone(), -b])
188+
}
184189
}
185190

186191
impl FieldElement<Degree6ExtensionField> {

math/src/elliptic_curve/short_weierstrass/curves/bls12_381/pairing.rs

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
use super::field_extension::{Degree12ExtensionField, Degree2ExtensionField};
2-
use crate::{
3-
elliptic_curve::short_weierstrass::curves::bls12_381::field_extension::Degree6ExtensionField,
4-
field::element::FieldElement, unsigned_integer::element::UnsignedInteger,
1+
use super::{
2+
curve::{BLS12381Curve, MILLER_LOOP_CONSTANT},
3+
field_extension::{Degree12ExtensionField, Degree2ExtensionField},
4+
twist::BLS12381TwistCurve,
55
};
6-
7-
use super::{curve::BLS12381Curve, twist::BLS12381TwistCurve};
86
use crate::{
97
cyclic_group::IsGroup,
10-
elliptic_curve::short_weierstrass::curves::bls12_381::field_extension::LevelTwoResidue,
11-
elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint,
12-
elliptic_curve::short_weierstrass::traits::IsShortWeierstrass,
13-
elliptic_curve::traits::IsPairing, field::extensions::cubic::HasCubicNonResidue,
8+
elliptic_curve::{
9+
short_weierstrass::{
10+
curves::bls12_381::field_extension::{Degree6ExtensionField, LevelTwoResidue},
11+
point::ShortWeierstrassProjectivePoint,
12+
traits::IsShortWeierstrass,
13+
},
14+
traits::IsPairing,
15+
},
16+
errors::PairingError,
17+
field::{element::FieldElement, extensions::cubic::HasCubicNonResidue},
18+
unsigned_integer::element::{UnsignedInteger, U256},
1419
};
1520

21+
pub const SUBGROUP_ORDER: U256 =
22+
U256::from_hex_unchecked("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
23+
1624
#[derive(Clone)]
1725
pub struct BLS12381AtePairing;
1826
impl IsPairing for BLS12381AtePairing {
@@ -23,21 +31,22 @@ impl IsPairing for BLS12381AtePairing {
2331
/// Compute the product of the ate pairings for a list of point pairs.
2432
fn compute_batch(
2533
pairs: &[(&Self::G1Point, &Self::G2Point)],
26-
) -> FieldElement<Self::OutputField> {
34+
) -> Result<FieldElement<Self::OutputField>, PairingError> {
2735
let mut result = FieldElement::one();
2836
for (p, q) in pairs {
37+
if !p.is_in_subgroup() || !q.is_in_subgroup() {
38+
return Err(PairingError::PointNotInSubgroup);
39+
}
2940
if !p.is_neutral_element() && !q.is_neutral_element() {
3041
let p = p.to_affine();
3142
let q = q.to_affine();
3243
result = result * miller(&q, &p);
3344
}
3445
}
35-
final_exponentiation(&result)
46+
Ok(final_exponentiation(&result))
3647
}
3748
}
3849

39-
/// This is equal to the frobenius trace of the BLS12 381 curve minus one.
40-
const MILLER_LOOP_CONSTANT: u64 = 0xd201000000010000;
4150
fn double_accumulate_line(
4251
t: &mut ShortWeierstrassProjectivePoint<BLS12381TwistCurve>,
4352
p: &ShortWeierstrassProjectivePoint<BLS12381Curve>,
@@ -255,20 +264,33 @@ mod tests {
255264
&p.operate_with_self(a * b).to_affine(),
256265
&q.neg().to_affine(),
257266
),
258-
]);
267+
])
268+
.unwrap();
259269
assert_eq!(result, FieldElement::one());
260270
}
261271

262272
#[test]
263273
fn ate_pairing_returns_one_when_one_element_is_the_neutral_element() {
264274
let p = BLS12381Curve::generator().to_affine();
265275
let q = ShortWeierstrassProjectivePoint::neutral_element();
266-
let result = BLS12381AtePairing::compute_batch(&[(&p.to_affine(), &q)]);
276+
let result = BLS12381AtePairing::compute_batch(&[(&p.to_affine(), &q)]).unwrap();
267277
assert_eq!(result, FieldElement::one());
268278

269279
let p = ShortWeierstrassProjectivePoint::neutral_element();
270280
let q = BLS12381TwistCurve::generator();
271-
let result = BLS12381AtePairing::compute_batch(&[(&p, &q.to_affine())]);
281+
let result = BLS12381AtePairing::compute_batch(&[(&p, &q.to_affine())]).unwrap();
272282
assert_eq!(result, FieldElement::one());
273283
}
284+
285+
#[test]
286+
fn ate_pairing_errors_when_one_element_is_not_in_subgroup() {
287+
let p = ShortWeierstrassProjectivePoint::new([
288+
FieldElement::one(),
289+
FieldElement::one(),
290+
FieldElement::one(),
291+
]);
292+
let q = ShortWeierstrassProjectivePoint::neutral_element();
293+
let result = BLS12381AtePairing::compute_batch(&[(&p.to_affine(), &q)]);
294+
assert!(result.is_err())
295+
}
274296
}

0 commit comments

Comments
 (0)