diff --git a/std/algebra/emulated/fields_bls12381/e12_pairing.go b/std/algebra/emulated/fields_bls12381/e12_pairing.go index 7bbb60b7e5..a2a3f25ebc 100644 --- a/std/algebra/emulated/fields_bls12381/e12_pairing.go +++ b/std/algebra/emulated/fields_bls12381/e12_pairing.go @@ -384,14 +384,15 @@ func (e Ext12) FrobeniusSquareTorus(y *E6) *E6 { return &E6{B0: *t0, B1: *t1, B2: *t2} } -// FinalExponentiationCheck checks that a Miller function output x lies in the +// AssertFinalExponentiationIsOne 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. +// The method is inspired from [On Proving Pairings] paper by A. Novakovic and +// L. Eagen, and is based on a personal communication with A. Novakovic. // // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf -func (e Ext12) FinalExponentiationCheck(x *E12) *E12 { - res, err := e.fp.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) +func (e Ext12) AssertFinalExponentiationIsOne(x *E12) { + res, err := e.fp.NewHint(finalExpHint, 18, &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) @@ -409,21 +410,27 @@ func (e Ext12) FinalExponentiationCheck(x *E12) *E12 { B2: E2{A0: *res[10], A1: *res[11]}, }, } + // constrain cubicNonResiduePower to be in Fp6 + scalingFactor := E12{ + C0: E6{ + B0: E2{A0: *res[12], A1: *res[13]}, + B1: E2{A0: *res[14], A1: *res[15]}, + B2: E2{A0: *res[16], A1: *res[17]}, + }, + C1: (*e.Ext6.Zero()), + } - // Check that x == residueWitness^r by checking that: - // x^k == residueWitness^(q-u) - // where k = (u-1)^2/3, u=-0xd201000000010000 the BLS12-381 seed - // and residueWitness from the hint. + // Check that x * scalingFactor == residueWitness^(q-u) + // where u=-0xd201000000010000 is the BLS12-381 seed, + // and residueWitness, scalingFactor from the hint. t0 := e.Frobenius(&residueWitness) // exponentiation by -u t1 := e.Expt(&residueWitness) t0 = e.Mul(t0, t1) - // exponentiation by U=(u-1)^2/3 - t1 = e.ExpByU(x) - e.AssertIsEqual(t0, t1) + t1 = e.Mul(x, &scalingFactor) - return nil + e.AssertIsEqual(t0, t1) } func (e Ext12) Frobenius(x *E12) *E12 { diff --git a/std/algebra/emulated/fields_bls12381/hints.go b/std/algebra/emulated/fields_bls12381/hints.go index 320aaacb9d..77863d3ffa 100644 --- a/std/algebra/emulated/fields_bls12381/hints.go +++ b/std/algebra/emulated/fields_bls12381/hints.go @@ -271,11 +271,11 @@ func divE12Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) erro } func finalExpHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - // This follows section 4.1 of https://eprint.iacr.org/2024/640.pdf (Th. 1) + // This is inspired from https://eprint.iacr.org/2024/640.pdf + // and based on a personal communication with the author Andrija Novakovic. return emulated.UnwrapHint(nativeInputs, nativeOutputs, func(mod *big.Int, inputs, outputs []*big.Int) error { - var millerLoop, residueWitness bls12381.E12 - var rInv big.Int + var millerLoop bls12381.E12 millerLoop.C0.B0.A0.SetBigInt(inputs[0]) millerLoop.C0.B0.A1.SetBigInt(inputs[1]) @@ -290,12 +290,71 @@ func finalExpHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) er 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("169662389312441398885310937191698694666993326870281216192803558492181163400934408837135364582394949149589560242411491538960982200559697133935443307582773537814554128992403254243871087441488619811839498788505657962013599019994544063402394719913759780901881538869078447034832302535303591303383830742161317593225991746471557492001710830538428792119562309446698444646787667517629943447802199824630112988907247336627481159245442124709621313522294197747687500252452962523217400829932174349352696726049683687654879009114460723993703760367089269403767790334911644010940272722630305066645230222732316445557889124653426141642271480304669447694344127599708992364443461893123938202386892312748211835322692697497854107961493711137028209148238339237355911496376520814450515612396561384525661635220451168152178239892009375229296874955612623691164738926395993739297557487207643426168321070539996994036837992284584225139752716615623194417718962478029165908544042568334172107008712033983002554672734519081879196926275059798317879322062358113986901925780890205936071364647548199159506709147492864081514759663116291487638998943660232689862634717010538047493292265992334130695994203833154950619462266484292385471162124464248375625748097868775829652908052615424796255913420292818674303286242639225711610323988077268116737", 10) - residueWitness.Exp(millerLoop, &rInv) - + var root, rootPthInverse, root27thInverse, residueWitness, scalingFactor bls12381.E12 + var order3rd, order3rdPower, exponent, exponentInv, finalExpFactor, polyFactor big.Int + // polyFactor = (1-x)/3 + polyFactor.SetString("5044125407647214251", 10) + // finalExpFactor = ((q^12 - 1) / r) / (27 * polyFactor) + finalExpFactor.SetString("2366356426548243601069753987687709088104621721678962410379583120840019275952471579477684846670499039076873213559162845121989217658133790336552276567078487633052653005423051750848782286407340332979263075575489766963251914185767058009683318020965829271737924625612375201545022326908440428522712877494557944965298566001441468676802477524234094954960009227631543471415676620753242466901942121887152806837594306028649150255258504417829961387165043999299071444887652375514277477719817175923289019181393803729926249507024121957184340179467502106891835144220611408665090353102353194448552304429530104218473070114105759487413726485729058069746063140422361472585604626055492939586602274983146215294625774144156395553405525711143696689756441298365274341189385646499074862712688473936093315628166094221735056483459332831845007196600723053356837526749543765815988577005929923802636375670820616189737737304893769679803809426304143627363860243558537831172903494450556755190448279875942974830469855835666815454271389438587399739607656399812689280234103023464545891697941661992848552456326290792224091557256350095392859243101357349751064730561345062266850238821755009430903520645523345000326783803935359711318798844368754833295302563158150573540616830138810935344206231367357992991289265295323280", 10) + + // 1. get pth-root inverse + exponent.Mul(&finalExpFactor, big.NewInt(27)) + root.Exp(millerLoop, &exponent) + if root.IsOne() { + rootPthInverse.SetOne() + } else { + exponentInv.ModInverse(&exponent, &polyFactor) + exponent.Neg(&exponentInv).Mod(&exponent, &polyFactor) + rootPthInverse.Exp(root, &exponent) + } + + // 2.1. get order of 3rd primitive root + var three big.Int + three.SetUint64(3) + exponent.Mul(&polyFactor, &finalExpFactor) + root.Exp(millerLoop, &exponent) + if root.IsOne() { + order3rdPower.SetUint64(0) + } + root.Exp(root, &three) + if root.IsOne() { + order3rdPower.SetUint64(1) + } + root.Exp(root, &three) + if root.IsOne() { + order3rdPower.SetUint64(2) + } + root.Exp(root, &three) + if root.IsOne() { + order3rdPower.SetUint64(3) + } + + // 2.2. get 27th root inverse + if order3rdPower.Uint64() == 0 { + root27thInverse.SetOne() + } else { + order3rd.Exp(&three, &order3rdPower, nil) + exponent.Mul(&polyFactor, &finalExpFactor) + root.Exp(millerLoop, &exponent) + exponentInv.ModInverse(&exponent, &order3rd) + exponent.Neg(&exponentInv).Mod(&exponent, &order3rd) + root27thInverse.Exp(root, &exponent) + } + + // 2.3. shift the Miller loop result so that millerLoop * scalingFactor + // is of order finalExpFactor + scalingFactor.Mul(&rootPthInverse, &root27thInverse) + millerLoop.Mul(&millerLoop, &scalingFactor) + + // 3. get the witness residue + // + // lambda = q - u, the optimal exponent + var lambda big.Int + lambda.SetString("4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129030796414117214202539", 10) + exponent.ModInverse(&lambda, &finalExpFactor) + residueWitness.Exp(millerLoop, &exponent) + + // return the witness residue residueWitness.C0.B0.A0.BigInt(outputs[0]) residueWitness.C0.B0.A1.BigInt(outputs[1]) residueWitness.C0.B1.A0.BigInt(outputs[2]) @@ -309,6 +368,14 @@ func finalExpHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) er residueWitness.C1.B2.A0.BigInt(outputs[10]) residueWitness.C1.B2.A1.BigInt(outputs[11]) + // return the scaling factor + scalingFactor.C0.B0.A0.BigInt(outputs[12]) + scalingFactor.C0.B0.A1.BigInt(outputs[13]) + scalingFactor.C0.B1.A0.BigInt(outputs[14]) + scalingFactor.C0.B1.A1.BigInt(outputs[15]) + scalingFactor.C0.B2.A0.BigInt(outputs[16]) + scalingFactor.C0.B2.A1.BigInt(outputs[17]) + return nil }) } diff --git a/std/algebra/emulated/fields_bn254/e12_pairing.go b/std/algebra/emulated/fields_bn254/e12_pairing.go index 64d0d11fb8..f7c93c75e7 100644 --- a/std/algebra/emulated/fields_bn254/e12_pairing.go +++ b/std/algebra/emulated/fields_bn254/e12_pairing.go @@ -422,13 +422,13 @@ func (e Ext12) FrobeniusCubeTorus(y *E6) *E6 { return res } -// FinalExponentiationCheck checks that a Miller function output x lies in the +// AssertFinalExponentiationIsOne 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 (e Ext12) FinalExponentiationCheck(x *E12) *E12 { +func (e Ext12) AssertFinalExponentiationIsOne(x *E12) { res, err := e.fp.NewHint(finalExpHint, 24, &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 @@ -474,8 +474,6 @@ func (e Ext12) FinalExponentiationCheck(x *E12) *E12 { t0 = e.Mul(t0, t1) e.AssertIsEqual(t0, t2) - - return nil } func (e Ext12) Frobenius(x *E12) *E12 { diff --git a/std/algebra/emulated/fields_bw6761/e6_pairing.go b/std/algebra/emulated/fields_bw6761/e6_pairing.go index 8361ed8146..8907c0535b 100644 --- a/std/algebra/emulated/fields_bw6761/e6_pairing.go +++ b/std/algebra/emulated/fields_bw6761/e6_pairing.go @@ -322,13 +322,13 @@ func (e *Ext6) MulBy02345(z *E6, x [5]*baseEl) *E6 { } } -// FinalExponentiationCheck checks that a Miller function output x lies in the +// AssertFinalExponentiationIsOne 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 is adapted from Section 4 of [On Proving Pairings] paper by A. Novakovic and L. Eagen. // // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf -func (e Ext6) FinalExponentiationCheck(x *E6) *E6 { +func (e Ext6) AssertFinalExponentiationIsOne(x *E6) { res, err := e.fp.NewHint(finalExpHint, 6, &x.A0, &x.A1, &x.A2, &x.A3, &x.A4, &x.A5) if err != nil { // err is non-nil only for invalid number of inputs @@ -357,8 +357,6 @@ func (e Ext6) FinalExponentiationCheck(x *E6) *E6 { t0 = e.DivUnchecked(t0, t1) e.AssertIsEqual(t0, x) - - return nil } // ExpByU2 set z to z^(x₀+1) in E12 and return z diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index c5f96e62d8..4bc99671d6 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -251,7 +251,7 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { } // We perform the easy part of the final exp to push f to the cyclotomic - // subgroup so that FinalExponentiationCheck is carried with optimized + // subgroup so that AssertFinalExponentiationIsOne is carried with optimized // cyclotomic squaring (e.g. Karabina12345). // // f = f^(p⁶-1)(p²+1) @@ -260,7 +260,7 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { f = pr.FrobeniusSquare(buf) f = pr.Mul(f, buf) - pr.FinalExponentiationCheck(f) + pr.AssertFinalExponentiationIsOne(f) return nil } diff --git a/std/algebra/emulated/sw_bls12381/pairing_test.go b/std/algebra/emulated/sw_bls12381/pairing_test.go index dc723eb012..9ffdd18d56 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), @@ -228,11 +231,13 @@ func TestGroupMembershipSolve(t *testing.T) { // bench func BenchmarkPairing(b *testing.B) { - + // 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.go b/std/algebra/emulated/sw_bn254/pairing.go index 9f6567946e..61a7e051e6 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -251,7 +251,7 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { } // We perform the easy part of the final exp to push f to the cyclotomic - // subgroup so that FinalExponentiationCheck is carried with optimized + // subgroup so that AssertFinalExponentiationIsOne is carried with optimized // cyclotomic squaring (e.g. Karabina12345). // // f = f^(p⁶-1)(p²+1) @@ -260,7 +260,7 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { f = pr.FrobeniusSquare(buf) f = pr.Mul(f, buf) - pr.FinalExponentiationCheck(f) + pr.AssertFinalExponentiationIsOne(f) return nil } diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go index d7910e7b2a..8b49607441 100644 --- a/std/algebra/emulated/sw_bn254/pairing_test.go +++ b/std/algebra/emulated/sw_bn254/pairing_test.go @@ -259,7 +259,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) } @@ -268,10 +268,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), @@ -477,11 +480,13 @@ func TestIsMillerLoopAndFinalExpCircuitTestSolve(t *testing.T) { // bench func BenchmarkPairing(b *testing.B) { - + // 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.go b/std/algebra/emulated/sw_bw6761/pairing.go index 88485288cb..47ad915567 100644 --- a/std/algebra/emulated/sw_bw6761/pairing.go +++ b/std/algebra/emulated/sw_bw6761/pairing.go @@ -147,7 +147,7 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { } // We perform the easy part of the final exp to push f to the cyclotomic - // subgroup so that FinalExponentiationCheck is carried with optimized + // subgroup so that AssertFinalExponentiationIsOne is carried with optimized // cyclotomic squaring (e.g. Karabina12345). // // f = f^(p³-1)(p+1) @@ -156,7 +156,7 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { f = pr.Frobenius(buf) f = pr.Mul(f, buf) - pr.FinalExponentiationCheck(f) + pr.AssertFinalExponentiationIsOne(f) return nil } diff --git a/std/algebra/emulated/sw_bw6761/pairing_test.go b/std/algebra/emulated/sw_bw6761/pairing_test.go index 06b3276afa..65f087a555 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), @@ -226,16 +229,18 @@ func TestGroupMembershipSolve(t *testing.T) { // bench func BenchmarkPairing(b *testing.B) { - - p, q := randomG1G2Affines() - res, err := bw6761.Pair([]bw6761.G1Affine{p}, []bw6761.G2Affine{q}) - if err != nil { - b.Fatal(err) - } - witness := PairCircuit{ - InG1: NewG1Affine(p), - InG2: NewG2Affine(q), - Res: NewGTEl(res), + // e(a,2b) * e(-2a,b) == 1 + p1, q1 := randomG1G2Affines() + var p2 bw6761.G1Affine + p2.Double(&p1).Neg(&p2) + var q2 bw6761.G2Affine + q2.Set(&q1) + q1.Double(&q1) + witness := PairingCheckCircuit{ + In1G1: NewG1Affine(p1), + In1G2: NewG2Affine(q1), + In2G1: NewG1Affine(p2), + In2G2: NewG2Affine(q2), } w, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField()) if err != nil { @@ -245,7 +250,7 @@ func BenchmarkPairing(b *testing.B) { b.Run("compile scs", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &PairCircuit{}); err != nil { + if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &PairingCheckCircuit{}); err != nil { b.Fatal(err) } } @@ -267,7 +272,7 @@ func BenchmarkPairing(b *testing.B) { b.Run("compile r1cs", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &PairCircuit{}); err != nil { + if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &PairingCheckCircuit{}); err != nil { b.Fatal(err) } } diff --git a/std/algebra/native/fields_bls12377/e12_pairing.go b/std/algebra/native/fields_bls12377/e12_pairing.go index a4895b425d..de72c44645 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,67 @@ 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 +} + +// AssertFinalExponentiationIsOne 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) AssertFinalExponentiationIsOne(api frontend.API) { + res, err := api.NewHint(finalExpHint, 18, 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, scalingFactor, t0, t1 E12 + residueWitness.assign(res[:12]) + // constrain cubicNonResiduePower to be in Fp6 + scalingFactor.C0.B0.A0 = res[12] + scalingFactor.C0.B0.A1 = res[13] + scalingFactor.C0.B1.A0 = res[14] + scalingFactor.C0.B1.A1 = res[15] + scalingFactor.C0.B2.A0 = res[16] + scalingFactor.C0.B2.A1 = res[17] + scalingFactor.C1.SetZero() + + // Check that x * scalingFactor == residueWitness^(q-u) + // where u=0x8508c00000000001 is the BLS12-377 seed, + // and residueWitness, scalingFactor from the hint. + t0.Frobenius(api, residueWitness) + // exponentiation by u + t1.ExpX0(api, residueWitness) + t0.DivUnchecked(api, t0, t1) + + t1.Mul(api, *x, scalingFactor) + + t0.AssertIsEqual(api, t1) +} diff --git a/std/algebra/native/fields_bls12377/hints.go b/std/algebra/native/fields_bls12377/hints.go index 9138ca997c..0325cf4aea 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,75 @@ func inverseE12Hint(_ *big.Int, inputs []*big.Int, res []*big.Int) error { return nil } + +func finalExpHint(_ *big.Int, inputs, outputs []*big.Int) error { + var millerLoop bls12377.E12 + + 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]) + + var root, rootPthInverse, residueWitness, scalingFactor bls12377.E12 + var exponent, exponentInv, finalExpFactor, polyFactor big.Int + // polyFactor = 12(x-1) + polyFactor.SetString("115033474957087604736", 10) + // finalExpFactor = ((q^12 - 1) / r) / polyFactor + finalExpFactor.SetString("92351561334497520756349650336409370070948672672207914824247073415859727964231807559847070685040742345026775319680739143654748316009031763764029886042408725311062057776702838555815712331129279611544378217895455619058809454575474763035923260395518532422855090028311239234310116353269618927871828693919559964406939845784130633021661399269804065961999062695977580539176029238189119059338698461832966347603096853909366901376879505972606045770762516580639801134008192256366142553202619529638202068488750102055204336502584141399828818871664747496033599618827160583206926869573005874449182200210044444351826855938563862937638034918413235278166699461287943529570559518592586872860190313088429391521694808994276205429071153237122495989095857292965461625387657577981811772819764071512345106346232882471034669258055302790607847924560040527682025558360106509628206144255667203317787586698694011876342903106644003067103035176245790275561392007119121995936066014208972135762663107247939004517852248103325700169848524693333524025685325993207375736519358185783520948988673594976115901587076295116293065682366935313875411927779217584729138600463438806153265891176654957439524358472291492028580820575807385461119025678550977847392818655362610734928283105671242634809807533919011078145", 10) + + // 1. get pth-root inverse + exponent.Set(&finalExpFactor) + root.Exp(millerLoop, &finalExpFactor) + if root.IsOne() { + rootPthInverse.SetOne() + } else { + exponentInv.ModInverse(&exponent, &polyFactor) + exponent.Neg(&exponentInv).Mod(&exponent, &polyFactor) + rootPthInverse.Exp(root, &exponent) + } + + // 3. shift the Miller loop result so that millerLoop * scalingFactor + // is of order finalExpFactor + scalingFactor.Set(&rootPthInverse) + millerLoop.Mul(&millerLoop, &scalingFactor) + + // 4. get the witness residue + // + // lambda = q - u, the optimal exponent + var lambda big.Int + lambda.SetString("258664426012969094010652733694893533536393512754914660539884262666720468348340822774968888139563774001527230824448", 10) + exponent.ModInverse(&lambda, &finalExpFactor) + residueWitness.Exp(millerLoop, &exponent) + + // return the witness residue + 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 the scaling factor + scalingFactor.C0.B0.A0.BigInt(outputs[12]) + scalingFactor.C0.B0.A1.BigInt(outputs[13]) + scalingFactor.C0.B1.A0.BigInt(outputs[14]) + scalingFactor.C0.B1.A1.BigInt(outputs[15]) + scalingFactor.C0.B2.A0.BigInt(outputs[16]) + scalingFactor.C0.B2.A1.BigInt(outputs[17]) + + return nil +} diff --git a/std/algebra/native/sw_bls12377/pairing.go b/std/algebra/native/sw_bls12377/pairing.go index fa9febf7c3..9cd1ce851b 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 AssertFinalExponentiationIsOne 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.AssertFinalExponentiationIsOne(api) return nil } diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 2faddb4039..d057ea13d1 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -312,13 +312,21 @@ 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) + // We perform the easy part of the final exp to push res to the cyclotomic + // subgroup so that AssertFinalExponentiationIsOne 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.AssertFinalExponentiationIsOne(p.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 }