diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index d85a922dcc..e483dccea8 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -673,6 +673,29 @@ func (pr Pairing) lineCompute(p1, p2 *G2Affine) *lineEvaluation { } +// MillerLoopAndMul computes the Miller loop between P and Q +// and multiplies it in 𝔽p¹² by previous. +// +// This method is needed for evmprecompiles/ecpair. +func (pr Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (*GTEl, error) { + res, err := pr.MillerLoop([]*G1Affine{P}, []*G2Affine{Q}) + if err != nil { + return nil, fmt.Errorf("miller loop: %w", err) + } + res = pr.Mul(res, previous) + return res, err +} + +// FinalExponentiationIsOne performs the final exponentiation on e +// and checks that the result in 1 in GT. +// +// This method is needed for evmprecompiles/ecpair. +func (pr Pairing) FinalExponentiationIsOne(e *GTEl) { + res := pr.finalExponentiation(e, false) + one := pr.One() + pr.AssertIsEqual(res, one) +} + // ---------------------------- // Fixed-argument pairing // ---------------------------- diff --git a/std/evmprecompiles/08-bnpairing.go b/std/evmprecompiles/08-bnpairing.go index 01dbbab99c..9d25cd20ec 100644 --- a/std/evmprecompiles/08-bnpairing.go +++ b/std/evmprecompiles/08-bnpairing.go @@ -8,7 +8,23 @@ import ( // ECPair implements [ALT_BN128_PAIRING_CHECK] precompile contract at address 0x08. // // [ALT_BN128_PAIRING_CHECK]: https://ethereum.github.io/execution-specs/autoapi/ethereum/paris/vm/precompiled_contracts/alt_bn128/index.html#alt-bn128-pairing-check +// +// To have a fixed-circuit regardless of the number of inputs, we need 2 fixed circuits: +// - A Miller loop of fixed size 1 followed with a multiplication in 𝔽p¹² (MillerLoopAndMul) +// - A final exponentiation followed with an equality check in GT (FinalExponentiationIsOne) +// +// N.B.: This is a sub-optimal routine but defines a fixed circuit regardless +// of the number of inputs. We can extend this routine to handle a 2-by-2 +// logic but we prefer a minimal number of circuits (2). + func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { + if len(P) != len(Q) { + panic("P and Q length mismatch") + } + if len(P) < 2 { + panic("invalid multipairing size bound") + } + n := len(P) pair, err := sw_bn254.NewPairing(api) if err != nil { panic(err) @@ -20,7 +36,15 @@ func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { } // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 - if err := pair.PairingCheck(P, Q); err != nil { - panic(err) + ml := pair.One() + for i := 0; i < n; i++ { + // fixed circuit 1 + ml, err = pair.MillerLoopAndMul(P[i], Q[i], ml) + if err != nil { + panic(err) + } } + + // fixed circuit 2 + pair.FinalExponentiationIsOne(ml) } diff --git a/std/evmprecompiles/bn_test.go b/std/evmprecompiles/bn_test.go index f5efe1baa3..24cb21c7ba 100644 --- a/std/evmprecompiles/bn_test.go +++ b/std/evmprecompiles/bn_test.go @@ -1,6 +1,7 @@ package evmprecompiles import ( + "fmt" "math/big" "testing" @@ -132,37 +133,65 @@ func TestECMulCircuitFull(t *testing.T) { ) } -type ecpairCircuit struct { - P [2]sw_bn254.G1Affine - Q [2]sw_bn254.G2Affine +type ecPairBatchCircuit struct { + P sw_bn254.G1Affine + NP sw_bn254.G1Affine + DP sw_bn254.G1Affine + Q sw_bn254.G2Affine + n int } -func (c *ecpairCircuit) Define(api frontend.API) error { - P := []*sw_bn254.G1Affine{&c.P[0], &c.P[1]} - Q := []*sw_bn254.G2Affine{&c.Q[0], &c.Q[1]} - ECPair(api, P, Q) +func (c *ecPairBatchCircuit) Define(api frontend.API) error { + Q := make([]*sw_bn254.G2Affine, c.n) + for i := range Q { + Q[i] = &c.Q + } + switch c.n { + case 2: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.P, &c.NP}, Q) + case 3: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.NP, &c.NP, &c.DP}, Q) + case 4: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.P, &c.NP, &c.P, &c.NP}, Q) + case 5: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + case 6: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) + case 7: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + case 8: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) + case 9: + ECPair(api, []*sw_emulated.AffinePoint[emulated.BN254Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + default: + return fmt.Errorf("not handled %d", c.n) + } return nil } -func TestECPairCircuitShort(t *testing.T) { +func TestECPairMulBatch(t *testing.T) { assert := test.NewAssert(t) - _, _, p1, q1 := bn254.Generators() + _, _, p, q := bn254.Generators() var u, v fr.Element u.SetRandom() v.SetRandom() - p1.ScalarMultiplication(&p1, u.BigInt(new(big.Int))) - q1.ScalarMultiplication(&q1, v.BigInt(new(big.Int))) - - var p2 bn254.G1Affine - var q2 bn254.G2Affine - p2.Neg(&p1) - q2.Set(&q1) - - err := test.IsSolved(&ecpairCircuit{}, &ecpairCircuit{ - P: [2]sw_bn254.G1Affine{sw_bn254.NewG1Affine(p1), sw_bn254.NewG1Affine(p2)}, - Q: [2]sw_bn254.G2Affine{sw_bn254.NewG2Affine(q1), sw_bn254.NewG2Affine(q2)}, - }, ecc.BN254.ScalarField()) - assert.NoError(err) + p.ScalarMultiplication(&p, u.BigInt(new(big.Int))) + q.ScalarMultiplication(&q, v.BigInt(new(big.Int))) + + var dp, np bn254.G1Affine + dp.Double(&p) + np.Neg(&p) + + for i := 2; i < 10; i++ { + err := test.IsSolved(&ecPairBatchCircuit{n: i}, &ecPairBatchCircuit{ + n: i, + P: sw_bn254.NewG1Affine(p), + NP: sw_bn254.NewG1Affine(np), + DP: sw_bn254.NewG1Affine(dp), + Q: sw_bn254.NewG2Affine(q), + }, ecc.BN254.ScalarField()) + assert.NoError(err) + } }