From 1453c00a2596e68ea6a499a9aea00adbc746ac96 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 16 Jul 2024 22:58:39 +0200 Subject: [PATCH] feat: add IsOnG2 for BN254 (#1204) * feat: add IsOnTwist method * feat: add IsEqual methods * feat: add IsOnG2 method * docs: rename internal method --- std/algebra/emulated/fields_bn254/e2.go | 8 ++ std/algebra/emulated/sw_bn254/g2.go | 8 ++ std/algebra/emulated/sw_bn254/pairing.go | 47 ++++++-- std/algebra/emulated/sw_bn254/pairing_test.go | 108 ++++++++++++++++++ 4 files changed, 161 insertions(+), 10 deletions(-) diff --git a/std/algebra/emulated/fields_bn254/e2.go b/std/algebra/emulated/fields_bn254/e2.go index 7533d7bf83..5d38c50eaf 100644 --- a/std/algebra/emulated/fields_bn254/e2.go +++ b/std/algebra/emulated/fields_bn254/e2.go @@ -296,6 +296,14 @@ func (e Ext2) AssertIsEqual(x, y *E2) { e.fp.AssertIsEqual(&x.A1, &y.A1) } +func (e Ext2) IsEqual(x, y *E2) frontend.Variable { + xDiff := e.fp.Sub(&x.A0, &y.A0) + yDiff := e.fp.Sub(&x.A1, &y.A1) + xIsZero := e.fp.IsZero(xDiff) + yIsZero := e.fp.IsZero(yDiff) + return e.api.And(xIsZero, yIsZero) +} + func FromE2(y *bn254.E2) E2 { return E2{ A0: emulated.ValueOf[emulated.BN254Fp](y.A0), diff --git a/std/algebra/emulated/sw_bn254/g2.go b/std/algebra/emulated/sw_bn254/g2.go index 1bd521c7ce..66493302bf 100644 --- a/std/algebra/emulated/sw_bn254/g2.go +++ b/std/algebra/emulated/sw_bn254/g2.go @@ -10,6 +10,7 @@ import ( ) type G2 struct { + api frontend.API *fields_bn254.Ext2 w *emulated.Element[BaseField] u, v *fields_bn254.E2 @@ -49,6 +50,7 @@ func NewG2(api frontend.API) *G2 { A1: emulated.ValueOf[BaseField]("3505843767911556378687030309984248845540243509899259641013678093033130930403"), } return &G2{ + api: api, Ext2: fields_bn254.NewExt2(api), w: &w, u: &u, @@ -262,3 +264,9 @@ func (g2 *G2) AssertIsEqual(p, q *G2Affine) { g2.Ext2.AssertIsEqual(&p.P.X, &q.P.X) g2.Ext2.AssertIsEqual(&p.P.Y, &q.P.Y) } + +func (g2 *G2) IsEqual(p, q *G2Affine) frontend.Variable { + xEqual := g2.Ext2.IsEqual(&p.P.X, &q.P.X) + yEqual := g2.Ext2.IsEqual(&p.P.Y, &q.P.Y) + return g2.api.And(xEqual, yEqual) +} diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index d346b6f00e..aa30901032 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -273,7 +273,7 @@ func (pr Pairing) AssertIsOnCurve(P *G1Affine) { pr.curve.AssertIsOnCurve(P) } -func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { +func (pr Pairing) computeTwistEquation(Q *G2Affine) (left, right *fields_bn254.E2) { // Twist: Y² == X³ + aX + b, where a=0 and b=3/(9+u) // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) @@ -281,25 +281,32 @@ func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { selector := pr.api.And(pr.Ext2.IsZero(&Q.P.X), pr.Ext2.IsZero(&Q.P.Y)) b := pr.Ext2.Select(selector, pr.Ext2.Zero(), pr.bTwist) - left := pr.Ext2.Square(&Q.P.Y) - right := pr.Ext2.Square(&Q.P.X) + left = pr.Ext2.Square(&Q.P.Y) + right = pr.Ext2.Square(&Q.P.X) right = pr.Ext2.Mul(right, &Q.P.X) right = pr.Ext2.Add(right, b) + return left, right +} + +func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { + left, right := pr.computeTwistEquation(Q) pr.Ext2.AssertIsEqual(left, right) } +// IsOnTwist returns a boolean indicating if the G2 point is in the twist. +func (pr Pairing) IsOnTwist(Q *G2Affine) frontend.Variable { + left, right := pr.computeTwistEquation(Q) + diff := pr.Ext2.Sub(left, right) + return pr.Ext2.IsZero(diff) +} + func (pr Pairing) AssertIsOnG1(P *G1Affine) { // BN254 has a prime order, so we only // 1- Check P is on the curve pr.AssertIsOnCurve(P) } -func (pr Pairing) AssertIsOnG2(Q *G2Affine) { - // 1- Check Q is on the curve - pr.AssertIsOnTwist(Q) - - // 2- Check Q has the right subgroup order - +func (pr Pairing) computeG2ShortVector(Q *G2Affine) (_Q *G2Affine) { // [x₀]Q xQ := pr.g2.scalarMulBySeed(Q) // ψ([x₀]Q) @@ -311,14 +318,34 @@ func (pr Pairing) AssertIsOnG2(Q *G2Affine) { psi3xxQ = pr.g2.psi(psi3xxQ) // _Q = ψ³([2x₀]Q) - ψ²([x₀]Q) - ψ([x₀]Q) - [x₀]Q - _Q := pr.g2.sub(psi2xQ, psi3xxQ) + _Q = pr.g2.sub(psi2xQ, psi3xxQ) _Q = pr.g2.sub(_Q, psixQ) _Q = pr.g2.sub(_Q, xQ) + return _Q +} + +func (pr Pairing) AssertIsOnG2(Q *G2Affine) { + // 1- Check Q is on the curve + pr.AssertIsOnTwist(Q) + // 2- Check Q has the right subgroup order + _Q := pr.computeG2ShortVector(Q) // [r]Q == 0 <==> _Q == Q pr.g2.AssertIsEqual(Q, _Q) } +// IsOnG2 returns a boolean indicating if the G2 point is in the subgroup. The +// method assumes that the point is already on the curve. Call +// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +func (pr Pairing) IsOnG2(Q *G2Affine) frontend.Variable { + // 1 - is Q on curve + isOnCurve := pr.IsOnTwist(Q) + // 2 - is Q in the subgroup + _Q := pr.computeG2ShortVector(Q) + isInSubgroup := pr.g2.IsEqual(Q, _Q) + return pr.api.And(isOnCurve, isInSubgroup) +} + // loopCounter = 6x₀+2 = 29793968203157093288 // // in 2-NAF diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go index 10a3913ce6..b6a9a751e2 100644 --- a/std/algebra/emulated/sw_bn254/pairing_test.go +++ b/std/algebra/emulated/sw_bn254/pairing_test.go @@ -263,6 +263,114 @@ func TestGroupMembershipSolve(t *testing.T) { assert.NoError(err) } +type IsOnTwistCircuit struct { + Q G2Affine + Expected frontend.Variable +} + +func (c *IsOnTwistCircuit) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + res := pairing.IsOnTwist(&c.Q) + api.AssertIsEqual(res, c.Expected) + return nil +} + +func TestIsOnTwistSolve(t *testing.T) { + assert := test.NewAssert(t) + // test for a point not on the twist + var Q bn254.G2Affine + _, err := Q.X.A0.SetString("0x119606e6d3ea97cea4eff54433f5c7dbc026b8d0670ddfbe6441e31225028d31") + assert.NoError(err) + _, err = Q.X.A1.SetString("0x1d3df5be6084324da6333a6ad1367091ca9fbceb70179ec484543a58b8cb5d63") + assert.NoError(err) + _, err = Q.Y.A0.SetString("0x1b9a36ea373fe2c5b713557042ce6deb2907d34e12be595f9bbe84c144de86ef") + assert.NoError(err) + _, err = Q.Y.A1.SetString("0x49fe60975e8c78b7b31a6ed16a338ac8b28cf6a065cfd2ca47e9402882518ba0") + assert.NoError(err) + assert.False(Q.IsOnCurve()) + witness := IsOnTwistCircuit{ + Q: NewG2Affine(Q), + Expected: 0, + } + err = test.IsSolved(&IsOnTwistCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + // test for a point on the twist + _, Q = randomG1G2Affines() + assert.True(Q.IsOnCurve()) + witness = IsOnTwistCircuit{ + Q: NewG2Affine(Q), + Expected: 1, + } + err = test.IsSolved(&IsOnTwistCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type IsOnG2Circuit struct { + Q G2Affine + Expected frontend.Variable +} + +func (c *IsOnG2Circuit) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + res := pairing.IsOnG2(&c.Q) + api.AssertIsEqual(res, c.Expected) + return nil +} + +func TestIsOnG2Solve(t *testing.T) { + assert := test.NewAssert(t) + // test for a point not on the curve + var Q bn254.G2Affine + _, err := Q.X.A0.SetString("0x119606e6d3ea97cea4eff54433f5c7dbc026b8d0670ddfbe6441e31225028d31") + assert.NoError(err) + _, err = Q.X.A1.SetString("0x1d3df5be6084324da6333a6ad1367091ca9fbceb70179ec484543a58b8cb5d63") + assert.NoError(err) + _, err = Q.Y.A0.SetString("0x1b9a36ea373fe2c5b713557042ce6deb2907d34e12be595f9bbe84c144de86ef") + assert.NoError(err) + _, err = Q.Y.A1.SetString("0x49fe60975e8c78b7b31a6ed16a338ac8b28cf6a065cfd2ca47e9402882518ba0") + assert.NoError(err) + assert.False(Q.IsOnCurve()) + witness := IsOnG2Circuit{ + Q: NewG2Affine(Q), + Expected: 0, + } + err = test.IsSolved(&IsOnG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + // test for a point on curve not in G2 + _, err = Q.X.A0.SetString("0x07192b9fd0e2a32e3e1caa8e59462b757326d48f641924e6a1d00d66478913eb") + assert.NoError(err) + _, err = Q.X.A1.SetString("0x15ce93f1b1c4946dd6cfbb3d287d9c9a1cdedb264bda7aada0844416d8a47a63") + assert.NoError(err) + _, err = Q.Y.A0.SetString("0x0fa65a9b48ba018361ed081e3b9e958451de5d9e8ae0bd251833ebb4b2fafc96") + assert.NoError(err) + _, err = Q.Y.A1.SetString("0x06e1f5e20f68f6dfa8a91a3bea048df66d9eaf56cc7f11215401f7e05027e0c6") + assert.NoError(err) + assert.True(Q.IsOnCurve()) + assert.False(Q.IsInSubGroup()) + witness = IsOnG2Circuit{ + Q: NewG2Affine(Q), + Expected: 0, + } + err = test.IsSolved(&IsOnG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + // test for a point in G2 + _, Q = randomG1G2Affines() + assert.True(Q.IsOnCurve()) + assert.True(Q.IsInSubGroup()) + witness = IsOnG2Circuit{ + Q: NewG2Affine(Q), + Expected: 1, + } + err = test.IsSolved(&IsOnG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + // bench func BenchmarkPairing(b *testing.B) {