@@ -182,3 +182,39 @@ TEST(g2, GeneratorIsCorrect)
182182 fq (" 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b" ) } };
183183 EXPECT_EQ (generator, expected);
184184}
185+
186+ // The generator, infinity, and arbitrary scalar multiples of the generator must be accepted as
187+ // members of the BN254 G2 prime-order subgroup.
188+ TEST (g2, IsInPrimeSubgroupAcceptsSubgroupPoints)
189+ {
190+ const g2::affine_element gen (Bn254G2Params::one_x, Bn254G2Params::one_y);
191+ EXPECT_TRUE (gen.is_in_prime_subgroup ());
192+ EXPECT_TRUE (g2::affine_element::infinity ().is_in_prime_subgroup ());
193+
194+ for (size_t i = 0 ; i < 4 ; ++i) {
195+ const g2::affine_element P (g2::element (gen) * fr::random_element ());
196+ EXPECT_TRUE (P.is_in_prime_subgroup ());
197+ }
198+ }
199+
200+ // BN254 G2 has cofactor h2 ≈ 2^254, so on-curve does NOT imply prime-order subgroup membership. The hardcoded point
201+ // below was constructed by sampling x = i + u (for the smallest positive integer i that yields a curve point) and
202+ // recovering y via Fq2 sqrt; because only a 1/h2 fraction of E'(Fq2) lies in G_r, this specimen lies in a cofactor
203+ // subgroup. Such a point must be rejected. Coordinates are in Montgomery form to match `Bn254G2Params::one_x` etc.
204+ TEST (g2, IsInPrimeSubgroupRejectsCofactorPoint)
205+ {
206+ const g2::affine_element off_subgroup{
207+ fq2{ fq{ 0xa6ba871b8b1e1b3a , 0x14f1d651eb8e167b , 0xccdd46def0f28c58 , 0x1c14ef83340fbe5e },
208+ fq{ 0xd35d438dc58f0d9d , 0x0a78eb28f5c70b3d , 0x666ea36f7879462c , 0x0e0a77c19a07df2f } },
209+ fq2{ fq{ 0x0294a5225573dc93 , 0x53874be07988f4f1 , 0x2a05d8b41ccce7d3 , 0x20045194f06acd0e },
210+ fq{ 0x3814c8e5e4179a98 , 0x793241f4d911e617 , 0x28cf8e4b0df4482e , 0x0d612bd6f79bd361 } }
211+ };
212+ ASSERT_TRUE (off_subgroup.on_curve ());
213+ EXPECT_FALSE (off_subgroup.is_in_prime_subgroup ());
214+
215+ // Sanity check that scalar multiplication via the Fr-typed `*` operator does NOT detect
216+ // subgroup membership — multiplying by `Fr(0)` (the additive identity, which equals `r mod r`)
217+ // gives infinity for every input, including off-subgroup points. This is precisely why
218+ // is_in_prime_subgroup() routes through a uint256_t scalar instead.
219+ EXPECT_TRUE ((off_subgroup * fr::zero ()).is_point_at_infinity ());
220+ }
0 commit comments