diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go index 8e39feb7ae..0782f40034 100644 --- a/std/algebra/emulated/sw_bls12381/hints.go +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -223,41 +223,50 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp var P bls12381.G1Affine var Q bls12381.G2Affine var previous bls12381.E12 + n := len(inputs) - 12 + p := make([]bls12381.G1Affine, 0, n/6) + q := make([]bls12381.G2Affine, 0, n/6) + // first one-third is G1 points + for k := 0; k < n/3; k += 2 { + P.X.SetBigInt(inputs[k]) + P.Y.SetBigInt(inputs[k+1]) + p = append(p, P) + } + // subsequent two-thirds are G2 points + for k := n / 3; k < n; k += 4 { + Q.X.A0.SetBigInt(inputs[k]) + Q.X.A1.SetBigInt(inputs[k+1]) + Q.Y.A0.SetBigInt(inputs[k+2]) + Q.Y.A1.SetBigInt(inputs[k+3]) + q = append(q, Q) + } - P.X.SetBigInt(inputs[0]) - P.Y.SetBigInt(inputs[1]) - Q.X.A0.SetBigInt(inputs[2]) - Q.X.A1.SetBigInt(inputs[3]) - Q.Y.A0.SetBigInt(inputs[4]) - Q.Y.A1.SetBigInt(inputs[5]) - - previous.C0.B0.A0.SetBigInt(inputs[6]) - previous.C0.B0.A1.SetBigInt(inputs[7]) - previous.C0.B1.A0.SetBigInt(inputs[8]) - previous.C0.B1.A1.SetBigInt(inputs[9]) - previous.C0.B2.A0.SetBigInt(inputs[10]) - previous.C0.B2.A1.SetBigInt(inputs[11]) - previous.C1.B0.A0.SetBigInt(inputs[12]) - previous.C1.B0.A1.SetBigInt(inputs[13]) - previous.C1.B1.A0.SetBigInt(inputs[14]) - previous.C1.B1.A1.SetBigInt(inputs[15]) - previous.C1.B2.A0.SetBigInt(inputs[16]) - previous.C1.B2.A1.SetBigInt(inputs[17]) + previous.C0.B0.A0.SetBigInt(inputs[n]) + previous.C0.B0.A1.SetBigInt(inputs[n+1]) + previous.C0.B1.A0.SetBigInt(inputs[n+2]) + previous.C0.B1.A1.SetBigInt(inputs[n+3]) + previous.C0.B2.A0.SetBigInt(inputs[n+4]) + previous.C0.B2.A1.SetBigInt(inputs[n+5]) + previous.C1.B0.A0.SetBigInt(inputs[n+6]) + previous.C1.B0.A1.SetBigInt(inputs[n+7]) + previous.C1.B1.A0.SetBigInt(inputs[n+8]) + previous.C1.B1.A1.SetBigInt(inputs[n+9]) + previous.C1.B2.A0.SetBigInt(inputs[n+10]) + previous.C1.B2.A1.SetBigInt(inputs[n+11]) if previous.IsZero() { return errors.New("previous Miller loop result is zero") } - lines := bls12381.PrecomputeLines(Q) - millerLoop, err := bls12381.MillerLoopFixedQ( - []bls12381.G1Affine{P}, - [][2][len(bls12381.LoopCounter) - 1]bls12381.LineEvaluationAff{lines}, - ) + lines := make([][2][len(bls12381.LoopCounter) - 1]bls12381.LineEvaluationAff, 0, len(q)) + for _, qi := range q { + lines = append(lines, bls12381.PrecomputeLines(qi)) + } + millerLoop, err := bls12381.MillerLoopFixedQ(p, lines) if err != nil { return err } millerLoop.Conjugate(&millerLoop) - millerLoop.Mul(&millerLoop, &previous) residueWitnessInv, scalingFactor := finalExpWitness(&millerLoop) diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index d333badb14..7c9f9935af 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -813,17 +813,44 @@ func (pr *Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (* // // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf func (pr *Pairing) AssertMillerLoopAndFinalExpIsOne(P *G1Affine, Q *G2Affine, previous *GTEl) { + t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous) + pr.AssertIsEqual(t2, pr.Ext12.One()) +} + +// AssertMultiMillerLoopAndFinalExpIsOne computes the Miller loop between +// multiple P and Q pairs and multiplies it in 𝔽p¹² by previous and +// returns a boolean indicating if the result lies in the same equivalence +// class as the reduced pairing purported to be 1. This check replaces the +// final exponentiation step in-circuit and 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 (pr *Pairing) AssertMultiMillerLoopAndFinalExpIsOne(P []*G1Affine, Q []*G2Affine, previous *GTEl) { t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) pr.AssertIsEqual(t2, pr.Ext12.One()) } // millerLoopAndFinalExpResult computes the Miller loop between P and Q, // multiplies it in 𝔽p¹² by previous and returns the result. -func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous *GTEl) *GTEl { +func (pr *Pairing) millerLoopAndFinalExpResult(P []*G1Affine, Q []*G2Affine, previous *GTEl) *GTEl { + nP := len(P) + nQ := len(Q) + if nP == 0 || nP != nQ { + return nil + } tower := pr.ToTower(previous) + inputs := make([]*baseEl, 0, 2*nP+4*nQ+12) + for _, p := range P { + inputs = append(inputs, &p.X, &p.Y) + } + for _, q := range Q { + inputs = append(inputs, &q.P.X.A0, &q.P.X.A1, &q.P.Y.A0, &q.P.Y.A1) + } + inputs = append(inputs, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11]) + // hint the non-residue witness - hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, &P.X, &P.Y, &Q.P.X.A0, &Q.P.X.A1, &Q.P.Y.A0, &Q.P.Y.A1, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11]) + hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, inputs...) if err != nil { // err is non-nil only for invalid number of inputs panic(err) @@ -859,18 +886,16 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou A11: *pr.curveF.Zero(), } - if Q.Lines == nil { - Qlines := pr.computeLines(&Q.P) - Q.Lines = &Qlines + lines := make([]lineEvaluations, nQ) + for i := range Q { + if Q[i].Lines == nil { + Qlines := pr.computeLines(&Q[i].P) + Q[i].Lines = &Qlines + } + lines[i] = *Q[i].Lines } - lines := *Q.Lines - res, err := pr.millerLoopLines( - []*G1Affine{P}, - []lineEvaluations{lines}, - residueWitnessInv, - false, - ) + res, err := pr.millerLoopLines(P, lines, residueWitnessInv, false) if err != nil { return nil } @@ -891,7 +916,6 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou res = pr.Ext12.Mul(res, t0) return res - } // IsMillerLoopAndFinalExpOne computes the Miller loop between P and Q, @@ -903,7 +927,7 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou // // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf func (pr *Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous *GTEl) frontend.Variable { - t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) + t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous) res := pr.IsEqual(t2, pr.Ext12.One()) return res diff --git a/std/algebra/emulated/sw_bls12381/pairing_test.go b/std/algebra/emulated/sw_bls12381/pairing_test.go index 73adf4676e..acbeac12f6 100644 --- a/std/algebra/emulated/sw_bls12381/pairing_test.go +++ b/std/algebra/emulated/sw_bls12381/pairing_test.go @@ -623,6 +623,53 @@ func (c *IsOnGroupCircuit) Define(api frontend.API) error { return nil } +type MultiMillerLoopAndFinalExpCircuit struct { + Prev GTEl + P1, P2, P3 G1Affine + Q1, Q2, Q3 G2Affine +} + +func (c *MultiMillerLoopAndFinalExpCircuit) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + pairing.AssertMultiMillerLoopAndFinalExpIsOne( + []*G1Affine{&c.P1, &c.P2, &c.P3}, + []*G2Affine{&c.Q1, &c.Q2, &c.Q3}, + &c.Prev, + ) + return nil +} + +func TestMultiMillerLoopAndFinalExpIsOneTestSolve(t *testing.T) { + assert := test.NewAssert(t) + p1, q1 := randomG1G2Affines() + p2, q2 := randomG1G2Affines() + var np1, np2 bls12381.G1Affine + np1.Neg(&p1) + np2.Neg(&p2) + + lines := bls12381.PrecomputeLines(q1) + previous, err := bls12381.MillerLoopFixedQ( + []bls12381.G1Affine{p1}, + [][2][len(bls12381.LoopCounter) - 1]bls12381.LineEvaluationAff{lines}, + ) + assert.NoError(err) + previous.Conjugate(&previous) + + witness := MultiMillerLoopAndFinalExpCircuit{ + Prev: NewGTEl(previous), + P1: NewG1Affine(np1), + P2: NewG1Affine(np2), + P3: NewG1Affine(p2), + Q1: NewG2Affine(q1), + Q2: NewG2Affine(q2), + Q3: NewG2Affine(q2), + } + assert.NoError(test.IsSolved(&MultiMillerLoopAndFinalExpCircuit{}, &witness, ecc.BN254.ScalarField())) +} + func TestIsOnG1(t *testing.T) { assert := test.NewAssert(t) assert.Run(func(assert *test.Assert) { diff --git a/std/algebra/emulated/sw_bn254/hints.go b/std/algebra/emulated/sw_bn254/hints.go index 6c2e966ca9..e1e7e21009 100644 --- a/std/algebra/emulated/sw_bn254/hints.go +++ b/std/algebra/emulated/sw_bn254/hints.go @@ -202,39 +202,51 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp // This follows section 4.3.2 of https://eprint.iacr.org/2024/640.pdf return emulated.UnwrapHint(nativeInputs, nativeOutputs, func(mod *big.Int, inputs, outputs []*big.Int) error { + var previous bn254.E12 var P bn254.G1Affine var Q bn254.G2Affine - var previous bn254.E12 + n := len(inputs) - 12 // inputs for points + p := make([]bn254.G1Affine, 0, n/6) + q := make([]bn254.G2Affine, 0, n/6) + // first one-third is G1 points + for k := 0; k < n/3; k += 2 { + P.X.SetBigInt(inputs[k]) + P.Y.SetBigInt(inputs[k+1]) + p = append(p, P) + } + // subsequent two-thirds are G2 points + for k := n / 3; k < n; k += 4 { + Q.X.A0.SetBigInt(inputs[k]) + Q.X.A1.SetBigInt(inputs[k+1]) + Q.Y.A0.SetBigInt(inputs[k+2]) + Q.Y.A1.SetBigInt(inputs[k+3]) + q = append(q, Q) + } - P.X.SetBigInt(inputs[0]) - P.Y.SetBigInt(inputs[1]) - Q.X.A0.SetBigInt(inputs[2]) - Q.X.A1.SetBigInt(inputs[3]) - Q.Y.A0.SetBigInt(inputs[4]) - Q.Y.A1.SetBigInt(inputs[5]) - - previous.C0.B0.A0.SetBigInt(inputs[6]) - previous.C0.B0.A1.SetBigInt(inputs[7]) - previous.C0.B1.A0.SetBigInt(inputs[8]) - previous.C0.B1.A1.SetBigInt(inputs[9]) - previous.C0.B2.A0.SetBigInt(inputs[10]) - previous.C0.B2.A1.SetBigInt(inputs[11]) - previous.C1.B0.A0.SetBigInt(inputs[12]) - previous.C1.B0.A1.SetBigInt(inputs[13]) - previous.C1.B1.A0.SetBigInt(inputs[14]) - previous.C1.B1.A1.SetBigInt(inputs[15]) - previous.C1.B2.A0.SetBigInt(inputs[16]) - previous.C1.B2.A1.SetBigInt(inputs[17]) + // previous Miller loop result is passed in the last 12 inputs + previous.C0.B0.A0.SetBigInt(inputs[n]) + previous.C0.B0.A1.SetBigInt(inputs[n+1]) + previous.C0.B1.A0.SetBigInt(inputs[n+2]) + previous.C0.B1.A1.SetBigInt(inputs[n+3]) + previous.C0.B2.A0.SetBigInt(inputs[n+4]) + previous.C0.B2.A1.SetBigInt(inputs[n+5]) + previous.C1.B0.A0.SetBigInt(inputs[n+6]) + previous.C1.B0.A1.SetBigInt(inputs[n+7]) + previous.C1.B1.A0.SetBigInt(inputs[n+8]) + previous.C1.B1.A1.SetBigInt(inputs[n+9]) + previous.C1.B2.A0.SetBigInt(inputs[n+10]) + previous.C1.B2.A1.SetBigInt(inputs[n+11]) if previous.IsZero() { return errors.New("previous Miller loop result is zero") } - lines := bn254.PrecomputeLines(Q) - millerLoop, err := bn254.MillerLoopFixedQ( - []bn254.G1Affine{P}, - [][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff{lines}, - ) + lines := make([][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff, 0, len(q)) + for _, qi := range q { + lines = append(lines, bn254.PrecomputeLines(qi)) + } + millerLoop, err := bn254.MillerLoopFixedQ(p, lines) + if err != nil { return err } diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index 8c1ac5a354..95b2ecbf03 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -905,11 +905,26 @@ func (pr *Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (* // millerLoopAndFinalExpResult computes the Miller loop between P and Q, // multiplies it in 𝔽p¹² by previous and returns the result. -func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous *GTEl) *GTEl { +func (pr *Pairing) millerLoopAndFinalExpResult(P []*G1Affine, Q []*G2Affine, previous *GTEl) *GTEl { tower := pr.ToTower(previous) + nP := len(P) + nQ := len(Q) + if nP != nQ { + return nil + } + + inputs := make([]*baseEl, 0, 2*nP+4*nQ+12) + for _, p := range P { + inputs = append(inputs, &p.X, &p.Y) + } + for _, q := range Q { + inputs = append(inputs, &q.P.X.A0, &q.P.X.A1, &q.P.Y.A0, &q.P.Y.A1) + } + inputs = append(inputs, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11]) + // hint the non-residue witness - hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, &P.X, &P.Y, &Q.P.X.A0, &Q.P.X.A1, &Q.P.Y.A0, &Q.P.Y.A1, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11]) + hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, inputs...) if err != nil { // err is non-nil only for invalid number of inputs panic(err) @@ -950,15 +965,18 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou // residueWitnessInv = 1 / residueWitness residueWitnessInv := pr.Ext12.Inverse(residueWitness) - if Q.Lines == nil { - Qlines := pr.computeLines(&Q.P) - Q.Lines = &Qlines + lines := make([]lineEvaluations, nQ) + for i := range Q { + if Q[i].Lines == nil { + Qlines := pr.computeLines(&Q[i].P) + Q[i].Lines = &Qlines + } + lines[i] = *Q[i].Lines } - lines := *Q.Lines res, err := pr.millerLoopLines( - []*G1Affine{P}, - []lineEvaluations{lines}, + P, + lines, residueWitnessInv, false, ) @@ -998,12 +1016,26 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou // // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf func (pr *Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous *GTEl) frontend.Variable { - t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) + t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous) res := pr.IsEqual(t2, pr.Ext12.One()) return res } +// AssertMultiMillerLoopAndFinalExpIsOne computes the Miller loop between +// multiple P and Q pairs and multiplies it in 𝔽p¹² by previous and +// returns a boolean indicating if the result lies in the same equivalence +// class as the reduced pairing purported to be 1. This check replaces the +// final exponentiation step in-circuit and 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 (pr *Pairing) AssertMultiMillerLoopAndFinalExpIsOne(P []*G1Affine, Q []*G2Affine, previous *GTEl) { + // t2 := pr.millerLoopAndFinalExpResult(P, Q, pr.Ext12.Mul(previous, pr.Ext12.Inverse(previous))) + t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) + pr.AssertIsEqual(t2, pr.Ext12.One()) +} + // AssertMillerLoopAndFinalExpIsOne computes the Miller loop between P and Q, // multiplies it in 𝔽p¹² by previous and checks that the result lies in the // same equivalence class as the reduced pairing purported to be 1. This check @@ -1014,6 +1046,6 @@ func (pr *Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous // // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf func (pr *Pairing) AssertMillerLoopAndFinalExpIsOne(P *G1Affine, Q *G2Affine, previous *GTEl) { - t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) + t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous) pr.AssertIsEqual(t2, pr.Ext12.One()) } diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go index e7cc8c476e..ea22e9e560 100644 --- a/std/algebra/emulated/sw_bn254/pairing_test.go +++ b/std/algebra/emulated/sw_bn254/pairing_test.go @@ -628,6 +628,52 @@ func TestIsMillerLoopAndFinalExpCircuitTestSolve(t *testing.T) { assert.NoError(err) } +type MultiMillerLoopAndFinalExpCircuit struct { + Prev GTEl + P1, P2, P3 G1Affine + Q1, Q2, Q3 G2Affine +} + +func (c *MultiMillerLoopAndFinalExpCircuit) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + pairing.AssertMultiMillerLoopAndFinalExpIsOne( + []*G1Affine{&c.P1, &c.P2, &c.P3}, + []*G2Affine{&c.Q1, &c.Q2, &c.Q3}, + &c.Prev, + ) + return nil +} + +func TestMultiMillerLoopAndFinalExpIsOneTestSolve(t *testing.T) { + assert := test.NewAssert(t) + p1, q1 := randomG1G2Affines() + p2, q2 := randomG1G2Affines() + var np1, np2 bn254.G1Affine + np1.Neg(&p1) + np2.Neg(&p2) + + lines := bn254.PrecomputeLines(q1) + previous, err := bn254.MillerLoopFixedQ( + []bn254.G1Affine{p1}, + [][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff{lines}, + ) + assert.NoError(err) + + witness := MultiMillerLoopAndFinalExpCircuit{ + Prev: NewGTEl(previous), + P1: NewG1Affine(np1), + P2: NewG1Affine(np2), + P3: NewG1Affine(p2), + Q1: NewG2Affine(q1), + Q2: NewG2Affine(q2), + Q3: NewG2Affine(q2), + } + assert.NoError(test.IsSolved(&MultiMillerLoopAndFinalExpCircuit{}, &witness, ecc.BN254.ScalarField())) +} + type MuxesCircuits struct { InG2 []G2Affine InGt []GTEl