From 53ed73b1a637df6b13bd020d5408ddc0d905b06f Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sun, 14 Jul 2024 17:00:23 +0200 Subject: [PATCH 1/3] perf: eliminate final exp in bls12-377 --- .../emulated/sw_bls12381/pairing_test.go | 9 ++- std/algebra/emulated/sw_bn254/pairing_test.go | 9 ++- .../emulated/sw_bw6761/pairing_test.go | 9 ++- .../native/fields_bls12377/e12_pairing.go | 67 ++++++++++++++++++- std/algebra/native/fields_bls12377/hints.go | 41 ++++++++++++ std/algebra/native/sw_bls12377/pairing.go | 17 +++-- .../native/sw_bls12377/pairing_test.go | 4 +- 7 files changed, 139 insertions(+), 17 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/pairing_test.go b/std/algebra/emulated/sw_bls12381/pairing_test.go index dc723eb012..76dc6764f2 100644 --- a/std/algebra/emulated/sw_bls12381/pairing_test.go +++ b/std/algebra/emulated/sw_bls12381/pairing_test.go @@ -177,7 +177,7 @@ func (c *PairingCheckCircuit) Define(api frontend.API) error { if err != nil { return fmt.Errorf("new pairing: %w", err) } - err = pairing.PairingCheck([]*G1Affine{&c.In1G1, &c.In1G1, &c.In2G1, &c.In2G1}, []*G2Affine{&c.In1G2, &c.In2G2, &c.In1G2, &c.In2G2}) + err = pairing.PairingCheck([]*G1Affine{&c.In1G1, &c.In2G1}, []*G2Affine{&c.In1G2, &c.In2G2}) if err != nil { return fmt.Errorf("pair: %w", err) } @@ -186,10 +186,13 @@ func (c *PairingCheckCircuit) Define(api frontend.API) error { func TestPairingCheckTestSolve(t *testing.T) { assert := test.NewAssert(t) + // e(a,2b) * e(-2a,b) == 1 p1, q1 := randomG1G2Affines() - _, q2 := randomG1G2Affines() var p2 bls12381.G1Affine - p2.Neg(&p1) + p2.Double(&p1).Neg(&p2) + var q2 bls12381.G2Affine + q2.Set(&q1) + q1.Double(&q1) witness := PairingCheckCircuit{ In1G1: NewG1Affine(p1), In1G2: NewG2Affine(q1), diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go index 10a3913ce6..ea7753b0a7 100644 --- a/std/algebra/emulated/sw_bn254/pairing_test.go +++ b/std/algebra/emulated/sw_bn254/pairing_test.go @@ -214,7 +214,7 @@ func (c *PairingCheckCircuit) Define(api frontend.API) error { if err != nil { return fmt.Errorf("new pairing: %w", err) } - err = pairing.PairingCheck([]*G1Affine{&c.In1G1, &c.In1G1, &c.In2G1, &c.In2G1}, []*G2Affine{&c.In1G2, &c.In2G2, &c.In1G2, &c.In2G2}) + err = pairing.PairingCheck([]*G1Affine{&c.In1G1, &c.In2G1}, []*G2Affine{&c.In1G2, &c.In2G2}) if err != nil { return fmt.Errorf("pair: %w", err) } @@ -223,10 +223,13 @@ func (c *PairingCheckCircuit) Define(api frontend.API) error { func TestPairingCheckTestSolve(t *testing.T) { assert := test.NewAssert(t) + // e(a,2b) * e(-2a,b) == 1 p1, q1 := randomG1G2Affines() - _, q2 := randomG1G2Affines() var p2 bn254.G1Affine - p2.Neg(&p1) + p2.Double(&p1).Neg(&p2) + var q2 bn254.G2Affine + q2.Set(&q1) + q1.Double(&q1) witness := PairingCheckCircuit{ In1G1: NewG1Affine(p1), In1G2: NewG2Affine(q1), diff --git a/std/algebra/emulated/sw_bw6761/pairing_test.go b/std/algebra/emulated/sw_bw6761/pairing_test.go index 06b3276afa..1af31c2016 100644 --- a/std/algebra/emulated/sw_bw6761/pairing_test.go +++ b/std/algebra/emulated/sw_bw6761/pairing_test.go @@ -175,7 +175,7 @@ func (c *PairingCheckCircuit) Define(api frontend.API) error { if err != nil { return fmt.Errorf("new pairing: %w", err) } - err = pairing.PairingCheck([]*G1Affine{&c.In1G1, &c.In1G1, &c.In2G1, &c.In2G1}, []*G2Affine{&c.In1G2, &c.In2G2, &c.In1G2, &c.In2G2}) + err = pairing.PairingCheck([]*G1Affine{&c.In1G1, &c.In2G1}, []*G2Affine{&c.In1G2, &c.In2G2}) if err != nil { return fmt.Errorf("pair: %w", err) } @@ -184,10 +184,13 @@ func (c *PairingCheckCircuit) Define(api frontend.API) error { func TestPairingCheckTestSolve(t *testing.T) { assert := test.NewAssert(t) + // e(a,2b) * e(-2a,b) == 1 p1, q1 := randomG1G2Affines() - _, q2 := randomG1G2Affines() var p2 bw6761.G1Affine - p2.Neg(&p1) + p2.Double(&p1).Neg(&p2) + var q2 bw6761.G2Affine + q2.Set(&q1) + q1.Double(&q1) witness := PairingCheckCircuit{ In1G1: NewG1Affine(p1), In1G2: NewG2Affine(q1), diff --git a/std/algebra/native/fields_bls12377/e12_pairing.go b/std/algebra/native/fields_bls12377/e12_pairing.go index a4895b425d..201e82d0c6 100644 --- a/std/algebra/native/fields_bls12377/e12_pairing.go +++ b/std/algebra/native/fields_bls12377/e12_pairing.go @@ -1,6 +1,8 @@ package fields_bls12377 -import "github.com/consensys/gnark/frontend" +import ( + "github.com/consensys/gnark/frontend" +) // nSquareKarabina2345 repeated compressed cyclotmic square func (e *E12) nSquareKarabina2345(api frontend.API, n int) { @@ -125,7 +127,7 @@ func (e *E12) MulBy01234(api frontend.API, x [5]E2) *E12 { return e } -// ExpX0 compute e1^X0, where X0=9586122913090633729 +// ExpX0 compute e1^X0, where X0=0x8508c00000000001 func (e *E12) ExpX0(api frontend.API, e1 E12) *E12 { res := e1 @@ -148,7 +150,7 @@ func (e *E12) ExpX0(api frontend.API, e1 E12) *E12 { } -// ExpX0Minus1Square computes e1^((X0-1)^2), where X0=9586122913090633729 +// ExpX0Minus1Square computes e1^((X0-1)^2), where X0=0x8508c00000000001 func (e *E12) ExpX0Minus1Square(api frontend.API, e1 E12) *E12 { var t0, t1, t2, t3, res E12 @@ -176,3 +178,62 @@ func (e *E12) ExpX0Minus1Square(api frontend.API, e1 E12) *E12 { return e } + +// ExpU compute e1^U, where U=(X0-1)^2/3 and X0=0x8508c00000000001 +func (e *E12) ExpU(api frontend.API, e1 E12) *E12 { + + var t0, t1, t2, t3 E12 + t0.CyclotomicSquare(api, e1) + e.Mul(api, e1, t0) + t0.Mul(api, t0, *e) + t1.CyclotomicSquare(api, t0) + t2.Mul(api, e1, t1) + t1.CyclotomicSquare(api, t2) + t1.Mul(api, e1, t1) + t3.CyclotomicSquare(api, t1) + t3.nSquareKarabina2345(api, 7) + t2.Mul(api, t2, t3) + t2.nSquareKarabina2345(api, 6) + t1.Mul(api, t1, t2) + t1.nSquareKarabina2345(api, 4) + t0.Mul(api, t0, t1) + t0.nSquareKarabina2345(api, 4) + t0.Mul(api, e1, t0) + t0.nSquareKarabina2345(api, 6) + e.Mul(api, *e, t0) + e.nSquareKarabina2345(api, 92) + + return e +} + +// FinalExponentiationCheck checks that a Miller function output x lies in the +// same equivalence class as the reduced pairing. This replaces the final +// exponentiation step in-circuit. +// The method follows Section 4 of [On Proving Pairings] paper by A. Novakovic and L. Eagen. +// +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +func (x *E12) FinalExponentiationCheck(api frontend.API) *E12 { + res, err := api.NewHint(finalExpHint, 12, x.C0.B0.A0, x.C0.B0.A1, x.C0.B1.A0, x.C0.B1.A1, x.C0.B2.A0, x.C0.B2.A1, x.C1.B0.A0, x.C1.B0.A1, x.C1.B1.A0, x.C1.B1.A1, x.C1.B2.A0, x.C1.B2.A1) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + + var residueWitness, t0, t1 E12 + residueWitness.assign(res[:12]) + + // Check that x == residueWitness^r by checking that: + // x^k == residueWitness^(q-u) + // where k = (u-1)^2/3, u=0x8508c00000000001 the BLS12-377 seed + // and residueWitness from the hint. + t0.Frobenius(api, residueWitness) + // exponentiation by u + t1.ExpX0(api, residueWitness) + t0.DivUnchecked(api, t0, t1) + // exponentiation by U=(u-1)^2/3 + t1.ExpU(api, *x) + + t0.AssertIsEqual(api, t1) + + return nil +} diff --git a/std/algebra/native/fields_bls12377/hints.go b/std/algebra/native/fields_bls12377/hints.go index 9138ca997c..b7503343eb 100644 --- a/std/algebra/native/fields_bls12377/hints.go +++ b/std/algebra/native/fields_bls12377/hints.go @@ -15,6 +15,7 @@ func GetHints() []solver.Hint { inverseE2Hint, inverseE6Hint, inverseE12Hint, + finalExpHint, } } @@ -183,3 +184,43 @@ func inverseE12Hint(_ *big.Int, inputs []*big.Int, res []*big.Int) error { return nil } + +func finalExpHint(_ *big.Int, inputs, outputs []*big.Int) error { + // This follows section 4.1 of https://eprint.iacr.org/2024/640.pdf (Th. 1) + var millerLoop, residueWitness bls12377.E12 + var rInv big.Int + + millerLoop.C0.B0.A0.SetBigInt(inputs[0]) + millerLoop.C0.B0.A1.SetBigInt(inputs[1]) + millerLoop.C0.B1.A0.SetBigInt(inputs[2]) + millerLoop.C0.B1.A1.SetBigInt(inputs[3]) + millerLoop.C0.B2.A0.SetBigInt(inputs[4]) + millerLoop.C0.B2.A1.SetBigInt(inputs[5]) + millerLoop.C1.B0.A0.SetBigInt(inputs[6]) + millerLoop.C1.B0.A1.SetBigInt(inputs[7]) + millerLoop.C1.B1.A0.SetBigInt(inputs[8]) + millerLoop.C1.B1.A1.SetBigInt(inputs[9]) + millerLoop.C1.B2.A0.SetBigInt(inputs[10]) + millerLoop.C1.B2.A1.SetBigInt(inputs[11]) + + // compute r-th root: + // Exponentiate to rInv where + // rInv = 1/r mod (p^12-1)/r + rInv.SetString("10208748786837724877156759805239199917177088105850452097581398357781838343395535357717189281303817989030853426518402432972842003898131337328036123767288343472740290055992597327047843733820915319931789911460922332092893340406586659134897564792528992841360758317184371729851779228767022291575333619687450508149408291808665688425747351943672765401504532352302933193380323797562510328188696218527623617803336085522730402534476672219254656573543964081905263468361471236263414063051754798717965006033535737262535426559024265337552194770692912115634227850824349287678445075667227500062644865552196269410386640693498752691820776510255615212781349365166954324182425386812172284685207545357376138024103577229096125179210029150108085917426622002636460159193270457153824484424740291304472618370893768349010724508505559223070061402562692522679753894779470665228357312064233458448950731987606712484774731314132451528794596084167373606499619244419076763354699647800243598600024554183146018594109053978613659377521869110074983807776968064443737295525761159893356460041590615623520614511285178649677625190127954790028024727845772017830862600186003274909528270245217670645634670358694128233669703480796660621582552083085232238280068277127279315415621696399036462472389073266975782056160166232984523898881", 10) + residueWitness.Exp(millerLoop, &rInv) + + residueWitness.C0.B0.A0.BigInt(outputs[0]) + residueWitness.C0.B0.A1.BigInt(outputs[1]) + residueWitness.C0.B1.A0.BigInt(outputs[2]) + residueWitness.C0.B1.A1.BigInt(outputs[3]) + residueWitness.C0.B2.A0.BigInt(outputs[4]) + residueWitness.C0.B2.A1.BigInt(outputs[5]) + residueWitness.C1.B0.A0.BigInt(outputs[6]) + residueWitness.C1.B0.A1.BigInt(outputs[7]) + residueWitness.C1.B1.A0.BigInt(outputs[8]) + residueWitness.C1.B1.A1.BigInt(outputs[9]) + residueWitness.C1.B2.A0.BigInt(outputs[10]) + residueWitness.C1.B2.A1.BigInt(outputs[11]) + + return nil +} diff --git a/std/algebra/native/sw_bls12377/pairing.go b/std/algebra/native/sw_bls12377/pairing.go index fa9febf7c3..759a55ca9b 100644 --- a/std/algebra/native/sw_bls12377/pairing.go +++ b/std/algebra/native/sw_bls12377/pairing.go @@ -253,13 +253,22 @@ func Pair(api frontend.API, P []G1Affine, Q []G2Affine) (GT, error) { // // This function doesn't check that the inputs are in the correct subgroups func PairingCheck(api frontend.API, P []G1Affine, Q []G2Affine) error { - f, err := Pair(api, P, Q) + f, err := MillerLoop(api, P, Q) if err != nil { return err } - var one GT - one.SetOne() - f.AssertIsEqual(api, one) + // We perform the easy part of the final exp to push f to the cyclotomic + // subgroup so that FinalExponentiationCheck is carried with optimized + // cyclotomic squaring (e.g. Karabina12345). + // + // f = f^(p⁶-1)(p²+1) + var buf GT + buf.Conjugate(api, f) + buf.DivUnchecked(api, buf, f) + f.FrobeniusSquare(api, buf). + Mul(api, f, buf) + + f.FinalExponentiationCheck(api) return nil } diff --git a/std/algebra/native/sw_bls12377/pairing_test.go b/std/algebra/native/sw_bls12377/pairing_test.go index 7524263c6e..1956764d2a 100644 --- a/std/algebra/native/sw_bls12377/pairing_test.go +++ b/std/algebra/native/sw_bls12377/pairing_test.go @@ -251,9 +251,11 @@ func pairingData() (P bls12377.G1Affine, Q bls12377.G2Affine, milRes, pairingRes } func pairingCheckData() (P [2]bls12377.G1Affine, Q [2]bls12377.G2Affine) { + // e(a,2b) * e(-2a,b) == 1 _, _, P[0], Q[0] = bls12377.Generators() - P[1].Neg(&P[0]) + P[1].Double(&P[0]).Neg(&P[1]) Q[1].Set(&Q[0]) + Q[0].Double(&Q[0]) return } From 5a08f35dd264a544ce2f738babc43801fb31ffa1 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 16 Jul 2024 08:54:22 +0100 Subject: [PATCH 2/3] fix(bls12-377): use FinalExponentiationCheck in pairing2.go --- std/algebra/native/sw_bls12377/pairing2.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index f977ab916d..5d6e736368 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -298,13 +298,11 @@ func (p *Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { for i := range Q { inQ[i] = *Q[i] } - res, err := Pair(p.api, inP, inQ) + res, err := MillerLoop(p.api, inP, inQ) if err != nil { return err } - var one fields_bls12377.E12 - one.SetOne() - res.AssertIsEqual(p.api, one) + res.FinalExponentiationCheck(p.api) return nil } From 6e61298eb6718592f7bd400c36230b87836b9fd5 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 16 Jul 2024 12:18:12 +0100 Subject: [PATCH 3/3] fix(bls12-377): push to cyclo group in pairing2.go --- std/algebra/native/sw_bls12377/pairing2.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 5d6e736368..a633024d24 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -302,6 +302,16 @@ func (p *Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { if err != nil { return err } + // We perform the easy part of the final exp to push res to the cyclotomic + // subgroup so that FinalExponentiationCheck is carried with optimized + // cyclotomic squaring (e.g. Karabina12345). + // + // res = res^(p⁶-1)(p²+1) + var buf GT + buf.Conjugate(p.api, res) + buf.DivUnchecked(p.api, buf, res) + res.FrobeniusSquare(p.api, buf).Mul(p.api, res, buf) + res.FinalExponentiationCheck(p.api) return nil }