Skip to content

Commit

Permalink
Merge pull request #698 from Consensys/evm/ecpair
Browse files Browse the repository at this point in the history
feat: emulated pairing 2-by-2 fixed circuit for EVM
  • Loading branch information
yelhousni committed Jul 4, 2023
2 parents 5f2979f + b18e0f7 commit b267eb4
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 24 deletions.
23 changes: 23 additions & 0 deletions std/algebra/emulated/sw_bn254/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ----------------------------
Expand Down
28 changes: 26 additions & 2 deletions std/evmprecompiles/08-bnpairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
73 changes: 51 additions & 22 deletions std/evmprecompiles/bn_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package evmprecompiles

import (
"fmt"
"math/big"
"testing"

Expand Down Expand Up @@ -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)
}
}

0 comments on commit b267eb4

Please sign in to comment.